Compare commits
2 Commits
main
...
vincentkoc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
913a1f61f7 | ||
|
|
87d9d062b9 |
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "vitest";
|
import { describe, expect, test } from "vitest";
|
||||||
import { resolveMainSessionKeyFromConfig } from "../config/sessions.js";
|
import { resolveAgentMainSessionKey, resolveMainSessionKeyFromConfig } from "../config/sessions.js";
|
||||||
import { drainSystemEvents, peekSystemEvents } from "../infra/system-events.js";
|
import { drainSystemEvents, peekSystemEvents } from "../infra/system-events.js";
|
||||||
import {
|
import {
|
||||||
cronIsolatedRun,
|
cronIsolatedRun,
|
||||||
@ -12,8 +12,21 @@ import {
|
|||||||
installGatewayTestHooks({ scope: "suite" });
|
installGatewayTestHooks({ scope: "suite" });
|
||||||
|
|
||||||
const resolveMainKey = () => resolveMainSessionKeyFromConfig();
|
const resolveMainKey = () => resolveMainSessionKeyFromConfig();
|
||||||
|
const resolveHooksMainKey = () => resolveAgentMainSessionKey({ agentId: "hooks" });
|
||||||
const HOOK_TOKEN = "hook-secret";
|
const HOOK_TOKEN = "hook-secret";
|
||||||
|
|
||||||
|
async function waitForEventsForSession(sessionKey: string, timeoutMs = 2_000): Promise<string[]> {
|
||||||
|
const deadline = Date.now() + timeoutMs;
|
||||||
|
while (Date.now() < deadline) {
|
||||||
|
const events = peekSystemEvents(sessionKey);
|
||||||
|
if (events.length > 0) {
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
|
}
|
||||||
|
throw new Error(`timeout waiting for system event in ${sessionKey}`);
|
||||||
|
}
|
||||||
|
|
||||||
function buildHookJsonHeaders(options?: {
|
function buildHookJsonHeaders(options?: {
|
||||||
token?: string | null;
|
token?: string | null;
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
@ -98,12 +111,14 @@ describe("gateway server hooks", () => {
|
|||||||
agentId: "hooks",
|
agentId: "hooks",
|
||||||
});
|
});
|
||||||
expect(resAgentWithId.status).toBe(200);
|
expect(resAgentWithId.status).toBe(200);
|
||||||
await waitForSystemEvent();
|
const hookEvents = await waitForEventsForSession(resolveHooksMainKey());
|
||||||
|
expect(hookEvents.some((e) => e.includes("Hook Email: done"))).toBe(true);
|
||||||
|
expect(peekSystemEvents(resolveMainKey()).length).toBe(0);
|
||||||
const routedCall = (cronIsolatedRun.mock.calls[0] as unknown[] | undefined)?.[0] as {
|
const routedCall = (cronIsolatedRun.mock.calls[0] as unknown[] | undefined)?.[0] as {
|
||||||
job?: { agentId?: string };
|
job?: { agentId?: string };
|
||||||
};
|
};
|
||||||
expect(routedCall?.job?.agentId).toBe("hooks");
|
expect(routedCall?.job?.agentId).toBe("hooks");
|
||||||
drainSystemEvents(resolveMainKey());
|
drainSystemEvents(resolveHooksMainKey());
|
||||||
|
|
||||||
mockIsolatedRunOkOnce();
|
mockIsolatedRunOkOnce();
|
||||||
const resAgentUnknown = await postHook(port, "/hooks/agent", {
|
const resAgentUnknown = await postHook(port, "/hooks/agent", {
|
||||||
@ -264,14 +279,15 @@ describe("gateway server hooks", () => {
|
|||||||
sessionKey: "agent:hooks:slack:channel:c123",
|
sessionKey: "agent:hooks:slack:channel:c123",
|
||||||
});
|
});
|
||||||
expect(resAgent.status).toBe(200);
|
expect(resAgent.status).toBe(200);
|
||||||
await waitForSystemEvent();
|
await waitForEventsForSession(resolveHooksMainKey());
|
||||||
|
|
||||||
const routedCall = (cronIsolatedRun.mock.calls[0] as unknown[] | undefined)?.[0] as
|
const routedCall = (cronIsolatedRun.mock.calls[0] as unknown[] | undefined)?.[0] as
|
||||||
| { sessionKey?: string; job?: { agentId?: string } }
|
| { sessionKey?: string; job?: { agentId?: string } }
|
||||||
| undefined;
|
| undefined;
|
||||||
expect(routedCall?.job?.agentId).toBe("hooks");
|
expect(routedCall?.job?.agentId).toBe("hooks");
|
||||||
expect(routedCall?.sessionKey).toBe("slack:channel:c123");
|
expect(routedCall?.sessionKey).toBe("slack:channel:c123");
|
||||||
drainSystemEvents(resolveMainKey());
|
expect(peekSystemEvents(resolveMainKey()).length).toBe(0);
|
||||||
|
drainSystemEvents(resolveHooksMainKey());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -307,12 +323,13 @@ describe("gateway server hooks", () => {
|
|||||||
agentId: "hooks",
|
agentId: "hooks",
|
||||||
});
|
});
|
||||||
expect(resAllowed.status).toBe(200);
|
expect(resAllowed.status).toBe(200);
|
||||||
await waitForSystemEvent();
|
await waitForEventsForSession(resolveHooksMainKey());
|
||||||
const allowedCall = (cronIsolatedRun.mock.calls[0] as unknown[] | undefined)?.[0] as {
|
const allowedCall = (cronIsolatedRun.mock.calls[0] as unknown[] | undefined)?.[0] as {
|
||||||
job?: { agentId?: string };
|
job?: { agentId?: string };
|
||||||
};
|
};
|
||||||
expect(allowedCall?.job?.agentId).toBe("hooks");
|
expect(allowedCall?.job?.agentId).toBe("hooks");
|
||||||
drainSystemEvents(resolveMainKey());
|
expect(peekSystemEvents(resolveMainKey()).length).toBe(0);
|
||||||
|
drainSystemEvents(resolveHooksMainKey());
|
||||||
|
|
||||||
const resDenied = await postHook(port, "/hooks/agent", {
|
const resDenied = await postHook(port, "/hooks/agent", {
|
||||||
message: "Denied",
|
message: "Denied",
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
|
import { resolveSessionAgentId } from "../../agents/agent-scope.js";
|
||||||
import type { CliDeps } from "../../cli/deps.js";
|
import type { CliDeps } from "../../cli/deps.js";
|
||||||
import { loadConfig } from "../../config/config.js";
|
import { loadConfig } from "../../config/config.js";
|
||||||
import { resolveMainSessionKeyFromConfig } from "../../config/sessions.js";
|
import {
|
||||||
|
resolveAgentMainSessionKey,
|
||||||
|
resolveMainSessionKey,
|
||||||
|
resolveMainSessionKeyFromConfig,
|
||||||
|
} from "../../config/sessions.js";
|
||||||
import { runCronIsolatedAgentTurn } from "../../cron/isolated-agent.js";
|
import { runCronIsolatedAgentTurn } from "../../cron/isolated-agent.js";
|
||||||
import type { CronJob } from "../../cron/types.js";
|
import type { CronJob } from "../../cron/types.js";
|
||||||
import { requestHeartbeatNow } from "../../infra/heartbeat-wake.js";
|
import { requestHeartbeatNow } from "../../infra/heartbeat-wake.js";
|
||||||
@ -38,7 +43,6 @@ export function createGatewayHooksRequestHandler(params: {
|
|||||||
sessionKey: value.sessionKey,
|
sessionKey: value.sessionKey,
|
||||||
targetAgentId: value.agentId,
|
targetAgentId: value.agentId,
|
||||||
});
|
});
|
||||||
const mainSessionKey = resolveMainSessionKeyFromConfig();
|
|
||||||
const jobId = randomUUID();
|
const jobId = randomUUID();
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const job: CronJob = {
|
const job: CronJob = {
|
||||||
@ -69,6 +73,11 @@ export function createGatewayHooksRequestHandler(params: {
|
|||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const cfg = loadConfig();
|
const cfg = loadConfig();
|
||||||
|
const targetAgentId = value.agentId ?? resolveSessionAgentId({ sessionKey, config: cfg });
|
||||||
|
const systemEventSessionKey =
|
||||||
|
cfg.session?.scope === "global"
|
||||||
|
? resolveMainSessionKey(cfg)
|
||||||
|
: resolveAgentMainSessionKey({ cfg, agentId: targetAgentId });
|
||||||
const result = await runCronIsolatedAgentTurn({
|
const result = await runCronIsolatedAgentTurn({
|
||||||
cfg,
|
cfg,
|
||||||
deps,
|
deps,
|
||||||
@ -82,7 +91,7 @@ export function createGatewayHooksRequestHandler(params: {
|
|||||||
result.status === "ok" ? `Hook ${value.name}` : `Hook ${value.name} (${result.status})`;
|
result.status === "ok" ? `Hook ${value.name}` : `Hook ${value.name} (${result.status})`;
|
||||||
if (!result.delivered) {
|
if (!result.delivered) {
|
||||||
enqueueSystemEvent(`${prefix}: ${summary}`.trim(), {
|
enqueueSystemEvent(`${prefix}: ${summary}`.trim(), {
|
||||||
sessionKey: mainSessionKey,
|
sessionKey: systemEventSessionKey,
|
||||||
});
|
});
|
||||||
if (value.wakeMode === "now") {
|
if (value.wakeMode === "now") {
|
||||||
requestHeartbeatNow({ reason: `hook:${jobId}` });
|
requestHeartbeatNow({ reason: `hook:${jobId}` });
|
||||||
@ -90,8 +99,14 @@ export function createGatewayHooksRequestHandler(params: {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logHooks.warn(`hook agent failed: ${String(err)}`);
|
logHooks.warn(`hook agent failed: ${String(err)}`);
|
||||||
|
const cfg = loadConfig();
|
||||||
|
const targetAgentId = value.agentId ?? resolveSessionAgentId({ sessionKey, config: cfg });
|
||||||
|
const systemEventSessionKey =
|
||||||
|
cfg.session?.scope === "global"
|
||||||
|
? resolveMainSessionKey(cfg)
|
||||||
|
: resolveAgentMainSessionKey({ cfg, agentId: targetAgentId });
|
||||||
enqueueSystemEvent(`Hook ${value.name} (error): ${String(err)}`, {
|
enqueueSystemEvent(`Hook ${value.name} (error): ${String(err)}`, {
|
||||||
sessionKey: mainSessionKey,
|
sessionKey: systemEventSessionKey,
|
||||||
});
|
});
|
||||||
if (value.wakeMode === "now") {
|
if (value.wakeMode === "now") {
|
||||||
requestHeartbeatNow({ reason: `hook:${jobId}:error` });
|
requestHeartbeatNow({ reason: `hook:${jobId}:error` });
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user