mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
Add opt-in ACP commentary relay
This commit is contained in:
@@ -1178,6 +1178,7 @@ Notes:
|
||||
hiddenBoundarySeparator: "paragraph", // none | space | newline | paragraph
|
||||
maxOutputChars: 50000,
|
||||
maxSessionUpdateChars: 500,
|
||||
assistantCommentary: false,
|
||||
},
|
||||
|
||||
runtime: {
|
||||
@@ -1201,6 +1202,7 @@ Notes:
|
||||
- `stream.hiddenBoundarySeparator`: separator before visible text after hidden tool events (default: `"paragraph"`).
|
||||
- `stream.maxOutputChars`: maximum assistant output characters projected per ACP turn.
|
||||
- `stream.maxSessionUpdateChars`: maximum characters for projected ACP status/update lines.
|
||||
- `stream.assistantCommentary`: when `true`, relay assistant commentary/progress text into ACP parent stream updates. Defaults to `false`.
|
||||
- `stream.tagVisibility`: record of tag names to boolean visibility overrides for streamed events.
|
||||
- `runtime.ttlMinutes`: idle TTL in minutes for ACP session workers before eligible cleanup.
|
||||
- `runtime.installCommand`: optional install command to run when bootstrapping an ACP runtime environment.
|
||||
|
||||
@@ -548,6 +548,9 @@ Two ways to start an ACP session:
|
||||
requester session as system events. Accepted responses include
|
||||
`streamLogPath` pointing to a session-scoped JSONL log
|
||||
(`<sessionId>.acp-stream.jsonl`) you can tail for full relay history.
|
||||
Assistant commentary/progress text is hidden by default; set
|
||||
`acp.stream.assistantCommentary: true` to include it in parent stream
|
||||
updates while keeping final-answer delivery unchanged.
|
||||
</ParamField>
|
||||
|
||||
ACP `sessions_spawn` runs use `agents.defaults.subagents.runTimeoutSeconds` for
|
||||
|
||||
@@ -485,6 +485,83 @@ describe("startAcpSpawnParentStreamRelay", () => {
|
||||
relay.dispose();
|
||||
});
|
||||
|
||||
it("relays commentary-phase assistant text when enabled", () => {
|
||||
const relay = startAcpSpawnParentStreamRelay({
|
||||
runId: "run-commentary-enabled",
|
||||
parentSessionKey: "agent:main:main",
|
||||
childSessionKey: "agent:codex:acp:child-commentary-enabled",
|
||||
agentId: "codex",
|
||||
streamFlushMs: 10,
|
||||
noOutputNoticeMs: 120_000,
|
||||
assistantCommentary: true,
|
||||
});
|
||||
|
||||
emitAgentEvent({
|
||||
runId: "run-commentary-enabled",
|
||||
stream: "assistant",
|
||||
data: {
|
||||
delta: "checking thread context; then post a tight progress reply here.",
|
||||
phase: "commentary",
|
||||
},
|
||||
});
|
||||
vi.advanceTimersByTime(15);
|
||||
|
||||
const texts = collectedTexts();
|
||||
expectTextWithFragment(
|
||||
texts,
|
||||
"codex: checking thread context; then post a tight progress reply here.",
|
||||
);
|
||||
relay.dispose();
|
||||
});
|
||||
|
||||
it("classifies opted-in commentary as visible output for stall notices", () => {
|
||||
const relay = startAcpSpawnParentStreamRelay({
|
||||
runId: "run-commentary-visible-stall",
|
||||
parentSessionKey: "agent:main:main",
|
||||
childSessionKey: "agent:codex:acp:child-commentary-visible-stall",
|
||||
agentId: "codex",
|
||||
streamFlushMs: 1,
|
||||
noOutputNoticeMs: 1_000,
|
||||
noOutputPollMs: 250,
|
||||
assistantCommentary: true,
|
||||
});
|
||||
|
||||
emitAgentEvent({
|
||||
runId: "run-commentary-visible-stall",
|
||||
stream: "acp",
|
||||
data: {
|
||||
phase: "prompt_submitted",
|
||||
at: Date.now(),
|
||||
proxyEnvKeys: [],
|
||||
},
|
||||
});
|
||||
emitAgentEvent({
|
||||
runId: "run-commentary-visible-stall",
|
||||
stream: "acp",
|
||||
data: {
|
||||
phase: "runtime_event",
|
||||
eventType: "status",
|
||||
text: "connecting to upstream",
|
||||
},
|
||||
});
|
||||
emitAgentEvent({
|
||||
runId: "run-commentary-visible-stall",
|
||||
stream: "assistant",
|
||||
data: {
|
||||
delta: "checking active files before patching.",
|
||||
phase: "commentary",
|
||||
},
|
||||
});
|
||||
vi.advanceTimersByTime(5);
|
||||
vi.advanceTimersByTime(1_500);
|
||||
|
||||
const texts = collectedTexts();
|
||||
expectTextWithFragment(texts, "codex: checking active files before patching.");
|
||||
expectNoTextWithFragment(texts, "has ACP runtime activity but no visible assistant output");
|
||||
expectTextWithFragment(texts, "has produced no visible output for 1s");
|
||||
relay.dispose();
|
||||
});
|
||||
|
||||
it("still relays final_answer assistant text after suppressed commentary", () => {
|
||||
const relay = startAcpSpawnParentStreamRelay({
|
||||
runId: "run-final",
|
||||
|
||||
@@ -117,6 +117,7 @@ export function startAcpSpawnParentStreamRelay(params: {
|
||||
noOutputPollMs?: number;
|
||||
maxRelayLifetimeMs?: number;
|
||||
emitStartNotice?: boolean;
|
||||
assistantCommentary?: boolean;
|
||||
}): AcpSpawnParentRelayHandle {
|
||||
const runId = normalizeOptionalString(params.runId) ?? "";
|
||||
const parentSessionKey = normalizeOptionalString(params.parentSessionKey) ?? "";
|
||||
@@ -209,6 +210,7 @@ export function startAcpSpawnParentStreamRelay(params: {
|
||||
});
|
||||
};
|
||||
const shouldSurfaceUpdates = params.surfaceUpdates !== false;
|
||||
const shouldRelayAssistantCommentary = params.assistantCommentary === true;
|
||||
const eventRouting = params.eventRouting ?? {
|
||||
mainKey: params.mainKey,
|
||||
sessionScope: params.sessionScope,
|
||||
@@ -398,7 +400,7 @@ export function startAcpSpawnParentStreamRelay(params: {
|
||||
...(assistantPhase ? { phase: assistantPhase } : {}),
|
||||
});
|
||||
|
||||
if (assistantPhase === "commentary") {
|
||||
if (assistantPhase === "commentary" && !shouldRelayAssistantCommentary) {
|
||||
lastProgressAt = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1551,6 +1551,7 @@ export async function spawnAcpDirect(
|
||||
const parentEventRouting = parentSessionKey
|
||||
? resolveEventSessionRoutingPolicy({ cfg, sessionKey: parentSessionKey })
|
||||
: undefined;
|
||||
const assistantCommentary = cfg.acp?.stream?.assistantCommentary === true;
|
||||
if (effectiveStreamToParent && parentSessionKey) {
|
||||
// Register relay before dispatch so fast lifecycle failures are not missed.
|
||||
parentRelay = startAcpSpawnParentStreamRelay({
|
||||
@@ -1564,6 +1565,7 @@ export async function spawnAcpDirect(
|
||||
logPath: streamLogPath,
|
||||
deliveryContext: parentDeliveryCtx,
|
||||
emitStartNotice: false,
|
||||
assistantCommentary,
|
||||
});
|
||||
}
|
||||
const gatewayAttachments = toGatewayImageAttachments(params.attachments);
|
||||
@@ -1623,6 +1625,7 @@ export async function spawnAcpDirect(
|
||||
logPath: streamLogPath,
|
||||
deliveryContext: parentDeliveryCtx,
|
||||
emitStartNotice: false,
|
||||
assistantCommentary,
|
||||
});
|
||||
}
|
||||
parentRelay?.notifyStarted();
|
||||
|
||||
@@ -220,6 +220,8 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Maximum assistant output characters projected per ACP turn before truncation notice is emitted.",
|
||||
"acp.stream.maxSessionUpdateChars":
|
||||
"Maximum characters for projected ACP session/update lines (tool/status updates).",
|
||||
"acp.stream.assistantCommentary":
|
||||
"When true, relay assistant commentary/progress text into ACP parent stream updates. Defaults off.",
|
||||
"acp.stream.tagVisibility":
|
||||
"Per-sessionUpdate visibility overrides for ACP projection (for example usage_update, available_commands_update).",
|
||||
"acp.runtime.ttlMinutes":
|
||||
|
||||
@@ -560,6 +560,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"acp.stream.hiddenBoundarySeparator": "ACP Stream Hidden Boundary Separator",
|
||||
"acp.stream.maxOutputChars": "ACP Stream Max Output Chars",
|
||||
"acp.stream.maxSessionUpdateChars": "ACP Stream Max Session Update Chars",
|
||||
"acp.stream.assistantCommentary": "ACP Stream Assistant Commentary",
|
||||
"acp.stream.tagVisibility": "ACP Stream Tag Visibility",
|
||||
"acp.runtime.ttlMinutes": "ACP Runtime TTL (minutes)",
|
||||
"acp.runtime.installCommand": "ACP Runtime Install Command",
|
||||
|
||||
@@ -21,6 +21,8 @@ export type AcpStreamConfig = {
|
||||
maxOutputChars?: number;
|
||||
/** Maximum visible characters for projected session/update lines. */
|
||||
maxSessionUpdateChars?: number;
|
||||
/** Relay assistant commentary/progress text into ACP parent stream updates. */
|
||||
assistantCommentary?: boolean;
|
||||
/**
|
||||
* Per-sessionUpdate visibility overrides.
|
||||
* Keys not listed here fall back to OpenClaw defaults.
|
||||
|
||||
@@ -778,6 +778,7 @@ export const OpenClawSchema = z
|
||||
.optional(),
|
||||
maxOutputChars: z.number().int().positive().optional(),
|
||||
maxSessionUpdateChars: z.number().int().positive().optional(),
|
||||
assistantCommentary: z.boolean().optional(),
|
||||
tagVisibility: z.record(z.string(), z.boolean()).optional(),
|
||||
})
|
||||
.strict()
|
||||
|
||||
Reference in New Issue
Block a user