@oh-my-pi/pi-coding-agent 17.2.13 → 17.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/{CHANGELOG-d8xh7keh.md → CHANGELOG-fk29zh06.md} +30 -0
- package/dist/cli.js +5114 -5417
- package/dist/types/cleanse/agent.d.ts +3 -0
- package/dist/types/cleanse/checkers.d.ts +21 -1
- package/dist/types/cleanse/index.d.ts +4 -0
- package/dist/types/cleanse/parsers.d.ts +3 -1
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/cleanse-picker.d.ts +17 -0
- package/dist/types/cli/command-help.d.ts +3 -0
- package/dist/types/cli/progress-reporter.d.ts +19 -0
- package/dist/types/commands/cleanse.d.ts +11 -0
- package/dist/types/commands/compress.d.ts +37 -0
- package/dist/types/commands/launch-help.d.ts +3 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/compress/index.d.ts +27 -0
- package/dist/types/compress/protocol.d.ts +49 -0
- package/dist/types/compress/session.d.ts +15 -0
- package/dist/types/compress/types.d.ts +54 -0
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/internal-urls/local-protocol.d.ts +8 -0
- package/dist/types/mcp/transports/header-policy.d.ts +8 -0
- package/dist/types/mcp/transports/http.d.ts +2 -0
- package/dist/types/mcp/types.d.ts +19 -0
- package/dist/types/modes/components/late-diagnostics-message.d.ts +2 -0
- package/dist/types/modes/components/todo-reminder.d.ts +2 -0
- package/dist/types/modes/components/tool-activity.d.ts +17 -0
- package/dist/types/modes/components/transcript-container.d.ts +2 -0
- package/dist/types/modes/components/ttsr-notification.d.ts +2 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +2 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +3 -1
- package/dist/types/session/agent-session-types.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/session-tools.d.ts +13 -0
- package/dist/types/tools/builtin-names.d.ts +1 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/think.d.ts +41 -0
- package/dist/types/utils/zip.d.ts +19 -9
- package/package.json +13 -13
- package/src/cleanse/agent.ts +67 -2
- package/src/cleanse/checkers.ts +252 -21
- package/src/cleanse/index.ts +90 -36
- package/src/cleanse/parsers.ts +243 -20
- package/src/cleanse/prompts/discovery.md +74 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/cleanse-picker.ts +86 -0
- package/src/cli/command-help.ts +4 -0
- package/src/cli/flag-tables.ts +1 -0
- package/src/cli/gallery-fixtures/agentic.ts +16 -0
- package/src/{cleanse/progress.ts → cli/progress-reporter.ts} +12 -7
- package/src/cli-commands.ts +5 -0
- package/src/commands/cleanse.ts +19 -4
- package/src/commands/compress.ts +45 -0
- package/src/commands/launch-help.ts +3 -0
- package/src/commit/agentic/prompts/analyze-file.md +6 -6
- package/src/commit/agentic/prompts/session-user.md +4 -4
- package/src/compress/index.ts +318 -0
- package/src/compress/prompts/request.md +11 -0
- package/src/compress/prompts/review.md +17 -0
- package/src/compress/prompts/system.md +81 -0
- package/src/compress/protocol.ts +210 -0
- package/src/compress/session.ts +72 -0
- package/src/compress/types.ts +59 -0
- package/src/config/settings-schema.ts +11 -0
- package/src/discovery/builtin-rules/go-add-cleanup.md +8 -8
- package/src/discovery/builtin-rules/go-exp-promoted.md +8 -8
- package/src/discovery/builtin-rules/go-ioutil.md +12 -12
- package/src/discovery/builtin-rules/go-new-expr.md +8 -8
- package/src/discovery/builtin-rules/go-range-int.md +5 -5
- package/src/discovery/builtin-rules/rs-box-leak.md +7 -7
- package/src/discovery/builtin-rules/rs-future-prelude.md +4 -4
- package/src/discovery/builtin-rules/rs-parking-lot.md +6 -6
- package/src/discovery/builtin-rules/ts-no-any.md +5 -5
- package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +8 -8
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +13 -14
- package/src/discovery/builtin-rules/ts-no-local-is-record.md +7 -7
- package/src/discovery/builtin-rules/ts-no-test-timers.md +3 -9
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +7 -7
- package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +2 -2
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +3 -9
- package/src/discovery/builtin-rules/ts-set-map.md +2 -4
- package/src/internal-urls/local-protocol.ts +42 -0
- package/src/live/prompts/live-instructions.md +10 -10
- package/src/main.ts +4 -0
- package/src/mcp/client.ts +7 -3
- package/src/mcp/transports/header-policy.ts +28 -0
- package/src/mcp/transports/http.ts +200 -35
- package/src/mcp/types.ts +21 -0
- package/src/modes/components/chat-transcript-builder.ts +17 -5
- package/src/modes/components/late-diagnostics-message.ts +12 -1
- package/src/modes/components/todo-reminder.ts +12 -0
- package/src/modes/components/tool-activity.ts +45 -0
- package/src/modes/components/transcript-container.ts +16 -0
- package/src/modes/components/ttsr-notification.ts +12 -0
- package/src/modes/controllers/event-controller.ts +3 -6
- package/src/modes/controllers/input-controller.ts +6 -6
- package/src/modes/controllers/selector-controller.ts +8 -6
- package/src/modes/interactive-mode.ts +5 -40
- package/src/modes/print-mode.ts +13 -54
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/transcript-render-helpers.ts +3 -2
- package/src/modes/utils/ui-helpers.ts +17 -7
- package/src/prompts/advisor/active-repo-watchdog.md +3 -4
- package/src/prompts/advisor/advise-tool.md +3 -3
- package/src/prompts/advisor/context-files.md +1 -1
- package/src/prompts/advisor/system.md +45 -65
- package/src/prompts/agents/designer.md +44 -44
- package/src/prompts/agents/init.md +18 -18
- package/src/prompts/agents/librarian.md +31 -31
- package/src/prompts/agents/reviewer.md +34 -39
- package/src/prompts/agents/security-reviewer.md +3 -5
- package/src/prompts/agents/task.md +11 -12
- package/src/prompts/bench.md +2 -5
- package/src/prompts/ci-green-request.md +13 -15
- package/src/prompts/dry-balance-bench.md +5 -5
- package/src/prompts/goals/goal-budget-limit.md +4 -5
- package/src/prompts/goals/goal-continuation.md +11 -11
- package/src/prompts/goals/goal-mode-active.md +7 -7
- package/src/prompts/goals/goal-todo-context.md +2 -2
- package/src/prompts/goals/guided-goal-interview.md +18 -24
- package/src/prompts/memories/read-path.md +9 -9
- package/src/prompts/memories/stage_one_system.md +9 -11
- package/src/prompts/review-custom-request.md +8 -11
- package/src/prompts/review-headless-request.md +3 -10
- package/src/prompts/security/scan-coordinator.md +6 -5
- package/src/prompts/security/validate-request.md +4 -7
- package/src/prompts/skills/user-invocation.md +2 -2
- package/src/prompts/steering/parent-irc.md +1 -1
- package/src/prompts/steering/user-interjection.md +1 -3
- package/src/prompts/system/active-repo-context.md +4 -2
- package/src/prompts/system/agent-creation-architect.md +22 -37
- package/src/prompts/system/agent-creation-user.md +3 -3
- package/src/prompts/system/auto-continue.md +1 -1
- package/src/prompts/system/auto-thinking-difficulty-local.md +5 -7
- package/src/prompts/system/auto-thinking-difficulty.md +8 -10
- package/src/prompts/system/autolearn-guidance-learn.md +2 -1
- package/src/prompts/system/autolearn-guidance.md +5 -4
- package/src/prompts/system/autolearn-nudge-autocontinue.md +3 -3
- package/src/prompts/system/background-tan-dispatch.md +3 -3
- package/src/prompts/system/btw-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +8 -6
- package/src/prompts/system/eager-task.md +3 -3
- package/src/prompts/system/empty-stop-retry.md +1 -1
- package/src/prompts/system/gemini-tool-call-reminder.md +5 -5
- package/src/prompts/system/interrupted-thinking.md +3 -3
- package/src/prompts/system/irc-autoreply.md +1 -1
- package/src/prompts/system/irc-incoming.md +3 -3
- package/src/prompts/system/manual-continue.md +3 -3
- package/src/prompts/system/mcp-xdev-guidance.md +2 -2
- package/src/prompts/system/memory-consolidation-system.md +2 -2
- package/src/prompts/system/mid-run-todo-nudge.md +1 -1
- package/src/prompts/system/orchestrate-notice.md +25 -25
- package/src/prompts/system/personalities/default.md +9 -9
- package/src/prompts/system/personalities/friendly.md +11 -11
- package/src/prompts/system/personalities/pragmatic.md +8 -8
- package/src/prompts/system/plan-mode-active.md +61 -64
- package/src/prompts/system/plan-mode-approved.md +10 -11
- package/src/prompts/system/plan-mode-compact-instructions.md +11 -11
- package/src/prompts/system/plan-mode-reference.md +5 -5
- package/src/prompts/system/plan-yolo-handoff.md +2 -2
- package/src/prompts/system/prewalk-checklist.md +5 -5
- package/src/prompts/system/prewalk-continue.md +1 -1
- package/src/prompts/system/prewalk-plan.md +8 -9
- package/src/prompts/system/project-prompt.md +11 -12
- package/src/prompts/system/recap-user.md +1 -1
- package/src/prompts/system/resolve-device-reminder.md +1 -1
- package/src/prompts/system/rewind-report.md +3 -3
- package/src/prompts/system/side-channel-no-tools.md +3 -1
- package/src/prompts/system/snapcompact-context-stub.md +1 -1
- package/src/prompts/system/snapcompact-system-frames-note.md +1 -1
- package/src/prompts/system/snapcompact-system-stub.md +1 -1
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -1
- package/src/prompts/system/speech-rewrite.md +11 -13
- package/src/prompts/system/subagent-async-pending.md +5 -5
- package/src/prompts/system/subagent-system-prompt.md +5 -15
- package/src/prompts/system/subagent-user-prompt.md +1 -1
- package/src/prompts/system/subagent-yield-reminder.md +12 -12
- package/src/prompts/system/system-prompt.md +110 -136
- package/src/prompts/system/tan-context-switch.md +8 -14
- package/src/prompts/system/task-label.md +3 -3
- package/src/prompts/system/thinking-loop-redirect.md +6 -6
- package/src/prompts/system/title-marker-instruction.md +2 -1
- package/src/prompts/system/title-system.md +3 -5
- package/src/prompts/system/ttsr-interrupt.md +3 -3
- package/src/prompts/system/ttsr-tool-reminder.md +1 -1
- package/src/prompts/system/ultrathink-notice.md +1 -1
- package/src/prompts/system/unexpected-stop-classifier.md +3 -3
- package/src/prompts/system/vibe-mode-active.md +15 -15
- package/src/prompts/system/web-search.md +16 -16
- package/src/prompts/system/workflow-notice.md +35 -33
- package/src/prompts/system/xdev-mount-notice.md +4 -4
- package/src/prompts/tools/apply-patch.md +22 -23
- package/src/prompts/tools/approve.md +5 -0
- package/src/prompts/tools/ask.md +10 -10
- package/src/prompts/tools/checkpoint.md +7 -7
- package/src/prompts/tools/computer.md +16 -16
- package/src/prompts/tools/github.md +12 -12
- package/src/prompts/tools/goal.md +8 -9
- package/src/prompts/tools/grep.md +5 -5
- package/src/prompts/tools/image-attachment-describe-system.md +6 -6
- package/src/prompts/tools/image-attachment-describe.md +3 -8
- package/src/prompts/tools/image-gen.md +4 -4
- package/src/prompts/tools/inspect-image-system.md +10 -10
- package/src/prompts/tools/inspect-image.md +10 -13
- package/src/prompts/tools/learn.md +4 -4
- package/src/prompts/tools/manage-skill.md +9 -6
- package/src/prompts/tools/memory-edit.md +8 -8
- package/src/prompts/tools/recall.md +4 -4
- package/src/prompts/tools/reflect.md +2 -2
- package/src/prompts/tools/replace.md +13 -11
- package/src/prompts/tools/retain.md +3 -4
- package/src/prompts/tools/rewind.md +7 -8
- package/src/prompts/tools/rewrite.md +12 -0
- package/src/prompts/tools/security-publish.md +5 -1
- package/src/prompts/tools/security-scan.md +10 -1
- package/src/prompts/tools/task-async-contract.md +7 -1
- package/src/prompts/tools/todo.md +27 -25
- package/src/prompts/tools/vibe-kill.md +2 -2
- package/src/prompts/tools/vibe-list.md +2 -2
- package/src/prompts/tools/vibe-send.md +6 -7
- package/src/prompts/tools/vibe-spawn.md +8 -6
- package/src/prompts/tools/web-search.md +5 -5
- package/src/sdk.ts +10 -1
- package/src/session/agent-session-types.ts +2 -0
- package/src/session/agent-session.ts +28 -0
- package/src/session/session-handoff.ts +21 -0
- package/src/session/session-tools.ts +47 -0
- package/src/tools/builtin-names.ts +1 -2
- package/src/tools/index.ts +10 -0
- package/src/tools/read-format.ts +8 -3
- package/src/tools/read.ts +18 -7
- package/src/tools/renderers.ts +2 -0
- package/src/tools/think.ts +84 -0
- package/src/tools/write.ts +1 -1
- package/src/utils/file-mentions.ts +8 -2
- package/src/utils/zip.ts +858 -94
- package/dist/types/cleanse/progress.d.ts +0 -14
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* MCP HTTP transport (Streamable HTTP).
|
|
3
3
|
*
|
|
4
4
|
* Implements JSON-RPC 2.0 over HTTP POST with optional SSE streaming.
|
|
5
|
-
*
|
|
5
|
+
* The negotiated protocol revision is carried in the `MCP-Protocol-Version`
|
|
6
|
+
* header on every request (see `MCP_PROTOCOL_VERSION`).
|
|
6
7
|
*/
|
|
7
8
|
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
8
|
-
import { logger, readSseJson } from "@oh-my-pi/pi-utils";
|
|
9
|
+
import { logger, readSseEvents, readSseJson } from "@oh-my-pi/pi-utils";
|
|
9
10
|
import type {
|
|
10
11
|
JsonRpcError,
|
|
11
12
|
JsonRpcMessage,
|
|
@@ -19,9 +20,37 @@ import type {
|
|
|
19
20
|
import { toJsonRpcError } from "../../mcp/types";
|
|
20
21
|
import { RequestIdAllocator } from "../request-id";
|
|
21
22
|
import { createMCPTimeout, getNeverAbortSignal, isMCPTimeoutEnabled, resolveMCPTimeoutMs } from "../timeout";
|
|
22
|
-
import { type MCPFetchInit, mcpFetch } from "./header-policy";
|
|
23
|
+
import { type MCPFetchInit, mcpFetch, withoutHeader } from "./header-policy";
|
|
23
24
|
|
|
24
25
|
const HTTP_SSE_CONNECT_TIMEOUT_MS = 1_000;
|
|
26
|
+
const DEFAULT_SSE_RETRY_MS = 3_000;
|
|
27
|
+
|
|
28
|
+
interface SSEResumeState {
|
|
29
|
+
lastEventId: string | null;
|
|
30
|
+
retryMs: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Failure resuming an accepted request's logical SSE stream. Carries a
|
|
35
|
+
* never-replay contract: by resume time the server has accepted (and possibly
|
|
36
|
+
* executed) the originating POST, so auth-retry paths must not re-send it.
|
|
37
|
+
*/
|
|
38
|
+
class SSEResumeError extends Error {}
|
|
39
|
+
|
|
40
|
+
/** Wait for the server-provided SSE retry interval while remaining abortable. */
|
|
41
|
+
async function waitForSSERetry(ms: number, signal: AbortSignal): Promise<void> {
|
|
42
|
+
if (signal.aborted) throw signal.reason;
|
|
43
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
44
|
+
const timer = setTimeout(resolve, ms);
|
|
45
|
+
const onAbort = (): void => reject(signal.reason);
|
|
46
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
47
|
+
try {
|
|
48
|
+
await promise;
|
|
49
|
+
} finally {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
signal.removeEventListener("abort", onAbort);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
25
54
|
/**
|
|
26
55
|
* Best-effort startup deadline for the optional Streamable HTTP GET SSE listener.
|
|
27
56
|
*
|
|
@@ -45,6 +74,14 @@ export class HttpTransport implements MCPTransport {
|
|
|
45
74
|
#sessionId: string | null = null;
|
|
46
75
|
#sseConnection: AbortController | null = null;
|
|
47
76
|
readonly #requestIds = new RequestIdAllocator();
|
|
77
|
+
/**
|
|
78
|
+
* Protocol version echoed in the `MCP-Protocol-Version` header. `null` until
|
|
79
|
+
* the `initialize` response is negotiated (via {@link setProtocolVersion}):
|
|
80
|
+
* the MCP spec requires the header only on requests *after* `initialize`, and
|
|
81
|
+
* a server that supports only an older revision may reject a header carrying
|
|
82
|
+
* a newer version sent before negotiation completes.
|
|
83
|
+
*/
|
|
84
|
+
#protocolVersion: string | null = null;
|
|
48
85
|
|
|
49
86
|
onClose?: () => void;
|
|
50
87
|
onError?: (error: Error) => void;
|
|
@@ -55,16 +92,32 @@ export class HttpTransport implements MCPTransport {
|
|
|
55
92
|
|
|
56
93
|
constructor(private config: MCPHttpServerConfig | MCPSseServerConfig) {}
|
|
57
94
|
|
|
58
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Fetch the configured endpoint with header precedence and origin policy.
|
|
97
|
+
*
|
|
98
|
+
* The transport fully owns `MCP-Protocol-Version`: it is stripped from
|
|
99
|
+
* configured headers so a user's `mcp.json` can never inject it, and added
|
|
100
|
+
* only once a version is negotiated (required by the MCP Streamable HTTP spec
|
|
101
|
+
* after `initialize`). Before negotiation — the `initialize` request itself —
|
|
102
|
+
* no protocol-version header is sent from either source.
|
|
103
|
+
*/
|
|
59
104
|
#fetch(init: MCPFetchInit, generated: Record<string, string>): Promise<Response> {
|
|
105
|
+
const configured = withoutHeader(this.config.headers, "MCP-Protocol-Version");
|
|
106
|
+
const withVersion =
|
|
107
|
+
this.#protocolVersion === null ? generated : { "MCP-Protocol-Version": this.#protocolVersion, ...generated };
|
|
60
108
|
return mcpFetch(
|
|
61
109
|
this.config.url,
|
|
62
110
|
init,
|
|
63
|
-
{ generated, configured
|
|
111
|
+
{ generated: withVersion, configured },
|
|
64
112
|
this.config.headerPolicy === "origin-locked",
|
|
65
113
|
);
|
|
66
114
|
}
|
|
67
115
|
|
|
116
|
+
/** Record the protocol version negotiated during `initialize`. */
|
|
117
|
+
setProtocolVersion(version: string): void {
|
|
118
|
+
this.#protocolVersion = version;
|
|
119
|
+
}
|
|
120
|
+
|
|
68
121
|
get connected(): boolean {
|
|
69
122
|
return this.#connected;
|
|
70
123
|
}
|
|
@@ -149,7 +202,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
149
202
|
// If the stream ends unexpectedly (server restart, network drop),
|
|
150
203
|
// fire onClose so the manager can trigger reconnection.
|
|
151
204
|
const signal = connection.signal;
|
|
152
|
-
void this.#
|
|
205
|
+
void this.#runSSEListener(response.body!, signal).finally(() => {
|
|
153
206
|
const wasConnected = this.#connected;
|
|
154
207
|
if (this.#sseConnection === connection) this.#sseConnection = null;
|
|
155
208
|
if (wasConnected) this.onClose?.();
|
|
@@ -169,6 +222,95 @@ export class HttpTransport implements MCPTransport {
|
|
|
169
222
|
}
|
|
170
223
|
}
|
|
171
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Read the long-lived GET SSE stream, resuming with `Last-Event-ID` when
|
|
227
|
+
* the server closes the physical connection mid-stream (2025-11-25 permits
|
|
228
|
+
* polling-style servers). Returns only when the logical stream ends — the
|
|
229
|
+
* caller fires `onClose` and the manager's reconnect path takes over. A
|
|
230
|
+
* resume cycle that delivers no events before dropping again ends the
|
|
231
|
+
* stream rather than retrying forever against a broken server.
|
|
232
|
+
*/
|
|
233
|
+
async #runSSEListener(initialBody: ReadableStream<Uint8Array>, signal: AbortSignal): Promise<void> {
|
|
234
|
+
const resume: SSEResumeState = { lastEventId: null, retryMs: DEFAULT_SSE_RETRY_MS };
|
|
235
|
+
let body = initialBody;
|
|
236
|
+
let progressed = true;
|
|
237
|
+
for (;;) {
|
|
238
|
+
try {
|
|
239
|
+
for await (const event of readSseEvents(body, signal)) {
|
|
240
|
+
progressed = true;
|
|
241
|
+
if (event.id !== undefined) resume.lastEventId = event.id || null;
|
|
242
|
+
if (event.retry !== undefined) resume.retryMs = event.retry;
|
|
243
|
+
if (event.data === "") continue;
|
|
244
|
+
if (!this.#connected) return;
|
|
245
|
+
this.#dispatchSSEMessage(JSON.parse(event.data) as JsonRpcMessage | JsonRpcMessage[]);
|
|
246
|
+
}
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
249
|
+
logger.debug("HTTP SSE stream error", {
|
|
250
|
+
url: this.config.url,
|
|
251
|
+
error: error instanceof Error ? error.message : String(error),
|
|
252
|
+
});
|
|
253
|
+
if (resume.lastEventId === null) {
|
|
254
|
+
if (error instanceof Error) this.onError?.(error);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (!this.#connected || signal.aborted || resume.lastEventId === null || !progressed) return;
|
|
259
|
+
progressed = false;
|
|
260
|
+
try {
|
|
261
|
+
const response = await this.#fetchSSEResume(resume, signal);
|
|
262
|
+
body = response.body as ReadableStream<Uint8Array>;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (!(error instanceof Error && error.name === "AbortError")) {
|
|
265
|
+
logger.debug("HTTP SSE listener resume failed", {
|
|
266
|
+
url: this.config.url,
|
|
267
|
+
error: error instanceof Error ? error.message : String(error),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Resume a logical SSE stream via GET + `Last-Event-ID`, honoring the
|
|
277
|
+
* server-provided retry interval and refreshing auth once on 401/403.
|
|
278
|
+
* Failures throw {@link SSEResumeError} so `request()` never replays the
|
|
279
|
+
* originating POST in response.
|
|
280
|
+
*/
|
|
281
|
+
async #fetchSSEResume(resume: SSEResumeState, signal: AbortSignal): Promise<Response> {
|
|
282
|
+
if (resume.lastEventId === null) {
|
|
283
|
+
throw new SSEResumeError("SSE stream ended without a resumable event ID");
|
|
284
|
+
}
|
|
285
|
+
await waitForSSERetry(resume.retryMs, signal);
|
|
286
|
+
const generated: Record<string, string> = {
|
|
287
|
+
Accept: "text/event-stream",
|
|
288
|
+
"Last-Event-ID": resume.lastEventId,
|
|
289
|
+
};
|
|
290
|
+
if (this.#sessionId) generated["Mcp-Session-Id"] = this.#sessionId;
|
|
291
|
+
let response = await this.#fetch({ method: "GET", signal }, generated);
|
|
292
|
+
if (this.onAuthError && (response.status === 401 || response.status === 403)) {
|
|
293
|
+
await response.body?.cancel();
|
|
294
|
+
const newHeaders = await this.onAuthError();
|
|
295
|
+
if (!newHeaders) {
|
|
296
|
+
throw new SSEResumeError(`HTTP ${response.status} resuming MCP SSE stream: auth refresh failed`);
|
|
297
|
+
}
|
|
298
|
+
// Persist refreshed headers so subsequent requests use them directly
|
|
299
|
+
this.config = { ...this.config, headers: newHeaders };
|
|
300
|
+
response = await this.#fetch({ method: "GET", signal }, generated);
|
|
301
|
+
}
|
|
302
|
+
if (!response.ok) {
|
|
303
|
+
const text = await response.text().catch(() => "");
|
|
304
|
+
throw new SSEResumeError(`HTTP ${response.status} resuming MCP SSE stream: ${text}`);
|
|
305
|
+
}
|
|
306
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
307
|
+
if (!contentType.includes("text/event-stream") || !response.body) {
|
|
308
|
+
await response.body?.cancel();
|
|
309
|
+
throw new SSEResumeError(`MCP SSE resume returned unsupported Content-Type: ${contentType || "(missing)"}`);
|
|
310
|
+
}
|
|
311
|
+
return response;
|
|
312
|
+
}
|
|
313
|
+
|
|
172
314
|
/** Route an SSE message (or batch) to the appropriate handler. */
|
|
173
315
|
#dispatchSSEMessage(message: JsonRpcMessage | JsonRpcMessage[]): void {
|
|
174
316
|
if (Array.isArray(message)) {
|
|
@@ -194,9 +336,12 @@ export class HttpTransport implements MCPTransport {
|
|
|
194
336
|
try {
|
|
195
337
|
return await this.#executeRequest<T>(method, params, options);
|
|
196
338
|
} catch (error) {
|
|
197
|
-
// Retry once on auth failure if onAuthError is wired
|
|
339
|
+
// Retry once on auth failure if onAuthError is wired. Never replay
|
|
340
|
+
// after an SSE resume failure: the server already accepted the
|
|
341
|
+
// original POST and may have executed it — replaying could run a
|
|
342
|
+
// state-changing tool twice.
|
|
198
343
|
const status = error instanceof Error ? AIError.status(error) : undefined;
|
|
199
|
-
if (this.onAuthError && (status === 401 || status === 403)) {
|
|
344
|
+
if (!(error instanceof SSEResumeError) && this.onAuthError && (status === 401 || status === 403)) {
|
|
200
345
|
const newHeaders = await this.onAuthError();
|
|
201
346
|
if (newHeaders) {
|
|
202
347
|
// Persist refreshed headers so subsequent requests use them directly
|
|
@@ -298,40 +443,60 @@ export class HttpTransport implements MCPTransport {
|
|
|
298
443
|
const signal = operation.signal ?? getNeverAbortSignal();
|
|
299
444
|
|
|
300
445
|
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
446
|
+
const resume: SSEResumeState = { lastEventId: null, retryMs: DEFAULT_SSE_RETRY_MS };
|
|
301
447
|
let captured = false;
|
|
302
448
|
|
|
303
|
-
// Drain
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
// Re-reading `response.body` after `for await` breaks would lock the
|
|
307
|
-
// stream a second time and surface as "ReadableStream already has a
|
|
308
|
-
// controller", so we must not exit the loop early.
|
|
449
|
+
// Drain each physical SSE connection without leaving its iterator early.
|
|
450
|
+
// A server may close a connection without terminating the logical stream;
|
|
451
|
+
// when it supplied an event ID, resume that stream via GET + Last-Event-ID.
|
|
309
452
|
const drain = async (): Promise<void> => {
|
|
453
|
+
let current = response;
|
|
310
454
|
try {
|
|
311
|
-
for
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
455
|
+
for (;;) {
|
|
456
|
+
if (!current.body) throw new Error("SSE response did not include a body");
|
|
457
|
+
try {
|
|
458
|
+
for await (const event of readSseEvents(current.body, signal)) {
|
|
459
|
+
if (event.id !== undefined) resume.lastEventId = event.id || null;
|
|
460
|
+
if (event.retry !== undefined) resume.retryMs = event.retry;
|
|
461
|
+
if (event.data === "") continue;
|
|
462
|
+
const raw = JSON.parse(event.data) as JsonRpcMessage | JsonRpcMessage[];
|
|
463
|
+
const messages = Array.isArray(raw) ? raw : [raw];
|
|
464
|
+
for (const message of messages) {
|
|
465
|
+
if (
|
|
466
|
+
!captured &&
|
|
467
|
+
"id" in message &&
|
|
468
|
+
message.id === expectedId &&
|
|
469
|
+
("result" in message || "error" in message)
|
|
470
|
+
) {
|
|
471
|
+
captured = true;
|
|
472
|
+
operation.clear();
|
|
473
|
+
if (message.error) {
|
|
474
|
+
reject(new Error(`MCP error ${message.error.code}: ${message.error.message}`));
|
|
475
|
+
} else {
|
|
476
|
+
resolve(message.result as T);
|
|
477
|
+
}
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (!this.#connected) continue;
|
|
481
|
+
this.#dispatchSSEMessage(message);
|
|
326
482
|
}
|
|
327
|
-
continue;
|
|
328
483
|
}
|
|
329
|
-
|
|
330
|
-
|
|
484
|
+
} catch (error) {
|
|
485
|
+
// An abrupt drop (socket reset, body-read failure) is as
|
|
486
|
+
// resumable as a server-initiated close once an event ID
|
|
487
|
+
// exists; the request timeout still bounds the total wait.
|
|
488
|
+
if (captured) return;
|
|
489
|
+
if (signal.aborted || resume.lastEventId === null) throw error;
|
|
490
|
+
logger.debug("MCP SSE response stream dropped; resuming", {
|
|
491
|
+
url: this.config.url,
|
|
492
|
+
error: error instanceof Error ? error.message : String(error),
|
|
493
|
+
});
|
|
331
494
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
495
|
+
if (captured) return;
|
|
496
|
+
if (resume.lastEventId === null) {
|
|
497
|
+
throw new Error(`No response received for request ID ${expectedId}`);
|
|
498
|
+
}
|
|
499
|
+
current = await this.#fetchSSEResume(resume, signal);
|
|
335
500
|
}
|
|
336
501
|
} catch (error) {
|
|
337
502
|
if (captured) return;
|
package/src/mcp/types.ts
CHANGED
|
@@ -151,6 +151,19 @@ export interface MCPConfigFile {
|
|
|
151
151
|
// MCP Protocol Types
|
|
152
152
|
// =============================================================================
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Latest MCP protocol revision this client negotiates.
|
|
156
|
+
*
|
|
157
|
+
* Sent as `protocolVersion` in the `initialize` request and, for Streamable
|
|
158
|
+
* HTTP, echoed back to the server in the `MCP-Protocol-Version` header on every
|
|
159
|
+
* subsequent request (per the MCP HTTP transport spec). Must track the current
|
|
160
|
+
* stable revision: AWS Bedrock AgentCore Gateway refuses tool calls on an
|
|
161
|
+
* outbound per-user OAuth (`AUTHORIZATION_CODE`) target below `2025-11-25`, and
|
|
162
|
+
* it checks the version *before* consulting its token vault, so an older client
|
|
163
|
+
* is refused even for a caller whose consent is already stored.
|
|
164
|
+
*/
|
|
165
|
+
export const MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
166
|
+
|
|
154
167
|
/** MCP implementation info */
|
|
155
168
|
export interface MCPImplementation {
|
|
156
169
|
name: string;
|
|
@@ -269,6 +282,14 @@ export interface MCPTransport {
|
|
|
269
282
|
/** Close the transport */
|
|
270
283
|
close(): Promise<void>;
|
|
271
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Record the protocol version negotiated in the `initialize` response.
|
|
287
|
+
* Streamable HTTP transports echo it in the `MCP-Protocol-Version` header on
|
|
288
|
+
* every subsequent request; transports that need no per-request version
|
|
289
|
+
* (stdio) omit this.
|
|
290
|
+
*/
|
|
291
|
+
setProtocolVersion?(version: string): void;
|
|
292
|
+
|
|
272
293
|
/** Whether the transport is connected */
|
|
273
294
|
readonly connected: boolean;
|
|
274
295
|
|
|
@@ -18,6 +18,7 @@ import type { AdvisorMessageDetails } from "../../advisor";
|
|
|
18
18
|
import { COLLAB_PROMPT_MESSAGE_TYPE, type CollabPromptDetails } from "../../collab/protocol";
|
|
19
19
|
import { settings } from "../../config/settings";
|
|
20
20
|
import type { MessageRenderer } from "../../extensibility/extensions/types";
|
|
21
|
+
import { LAUNCH_COMPLETION_MESSAGE_TYPE } from "../../session/launch-completion";
|
|
21
22
|
import {
|
|
22
23
|
BACKGROUND_TAN_DISPATCH_MESSAGE_TYPE,
|
|
23
24
|
type CustomMessage,
|
|
@@ -53,6 +54,7 @@ import { EvalExecutionComponent } from "./eval-execution";
|
|
|
53
54
|
import { type LateDiagnosticsFile, LateDiagnosticsMessageComponent } from "./late-diagnostics-message";
|
|
54
55
|
import { groupedReadUsageCallIds, ReadToolGroupComponent, readArgsCollapseIntoGroup } from "./read-tool-group";
|
|
55
56
|
import { SkillMessageComponent } from "./skill-message";
|
|
57
|
+
import { ToolActivityContainer } from "./tool-activity";
|
|
56
58
|
import { ToolExecutionComponent } from "./tool-execution";
|
|
57
59
|
import { TranscriptContainer } from "./transcript-container";
|
|
58
60
|
import { createUsageRowBlock } from "./usage-row";
|
|
@@ -95,7 +97,9 @@ export class ChatTranscriptBuilder {
|
|
|
95
97
|
#expandables: Array<{ setExpanded(expanded: boolean): void }> = [];
|
|
96
98
|
#expanded = false;
|
|
97
99
|
|
|
98
|
-
constructor(private readonly deps: ChatTranscriptBuilderDeps) {
|
|
100
|
+
constructor(private readonly deps: ChatTranscriptBuilderDeps) {
|
|
101
|
+
this.container.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
|
102
|
+
}
|
|
99
103
|
|
|
100
104
|
/** Whether the transcript currently holds any rendered rows. */
|
|
101
105
|
get isEmpty(): boolean {
|
|
@@ -192,7 +196,6 @@ export class ChatTranscriptBuilder {
|
|
|
192
196
|
this.#readGroup = new ReadToolGroupComponent({
|
|
193
197
|
showContentPreview: settings.get("read.toolResultPreview"),
|
|
194
198
|
});
|
|
195
|
-
this.#readGroup.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
|
196
199
|
this.#trackExpandable(this.#readGroup);
|
|
197
200
|
this.container.addChild(this.#readGroup);
|
|
198
201
|
}
|
|
@@ -407,7 +410,6 @@ export class ChatTranscriptBuilder {
|
|
|
407
410
|
this.deps.cwd,
|
|
408
411
|
content.id,
|
|
409
412
|
);
|
|
410
|
-
component.setToolActivityVisible(!settings.get("display.hideToolActivity"));
|
|
411
413
|
this.#trackExpandable(component);
|
|
412
414
|
this.container.addChild(component);
|
|
413
415
|
|
|
@@ -464,11 +466,11 @@ export class ChatTranscriptBuilder {
|
|
|
464
466
|
this.#todoSnapshot = pending;
|
|
465
467
|
}
|
|
466
468
|
}
|
|
467
|
-
|
|
468
469
|
#appendCustomMessage(message: Extract<AgentMessage, { role: "custom" | "hookMessage" }>): void {
|
|
469
470
|
if (!message.display) return;
|
|
470
471
|
if (message.customType === "async-result") {
|
|
471
|
-
|
|
472
|
+
const component = buildAsyncResultBlock(message);
|
|
473
|
+
this.container.addChild(component);
|
|
472
474
|
return;
|
|
473
475
|
}
|
|
474
476
|
if (message.customType === LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE) {
|
|
@@ -501,6 +503,16 @@ export class ChatTranscriptBuilder {
|
|
|
501
503
|
this.container.addChild(createAdvisorMessageCard(details, () => this.#expanded, theme));
|
|
502
504
|
return;
|
|
503
505
|
}
|
|
506
|
+
if (message.customType === LAUNCH_COMPLETION_MESSAGE_TYPE) {
|
|
507
|
+
const messageComponent = new CustomMessageComponent(
|
|
508
|
+
message as CustomMessage<unknown>,
|
|
509
|
+
this.deps.getMessageRenderer?.(message.customType),
|
|
510
|
+
);
|
|
511
|
+
this.#trackExpandable(messageComponent);
|
|
512
|
+
const component = new ToolActivityContainer(messageComponent);
|
|
513
|
+
this.container.addChild(component);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
504
516
|
if (message.customType === BACKGROUND_TAN_DISPATCH_MESSAGE_TYPE) {
|
|
505
517
|
this.container.addChild(createBackgroundTanDispatchBlock(message as CustomMessage<unknown>));
|
|
506
518
|
return;
|
|
@@ -17,7 +17,7 @@ export interface LateDiagnosticsFile {
|
|
|
17
17
|
*/
|
|
18
18
|
export class LateDiagnosticsMessageComponent extends Container {
|
|
19
19
|
#expanded = false;
|
|
20
|
-
|
|
20
|
+
#toolActivityVisible = true;
|
|
21
21
|
constructor(private readonly files: LateDiagnosticsFile[]) {
|
|
22
22
|
super();
|
|
23
23
|
this.#rebuild();
|
|
@@ -29,6 +29,17 @@ export class LateDiagnosticsMessageComponent extends Container {
|
|
|
29
29
|
this.#rebuild();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
setToolActivityVisible(visible: boolean): void {
|
|
33
|
+
if (this.#toolActivityVisible === visible) return;
|
|
34
|
+
this.#toolActivityVisible = visible;
|
|
35
|
+
this.invalidate();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override render(width: number): readonly string[] {
|
|
39
|
+
if (!this.#toolActivityVisible) return [];
|
|
40
|
+
return super.render(width);
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
override invalidate(): void {
|
|
33
44
|
super.invalidate();
|
|
34
45
|
this.#rebuild();
|
|
@@ -10,6 +10,7 @@ import type { TodoItem } from "../../tools/todo";
|
|
|
10
10
|
*/
|
|
11
11
|
export class TodoReminderComponent extends Container {
|
|
12
12
|
#box: Box;
|
|
13
|
+
#toolActivityVisible = true;
|
|
13
14
|
|
|
14
15
|
constructor(
|
|
15
16
|
private readonly todos: TodoItem[],
|
|
@@ -27,6 +28,17 @@ export class TodoReminderComponent extends Container {
|
|
|
27
28
|
this.#rebuild();
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
setToolActivityVisible(visible: boolean): void {
|
|
32
|
+
if (this.#toolActivityVisible === visible) return;
|
|
33
|
+
this.#toolActivityVisible = visible;
|
|
34
|
+
this.invalidate();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
override render(width: number): readonly string[] {
|
|
38
|
+
if (!this.#toolActivityVisible) return [];
|
|
39
|
+
return super.render(width);
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
#rebuild(): void {
|
|
31
43
|
this.#box.clear();
|
|
32
44
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type Component, Container } from "@oh-my-pi/pi-tui";
|
|
2
|
+
|
|
3
|
+
export interface ToolActivityComponent {
|
|
4
|
+
setToolActivityVisible(visible: boolean): void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isToolActivityComponent(component: Component): component is Component & ToolActivityComponent {
|
|
8
|
+
return typeof (component as Partial<ToolActivityComponent>).setToolActivityVisible === "function";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ToolActivityContainer extends Container implements ToolActivityComponent {
|
|
12
|
+
#visible = true;
|
|
13
|
+
|
|
14
|
+
constructor(component: Component | Component[]) {
|
|
15
|
+
super();
|
|
16
|
+
if (Array.isArray(component)) {
|
|
17
|
+
for (const child of component) this.addChild(child);
|
|
18
|
+
} else {
|
|
19
|
+
this.addChild(component);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setToolActivityVisible(visible: boolean): void {
|
|
24
|
+
if (this.#visible === visible) return;
|
|
25
|
+
this.#visible = visible;
|
|
26
|
+
this.invalidate();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Forward Ctrl+O expansion to wrapped children. The transcript's expansion
|
|
31
|
+
* traversal only visits top-level children, so the wrapper must proxy or
|
|
32
|
+
* wrapped renderers would freeze at their insertion-time expansion state.
|
|
33
|
+
*/
|
|
34
|
+
setExpanded(expanded: boolean): void {
|
|
35
|
+
for (const child of this.children) {
|
|
36
|
+
const expandable = child as Partial<{ setExpanded(expanded: boolean): void }>;
|
|
37
|
+
if (typeof expandable.setExpanded === "function") expandable.setExpanded(expanded);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override render(width: number): readonly string[] {
|
|
42
|
+
if (!this.#visible) return [];
|
|
43
|
+
return super.render(width);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type RenderStablePrefix,
|
|
7
7
|
type ViewportTailProvider,
|
|
8
8
|
} from "@oh-my-pi/pi-tui";
|
|
9
|
+
import { isToolActivityComponent } from "./tool-activity";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* A transcript block that is still mutating (a foreground tool awaiting its
|
|
@@ -160,6 +161,7 @@ export class TranscriptContainer
|
|
|
160
161
|
extends Container
|
|
161
162
|
implements NativeScrollbackLiveRegion, NativeScrollbackCommittedRows, RenderStablePrefix, ViewportTailProvider
|
|
162
163
|
{
|
|
164
|
+
#toolActivityVisible = true;
|
|
163
165
|
// Bumped to retire every block segment at once (theme change / clear); a
|
|
164
166
|
// segment is only reused when its stored generation matches.
|
|
165
167
|
#generation = 0;
|
|
@@ -182,6 +184,20 @@ export class TranscriptContainer
|
|
|
182
184
|
// consumes the report and re-bases the baseline). Out-of-band renders
|
|
183
185
|
// between engine frames lower it; they can never inflate it.
|
|
184
186
|
#stableRowsFloor = 0;
|
|
187
|
+
override addChild(component: Component): void {
|
|
188
|
+
if (isToolActivityComponent(component)) component.setToolActivityVisible(this.#toolActivityVisible);
|
|
189
|
+
super.addChild(component);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
setToolActivityVisible(visible: boolean): void {
|
|
193
|
+
if (this.#toolActivityVisible === visible) return;
|
|
194
|
+
this.#toolActivityVisible = visible;
|
|
195
|
+
for (const child of this.children) {
|
|
196
|
+
if (isToolActivityComponent(child)) child.setToolActivityVisible(visible);
|
|
197
|
+
}
|
|
198
|
+
this.invalidate();
|
|
199
|
+
}
|
|
200
|
+
|
|
185
201
|
override invalidate(): void {
|
|
186
202
|
// Theme/global invalidation: retire every diff snapshot so stale styling
|
|
187
203
|
// is not diffed against the recolored render.
|
|
@@ -16,6 +16,7 @@ export class TtsrNotificationComponent extends Container {
|
|
|
16
16
|
#box: Box;
|
|
17
17
|
#expanded = false;
|
|
18
18
|
#rules: Rule[];
|
|
19
|
+
#toolActivityVisible = true;
|
|
19
20
|
|
|
20
21
|
constructor(rules: Rule[]) {
|
|
21
22
|
super();
|
|
@@ -31,6 +32,17 @@ export class TtsrNotificationComponent extends Container {
|
|
|
31
32
|
this.#rebuild();
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
setToolActivityVisible(visible: boolean): void {
|
|
36
|
+
if (this.#toolActivityVisible === visible) return;
|
|
37
|
+
this.#toolActivityVisible = visible;
|
|
38
|
+
this.invalidate();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override render(width: number): readonly string[] {
|
|
42
|
+
if (!this.#toolActivityVisible) return [];
|
|
43
|
+
return super.render(width);
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
/** Merge additional rules into this block (deduped by rule name). */
|
|
35
47
|
addRules(rules: Rule[]): void {
|
|
36
48
|
let changed = false;
|
|
@@ -345,7 +345,6 @@ export class EventController {
|
|
|
345
345
|
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
346
346
|
});
|
|
347
347
|
group.setExpanded(this.ctx.toolOutputExpanded);
|
|
348
|
-
group.setToolActivityVisible(!this.ctx.hideToolActivity);
|
|
349
348
|
this.ctx.chatContainer.addChild(group);
|
|
350
349
|
this.#lastReadGroup = group;
|
|
351
350
|
}
|
|
@@ -1131,7 +1130,6 @@ export class EventController {
|
|
|
1131
1130
|
content.id,
|
|
1132
1131
|
);
|
|
1133
1132
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
1134
|
-
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
|
1135
1133
|
this.ctx.chatContainer.addChild(component);
|
|
1136
1134
|
this.ctx.pendingTools.set(content.id, component);
|
|
1137
1135
|
this.#toolTimelineComponents.set(content.id, component);
|
|
@@ -1384,7 +1382,6 @@ export class EventController {
|
|
|
1384
1382
|
);
|
|
1385
1383
|
component.setArgsComplete(event.toolCallId);
|
|
1386
1384
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
1387
|
-
component.setToolActivityVisible(!this.ctx.hideToolActivity);
|
|
1388
1385
|
this.ctx.chatContainer.addChild(component);
|
|
1389
1386
|
this.ctx.pendingTools.set(event.toolCallId, component);
|
|
1390
1387
|
this.#toolTimelineComponents.set(event.toolCallId, component);
|
|
@@ -1624,8 +1621,8 @@ export class EventController {
|
|
|
1624
1621
|
// This text can be a provider error copied verbatim off the wire (the
|
|
1625
1622
|
// Cursor todo bridge forwards the server's string), so it may carry
|
|
1626
1623
|
// ANSI escapes, other C0/C1 controls, tabs, newlines, or a line far
|
|
1627
|
-
// wider than the terminal. `showWarning` renders through a plain
|
|
1628
|
-
//
|
|
1624
|
+
// wider than the terminal. `showWarning` renders through a plain `Text`,
|
|
1625
|
+
// which strips none of that — an escape reaches the terminal
|
|
1629
1626
|
// and can repaint outside the row. `sanitizeText` drops the control
|
|
1630
1627
|
// sequences (and returns the same reference when there are none),
|
|
1631
1628
|
// then `previewLine` collapses the remaining whitespace and bounds
|
|
@@ -1637,6 +1634,7 @@ export class EventController {
|
|
|
1637
1634
|
const detail = textContent ? previewLine(sanitizeText(textContent), TRUNCATE_LENGTHS.LINE) : "";
|
|
1638
1635
|
this.ctx.showWarning(
|
|
1639
1636
|
`Todo update failed${detail ? `: ${detail}` : ". Progress may be stale until todo succeeds."}`,
|
|
1637
|
+
{ hideWithToolActivity: true },
|
|
1640
1638
|
);
|
|
1641
1639
|
}
|
|
1642
1640
|
// Plan approval rides a `write` to xd://propose: the dispatch metadata on
|
|
@@ -2027,7 +2025,6 @@ export class EventController {
|
|
|
2027
2025
|
const component = new TodoReminderComponent(event.todos, event.attempt, event.maxAttempts);
|
|
2028
2026
|
this.ctx.present(component);
|
|
2029
2027
|
}
|
|
2030
|
-
|
|
2031
2028
|
async #handleTodoAutoClear(_event: Extract<AgentSessionEvent, { type: "todo_auto_clear" }>): Promise<void> {
|
|
2032
2029
|
await this.ctx.reloadTodos();
|
|
2033
2030
|
}
|
|
@@ -10,7 +10,6 @@ import { AssistantMessageComponent } from "../../modes/components/assistant-mess
|
|
|
10
10
|
import { extractImagePathFromText } from "../../modes/components/custom-editor";
|
|
11
11
|
import { ReadToolGroupComponent } from "../../modes/components/read-tool-group";
|
|
12
12
|
import { renderSegmentTrack } from "../../modes/components/segment-track";
|
|
13
|
-
import { StrippedToolCallsPlaceholder } from "../../modes/components/stripped-tool-calls-placeholder";
|
|
14
13
|
import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
|
|
15
14
|
import { ToolExecutionComponent } from "../../modes/components/tool-execution";
|
|
16
15
|
import { TreeSelectorComponent } from "../../modes/components/tree-selector";
|
|
@@ -1925,15 +1924,16 @@ export class InputController {
|
|
|
1925
1924
|
}
|
|
1926
1925
|
|
|
1927
1926
|
for (const child of this.ctx.chatContainer.children) {
|
|
1928
|
-
if (
|
|
1929
|
-
|
|
1930
|
-
child
|
|
1927
|
+
if (
|
|
1928
|
+
!this.ctx.hideToolActivity &&
|
|
1929
|
+
(child instanceof ToolExecutionComponent || child instanceof ReadToolGroupComponent)
|
|
1930
|
+
) {
|
|
1931
|
+
child.setExpanded(false);
|
|
1931
1932
|
} else if (child instanceof AssistantMessageComponent) {
|
|
1932
1933
|
child.setToolResultImagesVisible(!this.ctx.hideToolActivity);
|
|
1933
|
-
} else if (child instanceof StrippedToolCallsPlaceholder) {
|
|
1934
|
-
child.setToolActivityVisible(!this.ctx.hideToolActivity);
|
|
1935
1934
|
}
|
|
1936
1935
|
}
|
|
1936
|
+
this.ctx.chatContainer.setToolActivityVisible(!this.ctx.hideToolActivity);
|
|
1937
1937
|
|
|
1938
1938
|
if (this.ctx.hideToolActivity) this.ctx.ui.clearInlineImages();
|
|
1939
1939
|
this.ctx.ui.resetDisplay();
|