@gajae-code/coding-agent 0.11.4 → 0.11.6
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 +23 -0
- package/dist/types/cli/notify-cli.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +31 -1
- 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/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/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/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 +58 -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/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/bus/chat-daemon-cli.d.ts +3 -1
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +42 -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 +11 -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 +98 -18
- package/dist/types/session/session-storage.d.ts +27 -0
- package/dist/types/session/streaming-output.d.ts +4 -0
- package/dist/types/session/yield-queue.d.ts +8 -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 +48 -0
- package/dist/types/tools/bash-skill-urls.d.ts +2 -2
- package/dist/types/tools/index.d.ts +4 -0
- package/package.json +9 -9
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/src/cli/notify-cli.ts +40 -18
- package/src/commands/sdk.ts +7 -2
- package/src/config/settings-schema.ts +31 -1
- 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/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/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-runtime.ts +38 -12
- 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 +8 -7
- package/src/internal-urls/local-protocol.ts +416 -18
- package/src/lsp/index.ts +4 -88
- package/src/main.ts +36 -12
- package/src/modes/acp/acp-agent.ts +8 -0
- package/src/modes/components/ansi-display-validator.ts +54 -0
- package/src/modes/components/notifications-settings-editor.ts +26 -2
- package/src/modes/components/session-observer-overlay.ts +40 -24
- package/src/modes/components/settings-selector.ts +3 -1
- package/src/modes/components/tool-transcript-format.ts +321 -0
- package/src/modes/components/transcript-viewer-overlay.ts +39 -17
- package/src/modes/controllers/command-controller.ts +15 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -2
- package/src/modes/controllers/goal-mode-controller.ts +20 -1
- 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 +70 -8
- package/src/modes/interactive-mode.ts +1 -0
- package/src/modes/runtime-init.ts +16 -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/session-index.ts +122 -64
- package/src/sdk/bus/chat-daemon-cli.ts +72 -10
- package/src/sdk/bus/chat-daemon-control.ts +538 -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 +388 -94
- 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 +11 -3
- package/src/sdk/bus/telegram-daemon-control.ts +312 -76
- package/src/sdk/bus/telegram-daemon.ts +1916 -285
- package/src/sdk/host/control/dispatch.ts +14 -5
- package/src/sdk/protocol/operation-inventory.generated.json +29 -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 +1447 -852
- package/src/session/artifacts.ts +188 -23
- package/src/session/context-estimation.ts +7 -2
- package/src/session/internal/managed-session-scope.ts +200 -38
- package/src/session/internal/managed-session-storage.ts +1152 -46
- package/src/session/session-manager.ts +1121 -148
- package/src/session/session-storage.ts +109 -11
- package/src/session/streaming-output.ts +6 -0
- package/src/session/yield-queue.ts +20 -0
- package/src/slash-commands/builtin-registry.ts +36 -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-answer-registry.ts +15 -1
- package/src/tools/ask.ts +129 -32
- package/src/tools/bash-skill-urls.ts +14 -3
- package/src/tools/bash.ts +2 -2
- package/src/tools/index.ts +4 -0
- package/src/tools/plan-mode-guard.ts +1 -0
- package/src/tools/sqlite-reader.ts +3 -2
- package/src/tools/subagent.ts +2 -0
|
@@ -1,5 +1,326 @@
|
|
|
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
|
+
|
|
1
9
|
export const MAX_TOOL_ARGS_CHARS = 500;
|
|
2
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
|
+
}
|
|
3
324
|
|
|
4
325
|
type ToolCallFields = {
|
|
5
326
|
name: string;
|
|
@@ -11,7 +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 {
|
|
14
|
+
import {
|
|
15
|
+
buildToolTranscriptEntry,
|
|
16
|
+
createToolTranscriptRenderDescriptor,
|
|
17
|
+
renderToolDisplayLines,
|
|
18
|
+
type ToolTranscriptRenderDescriptor,
|
|
19
|
+
} from "./tool-transcript-format";
|
|
15
20
|
|
|
16
21
|
const INDENT = " ";
|
|
17
22
|
const PAGE_SIZE = 15;
|
|
@@ -26,6 +31,8 @@ export type TranscriptViewerEntry = {
|
|
|
26
31
|
foldable?: boolean;
|
|
27
32
|
rawViewable?: boolean;
|
|
28
33
|
getDisplayText?: (expanded: boolean) => string;
|
|
34
|
+
renderDescriptor?: ToolTranscriptRenderDescriptor;
|
|
35
|
+
richRenderEligible?: boolean;
|
|
29
36
|
};
|
|
30
37
|
|
|
31
38
|
export type TranscriptViewerOverlayOptions = {
|
|
@@ -333,6 +340,19 @@ export class TranscriptViewerOverlay extends Container {
|
|
|
333
340
|
lines.push(
|
|
334
341
|
`${selected ? theme.fg("accent", "▶") : " "} ${theme.fg("muted", `[${sanitizeText(entry.label ?? entry.kind)}]`)}`,
|
|
335
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
|
+
}
|
|
336
356
|
const text = sanitizeText(
|
|
337
357
|
(raw
|
|
338
358
|
? entry.payload.text
|
|
@@ -404,21 +424,23 @@ export function transcriptViewerEntries(registry: TranscriptItemRegistry): Trans
|
|
|
404
424
|
? "User"
|
|
405
425
|
: item.kind;
|
|
406
426
|
const toolMetadata = hasToolTranscriptMetadata(payload.metadata) ? payload.metadata : undefined;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
+
];
|
|
444
|
+
return [{ id: item.id, kind: item.kind, label, payload, ...capabilities }];
|
|
423
445
|
});
|
|
424
446
|
}
|
|
@@ -1260,6 +1260,10 @@ export class CommandController {
|
|
|
1260
1260
|
}
|
|
1261
1261
|
|
|
1262
1262
|
async handleHandoffCommand(customInstructions?: string): Promise<void> {
|
|
1263
|
+
if (this.ctx.session.isStreaming) {
|
|
1264
|
+
this.ctx.showWarning("Wait for the current response to finish or abort it before handing off.");
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1263
1267
|
const entries = this.ctx.sessionManager.getEntries();
|
|
1264
1268
|
const messageCount = entries.filter(e => e.type === "message").length;
|
|
1265
1269
|
|
|
@@ -1321,6 +1325,17 @@ export class CommandController {
|
|
|
1321
1325
|
} else {
|
|
1322
1326
|
this.ctx.showError(`Handoff failed: ${message}`);
|
|
1323
1327
|
}
|
|
1328
|
+
// The failure is non-destructive: the current session is unchanged. If the
|
|
1329
|
+
// document was already generated, preserve it on the clipboard for retry.
|
|
1330
|
+
const retainedDocument =
|
|
1331
|
+
error &&
|
|
1332
|
+
typeof error === "object" &&
|
|
1333
|
+
typeof (error as { handoffDocument?: unknown }).handoffDocument === "string"
|
|
1334
|
+
? (error as { handoffDocument: string }).handoffDocument
|
|
1335
|
+
: undefined;
|
|
1336
|
+
if (retainedDocument) {
|
|
1337
|
+
this.#doCopy(retainedDocument, "Handoff document preserved on clipboard; current session is unchanged.");
|
|
1338
|
+
}
|
|
1324
1339
|
} finally {
|
|
1325
1340
|
handoffLoader.stop();
|
|
1326
1341
|
this.ctx.statusContainer.clear();
|
|
@@ -214,13 +214,25 @@ export class ExtensionUiController {
|
|
|
214
214
|
try {
|
|
215
215
|
return {
|
|
216
216
|
handoff: await session.handoff(
|
|
217
|
-
typeof input.
|
|
217
|
+
typeof input.target === "string"
|
|
218
|
+
? input.target
|
|
219
|
+
: typeof input.instructions === "string"
|
|
220
|
+
? input.instructions
|
|
221
|
+
: undefined,
|
|
218
222
|
),
|
|
219
223
|
};
|
|
220
224
|
} catch (error) {
|
|
225
|
+
const typed = error as { code?: unknown; handoffDocument?: unknown };
|
|
226
|
+
const handoffDocument =
|
|
227
|
+
typeof typed?.handoffDocument === "string" ? { handoffDocument: typed.handoffDocument } : undefined;
|
|
228
|
+
// Preserve a safe typed code (e.g. transient `busy`) so clients keep
|
|
229
|
+
// correct retry/backoff semantics; only synthesize invalid_request for
|
|
230
|
+
// otherwise-untyped failures.
|
|
231
|
+
const code = typed?.code === "busy" ? "busy" : "invalid_request";
|
|
221
232
|
throw Object.assign(
|
|
222
233
|
new Error(error instanceof Error ? error.message : "Handoff is unavailable for the current state."),
|
|
223
|
-
{ code
|
|
234
|
+
{ code },
|
|
235
|
+
handoffDocument,
|
|
224
236
|
);
|
|
225
237
|
}
|
|
226
238
|
case "session.export_html":
|
|
@@ -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;
|
|
@@ -334,6 +334,7 @@ export class PlanModeController {
|
|
|
334
334
|
return planFilePath.startsWith("local:")
|
|
335
335
|
? resolveLocalUrlToPath(normalizeLocalScheme(planFilePath), {
|
|
336
336
|
getArtifactsDir: () => this.ctx.sessionManager.getArtifactsDir(),
|
|
337
|
+
isManagedDestination: () => this.ctx.sessionManager.isManagedDestination(),
|
|
337
338
|
getSessionId: () => this.ctx.sessionManager.getSessionId(),
|
|
338
339
|
})
|
|
339
340
|
: path.resolve(this.ctx.sessionManager.getCwd(), planFilePath);
|
|
@@ -448,6 +449,7 @@ export class PlanModeController {
|
|
|
448
449
|
await Bun.write(
|
|
449
450
|
resolveLocalUrlToPath(options.finalPlanFilePath, {
|
|
450
451
|
getArtifactsDir: () => this.ctx.sessionManager.getArtifactsDir(),
|
|
452
|
+
isManagedDestination: () => this.ctx.sessionManager.isManagedDestination(),
|
|
451
453
|
getSessionId: () => this.ctx.sessionManager.getSessionId(),
|
|
452
454
|
}),
|
|
453
455
|
planContent,
|
|
@@ -1450,6 +1450,9 @@ export class MCPCommandController {
|
|
|
1450
1450
|
if (!this.ctx.mcpManager) {
|
|
1451
1451
|
return;
|
|
1452
1452
|
}
|
|
1453
|
+
if (this.ctx.mcpManager.isConnectionSetSealed()) {
|
|
1454
|
+
throw new Error("This session's plugin-bundle MCP connections are fixed for its lifetime.");
|
|
1455
|
+
}
|
|
1453
1456
|
|
|
1454
1457
|
// Disconnect all existing servers
|
|
1455
1458
|
await this.ctx.mcpManager.disconnectAll();
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
theme,
|
|
44
44
|
} from "../../modes/theme/theme";
|
|
45
45
|
import type { InteractiveModeContext, OAuthSelectorOptions } from "../../modes/types";
|
|
46
|
-
import { getNotificationConfig, maskToken } from "../../sdk/bus/config";
|
|
46
|
+
import { getNotificationConfig, isTelegramConfigured, maskToken } from "../../sdk/bus/config";
|
|
47
47
|
import {
|
|
48
48
|
clearTelegramActivationMarker,
|
|
49
49
|
createTelegramActivationMarker,
|
|
@@ -228,6 +228,13 @@ export interface NotificationsEditorOperationDependencies {
|
|
|
228
228
|
saveTelegramInactive: typeof saveTelegramInactive;
|
|
229
229
|
removeTelegramConfiguration: typeof removeTelegramConfiguration;
|
|
230
230
|
unregisterNotificationRoot: typeof unregisterNotificationRoot;
|
|
231
|
+
reloadTelegramDaemon(settings: Settings): Promise<{ ok: boolean; message: string }>;
|
|
232
|
+
restartTelegramDaemon(settings: Settings): Promise<{ ok: boolean; message: string }>;
|
|
233
|
+
stopTelegramDaemon(settings: Settings): Promise<{
|
|
234
|
+
ok: boolean;
|
|
235
|
+
message: string;
|
|
236
|
+
before?: { health?: string };
|
|
237
|
+
}>;
|
|
231
238
|
}
|
|
232
239
|
|
|
233
240
|
const notificationEditorOperationDependencies: NotificationsEditorOperationDependencies = {
|
|
@@ -245,6 +252,11 @@ const notificationEditorOperationDependencies: NotificationsEditorOperationDepen
|
|
|
245
252
|
saveTelegramInactive,
|
|
246
253
|
removeTelegramConfiguration,
|
|
247
254
|
unregisterNotificationRoot,
|
|
255
|
+
reloadTelegramDaemon: async settings =>
|
|
256
|
+
await new TelegramDaemonController(settings).reload({ spawnIfStopped: false }),
|
|
257
|
+
restartTelegramDaemon: async settings =>
|
|
258
|
+
await new TelegramDaemonController(settings).reload({ spawnIfStopped: true }),
|
|
259
|
+
stopTelegramDaemon: async settings => await new TelegramDaemonController(settings).stop(),
|
|
248
260
|
};
|
|
249
261
|
|
|
250
262
|
function unavailableNotificationSessionStatus(): NotificationSessionStatus {
|
|
@@ -333,6 +345,8 @@ export function createNotificationsEditorOperations(
|
|
|
333
345
|
sessionScope: config.sessionScope,
|
|
334
346
|
richEnabled: config.rich.enabled,
|
|
335
347
|
richDraftEnabled: config.richDraft.enabled,
|
|
348
|
+
toolActivityEnabled: config.toolActivity.enabled,
|
|
349
|
+
streamingEnabled: config.streaming.enabled,
|
|
336
350
|
},
|
|
337
351
|
};
|
|
338
352
|
},
|
|
@@ -470,6 +484,7 @@ export function createNotificationsEditorOperations(
|
|
|
470
484
|
tokenFingerprint: setup.tokenFingerprint,
|
|
471
485
|
richEnabled: input.richEnabled,
|
|
472
486
|
richDraftEnabled: input.richDraftEnabled,
|
|
487
|
+
streamingEnabled: input.streamingEnabled,
|
|
473
488
|
};
|
|
474
489
|
drafts.set(draft, token);
|
|
475
490
|
const pairingMessage =
|
|
@@ -513,6 +528,7 @@ export function createNotificationsEditorOperations(
|
|
|
513
528
|
{ path: "notifications.telegram.chatId", op: "set", value: draft.chatId },
|
|
514
529
|
{ path: "notifications.telegram.rich.enabled", op: "set", value: draft.richEnabled },
|
|
515
530
|
{ path: "notifications.telegram.richDraft.enabled", op: "set", value: draft.richDraftEnabled },
|
|
531
|
+
{ path: "notifications.telegram.streaming.enabled", op: "set", value: draft.streamingEnabled },
|
|
516
532
|
]);
|
|
517
533
|
drafts.delete(draft);
|
|
518
534
|
const activationMarker = createTelegramActivationMarker({
|
|
@@ -676,14 +692,60 @@ export function createNotificationsEditorOperations(
|
|
|
676
692
|
},
|
|
677
693
|
|
|
678
694
|
commitPreferences: async preferences => {
|
|
695
|
+
let daemonWasRunningForDisable = false;
|
|
679
696
|
try {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
697
|
+
const before = services.getNotificationConfig(ctx.settings);
|
|
698
|
+
const disablingToolActivity =
|
|
699
|
+
isTelegramConfigured(before) && before.toolActivity.enabled && !preferences.toolActivityEnabled;
|
|
700
|
+
if (disablingToolActivity) {
|
|
701
|
+
const stopped = await services.stopTelegramDaemon(ctx.settings);
|
|
702
|
+
if (!stopped.ok)
|
|
703
|
+
throw new Error(
|
|
704
|
+
`Notification preferences were not saved because daemon stop failed: ${stopped.message}`,
|
|
705
|
+
);
|
|
706
|
+
daemonWasRunningForDisable = stopped.before?.health === "running";
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
let receipt: Awaited<ReturnType<typeof ctx.settings.commitAtomicBatch>>;
|
|
710
|
+
try {
|
|
711
|
+
receipt = await ctx.settings.commitAtomicBatch([
|
|
712
|
+
{ path: "notifications.redact", op: "set", value: preferences.redact },
|
|
713
|
+
{ path: "notifications.verbosity", op: "set", value: preferences.verbosity },
|
|
714
|
+
{ path: "notifications.sessionScope", op: "set", value: preferences.sessionScope },
|
|
715
|
+
{ path: "notifications.telegram.rich.enabled", op: "set", value: preferences.richEnabled },
|
|
716
|
+
{ path: "notifications.telegram.richDraft.enabled", op: "set", value: preferences.richDraftEnabled },
|
|
717
|
+
{ path: "notifications.telegram.streaming.enabled", op: "set", value: preferences.streamingEnabled },
|
|
718
|
+
{
|
|
719
|
+
path: "notifications.telegram.toolActivity.enabled",
|
|
720
|
+
op: "set",
|
|
721
|
+
value: preferences.toolActivityEnabled,
|
|
722
|
+
},
|
|
723
|
+
]);
|
|
724
|
+
} catch (error) {
|
|
725
|
+
if (daemonWasRunningForDisable) {
|
|
726
|
+
try {
|
|
727
|
+
const restarted = await services.restartTelegramDaemon(ctx.settings);
|
|
728
|
+
if (!restarted.ok) throw new Error(restarted.message);
|
|
729
|
+
} catch (restartError) {
|
|
730
|
+
const commitMessage = error instanceof Error ? error.message : String(error);
|
|
731
|
+
const restartMessage = restartError instanceof Error ? restartError.message : String(restartError);
|
|
732
|
+
throw new Error(
|
|
733
|
+
`Notification preference commit failed (${commitMessage}) and daemon restart failed (${restartMessage}).`,
|
|
734
|
+
{ cause: new AggregateError([error, restartError]) },
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
throw error;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const config = services.getNotificationConfig(ctx.settings);
|
|
742
|
+
if (isTelegramConfigured(config)) {
|
|
743
|
+
const reload = daemonWasRunningForDisable
|
|
744
|
+
? await services.restartTelegramDaemon(ctx.settings)
|
|
745
|
+
: await services.reloadTelegramDaemon(ctx.settings);
|
|
746
|
+
if (!reload.ok)
|
|
747
|
+
throw new Error(`Notification preferences were saved, but daemon reload failed: ${reload.message}`);
|
|
748
|
+
}
|
|
687
749
|
await notifyAfterDurableCommit();
|
|
688
750
|
return { receipt, message: "Notification preferences saved atomically." };
|
|
689
751
|
} catch (error) {
|
|
@@ -1839,6 +1839,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1839
1839
|
isError: result?.isError ?? false,
|
|
1840
1840
|
resultText,
|
|
1841
1841
|
hasResult: toolResults.has(content.id),
|
|
1842
|
+
detailsData: result?.details,
|
|
1842
1843
|
},
|
|
1843
1844
|
source: { content, result },
|
|
1844
1845
|
}),
|