@gajae-code/coding-agent 0.9.2 → 0.9.4
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 +19 -0
- package/dist/types/build-metadata.d.ts +7 -0
- package/dist/types/capability/mcp.d.ts +3 -1
- package/dist/types/commands/mcp.d.ts +1 -1
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- 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/welcome.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +12 -0
- package/dist/types/notifications/telegram-daemon.d.ts +2 -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/session/agent-session.d.ts +16 -0
- package/dist/types/session/session-manager.d.ts +1 -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 +96 -0
- package/scripts/generate-docs-index.ts +1 -0
- package/src/build-metadata.ts +104 -0
- package/src/capability/mcp.ts +3 -1
- package/src/cli/mcp-cli.ts +108 -26
- package/src/cli.ts +21 -0
- package/src/commands/mcp.ts +8 -8
- package/src/commands/stats.ts +6 -2
- package/src/config/keybindings.ts +2 -2
- package/src/config/mcp-schema.json +21 -17
- 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/eval/js/executor.ts +7 -1
- package/src/gjc-runtime/state-runtime.ts +105 -12
- 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/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/hook-selector.ts +4 -0
- package/src/modes/components/session-selector.ts +2 -2
- 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 +13 -6
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +4 -1
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +9 -9
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +7 -3
- package/src/modes/interactive-mode.ts +12 -4
- package/src/modes/rpc/rpc-client.ts +30 -6
- package/src/modes/utils/ui-helpers.ts +154 -32
- package/src/notifications/telegram-daemon.ts +28 -2
- package/src/prompts/tools/skill.md +2 -2
- 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/session/agent-session.ts +117 -3
- package/src/session/session-manager.ts +68 -40
- package/src/slash-commands/builtin-registry.ts +9 -4
- 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/skill.ts +14 -11
- 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
|
@@ -64,6 +64,127 @@ type QueuedMessages = {
|
|
|
64
64
|
followUp: string[];
|
|
65
65
|
};
|
|
66
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
|
+
|
|
67
188
|
export class UiHelpers {
|
|
68
189
|
constructor(private ctx: InteractiveModeContext) {}
|
|
69
190
|
|
|
@@ -101,8 +222,8 @@ export class UiHelpers {
|
|
|
101
222
|
|
|
102
223
|
const spacer = new Spacer(1);
|
|
103
224
|
const text = new Text(rendered, 1, 0);
|
|
104
|
-
this.ctx
|
|
105
|
-
this.ctx
|
|
225
|
+
addChatChild(this.ctx, spacer);
|
|
226
|
+
addChatChild(this.ctx, text);
|
|
106
227
|
this.ctx.lastStatusSpacer = spacer;
|
|
107
228
|
this.ctx.lastStatusText = text;
|
|
108
229
|
this.ctx.ui.requestRender();
|
|
@@ -118,7 +239,7 @@ export class UiHelpers {
|
|
|
118
239
|
component.setComplete(message.exitCode, message.cancelled, {
|
|
119
240
|
truncation: message.meta?.truncation,
|
|
120
241
|
});
|
|
121
|
-
this.ctx
|
|
242
|
+
addChatChild(this.ctx, component);
|
|
122
243
|
break;
|
|
123
244
|
}
|
|
124
245
|
case "pythonExecution": {
|
|
@@ -129,7 +250,7 @@ export class UiHelpers {
|
|
|
129
250
|
component.setComplete(message.exitCode, message.cancelled, {
|
|
130
251
|
truncation: message.meta?.truncation,
|
|
131
252
|
});
|
|
132
|
-
this.ctx
|
|
253
|
+
addChatChild(this.ctx, component);
|
|
133
254
|
break;
|
|
134
255
|
}
|
|
135
256
|
case "hookMessage":
|
|
@@ -173,14 +294,14 @@ export class UiHelpers {
|
|
|
173
294
|
]
|
|
174
295
|
.filter(Boolean)
|
|
175
296
|
.join(" ");
|
|
176
|
-
this.ctx
|
|
297
|
+
addChatChild(this.ctx, new Text(line, 1, 0));
|
|
177
298
|
}
|
|
178
299
|
break;
|
|
179
300
|
}
|
|
180
301
|
if (message.customType === SKILL_PROMPT_MESSAGE_TYPE) {
|
|
181
302
|
const component = new SkillMessageComponent(message as CustomMessage<SkillPromptDetails>);
|
|
182
303
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
183
|
-
this.ctx
|
|
304
|
+
addChatChild(this.ctx, component);
|
|
184
305
|
break;
|
|
185
306
|
}
|
|
186
307
|
if (
|
|
@@ -217,12 +338,12 @@ export class UiHelpers {
|
|
|
217
338
|
const components: Component[] = [];
|
|
218
339
|
const header = `${theme.fg("accent", `[IRC] ${arrow}`)}`;
|
|
219
340
|
const headerComponent = new Text(header, 1, 0);
|
|
220
|
-
this.ctx
|
|
341
|
+
addChatChild(this.ctx, headerComponent);
|
|
221
342
|
components.push(headerComponent);
|
|
222
343
|
if (body) {
|
|
223
344
|
for (const line of body.split("\n")) {
|
|
224
345
|
const lineComponent = new Text(theme.fg("muted", ` ${line}`), 0, 0);
|
|
225
|
-
this.ctx
|
|
346
|
+
addChatChild(this.ctx, lineComponent);
|
|
226
347
|
components.push(lineComponent);
|
|
227
348
|
}
|
|
228
349
|
}
|
|
@@ -240,12 +361,12 @@ export class UiHelpers {
|
|
|
240
361
|
const components: Component[] = [];
|
|
241
362
|
const header = `${theme.fg("accent", `[Steer ${details?.state ?? "queued"}] ${details?.from ?? "?"} ⇨ ${details?.to ?? "?"}`)}`;
|
|
242
363
|
const headerComponent = new Text(header, 1, 0);
|
|
243
|
-
this.ctx
|
|
364
|
+
addChatChild(this.ctx, headerComponent);
|
|
244
365
|
components.push(headerComponent);
|
|
245
366
|
if (details?.body) {
|
|
246
367
|
for (const line of details.body.split("\n")) {
|
|
247
368
|
const lineComponent = new Text(theme.fg("muted", ` ${line}`), 0, 0);
|
|
248
|
-
this.ctx
|
|
369
|
+
addChatChild(this.ctx, lineComponent);
|
|
249
370
|
components.push(lineComponent);
|
|
250
371
|
}
|
|
251
372
|
}
|
|
@@ -255,22 +376,22 @@ export class UiHelpers {
|
|
|
255
376
|
// Both HookMessage and CustomMessage have the same structure, cast for compatibility
|
|
256
377
|
const component = new CustomMessageComponent(message as CustomMessage<unknown>, renderer);
|
|
257
378
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
258
|
-
this.ctx
|
|
379
|
+
addChatChild(this.ctx, component);
|
|
259
380
|
}
|
|
260
381
|
break;
|
|
261
382
|
}
|
|
262
383
|
case "compactionSummary": {
|
|
263
|
-
this.ctx
|
|
384
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
264
385
|
const component = new CompactionSummaryMessageComponent(message);
|
|
265
386
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
266
|
-
this.ctx
|
|
387
|
+
addChatChild(this.ctx, component);
|
|
267
388
|
break;
|
|
268
389
|
}
|
|
269
390
|
case "branchSummary": {
|
|
270
|
-
this.ctx
|
|
391
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
271
392
|
const component = new BranchSummaryMessageComponent(message);
|
|
272
393
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
273
|
-
this.ctx
|
|
394
|
+
addChatChild(this.ctx, component);
|
|
274
395
|
break;
|
|
275
396
|
}
|
|
276
397
|
case "fileMention": {
|
|
@@ -291,7 +412,7 @@ export class UiHelpers {
|
|
|
291
412
|
"accent",
|
|
292
413
|
file.path,
|
|
293
414
|
)} ${theme.fg("dim", suffix)}`;
|
|
294
|
-
this.ctx
|
|
415
|
+
addChatChild(this.ctx, new Text(text, 0, 0));
|
|
295
416
|
}
|
|
296
417
|
break;
|
|
297
418
|
}
|
|
@@ -301,7 +422,7 @@ export class UiHelpers {
|
|
|
301
422
|
if (textContent) {
|
|
302
423
|
const isSynthetic = message.role === "developer" ? true : (message.synthetic ?? false);
|
|
303
424
|
const userComponent = new UserMessageComponent(textContent, isSynthetic);
|
|
304
|
-
this.ctx
|
|
425
|
+
addChatChild(this.ctx, userComponent);
|
|
305
426
|
if (options?.populateHistory && message.role === "user" && !isSynthetic) {
|
|
306
427
|
this.ctx.editor.addToHistory(textContent);
|
|
307
428
|
}
|
|
@@ -312,7 +433,7 @@ export class UiHelpers {
|
|
|
312
433
|
const assistantComponent = new AssistantMessageComponent(message, this.ctx.hideThinkingBlock, () =>
|
|
313
434
|
this.ctx.ui.requestRender(),
|
|
314
435
|
);
|
|
315
|
-
this.ctx
|
|
436
|
+
addChatChild(this.ctx, assistantComponent);
|
|
316
437
|
break;
|
|
317
438
|
}
|
|
318
439
|
case "toolResult": {
|
|
@@ -392,7 +513,7 @@ export class UiHelpers {
|
|
|
392
513
|
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
393
514
|
});
|
|
394
515
|
readGroup.setExpanded(this.ctx.toolOutputExpanded);
|
|
395
|
-
this.ctx
|
|
516
|
+
addChatChild(this.ctx, readGroup);
|
|
396
517
|
}
|
|
397
518
|
readGroup.updateArgs(content.arguments, content.id);
|
|
398
519
|
readGroup.updateResult(
|
|
@@ -434,7 +555,7 @@ export class UiHelpers {
|
|
|
434
555
|
content.id,
|
|
435
556
|
);
|
|
436
557
|
component.setExpanded(this.ctx.toolOutputExpanded);
|
|
437
|
-
this.ctx
|
|
558
|
+
addChatChild(this.ctx, component);
|
|
438
559
|
|
|
439
560
|
if (hasErrorStop && errorMessage) {
|
|
440
561
|
component.updateResult(
|
|
@@ -472,7 +593,7 @@ export class UiHelpers {
|
|
|
472
593
|
showContentPreview: this.ctx.settings.get("read.toolResultPreview"),
|
|
473
594
|
});
|
|
474
595
|
readGroup.setExpanded(this.ctx.toolOutputExpanded);
|
|
475
|
-
this.ctx
|
|
596
|
+
addChatChild(this.ctx, readGroup);
|
|
476
597
|
}
|
|
477
598
|
const args = readToolCallArgs.get(message.toolCallId);
|
|
478
599
|
if (args) {
|
|
@@ -539,7 +660,7 @@ export class UiHelpers {
|
|
|
539
660
|
}
|
|
540
661
|
if (preservedChatChildren && preservedChatChildren.length > 0) {
|
|
541
662
|
for (const child of preservedChatChildren) {
|
|
542
|
-
this.ctx
|
|
663
|
+
addChatChild(this.ctx, child);
|
|
543
664
|
}
|
|
544
665
|
this.ctx.ui.requestRender();
|
|
545
666
|
}
|
|
@@ -559,8 +680,8 @@ export class UiHelpers {
|
|
|
559
680
|
process.stderr.write(`Error: ${errorMessage}\n`);
|
|
560
681
|
return;
|
|
561
682
|
}
|
|
562
|
-
this.ctx
|
|
563
|
-
this.ctx
|
|
683
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
684
|
+
addChatChild(this.ctx, new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
|
|
564
685
|
this.ctx.ui.requestRender();
|
|
565
686
|
}
|
|
566
687
|
|
|
@@ -569,15 +690,16 @@ export class UiHelpers {
|
|
|
569
690
|
process.stderr.write(`Warning: ${warningMessage}\n`);
|
|
570
691
|
return;
|
|
571
692
|
}
|
|
572
|
-
this.ctx
|
|
573
|
-
this.ctx
|
|
693
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
694
|
+
addChatChild(this.ctx, new Text(theme.fg("warning", `Warning: ${warningMessage}`), 1, 0));
|
|
574
695
|
this.ctx.ui.requestRender();
|
|
575
696
|
}
|
|
576
697
|
|
|
577
698
|
showNewVersionNotification(newVersion: string): void {
|
|
578
|
-
this.ctx
|
|
579
|
-
this.ctx
|
|
580
|
-
|
|
699
|
+
addChatChild(this.ctx, new Spacer(1));
|
|
700
|
+
addChatChild(this.ctx, new DynamicBorder(text => theme.fg("warning", text)));
|
|
701
|
+
addChatChild(
|
|
702
|
+
this.ctx,
|
|
581
703
|
new Text(
|
|
582
704
|
theme.bold(theme.fg("warning", "Update Available")) +
|
|
583
705
|
"\n" +
|
|
@@ -587,7 +709,7 @@ export class UiHelpers {
|
|
|
587
709
|
0,
|
|
588
710
|
),
|
|
589
711
|
);
|
|
590
|
-
this.ctx
|
|
712
|
+
addChatChild(this.ctx, new DynamicBorder(text => theme.fg("warning", text)));
|
|
591
713
|
this.ctx.ui.requestRender();
|
|
592
714
|
}
|
|
593
715
|
|
|
@@ -853,12 +975,12 @@ export class UiHelpers {
|
|
|
853
975
|
// removeChild() would tear them down before re-adding.
|
|
854
976
|
for (const component of this.ctx.pendingBashComponents) {
|
|
855
977
|
this.ctx.pendingMessagesContainer.detachChild(component);
|
|
856
|
-
this.ctx
|
|
978
|
+
addChatChild(this.ctx, component);
|
|
857
979
|
}
|
|
858
980
|
this.ctx.pendingBashComponents = [];
|
|
859
981
|
for (const component of this.ctx.pendingPythonComponents) {
|
|
860
982
|
this.ctx.pendingMessagesContainer.detachChild(component);
|
|
861
|
-
this.ctx
|
|
983
|
+
addChatChild(this.ctx, component);
|
|
862
984
|
}
|
|
863
985
|
this.ctx.pendingPythonComponents = [];
|
|
864
986
|
}
|
|
@@ -146,6 +146,7 @@ const TYPING_REFRESH_INTERVAL_MS = 4_000;
|
|
|
146
146
|
const QUEUED_REACTION = "👀";
|
|
147
147
|
const PENDING_TOPIC_FRAME_LIMIT = 20;
|
|
148
148
|
const SEEN_UPDATE_ID_LIMIT = 1_000;
|
|
149
|
+
const ORPHAN_TOPIC_GRACE_MS = 60_000;
|
|
149
150
|
const CONSUMED_REACTION = "✅";
|
|
150
151
|
|
|
151
152
|
function splitTelegramPlainText(text: string, max = TELEGRAM_MESSAGE_LIMIT): string[] {
|
|
@@ -1414,24 +1415,33 @@ export class TelegramNotificationDaemon {
|
|
|
1414
1415
|
async scanRoots(): Promise<void> {
|
|
1415
1416
|
const paths = daemonPaths(this.opts.settings.getAgentDir());
|
|
1416
1417
|
const rootState = await readJson<{ roots?: string[] }>(this.fsImpl, paths.roots);
|
|
1418
|
+
const endpointSessionIds = new Set<string>();
|
|
1419
|
+
let allRootsReadable = true;
|
|
1417
1420
|
for (const root of rootState?.roots ?? []) {
|
|
1418
1421
|
const dir = path.join(root, "notifications");
|
|
1419
1422
|
let files: string[];
|
|
1420
1423
|
try {
|
|
1421
1424
|
files = await this.fsImpl.readdir(dir);
|
|
1422
1425
|
} catch {
|
|
1426
|
+
allRootsReadable = false;
|
|
1423
1427
|
continue;
|
|
1424
1428
|
}
|
|
1425
1429
|
for (const file of files.filter(item => item.endsWith(".json"))) {
|
|
1426
1430
|
const sessionId = path.basename(file, ".json");
|
|
1431
|
+
endpointSessionIds.add(sessionId);
|
|
1427
1432
|
if (this.sessions.has(sessionId)) continue;
|
|
1428
1433
|
try {
|
|
1429
1434
|
const endpoint = readEndpoint(path.join(dir, file));
|
|
1430
1435
|
// Skip endpoint files whose owning process is gone or that are
|
|
1431
1436
|
// explicitly stale (e.g. a hard-closed session): reconnecting
|
|
1432
|
-
// would chase a dead, token-bearing record forever.
|
|
1437
|
+
// would chase a dead, token-bearing record forever. Once the
|
|
1438
|
+
// associated topic is past the grace window, reap it through the
|
|
1439
|
+
// same best-effort delete path as graceful session shutdown.
|
|
1433
1440
|
const pidAlive = this.opts.pidAlive ?? defaultPidAlive;
|
|
1434
|
-
if (endpoint.stale || (endpoint.pid !== undefined && !pidAlive(endpoint.pid)))
|
|
1441
|
+
if (endpoint.stale || (endpoint.pid !== undefined && !pidAlive(endpoint.pid))) {
|
|
1442
|
+
await this.deleteOrphanedTopic(sessionId);
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1435
1445
|
const endpointKey = endpointGenerationKey(endpoint.url, endpoint.token);
|
|
1436
1446
|
if (this.closedEndpointKeys.get(sessionId) === endpointKey) continue;
|
|
1437
1447
|
this.closedEndpointKeys.delete(sessionId);
|
|
@@ -1439,6 +1449,12 @@ export class TelegramNotificationDaemon {
|
|
|
1439
1449
|
} catch {}
|
|
1440
1450
|
}
|
|
1441
1451
|
}
|
|
1452
|
+
if (allRootsReadable) {
|
|
1453
|
+
for (const sessionId of this.topics.sessionIds()) {
|
|
1454
|
+
if (!this.sessions.has(sessionId) && !endpointSessionIds.has(sessionId))
|
|
1455
|
+
await this.deleteOrphanedTopic(sessionId);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1442
1458
|
}
|
|
1443
1459
|
|
|
1444
1460
|
connectSession(sessionId: string, url: string, token: string): void {
|
|
@@ -1683,6 +1699,16 @@ export class TelegramNotificationDaemon {
|
|
|
1683
1699
|
}
|
|
1684
1700
|
}
|
|
1685
1701
|
|
|
1702
|
+
private topicPastOrphanGrace(sessionId: string): boolean {
|
|
1703
|
+
const record = this.topics.get(sessionId);
|
|
1704
|
+
return record !== undefined && this.runtime.now() - record.createdAt >= ORPHAN_TOPIC_GRACE_MS;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
private async deleteOrphanedTopic(sessionId: string): Promise<void> {
|
|
1708
|
+
if (!this.topicPastOrphanGrace(sessionId)) return;
|
|
1709
|
+
await this.deleteTopic(sessionId);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1686
1712
|
/** Best-effort delete of a session topic once its local notification endpoint shuts down. */
|
|
1687
1713
|
private async deleteTopic(sessionId: string): Promise<void> {
|
|
1688
1714
|
const record = this.topics.get(sessionId);
|
|
@@ -8,8 +8,8 @@ Invoke another available skill in the current turn.
|
|
|
8
8
|
<instruction>
|
|
9
9
|
- `name` is the skill name as it appears in `/skill:<name>` (e.g. `ralplan`, `ultragoal`, `team`, `deep-interview`)
|
|
10
10
|
- `args` is the free-form argument string the skill would receive after `/skill:<name>` on the command line
|
|
11
|
-
- The tool loads the callee's SKILL.md into the current turn and handles
|
|
12
|
-
- The chain is refused while
|
|
11
|
+
- The tool loads the callee's SKILL.md into the current turn and handles native workflow caller→callee state handoff when the caller is one of the built-in GJC workflows.
|
|
12
|
+
- The chain is refused while a native workflow caller is still active. If your current skill is one of `deep-interview`, `ralplan`, `ultragoal`, or `team` and has not yet reached a terminal phase, prepare it first with `gjc state <skill> write --input '{"current_phase":"handoff"}' --json`; no other handoff command is needed. Runtime project/user skills do not use `gjc state <skill>`.
|
|
13
13
|
- Call once per chain step. To chain `A → B → C`, A calls `skill(B)`; B's next agent turn calls `skill(C)`.
|
|
14
14
|
</instruction>
|
|
15
15
|
|
|
@@ -238,10 +238,11 @@ export async function listMCPServers(filePath: string): Promise<string[]> {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/**
|
|
241
|
-
* Set a server's autoload flag
|
|
241
|
+
* Set a server's autoload flag for explicit runtime MCP consumers.
|
|
242
242
|
*
|
|
243
|
-
* Autoload defaults to true, so turning it on
|
|
244
|
-
* config file free of redundant fields; turning it
|
|
243
|
+
* Autoload defaults to true for consumers that honor it, so turning it on
|
|
244
|
+
* removes the key to keep the config file free of redundant fields; turning it
|
|
245
|
+
* off writes `autoload: false`.
|
|
245
246
|
*
|
|
246
247
|
* @throws Error if the server doesn't exist
|
|
247
248
|
*/
|
|
@@ -56,6 +56,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
|
|
|
56
56
|
};
|
|
57
57
|
if (server.args) config.args = server.args;
|
|
58
58
|
if (server.env) config.env = server.env;
|
|
59
|
+
if (server.noInheritEnv !== undefined) config.noInheritEnv = server.noInheritEnv;
|
|
59
60
|
if (server.cwd) config.cwd = server.cwd;
|
|
60
61
|
return config;
|
|
61
62
|
}
|
package/src/runtime-mcp/types.ts
CHANGED
|
@@ -62,9 +62,10 @@ interface MCPServerConfigBase {
|
|
|
62
62
|
/** Whether this server is enabled (default: true) */
|
|
63
63
|
enabled?: boolean;
|
|
64
64
|
/**
|
|
65
|
-
* Whether
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* Whether an explicit runtime MCP consumer should connect this server
|
|
66
|
+
* automatically when that consumer starts (default: true). Normal standalone
|
|
67
|
+
* `gjc`, `gjc --tmux`, and print-mode sessions do not consume `gjc mcp`
|
|
68
|
+
* registrations today.
|
|
68
69
|
*/
|
|
69
70
|
autoload?: boolean;
|
|
70
71
|
/** Connection timeout in milliseconds (default: 30000) */
|
|
@@ -114,7 +115,7 @@ export interface MCPSseServerConfig extends MCPServerConfigBase {
|
|
|
114
115
|
export type MCPServerConfig = MCPStdioServerConfig | MCPHttpServerConfig | MCPSseServerConfig;
|
|
115
116
|
|
|
116
117
|
export const MCP_CONFIG_SCHEMA_URL =
|
|
117
|
-
"https://raw.githubusercontent.com/
|
|
118
|
+
"https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/main/packages/coding-agent/src/config/mcp-schema.json";
|
|
118
119
|
|
|
119
120
|
/** Root mcp.json/.mcp.json file structure */
|
|
120
121
|
export interface MCPConfigFile {
|