@gajae-code/coding-agent 0.11.3 → 0.11.5
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 +47 -0
- package/dist/types/cli/notify-cli.d.ts +5 -1
- package/dist/types/cli/worktree-cli.d.ts +8 -25
- package/dist/types/commands/gc.d.ts +4 -0
- package/dist/types/commands/worktree.d.ts +7 -32
- package/dist/types/config/settings-schema.d.ts +20 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
- package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
- package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
- package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/owner.d.ts +3 -1
- package/dist/types/internal-urls/local-protocol.d.ts +18 -0
- package/dist/types/main.d.ts +2 -2
- package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +8 -3
- package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-transcript-format.d.ts +77 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
- package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
- package/dist/types/modes/controllers/input-controller.d.ts +8 -2
- package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
- package/dist/types/plan-mode/approved-plan.d.ts +1 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
- package/dist/types/sdk/broker/broker.d.ts +1 -0
- package/dist/types/sdk/broker/discovery.d.ts +10 -0
- package/dist/types/sdk/broker/ensure.d.ts +24 -1
- package/dist/types/sdk/broker/internal.d.ts +3 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -0
- package/dist/types/sdk/broker/state-version.d.ts +2 -1
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +39 -4
- package/dist/types/sdk/bus/config-commands.d.ts +8 -0
- package/dist/types/sdk/bus/config.d.ts +22 -0
- package/dist/types/sdk/bus/index.d.ts +162 -2
- package/dist/types/sdk/bus/notification-service.d.ts +71 -8
- package/dist/types/sdk/bus/session-control.d.ts +11 -8
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
- package/dist/types/sdk/session.d.ts +16 -1
- package/dist/types/secrets/index.d.ts +4 -2
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +31 -2
- package/dist/types/session/artifacts.d.ts +30 -8
- package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
- package/dist/types/session/session-manager.d.ts +88 -18
- package/dist/types/session/session-storage.d.ts +27 -0
- package/dist/types/session/streaming-output.d.ts +4 -0
- package/dist/types/task/executor.d.ts +9 -0
- package/dist/types/tool-discovery/tool-index.d.ts +2 -0
- package/dist/types/tools/ask.d.ts +87 -12
- package/dist/types/tools/bash-skill-urls.d.ts +2 -2
- package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
- package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/path-utils.d.ts +9 -14
- package/dist/types/tools/render-utils.d.ts +1 -8
- package/dist/types/tools/resource-gc.d.ts +14 -0
- package/dist/types/utils/image-loading.d.ts +23 -0
- package/dist/types/utils/image-resize.d.ts +2 -0
- package/dist/types/utils/pasted-image-loading.d.ts +39 -0
- package/dist/types/utils/pasted-image-path.d.ts +21 -19
- package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
- package/dist/types/web/scrapers/types.d.ts +1 -5
- package/package.json +25 -9
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-threat-model-check.ts +60 -0
- package/src/cli/notify-cli.ts +40 -18
- package/src/cli/worktree-cli.ts +9 -287
- package/src/cli.ts +10 -9
- package/src/commands/gc.ts +11 -1
- package/src/commands/sdk.ts +10 -4
- package/src/commands/worktree.ts +10 -53
- package/src/config/model-profiles.ts +15 -15
- package/src/config/settings-schema.ts +17 -0
- package/src/daemon/runtime.ts +2 -2
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
- package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
- package/src/defaults/gjc/skills/team/SKILL.md +7 -9
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/index.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/plugins/marketplace/manager.ts +94 -10
- package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
- package/src/gjc-runtime/deep-interview-state.ts +127 -3
- package/src/gjc-runtime/gc-render.ts +19 -1
- package/src/gjc-runtime/gc-runtime.ts +68 -3
- package/src/gjc-runtime/launch-tmux.ts +61 -6
- package/src/gjc-runtime/managed-owner-admission.ts +352 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
- package/src/gjc-runtime/state-argv.ts +292 -0
- package/src/gjc-runtime/state-runtime.ts +80 -222
- package/src/gjc-runtime/team-runtime.ts +1 -1
- package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
- package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
- package/src/harness-control-plane/owner.ts +59 -3
- package/src/hindsight/client.ts +65 -2
- package/src/internal-urls/agent-protocol.ts +76 -5
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/internal-urls/local-protocol.ts +416 -18
- package/src/lsp/index.ts +4 -88
- package/src/main.ts +37 -13
- package/src/modes/acp/acp-agent.ts +8 -0
- package/src/modes/components/ansi-display-validator.ts +54 -0
- package/src/modes/components/custom-editor.ts +103 -32
- package/src/modes/components/custom-provider-wizard.ts +26 -1
- package/src/modes/components/notifications-settings-editor.ts +26 -2
- package/src/modes/components/session-observer-overlay.ts +40 -37
- package/src/modes/components/settings-selector.ts +3 -1
- package/src/modes/components/tool-transcript-format.ts +393 -0
- package/src/modes/components/transcript-viewer-overlay.ts +63 -1
- package/src/modes/controllers/goal-mode-controller.ts +20 -1
- package/src/modes/controllers/input-controller.ts +98 -37
- package/src/modes/controllers/plan-mode-controller.ts +2 -0
- package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +71 -9
- package/src/modes/interactive-mode.ts +14 -2
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
- package/src/plan-mode/approved-plan.ts +3 -1
- package/src/prompts/agents/architect.md +1 -0
- package/src/prompts/agents/critic.md +1 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/lsp.md +3 -3
- package/src/runtime-mcp/manager.ts +16 -1
- package/src/runtime-mcp/oauth-discovery.ts +116 -26
- package/src/sdk/broker/broker.ts +139 -32
- package/src/sdk/broker/discovery.ts +125 -0
- package/src/sdk/broker/ensure.ts +103 -19
- package/src/sdk/broker/internal.ts +10 -0
- package/src/sdk/broker/session-index.ts +309 -38
- package/src/sdk/broker/state-version.ts +5 -2
- package/src/sdk/bus/chat-daemon-cli.ts +72 -10
- package/src/sdk/bus/chat-daemon-control.ts +526 -130
- package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
- package/src/sdk/bus/config-commands.ts +22 -0
- package/src/sdk/bus/config.ts +37 -0
- package/src/sdk/bus/index.ts +384 -92
- package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
- package/src/sdk/bus/notification-service.ts +284 -62
- package/src/sdk/bus/recent-activity.ts +14 -5
- package/src/sdk/bus/session-control.ts +150 -44
- package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -3
- package/src/sdk/bus/telegram-daemon-control.ts +294 -76
- package/src/sdk/bus/telegram-daemon.ts +1842 -281
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +155 -27
- package/src/secrets/index.ts +53 -10
- package/src/secrets/obfuscator.ts +21 -18
- package/src/secrets/regex.ts +2 -0
- package/src/session/agent-session.ts +495 -253
- package/src/session/artifacts.ts +188 -23
- package/src/session/context-estimation.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +215 -39
- package/src/session/internal/managed-session-storage.ts +1152 -46
- package/src/session/session-manager.ts +1055 -148
- package/src/session/session-storage.ts +109 -11
- package/src/session/streaming-output.ts +6 -0
- package/src/task/executor.ts +71 -26
- package/src/task/index.ts +63 -20
- package/src/tool-discovery/tool-index.ts +18 -0
- package/src/tools/ask.ts +329 -57
- package/src/tools/bash-allowed-prefixes.ts +79 -29
- package/src/tools/bash-skill-urls.ts +14 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/browser/launch.ts +3 -1
- package/src/tools/browser/readable.ts +1 -1
- package/src/tools/browser/screenshot-format.ts +34 -0
- package/src/tools/browser/tab-protocol.ts +3 -0
- package/src/tools/browser/tab-supervisor.ts +125 -122
- package/src/tools/browser/tab-worker-entry.ts +33 -14
- package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
- package/src/tools/browser/tab-worker-smoke.ts +87 -0
- package/src/tools/browser/tab-worker.ts +7 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/path-utils.ts +42 -35
- package/src/tools/plan-mode-guard.ts +1 -0
- package/src/tools/read.ts +7 -1
- package/src/tools/render-utils.ts +1 -27
- package/src/tools/resource-gc.ts +123 -34
- package/src/tools/sqlite-reader.ts +3 -2
- package/src/tools/subagent.ts +2 -0
- package/src/utils/image-loading.ts +85 -34
- package/src/utils/image-resize.ts +16 -9
- package/src/utils/pasted-image-loading.ts +514 -0
- package/src/utils/pasted-image-path.ts +223 -98
- package/src/web/scrapers/html-to-markdown.ts +68 -0
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/types.ts +1 -68
- package/src/web/search/providers/openai-compatible.ts +76 -23
|
@@ -8,10 +8,13 @@ import type { SessionMessageEntry } from "../../session/session-manager";
|
|
|
8
8
|
import { parseSessionEntries } from "../../session/session-manager";
|
|
9
9
|
import type { ObservableSession, SessionObserverRegistry } from "../session-observer-registry";
|
|
10
10
|
import { theme } from "../theme/theme";
|
|
11
|
+
import {
|
|
12
|
+
buildToolTranscriptEntry,
|
|
13
|
+
composeToolText,
|
|
14
|
+
createToolTranscriptRenderDescriptor,
|
|
15
|
+
} from "./tool-transcript-format";
|
|
11
16
|
import { type TranscriptViewerEntry, TranscriptViewerOverlay } from "./transcript-viewer-overlay";
|
|
12
17
|
|
|
13
|
-
const MAX_TOOL_ARGS_CHARS = 500;
|
|
14
|
-
|
|
15
18
|
/** Session-observer adapter. The shared viewer owns navigation and fold state. */
|
|
16
19
|
export class SessionObserverOverlayComponent extends TranscriptViewerOverlay {
|
|
17
20
|
#registry: SessionObserverRegistry;
|
|
@@ -149,7 +152,7 @@ export class SessionObserverOverlayComponent extends TranscriptViewerOverlay {
|
|
|
149
152
|
}
|
|
150
153
|
}
|
|
151
154
|
|
|
152
|
-
function entriesFromMessages(entries: readonly SessionMessageEntry[]): TranscriptViewerEntry[] {
|
|
155
|
+
export function entriesFromMessages(entries: readonly SessionMessageEntry[]): TranscriptViewerEntry[] {
|
|
153
156
|
const results = new Map<string, ToolResultMessage>();
|
|
154
157
|
for (const entry of entries)
|
|
155
158
|
if (entry.message.role === "toolResult") results.set(entry.message.toolCallId, entry.message);
|
|
@@ -191,25 +194,42 @@ function entriesFromMessages(entries: readonly SessionMessageEntry[]): Transcrip
|
|
|
191
194
|
.map(part => part.text)
|
|
192
195
|
.join("\n")
|
|
193
196
|
.trim() ?? "";
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
197
|
+
const canonicalPayload = {
|
|
198
|
+
text: composeToolText({
|
|
199
|
+
name: content.name,
|
|
200
|
+
args: content.arguments,
|
|
201
|
+
intent: content.intent,
|
|
202
|
+
resultText,
|
|
203
|
+
isError: result?.isError ?? false,
|
|
204
|
+
hasResult: results.has(content.id),
|
|
205
|
+
}),
|
|
206
|
+
metadata: {
|
|
207
|
+
name: content.name,
|
|
208
|
+
arguments: content.arguments,
|
|
209
|
+
intent: content.intent,
|
|
210
|
+
resultText,
|
|
211
|
+
isError: result?.isError ?? false,
|
|
212
|
+
hasResult: results.has(content.id),
|
|
213
|
+
detailsData: result?.details,
|
|
214
|
+
},
|
|
215
|
+
source: { call: content, result },
|
|
216
|
+
};
|
|
217
|
+
output.push(
|
|
218
|
+
buildToolTranscriptEntry({
|
|
219
|
+
canonicalPayload,
|
|
220
|
+
renderDescriptor: createToolTranscriptRenderDescriptor({
|
|
204
221
|
name: content.name,
|
|
205
|
-
|
|
222
|
+
args: content.arguments,
|
|
206
223
|
intent: content.intent,
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
224
|
+
resultContent: resultText,
|
|
225
|
+
isError: result?.isError,
|
|
226
|
+
hasResult: results.has(content.id),
|
|
227
|
+
detailsData: result?.details,
|
|
228
|
+
}),
|
|
229
|
+
capabilities: { copyable: true, foldable: true, rawViewable: true },
|
|
230
|
+
identity: { id: `tool:${content.id}`, label: content.name, display: "full" },
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
213
233
|
}
|
|
214
234
|
});
|
|
215
235
|
}
|
|
@@ -234,23 +254,6 @@ function entriesFromMessages(entries: readonly SessionMessageEntry[]): Transcrip
|
|
|
234
254
|
return output;
|
|
235
255
|
}
|
|
236
256
|
|
|
237
|
-
function formatToolArgs(name: string, args: Record<string, unknown>): string {
|
|
238
|
-
if (name === "read" || name === "write" || name === "edit") return args.path ? `path: ${args.path}` : "";
|
|
239
|
-
if (name === "bash") return typeof args.command === "string" ? args.command.replaceAll("\t", " ") : "";
|
|
240
|
-
if (name === "search")
|
|
241
|
-
return [
|
|
242
|
-
args.pattern ? `pattern: ${args.pattern}` : "",
|
|
243
|
-
Array.isArray(args.paths) ? `paths: ${args.paths.join(", ")}` : "",
|
|
244
|
-
]
|
|
245
|
-
.filter(Boolean)
|
|
246
|
-
.join(", ");
|
|
247
|
-
return Object.entries(args)
|
|
248
|
-
.filter(([key]) => !key.startsWith("_"))
|
|
249
|
-
.map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`)
|
|
250
|
-
.join(", ")
|
|
251
|
-
.slice(0, MAX_TOOL_ARGS_CHARS);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
257
|
function truncateThinking(text: string, expanded: boolean): string {
|
|
255
258
|
const limit = expanded ? 4_000 : 200;
|
|
256
259
|
return text.length > limit ? `${text.slice(0, limit)}...` : text;
|
|
@@ -669,6 +669,8 @@ export interface SettingsRuntimeContext {
|
|
|
669
669
|
cwd: string;
|
|
670
670
|
/** Whether this terminal can render the pet overlay. */
|
|
671
671
|
petAvailable?: boolean;
|
|
672
|
+
/** Terminal environment used to select unavailable-pet guidance. Omitted in production to use Bun.env. */
|
|
673
|
+
terminalEnv?: NodeJS.ProcessEnv;
|
|
672
674
|
}
|
|
673
675
|
|
|
674
676
|
/** Status line settings subset for preview */
|
|
@@ -896,7 +898,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
896
898
|
options = createPetSelectItems(options, currentValue, petAvailable);
|
|
897
899
|
// Unsupported terminals must see the same actionable guidance the
|
|
898
900
|
// startup notice and /pet show, not only dimmed option descriptions.
|
|
899
|
-
if (!petAvailable) description = getPetUnavailableWarning();
|
|
901
|
+
if (!petAvailable) description = getPetUnavailableWarning(this.context.terminalEnv);
|
|
900
902
|
}
|
|
901
903
|
// Preview handlers
|
|
902
904
|
let onPreview: ((value: string) => void | Promise<void>) | undefined;
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { wrapTextWithAnsi } from "@gajae-code/tui";
|
|
2
|
+
import { sanitizeText } from "@gajae-code/utils";
|
|
3
|
+
import { renderJsonTreeLines } from "../../tools/json-tree";
|
|
4
|
+
import { renderStatusLine } from "../../tui";
|
|
5
|
+
import type { Theme } from "../theme/theme";
|
|
6
|
+
import { validateDisplayLine } from "./ansi-display-validator";
|
|
7
|
+
import { renderDiff } from "./diff";
|
|
8
|
+
|
|
9
|
+
export const MAX_TOOL_ARGS_CHARS = 500;
|
|
10
|
+
export const TOOL_RESULT_MAX_EXPANDED_LINES = 100;
|
|
11
|
+
export const INPUT_MAX_SOURCE_BYTES = 1_048_576;
|
|
12
|
+
export const INPUT_MAX_SOURCE_LINES = 50_000;
|
|
13
|
+
export const INPUT_MAX_SCALAR_LEN = 8_192;
|
|
14
|
+
export const INPUT_MAX_JSON_DEPTH = 32;
|
|
15
|
+
export const INPUT_MAX_JSON_NODES = 20_000;
|
|
16
|
+
|
|
17
|
+
export type ToolDisplaySections = {
|
|
18
|
+
callLines: string[];
|
|
19
|
+
statusLines: string[];
|
|
20
|
+
resultText: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Produces final, terminal-safe rich display lines for an expanded tool entry.
|
|
25
|
+
* It owns validation, width-aware wrapping, and the result-only visible cap.
|
|
26
|
+
*/
|
|
27
|
+
export function renderToolDisplayLines(
|
|
28
|
+
descriptor: ToolTranscriptRenderDescriptor,
|
|
29
|
+
contentWidth: number,
|
|
30
|
+
theme: Theme,
|
|
31
|
+
): string[] {
|
|
32
|
+
const bounded =
|
|
33
|
+
descriptor.inputTruncated || exceedsInputBudget(descriptor) ? truncatedDescriptor(descriptor) : descriptor;
|
|
34
|
+
const sections = bounded.inputTruncated ? truncatedPlainSections(bounded) : richSections(bounded, theme);
|
|
35
|
+
const wrap = (lines: string[]) =>
|
|
36
|
+
balanceWrappedSgr(lines.flatMap(line => wrapTextWithAnsi(validateDisplayLine(line), Math.max(1, contentWidth))));
|
|
37
|
+
const result = cappedResultLines(sections.resultText);
|
|
38
|
+
const resultLines = wrap(result.lines);
|
|
39
|
+
const shown = resultLines.slice(0, TOOL_RESULT_MAX_EXPANDED_LINES);
|
|
40
|
+
if (result.omittedLineCount > 0) shown.push(`... ${result.omittedLineCount} more lines`);
|
|
41
|
+
else if (resultLines.length > TOOL_RESULT_MAX_EXPANDED_LINES)
|
|
42
|
+
shown.push(`... ${resultLines.length - TOOL_RESULT_MAX_EXPANDED_LINES} more lines`);
|
|
43
|
+
return [...wrap(sections.callLines), ...wrap(sections.statusLines), ...shown];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function truncatedPlainSections(descriptor: ToolTranscriptRenderDescriptor): ToolDisplaySections {
|
|
47
|
+
return {
|
|
48
|
+
callLines: [descriptor.name, ...composeToolCall(descriptor).split("\n").filter(Boolean)],
|
|
49
|
+
statusLines: [!descriptor.hasResult ? "⏳ pending" : descriptor.isError ? "✗ Error" : "✓ done"],
|
|
50
|
+
resultText: `... input truncated for rendering (press r for raw)\n${boundedPlainResult(descriptor.resultContent)}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function truncatedDescriptor(descriptor: ToolTranscriptRenderDescriptor): ToolTranscriptRenderDescriptor {
|
|
55
|
+
return {
|
|
56
|
+
...descriptor,
|
|
57
|
+
name: descriptor.name.slice(0, 256),
|
|
58
|
+
args: exceedsStructuredInputBudget(descriptor.args) ? {} : descriptor.args,
|
|
59
|
+
intent: exceedsScalarInputBudget(descriptor.intent) ? undefined : descriptor.intent,
|
|
60
|
+
resultContent: boundedPlainResult(descriptor.resultContent),
|
|
61
|
+
details: undefined,
|
|
62
|
+
detailsData: undefined,
|
|
63
|
+
inputTruncated: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function balanceWrappedSgr(lines: string[]): string[] {
|
|
68
|
+
let active: string[] = [];
|
|
69
|
+
return lines.map(line => {
|
|
70
|
+
const prefix = active.join("");
|
|
71
|
+
const matches = [...line.matchAll(/\x1b\[([0-9;]*)m/g)];
|
|
72
|
+
for (const match of matches) {
|
|
73
|
+
if (
|
|
74
|
+
match[1]!
|
|
75
|
+
.split(";")
|
|
76
|
+
.some(parameter => ["0", "22", "23", "24", "25", "27", "28", "29", "39", "49"].includes(parameter))
|
|
77
|
+
)
|
|
78
|
+
active = [];
|
|
79
|
+
else active = [match[0]];
|
|
80
|
+
}
|
|
81
|
+
const last = matches.at(-1);
|
|
82
|
+
return `${prefix}${line}${prefix || (last && !last[1]!.split(";").includes("0")) ? "\x1b[0m" : ""}`;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function plainSections(descriptor: ToolTranscriptRenderDescriptor, theme: Theme): ToolDisplaySections {
|
|
87
|
+
return {
|
|
88
|
+
callLines: [theme.fg("accent", descriptor.name), ...composeToolCall(descriptor).split("\n").filter(Boolean)],
|
|
89
|
+
statusLines: [statusLine(descriptor, theme)],
|
|
90
|
+
resultText: boundedPlainResult(descriptor.resultContent),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function richSections(descriptor: ToolTranscriptRenderDescriptor, theme: Theme): ToolDisplaySections {
|
|
95
|
+
const sections = plainSections(descriptor, theme);
|
|
96
|
+
const diff = extractDiff(descriptor.detailsData) ?? descriptor.details;
|
|
97
|
+
if ((descriptor.name === "edit" || descriptor.name === "write" || descriptor.name === "apply_patch") && diff) {
|
|
98
|
+
sections.resultText = renderDiff(diff, { filePath: extractPath(descriptor.detailsData) });
|
|
99
|
+
} else if (descriptor.detailsData && typeof descriptor.detailsData === "object") {
|
|
100
|
+
sections.resultText = renderJsonTreeLines(
|
|
101
|
+
descriptor.detailsData,
|
|
102
|
+
theme,
|
|
103
|
+
INPUT_MAX_JSON_DEPTH,
|
|
104
|
+
TOOL_RESULT_MAX_EXPANDED_LINES * 2,
|
|
105
|
+
INPUT_MAX_SCALAR_LEN,
|
|
106
|
+
).lines.join("\n");
|
|
107
|
+
}
|
|
108
|
+
return sections;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function statusLine(descriptor: ToolTranscriptRenderDescriptor, theme: Theme): string {
|
|
112
|
+
const status = !descriptor.hasResult
|
|
113
|
+
? "⏳ pending"
|
|
114
|
+
: descriptor.isError
|
|
115
|
+
? `✗ ${descriptor.resultContent.split("\n", 1)[0]?.trim() || "Error"}`
|
|
116
|
+
: "✓ done";
|
|
117
|
+
return renderStatusLine(
|
|
118
|
+
{ title: status, titleColor: descriptor.isError ? "error" : descriptor.hasResult ? "success" : "warning" },
|
|
119
|
+
theme,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function boundedPlainResult(text: string): string {
|
|
124
|
+
const boundedBytes =
|
|
125
|
+
Buffer.byteLength(text) > INPUT_MAX_SOURCE_BYTES
|
|
126
|
+
? Buffer.from(text).subarray(0, INPUT_MAX_SOURCE_BYTES).toString()
|
|
127
|
+
: text;
|
|
128
|
+
let end = boundedBytes.length;
|
|
129
|
+
let lineCount = 1;
|
|
130
|
+
for (let index = 0; index < boundedBytes.length; index++) {
|
|
131
|
+
if (boundedBytes[index] !== "\n") continue;
|
|
132
|
+
if (lineCount === INPUT_MAX_SOURCE_LINES) {
|
|
133
|
+
end = index;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
lineCount += 1;
|
|
137
|
+
}
|
|
138
|
+
return boundedBytes.slice(0, end);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function cappedResultLines(result: string): { lines: string[]; omittedLineCount: number } {
|
|
142
|
+
let lineCount = 1;
|
|
143
|
+
let prefixEnd = result.length;
|
|
144
|
+
for (let index = 0; index < result.length; index++) {
|
|
145
|
+
if (result[index] !== "\n") continue;
|
|
146
|
+
if (lineCount === TOOL_RESULT_MAX_EXPANDED_LINES) prefixEnd = index;
|
|
147
|
+
lineCount += 1;
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
lines: result.slice(0, prefixEnd).split("\n"),
|
|
151
|
+
omittedLineCount: Math.max(0, lineCount - TOOL_RESULT_MAX_EXPANDED_LINES),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function extractDiff(value: unknown): string | undefined {
|
|
156
|
+
if (!value || typeof value !== "object") return undefined;
|
|
157
|
+
const record = value as Record<string, unknown>;
|
|
158
|
+
if (typeof record.diff === "string") return record.diff;
|
|
159
|
+
if (Array.isArray(record.perFileResults))
|
|
160
|
+
return record.perFileResults
|
|
161
|
+
.map(item =>
|
|
162
|
+
item && typeof item === "object" && typeof (item as Record<string, unknown>).diff === "string"
|
|
163
|
+
? (item as Record<string, unknown>).diff
|
|
164
|
+
: "",
|
|
165
|
+
)
|
|
166
|
+
.filter(Boolean)
|
|
167
|
+
.join("\n");
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function extractPath(value: unknown): string | undefined {
|
|
172
|
+
if (!value || typeof value !== "object") return undefined;
|
|
173
|
+
const record = value as Record<string, unknown>;
|
|
174
|
+
return typeof record.path === "string" ? record.path : undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Field-specific input budgets:
|
|
179
|
+
* - resultContent is bounded by total bytes and lines.
|
|
180
|
+
* - args and detailsData are bounded by cumulative bytes, per-scalar length, depth, and nodes.
|
|
181
|
+
* - intent and details are bounded by per-scalar length.
|
|
182
|
+
*/
|
|
183
|
+
function exceedsInputBudget(descriptor: ToolTranscriptRenderDescriptor): boolean {
|
|
184
|
+
return (
|
|
185
|
+
exceedsResultContentBudget(descriptor.resultContent) ||
|
|
186
|
+
exceedsStructuredInputBudget(descriptor.args) ||
|
|
187
|
+
exceedsScalarInputBudget(descriptor.intent) ||
|
|
188
|
+
exceedsScalarInputBudget(descriptor.details) ||
|
|
189
|
+
exceedsStructuredInputBudget(descriptor.detailsData)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function exceedsResultContentBudget(value: string): boolean {
|
|
194
|
+
return Buffer.byteLength(value) > INPUT_MAX_SOURCE_BYTES || value.split("\n").length - 1 > INPUT_MAX_SOURCE_LINES;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function exceedsScalarInputBudget(value: unknown): boolean {
|
|
198
|
+
return typeof value === "string" && value.length > INPUT_MAX_SCALAR_LEN;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function exceedsStructuredInputBudget(value: unknown): boolean {
|
|
202
|
+
let nodes = 0;
|
|
203
|
+
let bytes = 0;
|
|
204
|
+
let exceeded = false;
|
|
205
|
+
const visit = (item: unknown, depth: number): void => {
|
|
206
|
+
if (exceeded) return;
|
|
207
|
+
nodes++;
|
|
208
|
+
if (nodes > INPUT_MAX_JSON_NODES || depth > INPUT_MAX_JSON_DEPTH) {
|
|
209
|
+
exceeded = true;
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (typeof item === "string") {
|
|
213
|
+
bytes += Buffer.byteLength(item);
|
|
214
|
+
if (item.length > INPUT_MAX_SCALAR_LEN || bytes > INPUT_MAX_SOURCE_BYTES) exceeded = true;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (Array.isArray(item)) for (const child of item) visit(child, depth + 1);
|
|
218
|
+
else if (item && typeof item === "object") for (const child of Object.values(item)) visit(child, depth + 1);
|
|
219
|
+
};
|
|
220
|
+
visit(value, 0);
|
|
221
|
+
return exceeded;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export type ToolTranscriptRenderDescriptor = Readonly<{
|
|
225
|
+
name: string;
|
|
226
|
+
args: Record<string, unknown>;
|
|
227
|
+
intent?: string;
|
|
228
|
+
resultContent: string;
|
|
229
|
+
details?: string;
|
|
230
|
+
detailsData?: unknown;
|
|
231
|
+
isError: boolean;
|
|
232
|
+
isPartial: boolean;
|
|
233
|
+
hasResult: boolean;
|
|
234
|
+
inputTruncated: boolean;
|
|
235
|
+
}>;
|
|
236
|
+
|
|
237
|
+
export type BuildToolTranscriptEntryInput = {
|
|
238
|
+
canonicalPayload: import("../transcript-item-registry").TranscriptSourcePayload;
|
|
239
|
+
renderDescriptor: ToolTranscriptRenderDescriptor;
|
|
240
|
+
capabilities: import("../transcript-item-registry").TranscriptItemCapabilities;
|
|
241
|
+
identity: { id: string; label: string; display?: "full" };
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Shared tool projection for the main transcript and session observer.
|
|
246
|
+
* `payload` is canonical/copyable source; display is derived only from the
|
|
247
|
+
* sanitized, frozen descriptor. The observer's whole-entry 100-line cap and
|
|
248
|
+
* the main view's tool-only result cap intentionally remain surface-specific.
|
|
249
|
+
*/
|
|
250
|
+
export function buildToolTranscriptEntry(
|
|
251
|
+
input: BuildToolTranscriptEntryInput,
|
|
252
|
+
): import("./transcript-viewer-overlay").TranscriptViewerEntry {
|
|
253
|
+
const { canonicalPayload: payload, renderDescriptor, capabilities, identity } = input;
|
|
254
|
+
const fields: ToolTranscriptFields = {
|
|
255
|
+
name: renderDescriptor.name,
|
|
256
|
+
args: renderDescriptor.args,
|
|
257
|
+
intent: renderDescriptor.intent,
|
|
258
|
+
resultText: renderDescriptor.resultContent,
|
|
259
|
+
isError: renderDescriptor.isError,
|
|
260
|
+
hasResult: renderDescriptor.hasResult,
|
|
261
|
+
};
|
|
262
|
+
return {
|
|
263
|
+
id: identity.id,
|
|
264
|
+
kind: "tool",
|
|
265
|
+
label: identity.label,
|
|
266
|
+
payload,
|
|
267
|
+
renderDescriptor,
|
|
268
|
+
...capabilities,
|
|
269
|
+
richRenderEligible: identity.display !== "full",
|
|
270
|
+
getDisplayText: expanded =>
|
|
271
|
+
identity.display === "full" ? composeToolText(fields) : toolDisplayText(fields, expanded),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Sanitizes and freezes all display-only values before tool formatting. */
|
|
276
|
+
export function createToolTranscriptRenderDescriptor(input: {
|
|
277
|
+
name: unknown;
|
|
278
|
+
args: unknown;
|
|
279
|
+
intent?: unknown;
|
|
280
|
+
resultContent: unknown;
|
|
281
|
+
details?: unknown;
|
|
282
|
+
detailsData?: unknown;
|
|
283
|
+
isError?: unknown;
|
|
284
|
+
isPartial?: unknown;
|
|
285
|
+
hasResult?: unknown;
|
|
286
|
+
inputTruncated?: unknown;
|
|
287
|
+
}): ToolTranscriptRenderDescriptor {
|
|
288
|
+
const argsTruncated = exceedsStructuredInputBudget(input.args);
|
|
289
|
+
const detailsDataTruncated = exceedsStructuredInputBudget(input.detailsData);
|
|
290
|
+
return freezeDisplayValue({
|
|
291
|
+
name: sanitizeString(input.name),
|
|
292
|
+
args: argsTruncated ? {} : ((sanitizeDisplayValue(input.args) ?? {}) as Record<string, unknown>),
|
|
293
|
+
intent: typeof input.intent === "string" ? sanitizeString(input.intent) : undefined,
|
|
294
|
+
resultContent: sanitizeString(input.resultContent),
|
|
295
|
+
details: typeof input.details === "string" ? sanitizeString(input.details) : undefined,
|
|
296
|
+
detailsData: detailsDataTruncated ? undefined : sanitizeDisplayValue(input.detailsData),
|
|
297
|
+
isError: Boolean(input.isError),
|
|
298
|
+
isPartial: Boolean(input.isPartial),
|
|
299
|
+
hasResult: Boolean(input.hasResult),
|
|
300
|
+
inputTruncated: Boolean(input.inputTruncated) || argsTruncated || detailsDataTruncated,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function sanitizeString(value: unknown): string {
|
|
305
|
+
return typeof value === "string" ? sanitizeText(value) : "";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function sanitizeDisplayValue(value: unknown): unknown {
|
|
309
|
+
if (typeof value === "string") return sanitizeText(value);
|
|
310
|
+
if (Array.isArray(value)) return value.map(sanitizeDisplayValue);
|
|
311
|
+
if (!value || typeof value !== "object") return value;
|
|
312
|
+
return Object.fromEntries(
|
|
313
|
+
Object.entries(value).map(([key, item]) => [sanitizeText(key), sanitizeDisplayValue(item)]),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function freezeDisplayValue<T>(value: T): T {
|
|
318
|
+
if (value && typeof value === "object") {
|
|
319
|
+
for (const item of Object.values(value)) freezeDisplayValue(item);
|
|
320
|
+
Object.freeze(value);
|
|
321
|
+
}
|
|
322
|
+
return value;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
type ToolCallFields = {
|
|
326
|
+
name: string;
|
|
327
|
+
args: Record<string, unknown>;
|
|
328
|
+
intent?: string;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
type ToolResultFields = {
|
|
332
|
+
resultText: string;
|
|
333
|
+
isError: boolean;
|
|
334
|
+
hasResult: boolean;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export type ToolTranscriptFields = ToolCallFields & ToolResultFields;
|
|
338
|
+
|
|
339
|
+
export function formatToolArgs(name: string, args: Record<string, unknown>): string {
|
|
340
|
+
if (name === "read" || name === "write" || name === "edit") return args.path ? `path: ${args.path}` : "";
|
|
341
|
+
if (name === "bash") return typeof args.command === "string" ? args.command.replaceAll("\t", " ") : "";
|
|
342
|
+
if (name === "search")
|
|
343
|
+
return [
|
|
344
|
+
args.pattern ? `pattern: ${args.pattern}` : "",
|
|
345
|
+
Array.isArray(args.paths) ? `paths: ${args.paths.join(", ")}` : "",
|
|
346
|
+
]
|
|
347
|
+
.filter(Boolean)
|
|
348
|
+
.join(", ");
|
|
349
|
+
return Object.entries(args)
|
|
350
|
+
.filter(([key]) => !key.startsWith("_"))
|
|
351
|
+
.map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`)
|
|
352
|
+
.join(", ")
|
|
353
|
+
.slice(0, MAX_TOOL_ARGS_CHARS);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function composeToolCall({ name, args, intent }: ToolCallFields): string {
|
|
357
|
+
return [formatToolArgs(name, args), intent].filter(Boolean).join("\n");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function composeToolResult({ resultText, isError, hasResult }: ToolResultFields): string {
|
|
361
|
+
const text = resultText.trim();
|
|
362
|
+
if (!hasResult) return "⏳ pending";
|
|
363
|
+
if (isError) return `✗ ${text || "Error"}`;
|
|
364
|
+
return text || "✓ done";
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function composeToolText(fields: ToolTranscriptFields): string {
|
|
368
|
+
return [composeToolCall(fields), composeToolResult(fields)].filter(Boolean).join("\n");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Caps result output without allocating an array for every source line. The final
|
|
373
|
+
* line count still requires scanning the result so the omitted-line count is exact.
|
|
374
|
+
*/
|
|
375
|
+
function expandedToolResult(result: string): string {
|
|
376
|
+
let lineCount = 1;
|
|
377
|
+
let prefixEnd = result.length;
|
|
378
|
+
for (let index = 0; index < result.length; index++) {
|
|
379
|
+
if (result[index] !== "\n") continue;
|
|
380
|
+
if (lineCount === TOOL_RESULT_MAX_EXPANDED_LINES) prefixEnd = index;
|
|
381
|
+
lineCount += 1;
|
|
382
|
+
}
|
|
383
|
+
const prefix = result.slice(0, prefixEnd);
|
|
384
|
+
return lineCount > TOOL_RESULT_MAX_EXPANDED_LINES
|
|
385
|
+
? `${prefix}\n... ${lineCount - TOOL_RESULT_MAX_EXPANDED_LINES} more lines`
|
|
386
|
+
: prefix;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function toolDisplayText(fields: ToolTranscriptFields, expanded: boolean): string {
|
|
390
|
+
const call = composeToolCall(fields);
|
|
391
|
+
if (!expanded) return call;
|
|
392
|
+
return [call, expandedToolResult(composeToolResult(fields))].filter(Boolean).join("\n");
|
|
393
|
+
}
|
|
@@ -11,6 +11,12 @@ import { sanitizeText } from "@gajae-code/utils";
|
|
|
11
11
|
import { getMarkdownTheme, theme } from "../theme/theme";
|
|
12
12
|
import type { TranscriptItemRegistry, TranscriptSourcePayload } from "../transcript-item-registry";
|
|
13
13
|
import { DynamicBorder } from "./dynamic-border";
|
|
14
|
+
import {
|
|
15
|
+
buildToolTranscriptEntry,
|
|
16
|
+
createToolTranscriptRenderDescriptor,
|
|
17
|
+
renderToolDisplayLines,
|
|
18
|
+
type ToolTranscriptRenderDescriptor,
|
|
19
|
+
} from "./tool-transcript-format";
|
|
14
20
|
|
|
15
21
|
const INDENT = " ";
|
|
16
22
|
const PAGE_SIZE = 15;
|
|
@@ -25,6 +31,8 @@ export type TranscriptViewerEntry = {
|
|
|
25
31
|
foldable?: boolean;
|
|
26
32
|
rawViewable?: boolean;
|
|
27
33
|
getDisplayText?: (expanded: boolean) => string;
|
|
34
|
+
renderDescriptor?: ToolTranscriptRenderDescriptor;
|
|
35
|
+
richRenderEligible?: boolean;
|
|
28
36
|
};
|
|
29
37
|
|
|
30
38
|
export type TranscriptViewerOverlayOptions = {
|
|
@@ -332,6 +340,19 @@ export class TranscriptViewerOverlay extends Container {
|
|
|
332
340
|
lines.push(
|
|
333
341
|
`${selected ? theme.fg("accent", "▶") : " "} ${theme.fg("muted", `[${sanitizeText(entry.label ?? entry.kind)}]`)}`,
|
|
334
342
|
);
|
|
343
|
+
if (
|
|
344
|
+
!raw &&
|
|
345
|
+
expanded &&
|
|
346
|
+
selected &&
|
|
347
|
+
entry.kind === "tool" &&
|
|
348
|
+
entry.richRenderEligible === true &&
|
|
349
|
+
entry.renderDescriptor
|
|
350
|
+
) {
|
|
351
|
+
for (const line of renderToolDisplayLines(entry.renderDescriptor, contentWidth, theme))
|
|
352
|
+
lines.push(`${INDENT}${line}`);
|
|
353
|
+
this.#renderedEntries.push({ lineStart: start, lineCount: lines.length - start });
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
335
356
|
const text = sanitizeText(
|
|
336
357
|
(raw
|
|
337
358
|
? entry.payload.text
|
|
@@ -363,6 +384,29 @@ export class TranscriptViewerOverlay extends Container {
|
|
|
363
384
|
}
|
|
364
385
|
}
|
|
365
386
|
|
|
387
|
+
type ToolTranscriptMetadata = {
|
|
388
|
+
name: string;
|
|
389
|
+
arguments: Record<string, unknown>;
|
|
390
|
+
intent?: unknown;
|
|
391
|
+
resultText: string;
|
|
392
|
+
isError: boolean;
|
|
393
|
+
hasResult: boolean;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
function hasToolTranscriptMetadata(
|
|
397
|
+
metadata: Readonly<Record<string, unknown>>,
|
|
398
|
+
): metadata is Readonly<ToolTranscriptMetadata> {
|
|
399
|
+
return (
|
|
400
|
+
typeof metadata.name === "string" &&
|
|
401
|
+
metadata.arguments !== null &&
|
|
402
|
+
typeof metadata.arguments === "object" &&
|
|
403
|
+
!Array.isArray(metadata.arguments) &&
|
|
404
|
+
typeof metadata.resultText === "string" &&
|
|
405
|
+
typeof metadata.isError === "boolean" &&
|
|
406
|
+
typeof metadata.hasResult === "boolean"
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
366
410
|
/** Main-session adapter over the registry's canonical payload resolver. */
|
|
367
411
|
export function transcriptViewerEntries(registry: TranscriptItemRegistry): TranscriptViewerEntry[] {
|
|
368
412
|
return registry.items().flatMap(item => {
|
|
@@ -375,10 +419,28 @@ export function transcriptViewerEntries(registry: TranscriptItemRegistry): Trans
|
|
|
375
419
|
: item.kind === "assistant-thinking"
|
|
376
420
|
? "Thinking"
|
|
377
421
|
: item.kind === "tool"
|
|
378
|
-
? "Tool"
|
|
422
|
+
? String(payload.metadata.name ?? "Tool")
|
|
379
423
|
: item.kind === "user"
|
|
380
424
|
? "User"
|
|
381
425
|
: item.kind;
|
|
426
|
+
const toolMetadata = hasToolTranscriptMetadata(payload.metadata) ? payload.metadata : undefined;
|
|
427
|
+
if (item.kind === "tool" && toolMetadata)
|
|
428
|
+
return [
|
|
429
|
+
buildToolTranscriptEntry({
|
|
430
|
+
canonicalPayload: payload,
|
|
431
|
+
renderDescriptor: createToolTranscriptRenderDescriptor({
|
|
432
|
+
name: toolMetadata.name,
|
|
433
|
+
args: toolMetadata.arguments,
|
|
434
|
+
intent: toolMetadata.intent,
|
|
435
|
+
resultContent: toolMetadata.resultText,
|
|
436
|
+
detailsData: payload.metadata.detailsData,
|
|
437
|
+
isError: toolMetadata.isError,
|
|
438
|
+
hasResult: toolMetadata.hasResult,
|
|
439
|
+
}),
|
|
440
|
+
capabilities: capabilities ?? { copyable: true, foldable: true, rawViewable: true },
|
|
441
|
+
identity: { id: item.id, label },
|
|
442
|
+
}),
|
|
443
|
+
];
|
|
382
444
|
return [{ id: item.id, kind: item.kind, label, payload, ...capabilities }];
|
|
383
445
|
});
|
|
384
446
|
}
|
|
@@ -227,19 +227,37 @@ export class GoalModeController {
|
|
|
227
227
|
this.scheduleContinuation();
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Restore durable goal state. Recovery hydration deliberately performs only
|
|
232
|
+
* in-memory restoration: it neither consumes a pending request nor repairs
|
|
233
|
+
* invalid persisted state by writing a fallback mode entry.
|
|
234
|
+
*/
|
|
235
|
+
async restoreFromSession(
|
|
236
|
+
sessionContext: SessionContext,
|
|
237
|
+
options?: { recoveryHydration?: boolean },
|
|
238
|
+
): Promise<boolean> {
|
|
239
|
+
const recoveryHydration = options?.recoveryHydration === true;
|
|
231
240
|
const goalEnabled = this.ctx.session.settings.get("goal.enabled");
|
|
232
241
|
if (!goalEnabled && (sessionContext.mode === "goal" || sessionContext.mode === "goal_paused")) {
|
|
242
|
+
if (recoveryHydration) return false;
|
|
233
243
|
this.ctx.sessionManager.appendModeChange("none");
|
|
234
244
|
return true;
|
|
235
245
|
}
|
|
236
246
|
if (sessionContext.mode === "goal" || sessionContext.mode === "goal_paused") {
|
|
237
247
|
const goal = normalizeGoal(sessionContext.modeData?.goal) ?? undefined;
|
|
238
248
|
if (!goal) {
|
|
249
|
+
if (recoveryHydration) throw new Error("Recovery hydration rejected an invalid persisted goal state.");
|
|
239
250
|
this.ctx.sessionManager.appendModeChange("none");
|
|
240
251
|
return true;
|
|
241
252
|
}
|
|
242
253
|
this.ctx.session.setGoalModeState({ enabled: sessionContext.mode === "goal", mode: "active", goal });
|
|
254
|
+
if (recoveryHydration) {
|
|
255
|
+
this.#enabled = sessionContext.mode === "goal";
|
|
256
|
+
this.#paused = sessionContext.mode === "goal_paused";
|
|
257
|
+
if (this.#enabled || this.#paused) this.ctx.modeGate.enter("goal");
|
|
258
|
+
this.#updateStatus();
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
243
261
|
const restored = await this.ctx.session.goalRuntime.onThreadResumed();
|
|
244
262
|
this.#enabled = restored?.enabled === true;
|
|
245
263
|
this.#paused = restored?.enabled !== true && restored?.goal.status === "paused";
|
|
@@ -251,6 +269,7 @@ export class GoalModeController {
|
|
|
251
269
|
this.#updateStatus();
|
|
252
270
|
return true;
|
|
253
271
|
}
|
|
272
|
+
if (recoveryHydration) return false;
|
|
254
273
|
const pendingGoal = goalEnabled
|
|
255
274
|
? await consumePendingGoalModeRequest(this.ctx.sessionManager.getCwd(), this.ctx.sessionManager.getSessionId())
|
|
256
275
|
: null;
|