refactor: use per-send run ids for gateway agent

This commit is contained in:
Peter Steinberger
2026-01-04 02:08:52 +01:00
parent fe67073b74
commit 1d06164e18
3 changed files with 14 additions and 9 deletions

View File

@@ -26,6 +26,7 @@
- CI: consolidate checks to avoid redundant installs (#144) — thanks @thewilloftheshadow. - CI: consolidate checks to avoid redundant installs (#144) — thanks @thewilloftheshadow.
- WhatsApp: support `gifPlayback` for MP4 GIF sends via CLI/gateway. - 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: 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: 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. - Auto-reply: fix typing TTL to 2 minutes and log TTL with s/m units.
- Bash tool: default auto-background delay to 10s. - Bash tool: default auto-background delay to 10s.

View File

@@ -61,6 +61,7 @@ type AgentCommandOpts = {
bestEffortDeliver?: boolean; bestEffortDeliver?: boolean;
abortSignal?: AbortSignal; abortSignal?: AbortSignal;
lane?: string; lane?: string;
runId?: string;
}; };
type SessionResolution = { type SessionResolution = {
@@ -209,9 +210,10 @@ export async function agentCommand(
persistedVerbose, persistedVerbose,
} = sessionResolution; } = sessionResolution;
let sessionEntry = resolvedSessionEntry; let sessionEntry = resolvedSessionEntry;
const runId = opts.runId?.trim() || sessionId;
if (sessionKey) { if (sessionKey) {
registerAgentRunContext(sessionId, { sessionKey }); registerAgentRunContext(runId, { sessionKey });
} }
if (opts.deliver === true) { if (opts.deliver === true) {
@@ -349,7 +351,7 @@ export async function agentCommand(
const startedAt = Date.now(); const startedAt = Date.now();
emitAgentEvent({ emitAgentEvent({
runId: sessionId, runId,
stream: "job", stream: "job",
data: { data: {
state: "started", state: "started",
@@ -383,19 +385,19 @@ export async function agentCommand(
thinkLevel: resolvedThinkLevel, thinkLevel: resolvedThinkLevel,
verboseLevel: resolvedVerboseLevel, verboseLevel: resolvedVerboseLevel,
timeoutMs, timeoutMs,
runId: sessionId, runId,
lane: opts.lane, lane: opts.lane,
abortSignal: opts.abortSignal, abortSignal: opts.abortSignal,
onAgentEvent: (evt) => { onAgentEvent: (evt) => {
emitAgentEvent({ emitAgentEvent({
runId: sessionId, runId,
stream: evt.stream, stream: evt.stream,
data: evt.data, data: evt.data,
}); });
}, },
}); });
emitAgentEvent({ emitAgentEvent({
runId: sessionId, runId,
stream: "job", stream: "job",
data: { data: {
state: "done", state: "done",
@@ -409,7 +411,7 @@ export async function agentCommand(
}); });
} catch (err) { } catch (err) {
emitAgentEvent({ emitAgentEvent({
runId: sessionId, runId,
stream: "job", stream: "job",
data: { data: {
state: "error", state: "error",

View File

@@ -57,7 +57,7 @@ import { type DiscordProbe, probeDiscord } from "../discord/probe.js";
import { shouldLogVerbose } from "../globals.js"; import { shouldLogVerbose } from "../globals.js";
import { sendMessageIMessage } from "../imessage/index.js"; import { sendMessageIMessage } from "../imessage/index.js";
import { type IMessageProbe, probeIMessage } from "../imessage/probe.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 type { startNodeBridgeServer } from "../infra/bridge/server.js";
import { getLastHeartbeatEvent } from "../infra/heartbeat-events.js"; import { getLastHeartbeatEvent } from "../infra/heartbeat-events.js";
import { setHeartbeatsEnabled } from "../infra/heartbeat-runner.js"; import { setHeartbeatsEnabled } from "../infra/heartbeat-runner.js";
@@ -2997,15 +2997,16 @@ export async function handleGatewayRequest(
resolvedSessionId = sessionId; resolvedSessionId = sessionId;
const mainKey = (cfg.session?.mainKey ?? "main").trim() || "main"; const mainKey = (cfg.session?.mainKey ?? "main").trim() || "main";
if (requestedSessionKey === mainKey) { if (requestedSessionKey === mainKey) {
addChatRun(sessionId, { addChatRun(idem, {
sessionKey: requestedSessionKey, sessionKey: requestedSessionKey,
clientRunId: idem, clientRunId: idem,
}); });
bestEffortDeliver = true; bestEffortDeliver = true;
} }
registerAgentRunContext(idem, { sessionKey: requestedSessionKey });
} }
const runId = resolvedSessionId || randomUUID(); const runId = idem;
const requestedChannelRaw = const requestedChannelRaw =
typeof params.channel === "string" ? params.channel.trim() : ""; typeof params.channel === "string" ? params.channel.trim() : "";
@@ -3119,6 +3120,7 @@ export async function handleGatewayRequest(
timeout: params.timeout?.toString(), timeout: params.timeout?.toString(),
bestEffortDeliver, bestEffortDeliver,
surface: "VoiceWake", surface: "VoiceWake",
runId,
lane: params.lane, lane: params.lane,
}, },
defaultRuntime, defaultRuntime,