feat: broadcast agent events over control channel
This commit is contained in:
@@ -25,6 +25,7 @@ import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { defaultRuntime, type RuntimeEnv } from "../runtime.js";
|
||||
import { normalizeE164 } from "../utils.js";
|
||||
import { sendViaIpc } from "../web/ipc.js";
|
||||
import { emitAgentEvent } from "../infra/agent-events.js";
|
||||
|
||||
type AgentCommandOpts = {
|
||||
message: string;
|
||||
@@ -293,20 +294,58 @@ export async function agentCommand(
|
||||
BodyStripped: commandBody,
|
||||
};
|
||||
|
||||
const result = await runCommandReply({
|
||||
reply: { ...replyCfg, mode: "command" },
|
||||
templatingCtx,
|
||||
sendSystemOnce,
|
||||
isNewSession,
|
||||
isFirstTurnInSession,
|
||||
systemSent,
|
||||
timeoutMs,
|
||||
timeoutSeconds,
|
||||
commandRunner: runCommandWithTimeout,
|
||||
thinkLevel: resolvedThinkLevel,
|
||||
verboseLevel: resolvedVerboseLevel,
|
||||
const startedAt = Date.now();
|
||||
emitAgentEvent({
|
||||
runId: sessionId,
|
||||
stream: "job",
|
||||
data: {
|
||||
state: "started",
|
||||
to: opts.to,
|
||||
sessionId,
|
||||
isNewSession,
|
||||
},
|
||||
});
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await runCommandReply({
|
||||
reply: { ...replyCfg, mode: "command" },
|
||||
templatingCtx,
|
||||
sendSystemOnce,
|
||||
isNewSession,
|
||||
isFirstTurnInSession,
|
||||
systemSent,
|
||||
timeoutMs,
|
||||
timeoutSeconds,
|
||||
commandRunner: runCommandWithTimeout,
|
||||
thinkLevel: resolvedThinkLevel,
|
||||
verboseLevel: resolvedVerboseLevel,
|
||||
});
|
||||
emitAgentEvent({
|
||||
runId: sessionId,
|
||||
stream: "job",
|
||||
data: {
|
||||
state: "done",
|
||||
to: opts.to,
|
||||
sessionId,
|
||||
durationMs: Date.now() - startedAt,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
emitAgentEvent({
|
||||
runId: sessionId,
|
||||
stream: "job",
|
||||
data: {
|
||||
state: "error",
|
||||
to: opts.to,
|
||||
sessionId,
|
||||
durationMs: Date.now() - startedAt,
|
||||
error: String(err),
|
||||
},
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
|
||||
// If the agent returned a new session id, persist it.
|
||||
const returnedSessionId = result.meta.agentMeta?.sessionId;
|
||||
if (
|
||||
|
||||
31
src/infra/agent-events.ts
Normal file
31
src/infra/agent-events.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export type AgentEventPayload = {
|
||||
runId: string;
|
||||
seq: number;
|
||||
stream: "job" | "tool" | string;
|
||||
ts: number;
|
||||
data: Record<string, unknown>;
|
||||
};
|
||||
|
||||
let seq = 0;
|
||||
const listeners = new Set<(evt: AgentEventPayload) => void>();
|
||||
|
||||
export function emitAgentEvent(event: Omit<AgentEventPayload, "seq" | "ts">) {
|
||||
const enriched: AgentEventPayload = {
|
||||
...event,
|
||||
seq: ++seq,
|
||||
ts: Date.now(),
|
||||
};
|
||||
for (const listener of listeners) {
|
||||
try {
|
||||
listener(enriched);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function onAgentEvent(listener: (evt: AgentEventPayload) => void) {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
type HeartbeatEventPayload,
|
||||
onHeartbeatEvent,
|
||||
} from "./heartbeat-events.js";
|
||||
import { onAgentEvent, type AgentEventPayload } from "./agent-events.js";
|
||||
|
||||
type ControlRequest = {
|
||||
type: "request";
|
||||
@@ -38,6 +39,7 @@ type Handlers = {
|
||||
type ControlServer = {
|
||||
close: () => Promise<void>;
|
||||
broadcastHeartbeat: (evt: HeartbeatEventPayload) => void;
|
||||
broadcastAgentEvent: (evt: AgentEventPayload) => void;
|
||||
};
|
||||
|
||||
const DEFAULT_PORT = 18789;
|
||||
@@ -91,6 +93,7 @@ export async function startControlChannel(
|
||||
});
|
||||
|
||||
const stopHeartbeat = onHeartbeatEvent((evt) => broadcast("heartbeat", evt));
|
||||
const stopAgent = onAgentEvent((evt) => broadcast("agent", evt));
|
||||
|
||||
const handleLine = async (socket: net.Socket, line: string) => {
|
||||
if (!line) return;
|
||||
@@ -184,6 +187,7 @@ export async function startControlChannel(
|
||||
return {
|
||||
close: async () => {
|
||||
stopHeartbeat();
|
||||
stopAgent();
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
for (const client of [...clients]) {
|
||||
client.destroy();
|
||||
@@ -194,5 +198,8 @@ export async function startControlChannel(
|
||||
emitHeartbeatEvent(evt);
|
||||
broadcast("heartbeat", evt);
|
||||
},
|
||||
broadcastAgentEvent: (evt: AgentEventPayload) => {
|
||||
broadcast("agent", evt);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user