From 1d06164e18328de97778fe068e346635fc9b5f3b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 4 Jan 2026 02:08:52 +0100 Subject: [PATCH] refactor: use per-send run ids for gateway agent --- CHANGELOG.md | 1 + src/commands/agent.ts | 14 ++++++++------ src/gateway/server-methods.ts | 8 +++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 403ec062a..4b699b44d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - CI: consolidate checks to avoid redundant installs (#144) — thanks @thewilloftheshadow. - WhatsApp: support `gifPlayback` for MP4 GIF sends via CLI/gateway. - Sessions: prevent `sessions_send` timeouts by running nested agent turns on a separate lane. +- Sessions: use per-send run IDs for gateway agent calls to avoid wait collisions. - Auto-reply: drop final payloads when block streaming to avoid duplicate Discord sends. - Auto-reply: fix typing TTL to 2 minutes and log TTL with s/m units. - Bash tool: default auto-background delay to 10s. diff --git a/src/commands/agent.ts b/src/commands/agent.ts index 83ca4e1f9..4e30b6cb6 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -61,6 +61,7 @@ type AgentCommandOpts = { bestEffortDeliver?: boolean; abortSignal?: AbortSignal; lane?: string; + runId?: string; }; type SessionResolution = { @@ -209,9 +210,10 @@ export async function agentCommand( persistedVerbose, } = sessionResolution; let sessionEntry = resolvedSessionEntry; + const runId = opts.runId?.trim() || sessionId; if (sessionKey) { - registerAgentRunContext(sessionId, { sessionKey }); + registerAgentRunContext(runId, { sessionKey }); } if (opts.deliver === true) { @@ -349,7 +351,7 @@ export async function agentCommand( const startedAt = Date.now(); emitAgentEvent({ - runId: sessionId, + runId, stream: "job", data: { state: "started", @@ -383,19 +385,19 @@ export async function agentCommand( thinkLevel: resolvedThinkLevel, verboseLevel: resolvedVerboseLevel, timeoutMs, - runId: sessionId, + runId, lane: opts.lane, abortSignal: opts.abortSignal, onAgentEvent: (evt) => { emitAgentEvent({ - runId: sessionId, + runId, stream: evt.stream, data: evt.data, }); }, }); emitAgentEvent({ - runId: sessionId, + runId, stream: "job", data: { state: "done", @@ -409,7 +411,7 @@ export async function agentCommand( }); } catch (err) { emitAgentEvent({ - runId: sessionId, + runId, stream: "job", data: { state: "error", diff --git a/src/gateway/server-methods.ts b/src/gateway/server-methods.ts index ca4af6440..acbeb4e36 100644 --- a/src/gateway/server-methods.ts +++ b/src/gateway/server-methods.ts @@ -57,7 +57,7 @@ import { type DiscordProbe, probeDiscord } from "../discord/probe.js"; import { shouldLogVerbose } from "../globals.js"; import { sendMessageIMessage } from "../imessage/index.js"; import { type IMessageProbe, probeIMessage } from "../imessage/probe.js"; -import { onAgentEvent } from "../infra/agent-events.js"; +import { onAgentEvent, registerAgentRunContext } from "../infra/agent-events.js"; import type { startNodeBridgeServer } from "../infra/bridge/server.js"; import { getLastHeartbeatEvent } from "../infra/heartbeat-events.js"; import { setHeartbeatsEnabled } from "../infra/heartbeat-runner.js"; @@ -2997,15 +2997,16 @@ export async function handleGatewayRequest( resolvedSessionId = sessionId; const mainKey = (cfg.session?.mainKey ?? "main").trim() || "main"; if (requestedSessionKey === mainKey) { - addChatRun(sessionId, { + addChatRun(idem, { sessionKey: requestedSessionKey, clientRunId: idem, }); bestEffortDeliver = true; } + registerAgentRunContext(idem, { sessionKey: requestedSessionKey }); } - const runId = resolvedSessionId || randomUUID(); + const runId = idem; const requestedChannelRaw = typeof params.channel === "string" ? params.channel.trim() : ""; @@ -3119,6 +3120,7 @@ export async function handleGatewayRequest( timeout: params.timeout?.toString(), bestEffortDeliver, surface: "VoiceWake", + runId, lane: params.lane, }, defaultRuntime,