fix: clarify sessions_send delivery semantics
This commit is contained in:
@@ -98,6 +98,7 @@
|
||||
- Control UI: add Docs link, remove chat composer divider, and add New session button.
|
||||
- Control UI: link sessions list to chat view. (#471) — thanks @HazAT
|
||||
- Sessions: support session `label` in store/list/UI and allow `sessions_send` lookup by label. (#570) — thanks @azade-c
|
||||
- Sessions: clarify `sessions_send` delivery semantics, log announce failures, and enforce Discord request timeouts. (#507) — thanks @steipete
|
||||
- Control UI: show/patch per-session reasoning level and render extracted reasoning in chat.
|
||||
- Control UI: queue outgoing chat messages, add Enter-to-send, and show queued items. (#527) — thanks @YuriNachos
|
||||
- Control UI: refactor chat layout with tool sidebar, grouped messages, and nav improvements. (#475) — thanks @rahthakor
|
||||
|
||||
@@ -76,6 +76,7 @@ Behavior:
|
||||
- `timeoutSeconds > 0`: wait up to N seconds for completion, then return `{ runId, status: "ok", reply }`.
|
||||
- If wait times out: `{ runId, status: "timeout", error }`. Run continues; call `sessions_history` later.
|
||||
- If the run fails: `{ runId, status: "error", error }`.
|
||||
- Announce delivery runs after the primary run completes and is best-effort; `status: "ok"` does not guarantee the announce was delivered.
|
||||
- Waits via gateway `agent.wait` (server-side) so reconnects don't drop the wait.
|
||||
- Agent-to-agent message context is injected for the primary run.
|
||||
- After the primary run completes, Clawdbot runs a **reply-back loop**:
|
||||
|
||||
@@ -206,6 +206,7 @@ Notes:
|
||||
- `main` is the canonical direct-chat key; global/unknown are hidden.
|
||||
- `messageLimit > 0` fetches last N messages per session (tool messages filtered).
|
||||
- `sessions_send` waits for final completion when `timeoutSeconds > 0`.
|
||||
- Delivery/announce happens after completion and is best-effort; `status: "ok"` confirms the agent run finished, not that the announce was delivered.
|
||||
- `sessions_spawn` starts a sub-agent run and posts an announce reply back to the requester chat.
|
||||
- `sessions_spawn` is non-blocking and returns `status: "accepted"` immediately.
|
||||
- `sessions_send` runs a reply‑back ping‑pong (reply `REPLY_SKIP` to stop; max turns via `session.agentToAgent.maxPingPongTurns`, 0–5).
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
"@sinclair/typebox": "0.34.47"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@buape/carbon": "patches/@buape__carbon.patch",
|
||||
"@mariozechner/pi-agent-core": "patches/@mariozechner__pi-agent-core.patch",
|
||||
"@mariozechner/pi-coding-agent": "patches/@mariozechner__pi-coding-agent.patch",
|
||||
"@mariozechner/pi-ai": "patches/@mariozechner__pi-ai.patch"
|
||||
@@ -221,6 +222,7 @@
|
||||
]
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@buape/carbon": "patches/@buape__carbon.patch",
|
||||
"@mariozechner/pi-agent-core": "patches/@mariozechner__pi-agent-core.patch",
|
||||
"@mariozechner/pi-coding-agent": "patches/@mariozechner__pi-coding-agent.patch",
|
||||
"@mariozechner/pi-ai": "patches/@mariozechner__pi-ai.patch",
|
||||
|
||||
53
patches/@buape__carbon.patch
Normal file
53
patches/@buape__carbon.patch
Normal file
@@ -0,0 +1,53 @@
|
||||
--- a/dist/src/classes/RequestClient.js
|
||||
+++ b/dist/src/classes/RequestClient.js
|
||||
@@ -118,6 +118,9 @@
|
||||
}
|
||||
}
|
||||
this.abortController = new AbortController();
|
||||
+ const timeoutMs = typeof this.options.timeout === "number" && this.options.timeout > 0
|
||||
+ ? this.options.timeout
|
||||
+ : undefined;
|
||||
let body;
|
||||
if (data?.body &&
|
||||
typeof data.body === "object" &&
|
||||
@@ -178,12 +181,26 @@
|
||||
body = JSON.stringify(data.body);
|
||||
}
|
||||
}
|
||||
- const response = await fetch(url, {
|
||||
- method,
|
||||
- headers,
|
||||
- body,
|
||||
- signal: this.abortController.signal
|
||||
- });
|
||||
+ let timeoutId;
|
||||
+ if (timeoutMs !== undefined) {
|
||||
+ timeoutId = setTimeout(() => {
|
||||
+ this.abortController?.abort();
|
||||
+ }, timeoutMs);
|
||||
+ }
|
||||
+ let response;
|
||||
+ try {
|
||||
+ response = await fetch(url, {
|
||||
+ method,
|
||||
+ headers,
|
||||
+ body,
|
||||
+ signal: this.abortController.signal
|
||||
+ });
|
||||
+ }
|
||||
+ finally {
|
||||
+ if (timeoutId) {
|
||||
+ clearTimeout(timeoutId);
|
||||
+ }
|
||||
+ }
|
||||
let rawBody = "";
|
||||
let parsedBody;
|
||||
try {
|
||||
@@ -405,4 +422,4 @@
|
||||
}
|
||||
}
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(ms, 0)));
|
||||
-//# sourceMappingURL=RequestClient.js.map
|
||||
\ No newline at end of file
|
||||
+//# sourceMappingURL=RequestClient.js.map
|
||||
|
||||
@@ -243,7 +243,11 @@ describe("sessions tools", () => {
|
||||
message: "ping",
|
||||
timeoutSeconds: 0,
|
||||
});
|
||||
expect(fire.details).toMatchObject({ status: "accepted", runId: "run-1" });
|
||||
expect(fire.details).toMatchObject({
|
||||
status: "accepted",
|
||||
runId: "run-1",
|
||||
delivery: { status: "pending", mode: "announce" },
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
@@ -256,6 +260,7 @@ describe("sessions tools", () => {
|
||||
expect(waited.details).toMatchObject({
|
||||
status: "ok",
|
||||
reply: "done",
|
||||
delivery: { status: "pending", mode: "announce" },
|
||||
});
|
||||
expect(typeof (waited.details as { runId?: string }).runId).toBe("string");
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Type } from "@sinclair/typebox";
|
||||
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { createSubsystemLogger } from "../../logging.js";
|
||||
import {
|
||||
isSubagentSessionKey,
|
||||
normalizeAgentId,
|
||||
@@ -35,6 +37,8 @@ import {
|
||||
resolvePingPongTurns,
|
||||
} from "./sessions-send-helpers.js";
|
||||
|
||||
const log = createSubsystemLogger("agents/sessions-send");
|
||||
|
||||
const SessionsSendToolSchema = Type.Object({
|
||||
sessionKey: Type.Optional(Type.String()),
|
||||
label: Type.Optional(
|
||||
@@ -308,11 +312,13 @@ export function createSessionsSendTool(opts?: {
|
||||
const requesterSessionKey = opts?.agentSessionKey;
|
||||
const requesterProvider = opts?.agentProvider;
|
||||
const maxPingPongTurns = resolvePingPongTurns(cfg);
|
||||
const delivery = { status: "pending", mode: "announce" as const };
|
||||
|
||||
const runAgentToAgentFlow = async (
|
||||
roundOneReply?: string,
|
||||
runInfo?: { runId: string },
|
||||
) => {
|
||||
const runContextId = runInfo?.runId ?? runId;
|
||||
try {
|
||||
let primaryReply = roundOneReply;
|
||||
let latestReply = roundOneReply;
|
||||
@@ -400,20 +406,32 @@ export function createSessionsSendTool(opts?: {
|
||||
announceReply.trim() &&
|
||||
!isAnnounceSkip(announceReply)
|
||||
) {
|
||||
await callGateway({
|
||||
method: "send",
|
||||
params: {
|
||||
to: announceTarget.to,
|
||||
message: announceReply.trim(),
|
||||
try {
|
||||
await callGateway({
|
||||
method: "send",
|
||||
params: {
|
||||
to: announceTarget.to,
|
||||
message: announceReply.trim(),
|
||||
provider: announceTarget.provider,
|
||||
accountId: announceTarget.accountId,
|
||||
idempotencyKey: crypto.randomUUID(),
|
||||
},
|
||||
timeoutMs: 10_000,
|
||||
});
|
||||
} catch (err) {
|
||||
log.warn("sessions_send announce delivery failed", {
|
||||
runId: runContextId,
|
||||
provider: announceTarget.provider,
|
||||
accountId: announceTarget.accountId,
|
||||
idempotencyKey: crypto.randomUUID(),
|
||||
},
|
||||
timeoutMs: 10_000,
|
||||
});
|
||||
to: announceTarget.to,
|
||||
error: formatErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Best-effort follow-ups; ignore failures to avoid breaking the caller response.
|
||||
} catch (err) {
|
||||
log.warn("sessions_send announce flow failed", {
|
||||
runId: runContextId,
|
||||
error: formatErrorMessage(err),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -432,6 +450,7 @@ export function createSessionsSendTool(opts?: {
|
||||
runId,
|
||||
status: "accepted",
|
||||
sessionKey: displayKey,
|
||||
delivery,
|
||||
});
|
||||
} catch (err) {
|
||||
const messageText =
|
||||
@@ -535,6 +554,7 @@ export function createSessionsSendTool(opts?: {
|
||||
status: "ok",
|
||||
reply,
|
||||
sessionKey: displayKey,
|
||||
delivery,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user