@gajae-code/coding-agent 0.9.1 → 0.9.3
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 +36 -0
- package/dist/types/capability/mcp.d.ts +3 -1
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +5 -0
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commands/mcp.d.ts +1 -1
- package/dist/types/config/model-profile-activation.d.ts +7 -7
- package/dist/types/config/model-registry.d.ts +7 -3
- package/dist/types/config/model-resolver.d.ts +2 -2
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +20 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +9 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -2
- package/dist/types/modes/components/status-line/types.d.ts +6 -0
- package/dist/types/modes/components/status-line.d.ts +1 -0
- package/dist/types/modes/components/thinking-selector.d.ts +6 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +12 -0
- package/dist/types/notifications/config-commands.d.ts +29 -0
- package/dist/types/notifications/index.d.ts +12 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +3 -3
- package/dist/types/notifications/threaded-render.d.ts +2 -0
- package/dist/types/notifications/topic-registry.d.ts +4 -5
- package/dist/types/runtime-credential-selector.d.ts +7 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +4 -3
- package/dist/types/runtime-mcp/types.d.ts +5 -4
- package/dist/types/sdk.d.ts +7 -1
- package/dist/types/session/agent-session.d.ts +22 -3
- package/dist/types/session/session-manager.d.ts +1 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/skill-discovery.d.ts +40 -0
- package/dist/types/tools/skill.d.ts +9 -8
- package/dist/types/utils/linkedom.d.ts +4 -0
- package/dist/types/utils/pasted-image-path.d.ts +5 -3
- package/package.json +11 -7
- package/scripts/build-binary.ts +2 -40
- package/scripts/compile-args.ts +91 -0
- package/scripts/generate-docs-index.ts +1 -0
- package/src/capability/mcp.ts +3 -1
- package/src/cli/args.ts +8 -0
- package/src/cli/mcp-cli.ts +108 -26
- package/src/cli/notify-cli.ts +27 -1
- package/src/cli.ts +26 -0
- package/src/commands/launch.ts +5 -0
- package/src/commands/mcp.ts +8 -8
- package/src/commands/notify.ts +2 -2
- package/src/commands/stats.ts +6 -2
- package/src/config/mcp-schema.json +21 -17
- package/src/config/model-profile-activation.ts +23 -7
- package/src/config/model-registry.ts +21 -4
- package/src/config/model-resolver.ts +5 -16
- package/src/config/settings-schema.ts +3 -2
- package/src/config/settings.ts +24 -1
- package/src/coordinator-mcp/server.ts +7 -4
- package/src/discovery/builtin.ts +14 -0
- package/src/discovery/mcp-json.ts +14 -0
- package/src/extensibility/runtime-skill-discovery.ts +229 -0
- package/src/gjc-runtime/launch-tmux.ts +72 -12
- package/src/gjc-runtime/psmux-detect.ts +17 -2
- package/src/gjc-runtime/state-runtime.ts +105 -12
- package/src/gjc-runtime/team-runtime.ts +35 -3
- package/src/gjc-runtime/workflow-manifest.generated.json +4 -28
- package/src/gjc-runtime/workflow-manifest.ts +1 -7
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/gjc-protocol.ts +14 -1
- package/src/main.ts +26 -3
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/settings-selector.ts +3 -3
- package/src/modes/components/status-line/presets.ts +5 -7
- package/src/modes/components/status-line/segments.ts +14 -0
- package/src/modes/components/status-line/types.ts +6 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/thinking-selector.ts +79 -18
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/controllers/event-controller.ts +9 -9
- package/src/modes/controllers/input-controller.ts +52 -11
- package/src/modes/controllers/selector-controller.ts +7 -5
- package/src/modes/interactive-mode.ts +12 -4
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +202 -47
- package/src/notifications/config-commands.ts +70 -0
- package/src/notifications/index.ts +156 -9
- package/src/notifications/rich-render.ts +1 -0
- package/src/notifications/telegram-daemon-cli.ts +13 -5
- package/src/notifications/telegram-daemon.ts +87 -4
- package/src/notifications/threaded-render.ts +14 -0
- package/src/notifications/topic-registry.ts +9 -8
- package/src/prompts/tools/skill-discovery.md +13 -0
- package/src/prompts/tools/skill.md +2 -2
- package/src/runtime-credential-selector.ts +35 -0
- package/src/runtime-mcp/config-writer.ts +4 -3
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/types.ts +5 -4
- package/src/sdk.ts +68 -5
- package/src/session/agent-session.ts +159 -21
- package/src/session/session-manager.ts +68 -40
- package/src/system-prompt.ts +4 -6
- package/src/tools/bash-interactive.ts +9 -4
- package/src/tools/browser/readable.ts +2 -2
- package/src/tools/fetch.ts +5 -5
- package/src/tools/index.ts +4 -0
- package/src/tools/skill-discovery.ts +73 -0
- package/src/tools/skill.ts +29 -13
- package/src/utils/linkedom.ts +14 -0
- package/src/utils/pasted-image-path.ts +23 -9
- package/src/web/scrapers/arxiv.ts +2 -2
- package/src/web/scrapers/go-pkg.ts +2 -2
- package/src/web/scrapers/iacr.ts +2 -2
- package/src/web/scrapers/readthedocs.ts +2 -2
- package/src/web/scrapers/twitter.ts +3 -2
- package/src/web/scrapers/wikipedia.ts +2 -2
- package/src/export/html/template.css +0 -1060
- package/src/export/html/template.html +0 -47
- package/src/export/html/template.js +0 -2314
- package/vendor/insane-search/engine/tests/test_hardening.py +0 -57
- package/vendor/insane-search/engine/tests/test_smoke.py +0 -152
- package/vendor/insane-search/engine/tests/test_u1.py +0 -200
- package/vendor/insane-search/engine/tests/test_u4.py +0 -131
- package/vendor/insane-search/engine/tests/test_u5.py +0 -163
- package/vendor/insane-search/engine/tests/test_u7.py +0 -124
- package/vendor/insane-search/engine/tests/test_u8.py +0 -216
|
@@ -36,17 +36,27 @@ interface RenderInitialMessagesOptions {
|
|
|
36
36
|
preserveExistingChat?: boolean;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function cloneRenderArgs(args: Record<string, unknown>): Record<string, unknown> {
|
|
40
|
+
try {
|
|
41
|
+
return structuredClone(args);
|
|
42
|
+
} catch {
|
|
43
|
+
return { ...args };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
export function argsWithPartialJson(args: unknown, partialJson: unknown): unknown {
|
|
40
48
|
if (typeof partialJson !== "string" || !args || typeof args !== "object" || Array.isArray(args)) return args;
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
|
|
49
|
+
// Keep the transient streaming buffer on a renderer-only snapshot. The live
|
|
50
|
+
// assistant message args are later validated/executed, so UI-only metadata or
|
|
51
|
+
// renderer mutations must never share that object reference.
|
|
52
|
+
const renderArgs = cloneRenderArgs(args as Record<string, unknown>);
|
|
53
|
+
Object.defineProperty(renderArgs, "__partialJson", {
|
|
44
54
|
value: partialJson,
|
|
45
55
|
enumerable: false,
|
|
46
56
|
configurable: true,
|
|
47
57
|
writable: true,
|
|
48
58
|
});
|
|
49
|
-
return
|
|
59
|
+
return renderArgs;
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
type QueuedMessages = {
|
|
@@ -54,6 +64,127 @@ type QueuedMessages = {
|
|
|
54
64
|
followUp: string[];
|
|
55
65
|
};
|
|
56
66
|
|
|
67
|
+
const CHAT_CHILD_CAP = 400;
|
|
68
|
+
const CHAT_COLLAPSE_BATCH_SIZE = 300;
|
|
69
|
+
|
|
70
|
+
const chatChildAddedAt = new WeakMap<Component, number>();
|
|
71
|
+
|
|
72
|
+
export class CollapsedChatHistoryComponent implements Component {
|
|
73
|
+
readonly isCollapsedChatHistory = true;
|
|
74
|
+
count: number;
|
|
75
|
+
startTime: number;
|
|
76
|
+
endTime: number;
|
|
77
|
+
|
|
78
|
+
constructor(count: number, startTime: number, endTime: number) {
|
|
79
|
+
this.count = count;
|
|
80
|
+
this.startTime = startTime;
|
|
81
|
+
this.endTime = endTime;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
merge(count: number, startTime: number, endTime: number): void {
|
|
85
|
+
this.count += count;
|
|
86
|
+
this.startTime = Math.min(this.startTime, startTime);
|
|
87
|
+
this.endTime = Math.max(this.endTime, endTime);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
render(_width: number): string[] {
|
|
91
|
+
const label = `[${this.count} earlier messages collapsed — ${formatChatCollapseTime(this.startTime)} to ${formatChatCollapseTime(this.endTime)}]`;
|
|
92
|
+
return [theme.fg("dim", label)];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
invalidate(): void {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatChatCollapseTime(timestamp: number): string {
|
|
99
|
+
return new Date(timestamp).toLocaleTimeString(undefined, {
|
|
100
|
+
hour: "2-digit",
|
|
101
|
+
minute: "2-digit",
|
|
102
|
+
hour12: false,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isCollapsedChatHistory(component: Component): component is CollapsedChatHistoryComponent {
|
|
107
|
+
return component instanceof CollapsedChatHistoryComponent;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isActiveChatChild(ctx: InteractiveModeContext, component: Component): boolean {
|
|
111
|
+
if ([...ctx.pendingTools.values()].includes(component as never)) return true;
|
|
112
|
+
if (ctx.pendingBashComponents.includes(component as never)) return true;
|
|
113
|
+
if (ctx.pendingPythonComponents.includes(component as never)) return true;
|
|
114
|
+
return component === ctx.bashComponent || component === ctx.pythonComponent || component === ctx.streamingComponent;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getChatChildTime(component: Component): number {
|
|
118
|
+
return chatChildAddedAt.get(component) ?? Date.now();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function addChatChild(ctx: InteractiveModeContext, component: Component): void {
|
|
122
|
+
ctx.chatContainer.addChild(component);
|
|
123
|
+
chatChildAddedAt.set(component, Date.now());
|
|
124
|
+
trimChatChildren(ctx);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function trimChatChildren(ctx: InteractiveModeContext): void {
|
|
128
|
+
const children = ctx.chatContainer.children;
|
|
129
|
+
|
|
130
|
+
// Collapse the oldest completed children into history placeholders until the cap is
|
|
131
|
+
// satisfied or no further reduction is structurally possible. Active (pending/running)
|
|
132
|
+
// children and existing placeholders are never collapsed, so a conversation whose
|
|
133
|
+
// top-level children are dominated by active components may legitimately exceed the cap.
|
|
134
|
+
let scanFrom = 0;
|
|
135
|
+
while (children.length > CHAT_CHILD_CAP) {
|
|
136
|
+
// Advance to the oldest collapsible completed child, skipping active children and
|
|
137
|
+
// existing placeholders so the cap is enforced even when the oldest child is active.
|
|
138
|
+
while (
|
|
139
|
+
scanFrom < children.length &&
|
|
140
|
+
(isActiveChatChild(ctx, children[scanFrom]) || isCollapsedChatHistory(children[scanFrom]))
|
|
141
|
+
) {
|
|
142
|
+
scanFrom++;
|
|
143
|
+
}
|
|
144
|
+
if (scanFrom >= children.length) return; // only active/placeholder children remain
|
|
145
|
+
|
|
146
|
+
// Extend over the oldest contiguous run of completed children (up to the batch size).
|
|
147
|
+
let collapseEndExclusive = scanFrom;
|
|
148
|
+
const maxEnd = Math.min(children.length, scanFrom + CHAT_COLLAPSE_BATCH_SIZE);
|
|
149
|
+
while (
|
|
150
|
+
collapseEndExclusive < maxEnd &&
|
|
151
|
+
!isActiveChatChild(ctx, children[collapseEndExclusive]) &&
|
|
152
|
+
!isCollapsedChatHistory(children[collapseEndExclusive])
|
|
153
|
+
) {
|
|
154
|
+
collapseEndExclusive++;
|
|
155
|
+
}
|
|
156
|
+
const collapseCount = collapseEndExclusive - scanFrom;
|
|
157
|
+
|
|
158
|
+
const prev = scanFrom > 0 ? children[scanFrom - 1] : undefined;
|
|
159
|
+
const mergeTarget = prev && isCollapsedChatHistory(prev) ? prev : undefined;
|
|
160
|
+
// A lone completed child with no adjacent placeholder to merge into cannot reduce
|
|
161
|
+
// the child count (collapsing it yields a 1-count placeholder). Skip past it.
|
|
162
|
+
if (!mergeTarget && collapseCount <= 1) {
|
|
163
|
+
scanFrom += Math.max(collapseCount, 1);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const collapsed = children.slice(scanFrom, collapseEndExclusive);
|
|
168
|
+
const startTime = getChatChildTime(collapsed[0]);
|
|
169
|
+
const endTime = getChatChildTime(collapsed[collapsed.length - 1]);
|
|
170
|
+
for (const child of collapsed) {
|
|
171
|
+
child.dispose?.();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (mergeTarget) {
|
|
175
|
+
mergeTarget.merge(collapseCount, startTime, endTime);
|
|
176
|
+
children.splice(scanFrom, collapseCount);
|
|
177
|
+
chatChildAddedAt.set(mergeTarget, mergeTarget.startTime);
|
|
178
|
+
// scanFrom stays; whatever follows the merged run is next.
|
|
179
|
+
} else {
|
|
180
|
+
const placeholder = new CollapsedChatHistoryComponent(collapseCount, startTime, endTime);
|
|
181
|
+
children.splice(scanFrom, collapseCount, placeholder);
|
|
182
|
+
chatChildAddedAt.set(placeholder, startTime);
|
|
183
|
+
scanFrom++; // step past the freshly created placeholder
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
57
188
|
export class UiHelpers {
|
|
58
189
|
constructor(private ctx: InteractiveModeContext) {}
|
|
59
190
|
|
|
@@ -91,8 +222,8 @@ export class UiHelpers {
|
|
|
91
222
|
|
|
92
223
|
const spacer = new Spacer(1);
|
|
93
224
|
const text = new Text(rendered, 1, 0);
|
|
94
|
-
this.ctx
|
|
95
|
-
this.ctx
|
|
225
|
+
addChatChild(this.ctx, spacer);
|
|
226
|
+
addChatChild(this.ctx, text);
|
|
96
227
|
this.ctx.lastStatusSpacer = spacer;
|
|
97
228
|
this.ctx.lastStatusText = text;
|
|
98
229
|
this.ctx.ui.requestRender();
|
|
@@ -108,7 +239,7 @@ export class UiHelpers {
|
|
|
108
239
|
component.setComplete(message.exitCode, message.cancelled, {
|
|
109
240
|
truncation: message.meta?.truncation,
|
|
110
241
|
});
|
|
111
|
-
this.ctx
|
|
242
|
+
addChatChild(this.ctx, component);
|
|
112
243
|
break;
|
|
113
244
|
}
|
|
114
245
|
case "pythonExecution": {
|
|
@@ -119,7 +250,7 @@ export class UiHelpers {
|
|
|
119
250
|
component.setComplete(message.exitCode, message.cancelled, {
|
|
120
251
|
truncation: message.meta?.truncation,
|
|
121
252
|
});
|
|
122
|
-
this.ctx
|
|
253
|
+
addChatChild(this.ctx, component);
|
|
123
254
|
break;
|
|
124
255
|
}
|
|
125
256
|
case "hookMessage":
|
|
@@ -163,14 +294,14 @@ export class UiHelpers {
|
|
|
163
294
|
]
|
|
164
295
|
.filter(Boolean)
|
|
165
296
|
.join(" ");
|
|
166
|
-
this.ctx
|
|
297
|
+
addChatChild(this.ctx, new Text(line, 1, 0));
|
|
167
298
|
}
|
|
168
299
|
break;
|
|
169
300
|
}
|
|
170
301
|
if (message.customType === SKILL_PROMPT_MESSAGE_TYPE) {
|
|
171
302
|
const component = new SkillMessageComponent(message as CustomMessage<SkillPromptDetails>);
|
|
172
303
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
173
|
-
this.ctx
|
|
304
|
+
addChatChild(this.ctx, component);
|
|
174
305
|
break;
|
|
175
306
|
}
|
|
176
307
|
if (
|
|
@@ -207,12 +338,12 @@ export class UiHelpers {
|
|
|
207
338
|
const components: Component[] = [];
|
|
208
339
|
const header = `${theme.fg("accent", `[IRC] ${arrow}`)}`;
|
|
209
340
|
const headerComponent = new Text(header, 1, 0);
|
|
210
|
-
this.ctx
|
|
341
|
+
addChatChild(this.ctx, headerComponent);
|
|
211
342
|
components.push(headerComponent);
|
|
212
343
|
if (body) {
|
|
213
344
|
for (const line of body.split("\n")) {
|
|
214
345
|
const lineComponent = new Text(theme.fg("muted", ` ${line}`), 0, 0);
|
|
215
|
-
this.ctx
|
|
346
|
+
addChatChild(this.ctx, lineComponent);
|
|
216
347
|
components.push(lineComponent);
|
|
217
348
|
}
|
|
218
349
|
}
|
|
@@ -230,12 +361,12 @@ export class UiHelpers {
|
|
|
230
361
|
const components: Component[] = [];
|
|
231
362
|
const header = `${theme.fg("accent", `[Steer ${details?.state ?? "queued"}] ${details?.from ?? "?"} ⇨ ${details?.to ?? "?"}`)}`;
|
|
232
363
|
const headerComponent = new Text(header, 1, 0);
|
|
233
|
-
this.ctx
|
|
364
|
+
addChatChild(this.ctx, headerComponent);
|
|
234
365
|
components.push(headerComponent);
|
|
235
366
|
if (details?.body) {
|
|
236
367
|
for (const line of details.body.split("\n")) {
|
|
237
368
|
const lineComponent = new Text(theme.fg("muted", ` ${line}`), 0, 0);
|
|
238
|
-
this.ctx
|
|
369
|
+
addChatChild(this.ctx, lineComponent);
|
|
239
370
|
components.push(lineComponent);
|
|
240
371
|
}
|
|
241
372
|
}
|
|
@@ -245,22 +376,22 @@ export class UiHelpers {
|
|
|
245
376
|
// Both HookMessage and CustomMessage have the same structure, cast for compatibility
|
|
246
377
|
const component = new CustomMessageComponent(message as CustomMessage<unknown>, renderer);
|
|
247
378
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
248
|
-
this.ctx
|
|
379
|
+
addChatChild(this.ctx, component);
|
|
249
380
|
}
|
|
250
381
|
break;
|
|
251
382
|
}
|
|
252
383
|
case "compactionSummary": {
|
|
253
|
-
this.ctx
|
|
384
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
254
385
|
const component = new CompactionSummaryMessageComponent(message);
|
|
255
386
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
256
|
-
this.ctx
|
|
387
|
+
addChatChild(this.ctx, component);
|
|
257
388
|
break;
|
|
258
389
|
}
|
|
259
390
|
case "branchSummary": {
|
|
260
|
-
this.ctx
|
|
391
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
261
392
|
const component = new BranchSummaryMessageComponent(message);
|
|
262
393
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
263
|
-
this.ctx
|
|
394
|
+
addChatChild(this.ctx, component);
|
|
264
395
|
break;
|
|
265
396
|
}
|
|
266
397
|
case "fileMention": {
|
|
@@ -281,7 +412,7 @@ export class UiHelpers {
|
|
|
281
412
|
"accent",
|
|
282
413
|
file.path,
|
|
283
414
|
)} ${theme.fg("dim", suffix)}`;
|
|
284
|
-
this.ctx
|
|
415
|
+
addChatChild(this.ctx, new Text(text, 0, 0));
|
|
285
416
|
}
|
|
286
417
|
break;
|
|
287
418
|
}
|
|
@@ -291,7 +422,7 @@ export class UiHelpers {
|
|
|
291
422
|
if (textContent) {
|
|
292
423
|
const isSynthetic = message.role === "developer" ? true : (message.synthetic ?? false);
|
|
293
424
|
const userComponent = new UserMessageComponent(textContent, isSynthetic);
|
|
294
|
-
this.ctx
|
|
425
|
+
addChatChild(this.ctx, userComponent);
|
|
295
426
|
if (options?.populateHistory && message.role === "user" && !isSynthetic) {
|
|
296
427
|
this.ctx.editor.addToHistory(textContent);
|
|
297
428
|
}
|
|
@@ -302,7 +433,7 @@ export class UiHelpers {
|
|
|
302
433
|
const assistantComponent = new AssistantMessageComponent(message, this.ctx.hideThinkingBlock, () =>
|
|
303
434
|
this.ctx.ui.requestRender(),
|
|
304
435
|
);
|
|
305
|
-
this.ctx
|
|
436
|
+
addChatChild(this.ctx, assistantComponent);
|
|
306
437
|
break;
|
|
307
438
|
}
|
|
308
439
|
case "toolResult": {
|
|
@@ -382,7 +513,7 @@ export class UiHelpers {
|
|
|
382
513
|
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
383
514
|
});
|
|
384
515
|
readGroup.setExpanded(this.ctx.toolOutputExpanded);
|
|
385
|
-
this.ctx
|
|
516
|
+
addChatChild(this.ctx, readGroup);
|
|
386
517
|
}
|
|
387
518
|
readGroup.updateArgs(content.arguments, content.id);
|
|
388
519
|
readGroup.updateResult(
|
|
@@ -424,7 +555,7 @@ export class UiHelpers {
|
|
|
424
555
|
content.id,
|
|
425
556
|
);
|
|
426
557
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
427
|
-
this.ctx
|
|
558
|
+
addChatChild(this.ctx, component);
|
|
428
559
|
|
|
429
560
|
if (hasErrorStop && errorMessage) {
|
|
430
561
|
component.updateResult(
|
|
@@ -462,7 +593,7 @@ export class UiHelpers {
|
|
|
462
593
|
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
463
594
|
});
|
|
464
595
|
readGroup.setExpanded(this.ctx.toolOutputExpanded);
|
|
465
|
-
this.ctx
|
|
596
|
+
addChatChild(this.ctx, readGroup);
|
|
466
597
|
}
|
|
467
598
|
const args = readToolCallArgs.get(message.toolCallId);
|
|
468
599
|
if (args) {
|
|
@@ -529,7 +660,7 @@ export class UiHelpers {
|
|
|
529
660
|
}
|
|
530
661
|
if (preservedChatChildren && preservedChatChildren.length > 0) {
|
|
531
662
|
for (const child of preservedChatChildren) {
|
|
532
|
-
this.ctx
|
|
663
|
+
addChatChild(this.ctx, child);
|
|
533
664
|
}
|
|
534
665
|
this.ctx.ui.requestRender();
|
|
535
666
|
}
|
|
@@ -549,8 +680,8 @@ export class UiHelpers {
|
|
|
549
680
|
process.stderr.write(`Error: ${errorMessage}\n`);
|
|
550
681
|
return;
|
|
551
682
|
}
|
|
552
|
-
this.ctx
|
|
553
|
-
this.ctx
|
|
683
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
684
|
+
addChatChild(this.ctx, new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
|
|
554
685
|
this.ctx.ui.requestRender();
|
|
555
686
|
}
|
|
556
687
|
|
|
@@ -559,15 +690,16 @@ export class UiHelpers {
|
|
|
559
690
|
process.stderr.write(`Warning: ${warningMessage}\n`);
|
|
560
691
|
return;
|
|
561
692
|
}
|
|
562
|
-
this.ctx
|
|
563
|
-
this.ctx
|
|
693
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
694
|
+
addChatChild(this.ctx, new Text(theme.fg("warning", `Warning: ${warningMessage}`), 1, 0));
|
|
564
695
|
this.ctx.ui.requestRender();
|
|
565
696
|
}
|
|
566
697
|
|
|
567
698
|
showNewVersionNotification(newVersion: string): void {
|
|
568
|
-
this.ctx
|
|
569
|
-
this.ctx
|
|
570
|
-
|
|
699
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
700
|
+
addChatChild(this.ctx, new DynamicBorder(text => theme.fg("warning", text)));
|
|
701
|
+
addChatChild(
|
|
702
|
+
this.ctx,
|
|
571
703
|
new Text(
|
|
572
704
|
theme.bold(theme.fg("warning", "Update Available")) +
|
|
573
705
|
"\n" +
|
|
@@ -577,7 +709,7 @@ export class UiHelpers {
|
|
|
577
709
|
0,
|
|
578
710
|
),
|
|
579
711
|
);
|
|
580
|
-
this.ctx
|
|
712
|
+
addChatChild(this.ctx, new DynamicBorder(text => theme.fg("warning", text)));
|
|
581
713
|
this.ctx.ui.requestRender();
|
|
582
714
|
}
|
|
583
715
|
|
|
@@ -619,7 +751,11 @@ export class UiHelpers {
|
|
|
619
751
|
}
|
|
620
752
|
|
|
621
753
|
queueCompactionMessage(text: string, mode: "steer" | "followUp"): void {
|
|
622
|
-
|
|
754
|
+
const entry: CompactionQueuedMessage = { text, mode };
|
|
755
|
+
if (mode === "followUp") {
|
|
756
|
+
entry.followUpQueuePolicy = "sequential";
|
|
757
|
+
}
|
|
758
|
+
this.ctx.compactionQueuedMessages.push(entry);
|
|
623
759
|
this.ctx.editor.addToHistory(text);
|
|
624
760
|
this.ctx.editor.setText("");
|
|
625
761
|
this.ctx.updatePendingMessagesDisplay();
|
|
@@ -633,6 +769,11 @@ export class UiHelpers {
|
|
|
633
769
|
return this.#hasSkillInvocations(text) || this.isKnownSlashCommand(text);
|
|
634
770
|
}
|
|
635
771
|
|
|
772
|
+
#compactionFollowUpQueuePolicy(message: CompactionQueuedMessage): "sequential" | undefined {
|
|
773
|
+
if (message.mode !== "followUp") return undefined;
|
|
774
|
+
return message.followUpQueuePolicy ?? "sequential";
|
|
775
|
+
}
|
|
776
|
+
|
|
636
777
|
async #deliverQueuedSkillMessage(message: CompactionQueuedMessage): Promise<boolean> {
|
|
637
778
|
const invocations = parseSkillInvocations(message.text, this.ctx.skillCommands ?? new Map());
|
|
638
779
|
if (invocations.length === 0) {
|
|
@@ -675,6 +816,13 @@ export class UiHelpers {
|
|
|
675
816
|
continue;
|
|
676
817
|
}
|
|
677
818
|
|
|
819
|
+
const promptOptions =
|
|
820
|
+
message.mode === "followUp"
|
|
821
|
+
? {
|
|
822
|
+
streamingBehavior: message.mode,
|
|
823
|
+
followUpQueuePolicy: this.#compactionFollowUpQueuePolicy(message),
|
|
824
|
+
}
|
|
825
|
+
: { streamingBehavior: message.mode };
|
|
678
826
|
await this.ctx.session.promptCustomMessage(
|
|
679
827
|
{
|
|
680
828
|
customType: SKILL_PROMPT_MESSAGE_TYPE,
|
|
@@ -683,7 +831,7 @@ export class UiHelpers {
|
|
|
683
831
|
details,
|
|
684
832
|
attribution: "user",
|
|
685
833
|
},
|
|
686
|
-
|
|
834
|
+
promptOptions,
|
|
687
835
|
);
|
|
688
836
|
}
|
|
689
837
|
|
|
@@ -703,7 +851,11 @@ export class UiHelpers {
|
|
|
703
851
|
return;
|
|
704
852
|
}
|
|
705
853
|
await this.ctx.withLocalSubmission(message.text, () =>
|
|
706
|
-
message.mode === "followUp"
|
|
854
|
+
message.mode === "followUp"
|
|
855
|
+
? this.ctx.session.followUp(message.text, undefined, {
|
|
856
|
+
followUpQueuePolicy: this.#compactionFollowUpQueuePolicy(message),
|
|
857
|
+
})
|
|
858
|
+
: this.ctx.session.steer(message.text),
|
|
707
859
|
);
|
|
708
860
|
}
|
|
709
861
|
|
|
@@ -794,14 +946,17 @@ export class UiHelpers {
|
|
|
794
946
|
// `restoreQueue` rather than rethrown, so we use the primitive
|
|
795
947
|
// recordLocalSubmission and dispose manually in the catch.
|
|
796
948
|
const disposeFirstPrompt = this.ctx.recordLocalSubmission(firstPrompt.text);
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
949
|
+
const firstPromptOptions =
|
|
950
|
+
firstPrompt.mode === "followUp"
|
|
951
|
+
? {
|
|
952
|
+
streamingBehavior: "followUp" as const,
|
|
953
|
+
followUpQueuePolicy: this.#compactionFollowUpQueuePolicy(firstPrompt),
|
|
954
|
+
}
|
|
955
|
+
: { streamingBehavior: "steer" as const };
|
|
956
|
+
const promptPromise = this.ctx.session.prompt(firstPrompt.text, firstPromptOptions).catch((error: unknown) => {
|
|
957
|
+
disposeFirstPrompt();
|
|
958
|
+
restoreQueue(error);
|
|
959
|
+
});
|
|
805
960
|
|
|
806
961
|
for (const message of rest) {
|
|
807
962
|
await this.#deliverQueuedMessage(message);
|
|
@@ -820,12 +975,12 @@ export class UiHelpers {
|
|
|
820
975
|
// removeChild() would tear them down before re-adding.
|
|
821
976
|
for (const component of this.ctx.pendingBashComponents) {
|
|
822
977
|
this.ctx.pendingMessagesContainer.detachChild(component);
|
|
823
|
-
this.ctx
|
|
978
|
+
addChatChild(this.ctx, component);
|
|
824
979
|
}
|
|
825
980
|
this.ctx.pendingBashComponents = [];
|
|
826
981
|
for (const component of this.ctx.pendingPythonComponents) {
|
|
827
982
|
this.ctx.pendingMessagesContainer.detachChild(component);
|
|
828
|
-
this.ctx
|
|
983
|
+
addChatChild(this.ctx, component);
|
|
829
984
|
}
|
|
830
985
|
this.ctx.pendingPythonComponents = [];
|
|
831
986
|
}
|
|
@@ -20,6 +20,76 @@ export interface ConfigCommandChange {
|
|
|
20
20
|
redact?: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export type TelegramControlCommandName = "reasoning" | "usage" | "context" | "compact";
|
|
24
|
+
|
|
25
|
+
export type TelegramControlCommand =
|
|
26
|
+
| { name: "reasoning"; action: "cycle" | "status" | "set"; level?: string }
|
|
27
|
+
| { name: "usage" }
|
|
28
|
+
| { name: "context" }
|
|
29
|
+
| { name: "compact"; instructions?: string };
|
|
30
|
+
|
|
31
|
+
export type TelegramControlCommandParseResult =
|
|
32
|
+
| { kind: "none" }
|
|
33
|
+
| { kind: "ignored"; commandName: TelegramControlCommandName }
|
|
34
|
+
| { kind: "command"; command: TelegramControlCommand }
|
|
35
|
+
| { kind: "invalid"; commandName: TelegramControlCommandName; usage: string };
|
|
36
|
+
|
|
37
|
+
const TELEGRAM_CONTROL_COMMANDS = new Set<TelegramControlCommandName>(["reasoning", "usage", "context", "compact"]);
|
|
38
|
+
const TELEGRAM_REASONING_LEVELS = new Set(["inherit", "off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
39
|
+
|
|
40
|
+
function splitTelegramBotSuffix(rawCommand: string): { name: string; suffix?: string } {
|
|
41
|
+
const [name, suffix] = rawCommand.toLowerCase().split("@", 2);
|
|
42
|
+
return suffix ? { name, suffix } : { name };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function telegramControlCommandUsage(commandName: TelegramControlCommandName): string {
|
|
46
|
+
switch (commandName) {
|
|
47
|
+
case "reasoning":
|
|
48
|
+
return "Usage: /reasoning [cycle|inherit|off|minimal|low|medium|high|xhigh|max]";
|
|
49
|
+
case "usage":
|
|
50
|
+
return "Usage: /usage";
|
|
51
|
+
case "context":
|
|
52
|
+
return "Usage: /context";
|
|
53
|
+
case "compact":
|
|
54
|
+
return "Usage: /compact [instructions]";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Parse deterministic Telegram session-control commands. Recognised roots fail closed. */
|
|
59
|
+
export function parseTelegramControlCommand(text: string, botUsername?: string): TelegramControlCommandParseResult {
|
|
60
|
+
const trimmed = text.trim();
|
|
61
|
+
if (!trimmed.startsWith("/")) return { kind: "none" };
|
|
62
|
+
const [rawRoot, ...rest] = trimmed.slice(1).split(/\s+/);
|
|
63
|
+
if (!rawRoot) return { kind: "none" };
|
|
64
|
+
const { name: root, suffix } = splitTelegramBotSuffix(rawRoot);
|
|
65
|
+
if (!TELEGRAM_CONTROL_COMMANDS.has(root as TelegramControlCommandName)) return { kind: "none" };
|
|
66
|
+
const commandName = root as TelegramControlCommandName;
|
|
67
|
+
if (suffix && (!botUsername || suffix !== botUsername.toLowerCase())) return { kind: "ignored", commandName };
|
|
68
|
+
const usage = telegramControlCommandUsage(commandName);
|
|
69
|
+
|
|
70
|
+
switch (commandName) {
|
|
71
|
+
case "usage":
|
|
72
|
+
case "context":
|
|
73
|
+
return rest.length === 0
|
|
74
|
+
? { kind: "command", command: { name: commandName } }
|
|
75
|
+
: { kind: "invalid", commandName, usage };
|
|
76
|
+
case "compact": {
|
|
77
|
+
const instructions = trimmed.slice(rawRoot.length + 1).trim();
|
|
78
|
+
return { kind: "command", command: instructions ? { name: "compact", instructions } : { name: "compact" } };
|
|
79
|
+
}
|
|
80
|
+
case "reasoning": {
|
|
81
|
+
if (rest.length === 0) return { kind: "command", command: { name: "reasoning", action: "status" } };
|
|
82
|
+
if (rest.length !== 1) return { kind: "invalid", commandName, usage };
|
|
83
|
+
const levelOrAction = rest[0]!.toLowerCase();
|
|
84
|
+
if (levelOrAction === "cycle") return { kind: "command", command: { name: "reasoning", action: "cycle" } };
|
|
85
|
+
if (TELEGRAM_REASONING_LEVELS.has(levelOrAction)) {
|
|
86
|
+
return { kind: "command", command: { name: "reasoning", action: "set", level: levelOrAction } };
|
|
87
|
+
}
|
|
88
|
+
return { kind: "invalid", commandName, usage };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
23
93
|
/**
|
|
24
94
|
* Parse an in-thread config command. Returns the requested change, or
|
|
25
95
|
* `undefined` when the text is not a recognised config command (so the daemon
|