mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
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: add Docs link, remove chat composer divider, and add New session button.
|
||||||
- Control UI: link sessions list to chat view. (#471) — thanks @HazAT
|
- 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: 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: 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: 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
|
- 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 }`.
|
- `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 wait times out: `{ runId, status: "timeout", error }`. Run continues; call `sessions_history` later.
|
||||||
- If the run fails: `{ runId, status: "error", error }`.
|
- 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.
|
- 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.
|
- Agent-to-agent message context is injected for the primary run.
|
||||||
- After the primary run completes, Clawdbot runs a **reply-back loop**:
|
- 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.
|
- `main` is the canonical direct-chat key; global/unknown are hidden.
|
||||||
- `messageLimit > 0` fetches last N messages per session (tool messages filtered).
|
- `messageLimit > 0` fetches last N messages per session (tool messages filtered).
|
||||||
- `sessions_send` waits for final completion when `timeoutSeconds > 0`.
|
- `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` 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_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).
|
- `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"
|
"@sinclair/typebox": "0.34.47"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
"@buape/carbon": "patches/@buape__carbon.patch",
|
||||||
"@mariozechner/pi-agent-core": "patches/@mariozechner__pi-agent-core.patch",
|
"@mariozechner/pi-agent-core": "patches/@mariozechner__pi-agent-core.patch",
|
||||||
"@mariozechner/pi-coding-agent": "patches/@mariozechner__pi-coding-agent.patch",
|
"@mariozechner/pi-coding-agent": "patches/@mariozechner__pi-coding-agent.patch",
|
||||||
"@mariozechner/pi-ai": "patches/@mariozechner__pi-ai.patch"
|
"@mariozechner/pi-ai": "patches/@mariozechner__pi-ai.patch"
|
||||||
@@ -221,6 +222,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
"@buape/carbon": "patches/@buape__carbon.patch",
|
||||||
"@mariozechner/pi-agent-core": "patches/@mariozechner__pi-agent-core.patch",
|
"@mariozechner/pi-agent-core": "patches/@mariozechner__pi-agent-core.patch",
|
||||||
"@mariozechner/pi-coding-agent": "patches/@mariozechner__pi-coding-agent.patch",
|
"@mariozechner/pi-coding-agent": "patches/@mariozechner__pi-coding-agent.patch",
|
||||||
"@mariozechner/pi-ai": "patches/@mariozechner__pi-ai.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",
|
message: "ping",
|
||||||
timeoutSeconds: 0,
|
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));
|
||||||
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({
|
expect(waited.details).toMatchObject({
|
||||||
status: "ok",
|
status: "ok",
|
||||||
reply: "done",
|
reply: "done",
|
||||||
|
delivery: { status: "pending", mode: "announce" },
|
||||||
});
|
});
|
||||||
expect(typeof (waited.details as { runId?: string }).runId).toBe("string");
|
expect(typeof (waited.details as { runId?: string }).runId).toBe("string");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { Type } from "@sinclair/typebox";
|
|||||||
|
|
||||||
import { loadConfig } from "../../config/config.js";
|
import { loadConfig } from "../../config/config.js";
|
||||||
import { callGateway } from "../../gateway/call.js";
|
import { callGateway } from "../../gateway/call.js";
|
||||||
|
import { formatErrorMessage } from "../../infra/errors.js";
|
||||||
|
import { createSubsystemLogger } from "../../logging.js";
|
||||||
import {
|
import {
|
||||||
isSubagentSessionKey,
|
isSubagentSessionKey,
|
||||||
normalizeAgentId,
|
normalizeAgentId,
|
||||||
@@ -35,6 +37,8 @@ import {
|
|||||||
resolvePingPongTurns,
|
resolvePingPongTurns,
|
||||||
} from "./sessions-send-helpers.js";
|
} from "./sessions-send-helpers.js";
|
||||||
|
|
||||||
|
const log = createSubsystemLogger("agents/sessions-send");
|
||||||
|
|
||||||
const SessionsSendToolSchema = Type.Object({
|
const SessionsSendToolSchema = Type.Object({
|
||||||
sessionKey: Type.Optional(Type.String()),
|
sessionKey: Type.Optional(Type.String()),
|
||||||
label: Type.Optional(
|
label: Type.Optional(
|
||||||
@@ -308,11 +312,13 @@ export function createSessionsSendTool(opts?: {
|
|||||||
const requesterSessionKey = opts?.agentSessionKey;
|
const requesterSessionKey = opts?.agentSessionKey;
|
||||||
const requesterProvider = opts?.agentProvider;
|
const requesterProvider = opts?.agentProvider;
|
||||||
const maxPingPongTurns = resolvePingPongTurns(cfg);
|
const maxPingPongTurns = resolvePingPongTurns(cfg);
|
||||||
|
const delivery = { status: "pending", mode: "announce" as const };
|
||||||
|
|
||||||
const runAgentToAgentFlow = async (
|
const runAgentToAgentFlow = async (
|
||||||
roundOneReply?: string,
|
roundOneReply?: string,
|
||||||
runInfo?: { runId: string },
|
runInfo?: { runId: string },
|
||||||
) => {
|
) => {
|
||||||
|
const runContextId = runInfo?.runId ?? runId;
|
||||||
try {
|
try {
|
||||||
let primaryReply = roundOneReply;
|
let primaryReply = roundOneReply;
|
||||||
let latestReply = roundOneReply;
|
let latestReply = roundOneReply;
|
||||||
@@ -400,20 +406,32 @@ export function createSessionsSendTool(opts?: {
|
|||||||
announceReply.trim() &&
|
announceReply.trim() &&
|
||||||
!isAnnounceSkip(announceReply)
|
!isAnnounceSkip(announceReply)
|
||||||
) {
|
) {
|
||||||
await callGateway({
|
try {
|
||||||
method: "send",
|
await callGateway({
|
||||||
params: {
|
method: "send",
|
||||||
to: announceTarget.to,
|
params: {
|
||||||
message: announceReply.trim(),
|
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,
|
provider: announceTarget.provider,
|
||||||
accountId: announceTarget.accountId,
|
to: announceTarget.to,
|
||||||
idempotencyKey: crypto.randomUUID(),
|
error: formatErrorMessage(err),
|
||||||
},
|
});
|
||||||
timeoutMs: 10_000,
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
// Best-effort follow-ups; ignore failures to avoid breaking the caller response.
|
log.warn("sessions_send announce flow failed", {
|
||||||
|
runId: runContextId,
|
||||||
|
error: formatErrorMessage(err),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -432,6 +450,7 @@ export function createSessionsSendTool(opts?: {
|
|||||||
runId,
|
runId,
|
||||||
status: "accepted",
|
status: "accepted",
|
||||||
sessionKey: displayKey,
|
sessionKey: displayKey,
|
||||||
|
delivery,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const messageText =
|
const messageText =
|
||||||
@@ -535,6 +554,7 @@ export function createSessionsSendTool(opts?: {
|
|||||||
status: "ok",
|
status: "ok",
|
||||||
reply,
|
reply,
|
||||||
sessionKey: displayKey,
|
sessionKey: displayKey,
|
||||||
|
delivery,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user