- Add isHeartbeat to AgentRunContext to track heartbeat runs - Pass isHeartbeat flag through agent runner execution - Suppress webchat broadcast (deltas + final) for heartbeat runs when showOk is false - Webchat uses channels.defaults.heartbeat settings (no per-channel config) - Default behavior: hide HEARTBEAT_OK from webchat (matches other channels) This allows users to control whether heartbeat responses appear in the webchat UI via channels.defaults.heartbeat.showOk (defaults to false).
82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
import type { VerboseLevel } from "../auto-reply/thinking.js";
|
|
|
|
export type AgentEventStream = "lifecycle" | "tool" | "assistant" | "error" | (string & {});
|
|
|
|
export type AgentEventPayload = {
|
|
runId: string;
|
|
seq: number;
|
|
stream: AgentEventStream;
|
|
ts: number;
|
|
data: Record<string, unknown>;
|
|
sessionKey?: string;
|
|
};
|
|
|
|
export type AgentRunContext = {
|
|
sessionKey?: string;
|
|
verboseLevel?: VerboseLevel;
|
|
isHeartbeat?: boolean;
|
|
};
|
|
|
|
// Keep per-run counters so streams stay strictly monotonic per runId.
|
|
const seqByRun = new Map<string, number>();
|
|
const listeners = new Set<(evt: AgentEventPayload) => void>();
|
|
const runContextById = new Map<string, AgentRunContext>();
|
|
|
|
export function registerAgentRunContext(runId: string, context: AgentRunContext) {
|
|
if (!runId) return;
|
|
const existing = runContextById.get(runId);
|
|
if (!existing) {
|
|
runContextById.set(runId, { ...context });
|
|
return;
|
|
}
|
|
if (context.sessionKey && existing.sessionKey !== context.sessionKey) {
|
|
existing.sessionKey = context.sessionKey;
|
|
}
|
|
if (context.verboseLevel && existing.verboseLevel !== context.verboseLevel) {
|
|
existing.verboseLevel = context.verboseLevel;
|
|
}
|
|
if (context.isHeartbeat !== undefined && existing.isHeartbeat !== context.isHeartbeat) {
|
|
existing.isHeartbeat = context.isHeartbeat;
|
|
}
|
|
}
|
|
|
|
export function getAgentRunContext(runId: string) {
|
|
return runContextById.get(runId);
|
|
}
|
|
|
|
export function clearAgentRunContext(runId: string) {
|
|
runContextById.delete(runId);
|
|
}
|
|
|
|
export function resetAgentRunContextForTest() {
|
|
runContextById.clear();
|
|
}
|
|
|
|
export function emitAgentEvent(event: Omit<AgentEventPayload, "seq" | "ts">) {
|
|
const nextSeq = (seqByRun.get(event.runId) ?? 0) + 1;
|
|
seqByRun.set(event.runId, nextSeq);
|
|
const context = runContextById.get(event.runId);
|
|
const sessionKey =
|
|
typeof event.sessionKey === "string" && event.sessionKey.trim()
|
|
? event.sessionKey
|
|
: context?.sessionKey;
|
|
const enriched: AgentEventPayload = {
|
|
...event,
|
|
sessionKey,
|
|
seq: nextSeq,
|
|
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);
|
|
}
|