@gajae-code/coding-agent 0.16.3 → 0.16.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 +59 -0
- package/dist/types/cli/args.d.ts +2 -2
- package/dist/types/cli/root-flags.d.ts +4 -2
- package/dist/types/cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +1 -1
- package/dist/types/config/autorouting-tier-map.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +9 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/discovery/claude-plugins.d.ts +3 -1
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/extensibility/gjc-plugins/mcp-policy.d.ts +16 -0
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +1 -1
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/settings-selector.d.ts +6 -1
- package/dist/types/modes/components/status-line/command.d.ts +35 -0
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -0
- package/dist/types/modes/components/tool-status-header.d.ts +14 -0
- package/dist/types/modes/controllers/event-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/runtime-mcp/client.d.ts +9 -1
- package/dist/types/runtime-mcp/manager.d.ts +7 -1
- package/dist/types/runtime-mcp/pool.d.ts +22 -3
- package/dist/types/runtime-mcp/transports/stdio.d.ts +0 -4
- package/dist/types/runtime-mcp/types.d.ts +22 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +12 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +6 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +12 -0
- package/dist/types/sdk/host/session-runtime.d.ts +23 -0
- package/dist/types/sdk/session-api-key.d.ts +26 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +9 -0
- package/dist/types/setup/paseo/announce.d.ts +111 -0
- package/dist/types/setup/paseo/setup-deps.d.ts +2 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +2 -1
- package/dist/types/tools/fetch.d.ts +7 -0
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/path-utils.d.ts +2 -0
- package/dist/types/tools/search.d.ts +6 -0
- package/package.json +7 -7
- package/scripts/capture-status-line-custom-editor-showcase.ts +151 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -0
- package/scripts/verify-status-line-custom-editor-showcase.ts +309 -0
- package/src/cli/args.ts +20 -9
- package/src/cli/root-flags.ts +5 -3
- package/src/config/autorouting-tier-map.ts +1 -0
- package/src/config/file-lock.ts +181 -17
- package/src/config/model-preset-registry.ts +5 -3
- package/src/config/model-profiles.ts +40 -0
- package/src/config/model-registry.ts +57 -11
- package/src/config/model-resolver.ts +48 -0
- package/src/config/settings-schema.ts +33 -1
- package/src/config/settings.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +11 -11
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +13 -2
- package/src/discovery/builtin.ts +13 -4
- package/src/discovery/claude-plugins.ts +12 -4
- package/src/discovery/helpers.ts +10 -2
- package/src/extensibility/gjc-plugins/compiler.ts +208 -58
- package/src/extensibility/gjc-plugins/installer.ts +13 -12
- package/src/extensibility/gjc-plugins/mcp-policy.ts +81 -52
- package/src/extensibility/gjc-plugins/paths.ts +13 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +758 -16
- package/src/extensibility/runtime-skill-discovery.ts +77 -151
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +44 -16
- package/src/gjc-runtime/repository-binding.ts +1 -1
- package/src/gjc-runtime/session-state-lock.ts +92 -2
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/main.ts +72 -34
- package/src/modes/DESIGN.md +54 -0
- package/src/modes/components/assistant-message.ts +22 -9
- package/src/modes/components/model-selector.ts +166 -33
- package/src/modes/components/settings-selector.ts +1075 -235
- package/src/modes/components/status-line/command.ts +190 -0
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/segments.ts +32 -5
- package/src/modes/components/status-line/types.ts +5 -0
- package/src/modes/components/tool-status-header.ts +393 -21
- package/src/modes/controllers/command-controller.ts +2 -0
- package/src/modes/controllers/event-controller.ts +148 -5
- package/src/modes/controllers/selector-controller.ts +21 -0
- package/src/modes/interactive-mode.ts +47 -1
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/prompts/tools/search.md +2 -0
- package/src/runtime-mcp/client.ts +60 -17
- package/src/runtime-mcp/manager.ts +171 -14
- package/src/runtime-mcp/pool.test.ts +209 -0
- package/src/runtime-mcp/pool.ts +224 -23
- package/src/runtime-mcp/transports/stdio.ts +158 -24
- package/src/runtime-mcp/types.ts +24 -0
- package/src/sdk/broker/broker.ts +5 -4
- package/src/sdk/bus/chat-daemon-control.ts +1 -1
- package/src/sdk/bus/chat-daemon-runtime.ts +37 -1
- package/src/sdk/bus/slack-daemon.ts +92 -2
- package/src/sdk/bus/slack-live-provider.ts +12 -0
- package/src/sdk/bus/slack-provider.ts +5 -0
- package/src/sdk/cli/session-cli.ts +9 -6
- package/src/sdk/host/session-runtime.test.ts +2 -2
- package/src/sdk/host/session-runtime.ts +275 -43
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/session-api-key.ts +94 -0
- package/src/sdk/session.ts +73 -29
- package/src/sdk/transport/serve-cli.ts +3 -3
- package/src/session/agent-session.ts +123 -38
- package/src/session/contribution-prep.ts +214 -21
- package/src/setup/paseo/announce.ts +450 -0
- package/src/setup/paseo/setup-deps.ts +15 -3
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/system-prompt.ts +4 -0
- package/src/task/executor.ts +2 -1
- package/src/task/index.ts +17 -4
- package/src/tools/bash.ts +35 -8
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +29 -1
- package/src/tools/index.ts +8 -0
- package/src/tools/job.ts +8 -2
- package/src/tools/monitor.ts +8 -2
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/search.ts +116 -58
- package/src/tools/skill.ts +22 -2
- package/src/tools/subagent.ts +3 -1
- package/src/tools/tool-catalog.generated.ts +8 -1
- package/src/web/insane/bridge.ts +113 -23
- package/src/web/search/providers/perplexity.ts +102 -41
|
@@ -128,6 +128,15 @@ type AgentSessionEventHandlers = {
|
|
|
128
128
|
[E in AgentSessionEventKind]: (event: Extract<AgentSessionEvent, { type: E }>) => Promise<void>;
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
+
interface AssistantTextPresentation {
|
|
132
|
+
component: AssistantMessageComponent;
|
|
133
|
+
latestMessage: AssistantMessage;
|
|
134
|
+
sessionIdentity: InteractiveModeContext["session"];
|
|
135
|
+
sessionEpoch: number;
|
|
136
|
+
paintEpoch: number;
|
|
137
|
+
cancel: () => void;
|
|
138
|
+
}
|
|
139
|
+
|
|
131
140
|
export class EventController {
|
|
132
141
|
#lastReadGroup: ReadToolGroupComponent | undefined = undefined;
|
|
133
142
|
#lastThinkingCount = 0;
|
|
@@ -146,6 +155,16 @@ export class EventController {
|
|
|
146
155
|
#visibleTranscriptChanged = false;
|
|
147
156
|
#handlingEvent = false;
|
|
148
157
|
#eventQueue: Promise<void> = Promise.resolve();
|
|
158
|
+
#disposed = false;
|
|
159
|
+
#assistantTextSuspended = false;
|
|
160
|
+
#sessionPresentationEpoch = 0;
|
|
161
|
+
#paintEpoch = 0;
|
|
162
|
+
#pendingAssistantText: AssistantTextPresentation | undefined;
|
|
163
|
+
#assistantTextMutationScope: AssistantTextPresentation | undefined;
|
|
164
|
+
#assistantLifetimes = new WeakMap<
|
|
165
|
+
AssistantMessageComponent,
|
|
166
|
+
{ sessionIdentity: InteractiveModeContext["session"]; sessionEpoch: number }
|
|
167
|
+
>();
|
|
149
168
|
|
|
150
169
|
constructor(private ctx: InteractiveModeContext) {
|
|
151
170
|
this.#handlers = {
|
|
@@ -177,6 +196,8 @@ export class EventController {
|
|
|
177
196
|
}
|
|
178
197
|
|
|
179
198
|
dispose(): void {
|
|
199
|
+
this.#disposed = true;
|
|
200
|
+
this.resetAssistantTextPresentation();
|
|
180
201
|
this.#cancelIdleCompaction();
|
|
181
202
|
this.#clearRetryCountdown();
|
|
182
203
|
if (this.ctx.retryEscapeHandler) {
|
|
@@ -199,6 +220,104 @@ export class EventController {
|
|
|
199
220
|
this.clearIrcExpiryTimers();
|
|
200
221
|
}
|
|
201
222
|
|
|
223
|
+
#cancelAssistantTextPresentation(): void {
|
|
224
|
+
const pending = this.#pendingAssistantText;
|
|
225
|
+
this.#pendingAssistantText = undefined;
|
|
226
|
+
pending?.cancel();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Temporary TUI stop discards scheduling, not historical assistant callback lifetimes. */
|
|
230
|
+
suspendAssistantTextPresentation(): void {
|
|
231
|
+
this.#assistantTextSuspended = true;
|
|
232
|
+
this.#paintEpoch += 1;
|
|
233
|
+
this.#cancelAssistantTextPresentation();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Rearm before the first restarted frame, even without a new provider event. */
|
|
237
|
+
resumeAssistantTextPresentation(): void {
|
|
238
|
+
this.#cancelAssistantTextPresentation();
|
|
239
|
+
this.#assistantTextSuspended = false;
|
|
240
|
+
const component = this.ctx.streamingComponent;
|
|
241
|
+
const message = this.ctx.streamingMessage;
|
|
242
|
+
if (component && message && this.#isLiveAssistant(component)) {
|
|
243
|
+
this.#queueAssistantText(component, message);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Session/transcript replacement invalidates callbacks before removing old children. */
|
|
248
|
+
resetAssistantTextPresentation(): void {
|
|
249
|
+
this.#sessionPresentationEpoch += 1;
|
|
250
|
+
this.#cancelAssistantTextPresentation();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#isLiveAssistant(component: AssistantMessageComponent): boolean {
|
|
254
|
+
const lifetime = this.#assistantLifetimes.get(component);
|
|
255
|
+
return (
|
|
256
|
+
!this.#disposed &&
|
|
257
|
+
!this.ctx.isStopped?.() &&
|
|
258
|
+
lifetime !== undefined &&
|
|
259
|
+
lifetime.sessionIdentity === this.ctx.session &&
|
|
260
|
+
lifetime.sessionEpoch === this.#sessionPresentationEpoch &&
|
|
261
|
+
this.ctx.chatContainer.hasLiveChild(component)
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
#isValidAssistantText(record: AssistantTextPresentation): boolean {
|
|
266
|
+
return (
|
|
267
|
+
!this.#assistantTextSuspended &&
|
|
268
|
+
this.#isLiveAssistant(record.component) &&
|
|
269
|
+
record.sessionIdentity === this.ctx.session &&
|
|
270
|
+
record.sessionEpoch === this.#sessionPresentationEpoch &&
|
|
271
|
+
record.paintEpoch === this.#paintEpoch &&
|
|
272
|
+
record.component === this.ctx.streamingComponent
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
#queueAssistantText(component: AssistantMessageComponent, latestMessage: AssistantMessage): void {
|
|
277
|
+
if (this.#assistantTextSuspended || !this.#isLiveAssistant(component)) return;
|
|
278
|
+
const pending = this.#pendingAssistantText;
|
|
279
|
+
if (pending && this.#isValidAssistantText(pending)) {
|
|
280
|
+
pending.latestMessage = latestMessage;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
this.#cancelAssistantTextPresentation();
|
|
284
|
+
const record: AssistantTextPresentation = {
|
|
285
|
+
component,
|
|
286
|
+
latestMessage,
|
|
287
|
+
sessionIdentity: this.ctx.session,
|
|
288
|
+
sessionEpoch: this.#sessionPresentationEpoch,
|
|
289
|
+
paintEpoch: this.#paintEpoch,
|
|
290
|
+
cancel: () => {},
|
|
291
|
+
};
|
|
292
|
+
this.#pendingAssistantText = record;
|
|
293
|
+
record.cancel = this.ctx.ui.enqueueBeforeRender(() => this.#flushAssistantText(record));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#flushAssistantText(record = this.#pendingAssistantText): void {
|
|
297
|
+
if (!record || this.#pendingAssistantText !== record) return;
|
|
298
|
+
this.#cancelAssistantTextPresentation();
|
|
299
|
+
if (!this.#isValidAssistantText(record)) return;
|
|
300
|
+
const previousScope = this.#assistantTextMutationScope;
|
|
301
|
+
this.#assistantTextMutationScope = record;
|
|
302
|
+
try {
|
|
303
|
+
record.component.updateContent(record.latestMessage, { streaming: true });
|
|
304
|
+
} finally {
|
|
305
|
+
this.#assistantTextMutationScope = previousScope;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
#observeAssistantMutation(component: AssistantMessageComponent): void {
|
|
310
|
+
if (!this.#isLiveAssistant(component)) return;
|
|
311
|
+
const scope = this.#assistantTextMutationScope;
|
|
312
|
+
if (scope?.component === component && this.#isValidAssistantText(scope)) {
|
|
313
|
+
// Only this synchronous projection publishes before frame source selection.
|
|
314
|
+
// An unrelated awaited event retains its own dirty flag and settlement semantics.
|
|
315
|
+
this.ctx.recordVisibleTranscriptMutation?.();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
this.#observeVisibleTranscriptMutation();
|
|
319
|
+
}
|
|
320
|
+
|
|
202
321
|
#recordVisibleTranscriptMutation(): void {
|
|
203
322
|
this.#visibleTranscriptChanged = true;
|
|
204
323
|
}
|
|
@@ -292,9 +411,10 @@ export class EventController {
|
|
|
292
411
|
}
|
|
293
412
|
|
|
294
413
|
async #dispatchEvent(event: AgentSessionEvent): Promise<void> {
|
|
414
|
+
if (this.#disposed) return;
|
|
295
415
|
if (this.ctx.isStopped?.()) return;
|
|
296
416
|
if (!this.ctx.isInitialized) await this.ctx.init();
|
|
297
|
-
if (this.ctx.isStopped?.()) return;
|
|
417
|
+
if (this.#disposed || this.ctx.isStopped?.()) return;
|
|
298
418
|
this.#visibleTranscriptChanged = false;
|
|
299
419
|
this.#handlingEvent = true;
|
|
300
420
|
try {
|
|
@@ -462,18 +582,26 @@ export class EventController {
|
|
|
462
582
|
this.ctx.ui.requestRender();
|
|
463
583
|
this.#recordVisibleTranscriptMutation();
|
|
464
584
|
} else if (event.message.role === "assistant") {
|
|
585
|
+
this.#cancelAssistantTextPresentation();
|
|
465
586
|
this.#lastThinkingCount = 0;
|
|
466
587
|
this.#resetReadGroup();
|
|
467
588
|
this.#toolIntentCache.clear();
|
|
468
589
|
this.#thinkingContentIndices.clear();
|
|
469
|
-
|
|
590
|
+
const component: AssistantMessageComponent = new AssistantMessageComponent(
|
|
470
591
|
undefined,
|
|
471
592
|
this.ctx.hideThinkingBlock,
|
|
472
|
-
() =>
|
|
593
|
+
() => {
|
|
594
|
+
if (this.#isLiveAssistant(component)) this.ctx.ui.requestRender();
|
|
595
|
+
},
|
|
473
596
|
this.ctx.getAssistantViewportAnchorId?.(event.message),
|
|
474
|
-
() => this.#
|
|
597
|
+
() => this.#observeAssistantMutation(component),
|
|
475
598
|
this.ctx.session,
|
|
476
599
|
);
|
|
600
|
+
this.#assistantLifetimes.set(component, {
|
|
601
|
+
sessionIdentity: this.ctx.session,
|
|
602
|
+
sessionEpoch: this.#sessionPresentationEpoch,
|
|
603
|
+
});
|
|
604
|
+
this.ctx.streamingComponent = component;
|
|
477
605
|
this.ctx.streamingMessage = event.message;
|
|
478
606
|
addChatChild(this.ctx, this.ctx.streamingComponent);
|
|
479
607
|
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
@@ -605,8 +733,20 @@ export class EventController {
|
|
|
605
733
|
transferSessionMessageIdentity([this.ctx.streamingMessage], [event.message]);
|
|
606
734
|
}
|
|
607
735
|
this.ctx.streamingMessage = event.message;
|
|
608
|
-
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
609
736
|
const contentIndex = event.assistantMessageEvent?.contentIndex;
|
|
737
|
+
if (
|
|
738
|
+
event.assistantMessageEvent?.type === "text_delta" &&
|
|
739
|
+
typeof contentIndex === "number" &&
|
|
740
|
+
Number.isInteger(contentIndex) &&
|
|
741
|
+
contentIndex >= 0 &&
|
|
742
|
+
contentIndex < event.message.content.length &&
|
|
743
|
+
event.message.content[contentIndex]?.type === "text"
|
|
744
|
+
) {
|
|
745
|
+
this.#queueAssistantText(this.ctx.streamingComponent, event.message);
|
|
746
|
+
} else {
|
|
747
|
+
this.#cancelAssistantTextPresentation();
|
|
748
|
+
this.ctx.streamingComponent.updateContent(this.ctx.streamingMessage, { streaming: true });
|
|
749
|
+
}
|
|
610
750
|
const changedContent =
|
|
611
751
|
typeof contentIndex === "number" && contentIndex >= 0
|
|
612
752
|
? this.ctx.streamingMessage.content[contentIndex]
|
|
@@ -714,6 +854,7 @@ export class EventController {
|
|
|
714
854
|
|
|
715
855
|
async #handleMessageEnd(event: Extract<AgentSessionEvent, { type: "message_end" }>): Promise<void> {
|
|
716
856
|
if (event.message.role === "user") return;
|
|
857
|
+
if (event.message.role === "assistant") this.#cancelAssistantTextPresentation();
|
|
717
858
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
718
859
|
if (this.ctx.streamingMessage?.role === "assistant") {
|
|
719
860
|
transferSessionMessageIdentity([this.ctx.streamingMessage], [event.message]);
|
|
@@ -916,6 +1057,8 @@ export class EventController {
|
|
|
916
1057
|
}
|
|
917
1058
|
|
|
918
1059
|
async #handleAgentEnd(event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
1060
|
+
this.#flushAssistantText();
|
|
1061
|
+
this.#cancelAssistantTextPresentation();
|
|
919
1062
|
this.ctx.setWorkingMessage(undefined);
|
|
920
1063
|
stopInteractiveActivityIndicator(this.ctx, { foregroundSettled: true });
|
|
921
1064
|
if (this.ctx.streamingComponent) {
|
|
@@ -1976,6 +1976,22 @@ export class SelectorController {
|
|
|
1976
1976
|
width ?? this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
|
|
1977
1977
|
return this.ctx.statusLine.getPreviewContent(availableWidth);
|
|
1978
1978
|
},
|
|
1979
|
+
getStatusLinePreviewForSettings: (previewSettings, width?: number) => {
|
|
1980
|
+
const availableWidth =
|
|
1981
|
+
width ?? this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
|
|
1982
|
+
return this.ctx.statusLine.getPreviewContentForSettings(availableWidth, {
|
|
1983
|
+
...buildStatusLineSettings(settings),
|
|
1984
|
+
...previewSettings,
|
|
1985
|
+
});
|
|
1986
|
+
},
|
|
1987
|
+
getStatusLinePreviewPartsForSettings: (previewSettings, width?: number) => {
|
|
1988
|
+
const availableWidth =
|
|
1989
|
+
width ?? this.ctx.editor.getTopBorderAvailableWidth(this.ctx.ui.terminal.columns);
|
|
1990
|
+
return this.ctx.statusLine.getPreviewPartsForSettings(availableWidth, {
|
|
1991
|
+
...buildStatusLineSettings(settings),
|
|
1992
|
+
...previewSettings,
|
|
1993
|
+
});
|
|
1994
|
+
},
|
|
1979
1995
|
onPluginsChanged: () => {
|
|
1980
1996
|
this.ctx.ui.requestRender();
|
|
1981
1997
|
},
|
|
@@ -2982,6 +2998,8 @@ export class SelectorController {
|
|
|
2982
2998
|
this.ctx.ui.requestRender();
|
|
2983
2999
|
try {
|
|
2984
3000
|
await mgr.uninstallPlugin(pluginId, scope);
|
|
3001
|
+
await this.ctx.session.reloadSkills();
|
|
3002
|
+
await this.ctx.refreshSlashCommandState();
|
|
2985
3003
|
this.ctx.showStatus(`Uninstalled ${pluginId}`);
|
|
2986
3004
|
} catch (err) {
|
|
2987
3005
|
this.ctx.showStatus(`Uninstall failed: ${err}`);
|
|
@@ -3019,6 +3037,8 @@ export class SelectorController {
|
|
|
3019
3037
|
try {
|
|
3020
3038
|
const force = installedIds.has(`${name}@${marketplace}`);
|
|
3021
3039
|
await mgr.installPlugin(name, marketplace, { force });
|
|
3040
|
+
await this.ctx.session.reloadSkills();
|
|
3041
|
+
await this.ctx.refreshSlashCommandState();
|
|
3022
3042
|
this.ctx.showStatus(`Installed ${name} from ${marketplace}`);
|
|
3023
3043
|
} catch (err) {
|
|
3024
3044
|
this.ctx.showStatus(`Install failed: ${err}`);
|
|
@@ -3245,6 +3265,7 @@ export class SelectorController {
|
|
|
3245
3265
|
}
|
|
3246
3266
|
|
|
3247
3267
|
#clearTransientSessionUi(options?: { restoreBackground?: boolean; clearSpecializedLoaders?: boolean }): void {
|
|
3268
|
+
this.ctx.resetAssistantTextPresentation();
|
|
3248
3269
|
if (options?.clearSpecializedLoaders) clearInteractiveActivityLoaders(this.ctx);
|
|
3249
3270
|
stopInteractiveActivityIndicator(this.ctx, {
|
|
3250
3271
|
restoreBackground: options?.restoreBackground,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
Text,
|
|
14
14
|
TUI,
|
|
15
15
|
} from "@gajae-code/tui";
|
|
16
|
-
import { APP_NAME, adjustHsv, getProjectDir, logger, postmortem, sanitizeText } from "@gajae-code/utils";
|
|
16
|
+
import { APP_NAME, adjustHsv, getAgentDir, getProjectDir, logger, postmortem, sanitizeText } from "@gajae-code/utils";
|
|
17
17
|
import chalk from "chalk";
|
|
18
18
|
import { AsyncJobManager } from "../async";
|
|
19
19
|
import {
|
|
@@ -49,6 +49,7 @@ import type { AgentSession, AgentSessionEvent, AsyncJobSnapshotItem } from "../s
|
|
|
49
49
|
import type { HistoryStorage } from "../session/history-storage";
|
|
50
50
|
import type { SessionContext, SessionManager } from "../session/session-manager";
|
|
51
51
|
import { getRecentSessions, getSessionMessageEntryId } from "../session/session-manager";
|
|
52
|
+
import { createDefaultPaseoAnnounceDependencies, startPaseoAnnouncement } from "../setup/paseo/announce";
|
|
52
53
|
import type { LspStartupServerInfo } from "../tools";
|
|
53
54
|
import { formatPhaseDisplayName } from "../tools/todo-write";
|
|
54
55
|
import { copyToClipboard } from "../utils/clipboard";
|
|
@@ -745,6 +746,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
745
746
|
version: this.#version,
|
|
746
747
|
focusDomain: "composer",
|
|
747
748
|
keyDisplayContext: this.#keyDisplayContext,
|
|
749
|
+
onUpdate: () => {
|
|
750
|
+
this.updateEditorChrome();
|
|
751
|
+
this.ui.requestRender();
|
|
752
|
+
},
|
|
748
753
|
});
|
|
749
754
|
this.statusLine.setAutoCompactEnabled(session.autoCompactionEnabled);
|
|
750
755
|
|
|
@@ -776,6 +781,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
776
781
|
this.#btwController = new BtwController(this);
|
|
777
782
|
this.#extensionUiController = new ExtensionUiController(this);
|
|
778
783
|
this.#eventController = new EventController(this);
|
|
784
|
+
this.ui.setRenderPreparationLifecycleCallbacks({
|
|
785
|
+
invalidate: () => this.#eventController.suspendAssistantTextPresentation(),
|
|
786
|
+
beforeStart: () => this.#eventController.resumeAssistantTextPresentation(),
|
|
787
|
+
});
|
|
779
788
|
this.#commandController = new CommandController(this);
|
|
780
789
|
this.#todoCommandController = new TodoCommandController(this);
|
|
781
790
|
this.#selectorController = new SelectorController(this);
|
|
@@ -1065,6 +1074,37 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1065
1074
|
crashRelayTimer.unref?.();
|
|
1066
1075
|
}
|
|
1067
1076
|
|
|
1077
|
+
// Make this session visible in Paseo. Interactive-only by construction: a
|
|
1078
|
+
// `gjc acp` provider process never builds an InteractiveMode, so an imported
|
|
1079
|
+
// session can never announce itself back into Paseo.
|
|
1080
|
+
if (this.settings.getGlobal("paseo.autoImport") === true) {
|
|
1081
|
+
const sessionId = this.sessionManager.getSessionId();
|
|
1082
|
+
const announcement = startPaseoAnnouncement(
|
|
1083
|
+
{
|
|
1084
|
+
sessionId,
|
|
1085
|
+
cwd: this.sessionManager.getCwd(),
|
|
1086
|
+
isEnabled: () => this.settings.getGlobal("paseo.autoImport") === true,
|
|
1087
|
+
onOutcome: outcome => {
|
|
1088
|
+
if (this.#stopped || outcome.kind === "skipped") return;
|
|
1089
|
+
this.showStatus(
|
|
1090
|
+
outcome.kind === "failed"
|
|
1091
|
+
? `Paseo import failed: ${outcome.detail}`
|
|
1092
|
+
: `Controllable in Paseo as ${outcome.providerKey}`,
|
|
1093
|
+
{ dim: true },
|
|
1094
|
+
);
|
|
1095
|
+
},
|
|
1096
|
+
},
|
|
1097
|
+
createDefaultPaseoAnnounceDependencies(getAgentDir()),
|
|
1098
|
+
);
|
|
1099
|
+
const stopWatchingSettings = this.settings.onChanged(path => {
|
|
1100
|
+
if (path === "paseo.autoImport" && this.settings.getGlobal(path) !== true) announcement.cancel();
|
|
1101
|
+
});
|
|
1102
|
+
this.#stopListeners.add(() => {
|
|
1103
|
+
stopWatchingSettings();
|
|
1104
|
+
announcement.cancel();
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1068
1108
|
if (starReminderGate.schedule) {
|
|
1069
1109
|
scheduleLaunchStarReminderAfterFirstRender({
|
|
1070
1110
|
confirm: (title, message) =>
|
|
@@ -1517,6 +1557,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1517
1557
|
|
|
1518
1558
|
rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void {
|
|
1519
1559
|
prepareTranscriptRebuild(this.ui, policy);
|
|
1560
|
+
this.resetAssistantTextPresentation();
|
|
1520
1561
|
this.chatContainer.clear();
|
|
1521
1562
|
const context = this.session.buildDisplaySessionContext();
|
|
1522
1563
|
this.renderSessionContext(context);
|
|
@@ -1649,6 +1690,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1649
1690
|
stop(): void {
|
|
1650
1691
|
const wasInitialized = this.isInitialized;
|
|
1651
1692
|
this.#stopped = true;
|
|
1693
|
+
this.ui.setRenderPreparationLifecycleCallbacks(undefined);
|
|
1652
1694
|
this.#inputController.discardDeferredSubmission();
|
|
1653
1695
|
for (const listener of this.#stopListeners) {
|
|
1654
1696
|
try {
|
|
@@ -2077,6 +2119,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
2077
2119
|
return this.#uiHelpers.isKnownSlashCommand(text);
|
|
2078
2120
|
}
|
|
2079
2121
|
|
|
2122
|
+
resetAssistantTextPresentation(): void {
|
|
2123
|
+
this.#eventController.resetAssistantTextPresentation();
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2080
2126
|
/** Advances the sticky-viewport source only for semantic transcript output. */
|
|
2081
2127
|
recordVisibleTranscriptMutation(): void {
|
|
2082
2128
|
const identity = `session:${this.sessionManager.getSessionId()}`;
|
package/src/modes/types.ts
CHANGED
|
@@ -355,6 +355,7 @@ export interface InteractiveModeContext {
|
|
|
355
355
|
extractAssistantText(message: AssistantMessage): string;
|
|
356
356
|
/** Records one semantic visible-transcript mutation for the sticky viewport. */
|
|
357
357
|
recordVisibleTranscriptMutation?(): void;
|
|
358
|
+
resetAssistantTextPresentation(): void;
|
|
358
359
|
updateEditorTopBorder(): void;
|
|
359
360
|
updateEditorBorderColor(): void;
|
|
360
361
|
rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void;
|
|
@@ -944,6 +944,7 @@ export class UiHelpers {
|
|
|
944
944
|
this.ctx.pendingPythonComponents = this.ctx.pendingPythonComponents.filter(component =>
|
|
945
945
|
runningExecutionComponents.includes(component),
|
|
946
946
|
);
|
|
947
|
+
this.ctx.resetAssistantTextPresentation();
|
|
947
948
|
this.ctx.chatContainer.clear();
|
|
948
949
|
|
|
949
950
|
// Reuse a pre-built context when available (e.g. from navigateTree) to avoid a second O(N) walk.
|
|
@@ -47,6 +47,14 @@ Optimize for correctness first, maintainability second, and brevity third. Prefe
|
|
|
47
47
|
- Do not defer actionable work. Underpromise and overdeliver: report only what is done or in progress, never announce remaining work instead of doing it.
|
|
48
48
|
</communication>
|
|
49
49
|
|
|
50
|
+
{{#if reasoningLanguageEnglish}}
|
|
51
|
+
<reasoning-language>
|
|
52
|
+
- Reason through development and technical problem-solving in English.
|
|
53
|
+
- Keep user-facing answers in the language the user requested or used.
|
|
54
|
+
- This changes reasoning language only; it does not relax correctness, safety, or communication requirements.
|
|
55
|
+
</reasoning-language>
|
|
56
|
+
{{/if}}
|
|
57
|
+
|
|
50
58
|
<completion-contract>
|
|
51
59
|
- Never present partial work as complete.
|
|
52
60
|
- Never suppress tests or warnings to make code pass.
|
|
@@ -5,6 +5,7 @@ Searches files using powerful regex matching.
|
|
|
5
5
|
- `paths` accepts an array of files, directories, globs, or internal URLs; when omitted, the whole working directory is searched
|
|
6
6
|
- `paths` is an array; do not embed commas or spaces inside a single entry. Pass `["src", "tests"]` not `["src,tests"]`.
|
|
7
7
|
- Cross-line patterns are detected from literal `\n` or escaped `\\n` in `pattern`
|
|
8
|
+
- `timeout` is in seconds (default 5, range 0.5–60). Increase it only for intentionally broad searches; narrow `paths` first
|
|
8
9
|
</instruction>
|
|
9
10
|
|
|
10
11
|
<output>
|
|
@@ -20,4 +21,5 @@ Searches files using powerful regex matching.
|
|
|
20
21
|
<critical>
|
|
21
22
|
- Search paths are an array; pass separate entries rather than comma-joined paths.
|
|
22
23
|
- Use a cross-line pattern only when the match actually spans lines.
|
|
24
|
+
- Do not retry a timed-out search unchanged; narrow `paths`/`pattern` or explicitly increase `timeout`.
|
|
23
25
|
</critical>
|
|
@@ -24,8 +24,8 @@ import {
|
|
|
24
24
|
normalizeMcpCacheHints,
|
|
25
25
|
resolveMCPProtocolPreference,
|
|
26
26
|
} from "./protocol";
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
27
|
+
import { HttpTransport } from "./transports/http";
|
|
28
|
+
import { StdioTransport } from "./transports/stdio";
|
|
29
29
|
import type {
|
|
30
30
|
MCPDiscoverResultLite,
|
|
31
31
|
MCPGetPromptParams,
|
|
@@ -188,18 +188,18 @@ async function defaultRequestHandler(method: string, _params: unknown): Promise<
|
|
|
188
188
|
/**
|
|
189
189
|
* Create a transport for the given server config.
|
|
190
190
|
*/
|
|
191
|
-
|
|
191
|
+
function createTransport(config: MCPServerConfig): StdioTransport | HttpTransport {
|
|
192
192
|
const serverType = config.type ?? "stdio";
|
|
193
193
|
|
|
194
194
|
switch (serverType) {
|
|
195
195
|
case "stdio":
|
|
196
|
-
return
|
|
196
|
+
return new StdioTransport(config as MCPStdioServerConfig);
|
|
197
197
|
case "http":
|
|
198
|
-
return
|
|
198
|
+
return new HttpTransport(config as MCPHttpServerConfig);
|
|
199
199
|
case "sse":
|
|
200
200
|
// Compatibility: `sse` configs use Streamable HTTP, not the legacy MCP SSE
|
|
201
201
|
// protocol. The configured URL receives JSON-RPC POST requests directly.
|
|
202
|
-
return
|
|
202
|
+
return new HttpTransport(config as MCPSseServerConfig);
|
|
203
203
|
default:
|
|
204
204
|
throw new Error(`Unknown server type: ${serverType}`);
|
|
205
205
|
}
|
|
@@ -295,6 +295,42 @@ function unwrapTransportFailure(error: unknown): unknown {
|
|
|
295
295
|
return error instanceof MCPExpectedFailure && error.cause !== undefined ? error.cause : error;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
function transportFailureCauses(error: unknown): unknown[] {
|
|
299
|
+
const failure = unwrapTransportFailure(error);
|
|
300
|
+
return failure instanceof AggregateError ? failure.errors : [failure];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function connectionAndCleanupCause(connectionError: unknown, cleanupError: unknown): AggregateError {
|
|
304
|
+
return new AggregateError(
|
|
305
|
+
[...transportFailureCauses(connectionError), ...transportFailureCauses(cleanupError)],
|
|
306
|
+
"MCP connection failed and transport cleanup also failed",
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function combinedConnectionAndCleanupFailure(connectionError: unknown, cleanupError: unknown): MCPExpectedFailure {
|
|
311
|
+
return new MCPExpectedFailure(connectionAndCleanupCause(connectionError, cleanupError));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Failed connection whose still-owned transport cleanup can be retried explicitly. */
|
|
315
|
+
export class MCPConnectionCleanupFailure extends MCPExpectedFailure {
|
|
316
|
+
#transport: MCPTransport;
|
|
317
|
+
|
|
318
|
+
constructor(cause: unknown, transport: MCPTransport) {
|
|
319
|
+
super(cause);
|
|
320
|
+
this.name = "MCPConnectionCleanupFailure";
|
|
321
|
+
this.#transport = transport;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Retry the same retained transport cleanup; failures preserve all prior causes. */
|
|
325
|
+
async close(): Promise<void> {
|
|
326
|
+
try {
|
|
327
|
+
await this.#transport.close();
|
|
328
|
+
} catch (cleanupError) {
|
|
329
|
+
throw new MCPConnectionCleanupFailure(connectionAndCleanupCause(this, cleanupError), this.#transport);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
298
334
|
type ModernNegotiation =
|
|
299
335
|
| { outcome: "modern"; discovery: MCPDiscoverResultLite | null; discoverState: "yes" | "no"; versionRetry: boolean }
|
|
300
336
|
| { outcome: "legacy-fallback"; reason: MCPDowngradeReason };
|
|
@@ -505,14 +541,14 @@ export async function connectToServer(
|
|
|
505
541
|
},
|
|
506
542
|
): Promise<MCPServerConnection> {
|
|
507
543
|
const timeoutMs = config.timeout ?? CONNECTION_TIMEOUT_MS;
|
|
508
|
-
let transport:
|
|
544
|
+
let transport: StdioTransport | HttpTransport | undefined;
|
|
509
545
|
const connectAbort = new AbortController();
|
|
510
546
|
const connectSignal = options?.signal ? AbortSignal.any([options.signal, connectAbort.signal]) : connectAbort.signal;
|
|
511
547
|
|
|
512
548
|
const preference = resolveMCPProtocolPreference(config.protocol);
|
|
513
549
|
|
|
514
550
|
const connect = async (): Promise<MCPServerConnection> => {
|
|
515
|
-
transport =
|
|
551
|
+
transport = createTransport(config);
|
|
516
552
|
if (options?.onNotification) {
|
|
517
553
|
transport.onNotification = options.onNotification;
|
|
518
554
|
}
|
|
@@ -561,6 +597,7 @@ export async function connectToServer(
|
|
|
561
597
|
};
|
|
562
598
|
|
|
563
599
|
try {
|
|
600
|
+
await transport.connect();
|
|
564
601
|
// stdio has no per-request HTTP era signals; it stays on the legacy handshake.
|
|
565
602
|
if ((config.type ?? "stdio") === "stdio") {
|
|
566
603
|
return await connectLegacy("legacy-forced", "stdio-transport");
|
|
@@ -609,8 +646,8 @@ export async function connectToServer(
|
|
|
609
646
|
} catch (error) {
|
|
610
647
|
try {
|
|
611
648
|
await transport.close();
|
|
612
|
-
} catch {
|
|
613
|
-
|
|
649
|
+
} catch (cleanupError) {
|
|
650
|
+
throw combinedConnectionAndCleanupFailure(error, cleanupError);
|
|
614
651
|
}
|
|
615
652
|
throw error;
|
|
616
653
|
}
|
|
@@ -625,20 +662,26 @@ export async function connectToServer(
|
|
|
625
662
|
// abort initialization and wait for transport cleanup before returning.
|
|
626
663
|
const aborted = options?.signal?.aborted === true;
|
|
627
664
|
connectAbort.abort(error);
|
|
665
|
+
let failure = error;
|
|
666
|
+
let cleanupIncomplete = false;
|
|
628
667
|
if (transport) {
|
|
629
668
|
try {
|
|
630
669
|
await transport.close();
|
|
631
|
-
} catch {
|
|
632
|
-
|
|
670
|
+
} catch (cleanupError) {
|
|
671
|
+
failure = combinedConnectionAndCleanupFailure(failure, cleanupError);
|
|
672
|
+
cleanupIncomplete = true;
|
|
633
673
|
}
|
|
634
674
|
}
|
|
635
|
-
if (
|
|
636
|
-
throw
|
|
675
|
+
if (cleanupIncomplete && transport) {
|
|
676
|
+
throw new MCPConnectionCleanupFailure(unwrapTransportFailure(failure), transport);
|
|
637
677
|
}
|
|
638
|
-
if (
|
|
639
|
-
throw
|
|
678
|
+
if (failure instanceof MCPExpectedFailure) {
|
|
679
|
+
throw failure;
|
|
640
680
|
}
|
|
641
|
-
|
|
681
|
+
if (aborted || (failure instanceof Error && failure.message === connectionTimeoutMessage)) {
|
|
682
|
+
throw new MCPExpectedFailure(failure);
|
|
683
|
+
}
|
|
684
|
+
throw failure;
|
|
642
685
|
}
|
|
643
686
|
}
|
|
644
687
|
|