@gajae-code/coding-agent 0.12.0 → 0.12.2
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 +111 -0
- package/dist/types/async/job-manager.d.ts +17 -3
- package/dist/types/config/settings-schema.d.ts +56 -2
- package/dist/types/exa/mcp-client.d.ts +10 -1
- package/dist/types/exec/bash-executor.d.ts +45 -12
- package/dist/types/extensibility/extensions/types.d.ts +16 -2
- package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
- package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
- package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
- package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
- package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +23 -3
- package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +37 -80
- package/dist/types/modes/acp/acp-agent.d.ts +2 -19
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
- package/dist/types/modes/components/settings-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line/gh.d.ts +5 -0
- package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +1 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +32 -0
- package/dist/types/reminders/star-reminder.d.ts +3 -13
- package/dist/types/runtime-mcp/manager.d.ts +1 -0
- package/dist/types/sdk/acp/adapter.d.ts +3 -1
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/config.d.ts +6 -2
- package/dist/types/sdk/bus/index.d.ts +29 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
- package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
- package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
- package/dist/types/sdk/bus/rich-render.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
- package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -4
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
- package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
- package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
- package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
- package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
- package/dist/types/sdk/prompt-status.d.ts +23 -3
- package/dist/types/sdk/session.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +40 -2
- package/dist/types/session/artifacts.d.ts +8 -0
- package/dist/types/session/blob-store.d.ts +57 -4
- package/dist/types/session/internal/managed-session-storage.d.ts +30 -4
- package/dist/types/session/session-manager.d.ts +23 -6
- package/dist/types/session/session-storage.d.ts +4 -0
- package/dist/types/session/streaming-output.d.ts +36 -7
- package/dist/types/setup/credential-import.d.ts +12 -0
- package/dist/types/skill-state/active-state.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +6 -0
- package/dist/types/task/executor.d.ts +3 -2
- package/dist/types/task/index.d.ts +7 -1
- package/dist/types/task/output-manager.d.ts +1 -0
- package/dist/types/task/provider-retry-status.d.ts +8 -0
- package/dist/types/task/receipt.d.ts +9 -1
- package/dist/types/task/types.d.ts +23 -0
- package/dist/types/tools/ask-answer-registry.d.ts +2 -2
- package/dist/types/tools/bash-interactive.d.ts +4 -1
- package/dist/types/tools/bash.d.ts +3 -1
- package/dist/types/tools/image-gen.d.ts +4 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/tools/output-meta.d.ts +23 -2
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/read.d.ts +0 -6
- package/dist/types/tools/review.d.ts +5 -7
- package/dist/types/tools/subagent.d.ts +2 -0
- package/dist/types/utils/gh.d.ts +13 -0
- package/dist/types/utils/image-resize.d.ts +1 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/searxng.d.ts +7 -0
- package/dist/types/web/search/providers/xai.d.ts +2 -0
- package/package.json +10 -8
- package/scripts/acp-conformance-agent.ts +218 -0
- package/scripts/capture-gjc-bundle-settings.ts +306 -0
- package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
- package/scripts/generate-sdk-operation-inventory.ts +5 -0
- package/scripts/resident-memory-bench.ts +1341 -0
- package/scripts/run-acp-conformance.ts +290 -0
- package/src/async/job-manager.ts +99 -39
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/plugin-cli.ts +176 -18
- package/src/commands/team.ts +19 -1
- package/src/config/file-lock.ts +11 -2
- package/src/config/settings-schema.ts +53 -2
- package/src/coordinator-mcp/question-gate-codec.ts +1 -1
- package/src/coordinator-mcp/server.ts +5 -12
- package/src/debug/index.ts +42 -17
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
- package/src/defaults/gjc/skills/ralplan/SKILL.md +25 -20
- package/src/defaults/gjc/skills/team/SKILL.md +2 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +39 -26
- package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
- package/src/defaults/gjc-defaults.ts +4 -4
- package/src/exa/mcp-client.ts +12 -3
- package/src/exec/bash-executor.ts +144 -27
- package/src/extensibility/extensions/runner.ts +15 -0
- package/src/extensibility/extensions/types.ts +18 -1
- package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
- package/src/extensibility/gjc-plugins/index.ts +19 -2
- package/src/extensibility/gjc-plugins/installer.ts +189 -50
- package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
- package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
- package/src/extensibility/gjc-plugins/observability.ts +3 -3
- package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
- package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
- package/src/extensibility/gjc-plugins/schema.ts +84 -12
- package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
- package/src/extensibility/gjc-plugins/types.ts +141 -0
- package/src/extensibility/gjc-plugins/validation.ts +9 -0
- package/src/gjc-runtime/launch-tmux.ts +25 -6
- package/src/gjc-runtime/launch-worktree.ts +103 -1
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/managed-owner-admission.ts +1 -1
- package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
- package/src/gjc-runtime/ralplan-runtime.ts +491 -27
- package/src/gjc-runtime/team-launch.ts +8 -0
- package/src/gjc-runtime/team-runtime.ts +140 -32
- package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
- package/src/gjc-runtime/tmux-sessions.ts +58 -13
- package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
- package/src/gjc-runtime/ultragoal-runtime.ts +635 -612
- package/src/gjc-runtime/workflow-manifest.generated.json +37 -99
- package/src/gjc-runtime/workflow-manifest.ts +21 -70
- package/src/internal-urls/docs-index.generated.ts +16 -16
- package/src/modes/DESIGN.md +104 -0
- package/src/modes/acp/acp-agent.ts +298 -186
- package/src/modes/components/gjc-bundle-settings.ts +508 -0
- package/src/modes/components/irc-sidebar.ts +30 -1
- package/src/modes/components/notifications-settings-editor.ts +12 -1
- package/src/modes/components/oauth-selector.ts +68 -30
- package/src/modes/components/session-observer-overlay.ts +305 -86
- package/src/modes/components/settings-selector.ts +56 -5
- package/src/modes/components/status-line/gh.ts +16 -0
- package/src/modes/components/tool-status-header.ts +3 -15
- package/src/modes/components/transcript-viewer-overlay.ts +99 -24
- package/src/modes/controllers/command-controller.ts +42 -34
- package/src/modes/controllers/event-controller.ts +27 -16
- package/src/modes/controllers/extension-ui-controller.ts +193 -34
- package/src/modes/controllers/selector-controller.ts +160 -22
- package/src/modes/interactive-mode.ts +189 -62
- package/src/modes/jobs-observer.ts +1 -1
- package/src/modes/runtime-init.ts +3 -0
- package/src/modes/theme/theme.ts +11 -9
- package/src/modes/types.ts +88 -0
- package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
- package/src/prompts/system/eager-todo.md +8 -4
- package/src/prompts/system/system-prompt.md +11 -4
- package/src/prompts/tools/bash.md +5 -5
- package/src/prompts/tools/search-tool-bm25.md +5 -0
- package/src/prompts/tools/task.md +1 -1
- package/src/reminders/star-reminder.ts +4 -42
- package/src/runtime-mcp/manager.ts +19 -4
- package/src/sdk/acp/adapter.ts +22 -6
- package/src/sdk/broker/lifecycle.ts +18 -13
- package/src/sdk/broker/transport.ts +35 -3
- package/src/sdk/bus/attachment-registry.ts +5 -1
- package/src/sdk/bus/chat-daemon-cli.ts +4 -39
- package/src/sdk/bus/chat-daemon-control.ts +7 -3
- package/src/sdk/bus/config.ts +18 -2
- package/src/sdk/bus/index.ts +661 -126
- package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
- package/src/sdk/bus/lifecycle-commands.ts +4 -1
- package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
- package/src/sdk/bus/reconciliation-store.ts +95 -16
- package/src/sdk/bus/rich-draft.ts +1 -1
- package/src/sdk/bus/rich-render.ts +2 -2
- package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
- package/src/sdk/bus/telegram-cli.ts +33 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -4
- package/src/sdk/bus/telegram-daemon.ts +916 -23
- package/src/sdk/bus/telegram-reference.ts +40 -3
- package/src/sdk/bus/threaded-render.ts +48 -3
- package/src/sdk/bus/topic-registry.ts +20 -0
- package/src/sdk/host/query/revision-store.ts +36 -2
- package/src/sdk/host/reverse-leases.ts +44 -7
- package/src/sdk/prompt-status.ts +16 -3
- package/src/sdk/protocol/operation-inventory.generated.json +33 -0
- package/src/sdk/session.ts +89 -9
- package/src/session/agent-session.ts +844 -173
- package/src/session/artifacts.ts +22 -0
- package/src/session/auth-broker-config.ts +10 -3
- package/src/session/blob-store.ts +915 -24
- package/src/session/internal/managed-session-scope.ts +200 -111
- package/src/session/internal/managed-session-storage.ts +492 -62
- package/src/session/session-manager.ts +1614 -435
- package/src/session/session-storage.ts +38 -45
- package/src/session/streaming-output.ts +238 -68
- package/src/setup/credential-import.ts +75 -13
- package/src/skill-state/active-state.ts +32 -0
- package/src/skill-state/workflow-hud.ts +17 -0
- package/src/skill-state/workflow-mutation-guard.ts +496 -2
- package/src/task/executor.ts +122 -39
- package/src/task/index.ts +280 -45
- package/src/task/output-manager.ts +24 -14
- package/src/task/provider-retry-status.ts +16 -0
- package/src/task/receipt.ts +34 -8
- package/src/task/render.ts +6 -2
- package/src/task/types.ts +108 -0
- package/src/tools/ask-answer-registry.ts +12 -6
- package/src/tools/ask.ts +86 -55
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash-interactive.ts +8 -3
- package/src/tools/bash.ts +503 -60
- package/src/tools/find.ts +1 -0
- package/src/tools/image-gen.ts +29 -5
- package/src/tools/index.ts +6 -0
- package/src/tools/output-meta.ts +108 -14
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +18 -0
- package/src/tools/review.ts +5 -7
- package/src/tools/search.ts +1 -0
- package/src/tools/subagent-render.ts +6 -2
- package/src/tools/subagent.ts +6 -0
- package/src/tools/telegram-send.ts +6 -3
- package/src/utils/gh.ts +39 -0
- package/src/utils/image-resize.ts +3 -0
- package/src/web/search/providers/anthropic.ts +4 -4
- package/src/web/search/providers/kimi.ts +15 -5
- package/src/web/search/providers/searxng.ts +47 -4
- package/src/web/search/providers/xai.ts +7 -2
- package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
- package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
|
@@ -22,7 +22,12 @@ import { HookEditorComponent } from "../../modes/components/hook-editor";
|
|
|
22
22
|
import { HookInputComponent } from "../../modes/components/hook-input";
|
|
23
23
|
import { HookSelectorComponent } from "../../modes/components/hook-selector";
|
|
24
24
|
import { getAvailableThemesWithPaths, getThemeByName, setTheme, type Theme, theme } from "../../modes/theme/theme";
|
|
25
|
-
import
|
|
25
|
+
import {
|
|
26
|
+
clearInteractiveActivityLoaders,
|
|
27
|
+
type InteractiveModeContext,
|
|
28
|
+
stopInteractiveActivityIndicator,
|
|
29
|
+
syncInteractiveActivityIndicator,
|
|
30
|
+
} from "../../modes/types";
|
|
26
31
|
import { createReadonlySessionManager } from "../../session/session-manager";
|
|
27
32
|
import { parseThinkingLevel } from "../../thinking";
|
|
28
33
|
import type { TodoPhase } from "../../tools/todo-write";
|
|
@@ -36,12 +41,55 @@ const HOOK_SELECTOR_CHROME_ROWS = 7;
|
|
|
36
41
|
const HOOK_SELECTOR_OUTLINE_ROWS = 2;
|
|
37
42
|
const HOOK_SELECTOR_INLINE_INPUT_ROWS = 2;
|
|
38
43
|
|
|
44
|
+
const EXTENSION_ACTION_MUTATIONS: ReadonlySet<PropertyKey> = new Set([
|
|
45
|
+
"sendMessage",
|
|
46
|
+
"sendUserMessage",
|
|
47
|
+
"appendEntry",
|
|
48
|
+
"setLabel",
|
|
49
|
+
"setActiveTools",
|
|
50
|
+
"setModel",
|
|
51
|
+
"setThinkingLevel",
|
|
52
|
+
"setThinkingVisibility",
|
|
53
|
+
"cycleThinkingLevel",
|
|
54
|
+
"setThinkingLevelForControl",
|
|
55
|
+
"setThinkingVisibilityForControl",
|
|
56
|
+
"setModelTemporaryForControl",
|
|
57
|
+
"setSessionName",
|
|
58
|
+
]);
|
|
59
|
+
const EXTENSION_CONTEXT_MUTATIONS: ReadonlySet<PropertyKey> = new Set([
|
|
60
|
+
"abort",
|
|
61
|
+
"abortPromptAndWait",
|
|
62
|
+
"shutdown",
|
|
63
|
+
"compact",
|
|
64
|
+
"clearContext",
|
|
65
|
+
"cycleModel",
|
|
66
|
+
"setModelProfile",
|
|
67
|
+
"cycleThinkingLevel",
|
|
68
|
+
"setQueueMode",
|
|
69
|
+
"invokeSkill",
|
|
70
|
+
"setPlanMode",
|
|
71
|
+
"operateGoal",
|
|
72
|
+
"setSdkPermissionProvider",
|
|
73
|
+
"setSdkClientBridge",
|
|
74
|
+
"sdkControl",
|
|
75
|
+
]);
|
|
76
|
+
const EXTENSION_COMMAND_MUTATIONS: ReadonlySet<PropertyKey> = new Set([
|
|
77
|
+
"reload",
|
|
78
|
+
"newSession",
|
|
79
|
+
"branch",
|
|
80
|
+
"navigateTree",
|
|
81
|
+
"compact",
|
|
82
|
+
"switchSession",
|
|
83
|
+
]);
|
|
84
|
+
|
|
39
85
|
export class ExtensionUiController {
|
|
40
86
|
#extensionTerminalInputUnsubscribers = new Set<() => void>();
|
|
87
|
+
#extensionErrorUnsubscribe?: () => void;
|
|
41
88
|
#hookWidgetsAbove = new Map<string, ExtensionUiComponent>();
|
|
42
89
|
#hookWidgetsBelow = new Map<string, ExtensionUiComponent>();
|
|
43
90
|
#activeHookCustomComponent?: Component & { dispose?(): void };
|
|
44
91
|
#activeHookCustomOverlay?: OverlayHandle;
|
|
92
|
+
#activeHookCustomCancel?: () => void;
|
|
45
93
|
|
|
46
94
|
constructor(private ctx: InteractiveModeContext) {}
|
|
47
95
|
|
|
@@ -317,6 +365,27 @@ export class ExtensionUiController {
|
|
|
317
365
|
this.ctx.restoreComposer();
|
|
318
366
|
}
|
|
319
367
|
|
|
368
|
+
#isStopped(): boolean {
|
|
369
|
+
return this.ctx.isStopped?.() === true;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
#assertActive(): void {
|
|
373
|
+
if (this.#isStopped()) throw Object.assign(new Error("Interactive mode stopped"), { code: "cancelled" });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#guardMutations<T extends object>(target: T, mutationNames: ReadonlySet<PropertyKey>): T {
|
|
377
|
+
return new Proxy(target, {
|
|
378
|
+
get: (current, property, receiver) => {
|
|
379
|
+
const member = Reflect.get(current, property, receiver) as unknown;
|
|
380
|
+
if (!mutationNames.has(property) || typeof member !== "function") return member;
|
|
381
|
+
return (...args: unknown[]) => {
|
|
382
|
+
this.#assertActive();
|
|
383
|
+
return Reflect.apply(member, current, args);
|
|
384
|
+
};
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
320
389
|
/**
|
|
321
390
|
* Initialize the hook system with TUI-based UI context.
|
|
322
391
|
*/
|
|
@@ -329,13 +398,19 @@ export class ExtensionUiController {
|
|
|
329
398
|
notify: (message, type) => this.showHookNotify(message, type),
|
|
330
399
|
onTerminalInput: handler => this.addExtensionTerminalInputListener(handler),
|
|
331
400
|
setStatus: (key, text) => this.setHookStatus(key, text),
|
|
332
|
-
setWorkingMessage: message =>
|
|
401
|
+
setWorkingMessage: message => {
|
|
402
|
+
if (!this.#isStopped()) this.ctx.setWorkingMessage(message);
|
|
403
|
+
},
|
|
333
404
|
setWidget: (key, content, options) => this.setHookWidget(key, content, options),
|
|
334
|
-
setTitle: title =>
|
|
405
|
+
setTitle: title => {
|
|
406
|
+
if (!this.#isStopped()) setTerminalTitle(title);
|
|
407
|
+
},
|
|
335
408
|
custom: (factory, options) => this.showHookCustom(factory, options),
|
|
336
|
-
setEditorText: text =>
|
|
409
|
+
setEditorText: text => {
|
|
410
|
+
if (!this.#isStopped()) this.ctx.editor.setText(text);
|
|
411
|
+
},
|
|
337
412
|
pasteToEditor: text => {
|
|
338
|
-
this.ctx.editor.handleInput(`\x1b[200~${text}\x1b[201~`);
|
|
413
|
+
if (!this.#isStopped()) this.ctx.editor.handleInput(`\x1b[200~${text}\x1b[201~`);
|
|
339
414
|
},
|
|
340
415
|
getEditorText: () => this.ctx.editor.getText(),
|
|
341
416
|
editor: (title, prefill, dialogOptions, editorOptions) =>
|
|
@@ -346,17 +421,22 @@ export class ExtensionUiController {
|
|
|
346
421
|
getAllThemes: async () => (await getAvailableThemesWithPaths()).map(t => ({ name: t.name, path: t.path })),
|
|
347
422
|
getTheme: name => getThemeByName(name),
|
|
348
423
|
setTheme: async themeArg => {
|
|
424
|
+
if (this.#isStopped()) return { success: false, error: "Interactive mode stopped" };
|
|
349
425
|
if (typeof themeArg === "string") {
|
|
350
|
-
return await setTheme(themeArg, true);
|
|
426
|
+
return await setTheme(themeArg, true, { shouldApply: () => !this.#isStopped() });
|
|
351
427
|
}
|
|
352
428
|
// Theme object passed directly - not supported in current implementation
|
|
353
429
|
return Promise.resolve({ success: false, error: "Direct theme object not supported" });
|
|
354
430
|
},
|
|
355
431
|
setFooter: () => {},
|
|
356
432
|
setHeader: () => {},
|
|
357
|
-
setEditorComponent: factory =>
|
|
433
|
+
setEditorComponent: factory => {
|
|
434
|
+
if (!this.#isStopped()) this.ctx.setEditorComponent(factory);
|
|
435
|
+
},
|
|
358
436
|
getToolsExpanded: () => this.ctx.toolOutputExpanded,
|
|
359
|
-
setToolsExpanded: expanded =>
|
|
437
|
+
setToolsExpanded: expanded => {
|
|
438
|
+
if (!this.#isStopped()) this.ctx.setToolsExpanded(expanded);
|
|
439
|
+
},
|
|
360
440
|
};
|
|
361
441
|
this.ctx.setToolUIContext(uiContext, true);
|
|
362
442
|
|
|
@@ -370,8 +450,11 @@ export class ExtensionUiController {
|
|
|
370
450
|
const wasStreaming = this.ctx.session.isStreaming;
|
|
371
451
|
this.ctx.session
|
|
372
452
|
.sendCustomMessage(message, options)
|
|
373
|
-
.then(() =>
|
|
453
|
+
.then(() => {
|
|
454
|
+
if (!this.#isStopped()) this.#applyCustomMessageDisplay(wasStreaming, message.display);
|
|
455
|
+
})
|
|
374
456
|
.catch((err: unknown) => {
|
|
457
|
+
if (this.#isStopped()) return;
|
|
375
458
|
this.ctx.showError(
|
|
376
459
|
`Extension sendMessage failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
377
460
|
);
|
|
@@ -416,7 +499,9 @@ export class ExtensionUiController {
|
|
|
416
499
|
const contextActions: ExtensionContextActions = {
|
|
417
500
|
getModel: () => this.ctx.session.model,
|
|
418
501
|
isIdle: () => !this.ctx.session.isStreaming,
|
|
502
|
+
getActivePromptHandle: () => this.ctx.session.activePromptHandle,
|
|
419
503
|
abort: () => this.ctx.session.abort(),
|
|
504
|
+
abortPromptAndWait: (handle, options) => this.ctx.session.abortPromptAndWait(handle, options),
|
|
420
505
|
hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
|
|
421
506
|
getPendingMessageCounts: () => this.ctx.session.pendingMessageCounts,
|
|
422
507
|
getTranscript: () => this.ctx.session.getTranscript(),
|
|
@@ -468,6 +553,7 @@ export class ExtensionUiController {
|
|
|
468
553
|
getBranchCandidates: () => this.ctx.sessionManager.getTree(),
|
|
469
554
|
getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
|
|
470
555
|
setSdkPermissionProvider: provider => this.ctx.session.setSdkPermissionProvider(provider),
|
|
556
|
+
setSdkClientBridge: bridge => this.ctx.session.setClientBridge(bridge),
|
|
471
557
|
sdkControl: this.#sdkControl,
|
|
472
558
|
};
|
|
473
559
|
const commandActions: ExtensionCommandContextActions = {
|
|
@@ -476,10 +562,12 @@ export class ExtensionUiController {
|
|
|
476
562
|
reload: async () => {
|
|
477
563
|
const previousSessionId = this.ctx.sessionManager.getSessionId();
|
|
478
564
|
await this.ctx.session.reload();
|
|
565
|
+
if (this.#isStopped()) return;
|
|
479
566
|
const sessionIdentityChanged = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
480
567
|
if (sessionIdentityChanged) this.ctx.resetIrcSidebarSession();
|
|
481
568
|
this.ctx.rebuildInitialMessages(sessionIdentityChanged ? "replace-identity" : "reconcile-same-transcript");
|
|
482
569
|
await this.ctx.reloadTodos();
|
|
570
|
+
if (this.#isStopped()) return;
|
|
483
571
|
this.ctx.showStatus("Reloaded session");
|
|
484
572
|
},
|
|
485
573
|
newSession: async options => {
|
|
@@ -488,18 +576,17 @@ export class ExtensionUiController {
|
|
|
488
576
|
if (!success) {
|
|
489
577
|
return { cancelled: true };
|
|
490
578
|
}
|
|
579
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
580
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
491
581
|
cleanupPreviousSessionUi();
|
|
492
582
|
|
|
493
|
-
|
|
494
|
-
this.ctx.loadingAnimation.stop();
|
|
495
|
-
this.ctx.loadingAnimation = undefined;
|
|
496
|
-
}
|
|
497
|
-
this.ctx.statusContainer.clear();
|
|
583
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
498
584
|
this.ctx.resetIrcSidebarSession();
|
|
499
585
|
setSessionTerminalTitle(this.ctx.sessionManager.getSessionName(), this.ctx.sessionManager.getCwd());
|
|
500
586
|
|
|
501
587
|
if (options?.setup) {
|
|
502
588
|
await options.setup(this.ctx.sessionManager);
|
|
589
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
503
590
|
}
|
|
504
591
|
|
|
505
592
|
this.ctx.statusLine.invalidate();
|
|
@@ -519,12 +606,14 @@ export class ExtensionUiController {
|
|
|
519
606
|
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 0),
|
|
520
607
|
);
|
|
521
608
|
await this.ctx.reloadTodos();
|
|
609
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
522
610
|
this.ctx.ui.requestRender();
|
|
523
611
|
|
|
524
612
|
return { cancelled: false };
|
|
525
613
|
},
|
|
526
614
|
branch: async entryId => {
|
|
527
615
|
const result = await this.ctx.session.branch(entryId);
|
|
616
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
528
617
|
if (result.cancelled) {
|
|
529
618
|
return { cancelled: true };
|
|
530
619
|
}
|
|
@@ -533,6 +622,7 @@ export class ExtensionUiController {
|
|
|
533
622
|
// Update UI
|
|
534
623
|
this.ctx.rebuildInitialMessages("replace-identity");
|
|
535
624
|
await this.ctx.reloadTodos();
|
|
625
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
536
626
|
this.ctx.editor.setText(result.selectedText);
|
|
537
627
|
this.ctx.showStatus("Branched to new session");
|
|
538
628
|
|
|
@@ -540,6 +630,7 @@ export class ExtensionUiController {
|
|
|
540
630
|
},
|
|
541
631
|
navigateTree: async (targetId, options) => {
|
|
542
632
|
const result = await this.ctx.session.navigateTree(targetId, { summarize: options?.summarize });
|
|
633
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
543
634
|
if (result.cancelled) {
|
|
544
635
|
return { cancelled: true };
|
|
545
636
|
}
|
|
@@ -547,6 +638,7 @@ export class ExtensionUiController {
|
|
|
547
638
|
// Update UI
|
|
548
639
|
this.ctx.rebuildInitialMessages("reconcile-same-transcript");
|
|
549
640
|
await this.ctx.reloadTodos();
|
|
641
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
550
642
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
551
643
|
this.ctx.editor.setText(result.editorText);
|
|
552
644
|
}
|
|
@@ -560,9 +652,11 @@ export class ExtensionUiController {
|
|
|
560
652
|
|
|
561
653
|
this.clearHookWidgets();
|
|
562
654
|
const result = await this.ctx.session.switchSession(sessionPath);
|
|
655
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
563
656
|
if (!result) {
|
|
564
657
|
return { cancelled: true };
|
|
565
658
|
}
|
|
659
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
566
660
|
const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
567
661
|
if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
|
|
568
662
|
|
|
@@ -571,15 +665,23 @@ export class ExtensionUiController {
|
|
|
571
665
|
switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript",
|
|
572
666
|
);
|
|
573
667
|
await this.ctx.reloadTodos();
|
|
668
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
669
|
+
syncInteractiveActivityIndicator(this.ctx);
|
|
574
670
|
return { cancelled: false };
|
|
575
671
|
},
|
|
576
672
|
};
|
|
577
673
|
|
|
578
|
-
extensionRunner.initialize(
|
|
674
|
+
extensionRunner.initialize(
|
|
675
|
+
this.#guardMutations(actions, EXTENSION_ACTION_MUTATIONS),
|
|
676
|
+
this.#guardMutations(contextActions, EXTENSION_CONTEXT_MUTATIONS),
|
|
677
|
+
this.#guardMutations(commandActions, EXTENSION_COMMAND_MUTATIONS),
|
|
678
|
+
uiContext,
|
|
679
|
+
);
|
|
579
680
|
|
|
580
681
|
// Subscribe to extension errors
|
|
581
|
-
|
|
582
|
-
|
|
682
|
+
this.#extensionErrorUnsubscribe?.();
|
|
683
|
+
this.#extensionErrorUnsubscribe = extensionRunner.onError((error: ExtensionError) => {
|
|
684
|
+
if (!this.#isStopped()) this.showExtensionError(error.extensionPath, error.error);
|
|
583
685
|
});
|
|
584
686
|
|
|
585
687
|
// Emit session_start event
|
|
@@ -589,6 +691,7 @@ export class ExtensionUiController {
|
|
|
589
691
|
}
|
|
590
692
|
|
|
591
693
|
setHookWidget(key: string, content: ExtensionWidgetContent, options?: ExtensionWidgetOptions): void {
|
|
694
|
+
if (this.#isStopped()) return;
|
|
592
695
|
const placement = options?.placement ?? "aboveEditor";
|
|
593
696
|
this.#removeHookWidget(this.#hookWidgetsAbove, key);
|
|
594
697
|
this.#removeHookWidget(this.#hookWidgetsBelow, key);
|
|
@@ -715,7 +818,9 @@ export class ExtensionUiController {
|
|
|
715
818
|
const contextActions: ExtensionContextActions = {
|
|
716
819
|
getModel: () => this.ctx.session.model,
|
|
717
820
|
isIdle: () => !this.ctx.session.isStreaming,
|
|
821
|
+
getActivePromptHandle: () => this.ctx.session.activePromptHandle,
|
|
718
822
|
abort: () => this.ctx.session.abort(),
|
|
823
|
+
abortPromptAndWait: (handle, options) => this.ctx.session.abortPromptAndWait(handle, options),
|
|
719
824
|
hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
|
|
720
825
|
getPendingMessageCounts: () => this.ctx.session.pendingMessageCounts,
|
|
721
826
|
getTranscript: () => this.ctx.session.getTranscript(),
|
|
@@ -764,6 +869,7 @@ export class ExtensionUiController {
|
|
|
764
869
|
getBranchCandidates: () => this.ctx.sessionManager.getTree(),
|
|
765
870
|
getExtensions: () => this.ctx.session.extensionRunner?.getExtensionPaths() ?? [],
|
|
766
871
|
setSdkPermissionProvider: provider => this.ctx.session.setSdkPermissionProvider(provider),
|
|
872
|
+
setSdkClientBridge: bridge => this.ctx.session.setClientBridge(bridge),
|
|
767
873
|
sdkControl: this.#sdkControl,
|
|
768
874
|
};
|
|
769
875
|
const commandActions: ExtensionCommandContextActions = {
|
|
@@ -775,10 +881,12 @@ export class ExtensionUiController {
|
|
|
775
881
|
}
|
|
776
882
|
const previousSessionId = this.ctx.sessionManager.getSessionId();
|
|
777
883
|
await this.ctx.session.reload();
|
|
884
|
+
if (this.#isStopped()) return;
|
|
778
885
|
const sessionIdentityChanged = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
779
886
|
if (sessionIdentityChanged) this.ctx.resetIrcSidebarSession();
|
|
780
887
|
this.ctx.rebuildInitialMessages(sessionIdentityChanged ? "replace-identity" : "reconcile-same-transcript");
|
|
781
888
|
await this.ctx.reloadTodos();
|
|
889
|
+
if (this.#isStopped()) return;
|
|
782
890
|
this.ctx.showStatus("Reloaded session");
|
|
783
891
|
},
|
|
784
892
|
newSession: async options => {
|
|
@@ -790,17 +898,16 @@ export class ExtensionUiController {
|
|
|
790
898
|
if (!success) {
|
|
791
899
|
return { cancelled: true };
|
|
792
900
|
}
|
|
901
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
902
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
793
903
|
cleanupPreviousSessionUi();
|
|
794
904
|
|
|
795
|
-
|
|
796
|
-
this.ctx.loadingAnimation.stop();
|
|
797
|
-
this.ctx.loadingAnimation = undefined;
|
|
798
|
-
}
|
|
799
|
-
this.ctx.statusContainer.clear();
|
|
905
|
+
stopInteractiveActivityIndicator(this.ctx);
|
|
800
906
|
this.ctx.resetIrcSidebarSession();
|
|
801
907
|
|
|
802
908
|
if (options?.setup) {
|
|
803
909
|
await options.setup(this.ctx.sessionManager);
|
|
910
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
804
911
|
}
|
|
805
912
|
|
|
806
913
|
prepareTranscriptRebuild(this.ctx.ui, "replace-identity");
|
|
@@ -815,6 +922,7 @@ export class ExtensionUiController {
|
|
|
815
922
|
new Text(`${theme.fg("accent", `${theme.status.success} New session started`)}`, 1, 0),
|
|
816
923
|
);
|
|
817
924
|
await this.ctx.reloadTodos();
|
|
925
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
818
926
|
this.ctx.ui.requestRender();
|
|
819
927
|
|
|
820
928
|
return { cancelled: false };
|
|
@@ -824,6 +932,7 @@ export class ExtensionUiController {
|
|
|
824
932
|
return { cancelled: true };
|
|
825
933
|
}
|
|
826
934
|
const result = await this.ctx.session.branch(entryId);
|
|
935
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
827
936
|
if (result.cancelled) {
|
|
828
937
|
return { cancelled: true };
|
|
829
938
|
}
|
|
@@ -832,6 +941,7 @@ export class ExtensionUiController {
|
|
|
832
941
|
// Update UI
|
|
833
942
|
this.ctx.rebuildInitialMessages("replace-identity");
|
|
834
943
|
await this.ctx.reloadTodos();
|
|
944
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
835
945
|
this.ctx.editor.setText(result.selectedText);
|
|
836
946
|
this.ctx.showStatus("Branched to new session");
|
|
837
947
|
|
|
@@ -842,6 +952,7 @@ export class ExtensionUiController {
|
|
|
842
952
|
return { cancelled: true };
|
|
843
953
|
}
|
|
844
954
|
const result = await this.ctx.session.navigateTree(targetId, { summarize: options?.summarize });
|
|
955
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
845
956
|
if (result.cancelled) {
|
|
846
957
|
return { cancelled: true };
|
|
847
958
|
}
|
|
@@ -849,6 +960,7 @@ export class ExtensionUiController {
|
|
|
849
960
|
// Update UI
|
|
850
961
|
this.ctx.rebuildInitialMessages("reconcile-same-transcript");
|
|
851
962
|
await this.ctx.reloadTodos();
|
|
963
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
852
964
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
853
965
|
this.ctx.editor.setText(result.editorText);
|
|
854
966
|
}
|
|
@@ -865,20 +977,29 @@ export class ExtensionUiController {
|
|
|
865
977
|
|
|
866
978
|
this.clearHookWidgets();
|
|
867
979
|
const result = await this.ctx.session.switchSession(sessionPath);
|
|
980
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
868
981
|
if (!result) {
|
|
869
982
|
return { cancelled: true };
|
|
870
983
|
}
|
|
984
|
+
clearInteractiveActivityLoaders(this.ctx);
|
|
871
985
|
const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
872
986
|
if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
|
|
873
987
|
this.ctx.rebuildInitialMessages(
|
|
874
988
|
switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript",
|
|
875
989
|
);
|
|
876
990
|
await this.ctx.reloadTodos();
|
|
991
|
+
if (this.#isStopped()) return { cancelled: true };
|
|
992
|
+
syncInteractiveActivityIndicator(this.ctx);
|
|
877
993
|
return { cancelled: false };
|
|
878
994
|
},
|
|
879
995
|
};
|
|
880
996
|
|
|
881
|
-
extensionRunner.initialize(
|
|
997
|
+
extensionRunner.initialize(
|
|
998
|
+
this.#guardMutations(actions, EXTENSION_ACTION_MUTATIONS),
|
|
999
|
+
this.#guardMutations(contextActions, EXTENSION_CONTEXT_MUTATIONS),
|
|
1000
|
+
this.#guardMutations(commandActions, EXTENSION_COMMAND_MUTATIONS),
|
|
1001
|
+
uiContext,
|
|
1002
|
+
);
|
|
882
1003
|
}
|
|
883
1004
|
|
|
884
1005
|
createBackgroundUiContext(): ExtensionUIContext {
|
|
@@ -935,6 +1056,7 @@ export class ExtensionUiController {
|
|
|
935
1056
|
sessionManager: createReadonlySessionManager(this.ctx.session.sessionManager),
|
|
936
1057
|
modelRegistry: this.ctx.session.modelRegistry,
|
|
937
1058
|
model: this.ctx.session.model,
|
|
1059
|
+
getActivePromptHandle: () => this.ctx.session.activePromptHandle,
|
|
938
1060
|
isIdle: () => !this.ctx.session.isStreaming,
|
|
939
1061
|
hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
|
|
940
1062
|
getPendingMessageCounts: () => this.ctx.session.pendingMessageCounts,
|
|
@@ -1022,7 +1144,7 @@ export class ExtensionUiController {
|
|
|
1022
1144
|
* Set hook status text in the footer.
|
|
1023
1145
|
*/
|
|
1024
1146
|
setHookStatus(key: string, text: string | undefined): void {
|
|
1025
|
-
if (this.ctx.isBackgrounded) {
|
|
1147
|
+
if (this.#isStopped() || this.ctx.isBackgrounded) {
|
|
1026
1148
|
return;
|
|
1027
1149
|
}
|
|
1028
1150
|
this.ctx.statusLine.setHookStatus(key, text);
|
|
@@ -1037,6 +1159,7 @@ export class ExtensionUiController {
|
|
|
1037
1159
|
options: string[],
|
|
1038
1160
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
1039
1161
|
): Promise<string | undefined> {
|
|
1162
|
+
if (this.#isStopped()) return Promise.resolve(undefined);
|
|
1040
1163
|
const { promise, finish, attachAbort } = this.#createHookDialogState(
|
|
1041
1164
|
() => this.hideHookSelector(),
|
|
1042
1165
|
dialogOptions?.signal,
|
|
@@ -1141,8 +1264,9 @@ export class ExtensionUiController {
|
|
|
1141
1264
|
*/
|
|
1142
1265
|
hideHookSelector(): void {
|
|
1143
1266
|
this.ctx.hookSelector?.dispose();
|
|
1144
|
-
this.#restoreComposerEditor();
|
|
1145
1267
|
this.ctx.hookSelector = undefined;
|
|
1268
|
+
if (this.#isStopped()) return;
|
|
1269
|
+
this.#restoreComposerEditor();
|
|
1146
1270
|
this.ctx.ui.setFocus(this.ctx.editor);
|
|
1147
1271
|
this.ctx.ui.requestRender();
|
|
1148
1272
|
}
|
|
@@ -1164,6 +1288,7 @@ export class ExtensionUiController {
|
|
|
1164
1288
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
1165
1289
|
inputOptions?: { readonly initialValue?: string },
|
|
1166
1290
|
): Promise<string | undefined> {
|
|
1291
|
+
if (this.#isStopped()) return Promise.resolve(undefined);
|
|
1167
1292
|
const { promise, finish, attachAbort } = this.#createHookDialogState(
|
|
1168
1293
|
() => this.hideHookInput(),
|
|
1169
1294
|
dialogOptions?.signal,
|
|
@@ -1202,8 +1327,9 @@ export class ExtensionUiController {
|
|
|
1202
1327
|
*/
|
|
1203
1328
|
hideHookInput(): void {
|
|
1204
1329
|
this.ctx.hookInput?.dispose();
|
|
1205
|
-
this.#restoreComposerEditor();
|
|
1206
1330
|
this.ctx.hookInput = undefined;
|
|
1331
|
+
if (this.#isStopped()) return;
|
|
1332
|
+
this.#restoreComposerEditor();
|
|
1207
1333
|
this.ctx.ui.setFocus(this.ctx.editor);
|
|
1208
1334
|
this.ctx.ui.requestRender();
|
|
1209
1335
|
}
|
|
@@ -1217,6 +1343,7 @@ export class ExtensionUiController {
|
|
|
1217
1343
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
1218
1344
|
editorOptions?: { promptStyle?: boolean },
|
|
1219
1345
|
): Promise<string | undefined> {
|
|
1346
|
+
if (this.#isStopped()) return Promise.resolve(undefined);
|
|
1220
1347
|
const { promise, finish, attachAbort } = this.#createHookDialogState(
|
|
1221
1348
|
() => this.hideHookEditor(),
|
|
1222
1349
|
dialogOptions?.signal,
|
|
@@ -1251,6 +1378,11 @@ export class ExtensionUiController {
|
|
|
1251
1378
|
* Hide the hook editor.
|
|
1252
1379
|
*/
|
|
1253
1380
|
hideHookEditor(): void {
|
|
1381
|
+
if (this.#isStopped()) {
|
|
1382
|
+
this.ctx.hookEditor?.dispose();
|
|
1383
|
+
this.ctx.hookEditor = undefined;
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1254
1386
|
this.#restoreComposerEditor();
|
|
1255
1387
|
this.ctx.hookEditor = undefined;
|
|
1256
1388
|
this.ctx.ui.setFocus(this.ctx.editor);
|
|
@@ -1261,6 +1393,7 @@ export class ExtensionUiController {
|
|
|
1261
1393
|
* Show a notification for hooks.
|
|
1262
1394
|
*/
|
|
1263
1395
|
showHookNotify(message: string, type?: "info" | "warning" | "error"): void {
|
|
1396
|
+
if (this.#isStopped()) return;
|
|
1264
1397
|
if (type === "error") {
|
|
1265
1398
|
this.ctx.showError(message);
|
|
1266
1399
|
} else if (type === "warning") {
|
|
@@ -1282,6 +1415,7 @@ export class ExtensionUiController {
|
|
|
1282
1415
|
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
|
1283
1416
|
options?: { overlay?: boolean },
|
|
1284
1417
|
): Promise<T> {
|
|
1418
|
+
if (this.#isStopped()) return undefined as T;
|
|
1285
1419
|
const savedText = this.ctx.editor.getText();
|
|
1286
1420
|
const keybindings = KeybindingsManager.inMemory();
|
|
1287
1421
|
|
|
@@ -1292,7 +1426,12 @@ export class ExtensionUiController {
|
|
|
1292
1426
|
const close = (result: T) => {
|
|
1293
1427
|
if (closed) return;
|
|
1294
1428
|
closed = true;
|
|
1429
|
+
this.#activeHookCustomCancel = undefined;
|
|
1295
1430
|
this.#clearActiveHookCustom();
|
|
1431
|
+
if (this.#isStopped()) {
|
|
1432
|
+
resolve(result);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1296
1435
|
if (!options?.overlay) {
|
|
1297
1436
|
this.#restoreComposerEditor();
|
|
1298
1437
|
this.ctx.editor.setText(savedText);
|
|
@@ -1302,10 +1441,16 @@ export class ExtensionUiController {
|
|
|
1302
1441
|
resolve(result);
|
|
1303
1442
|
};
|
|
1304
1443
|
|
|
1444
|
+
this.#activeHookCustomCancel?.();
|
|
1305
1445
|
this.#clearActiveHookCustom();
|
|
1446
|
+
this.#activeHookCustomCancel = () => close(undefined as T);
|
|
1306
1447
|
Promise.try(() => factory(this.ctx.ui, theme, keybindings, close)).then(c => {
|
|
1307
|
-
if (closed) {
|
|
1448
|
+
if (closed || this.#isStopped()) {
|
|
1308
1449
|
c.dispose?.();
|
|
1450
|
+
if (!closed) {
|
|
1451
|
+
closed = true;
|
|
1452
|
+
resolve(undefined as T);
|
|
1453
|
+
}
|
|
1309
1454
|
return;
|
|
1310
1455
|
}
|
|
1311
1456
|
component = c;
|
|
@@ -1334,6 +1479,7 @@ export class ExtensionUiController {
|
|
|
1334
1479
|
* Show an extension error in the UI.
|
|
1335
1480
|
*/
|
|
1336
1481
|
addExtensionTerminalInputListener(handler: TerminalInputHandler): () => void {
|
|
1482
|
+
if (this.#isStopped()) return () => {};
|
|
1337
1483
|
const unsubscribe = this.ctx.ui.addInputListener(handler);
|
|
1338
1484
|
this.#extensionTerminalInputUnsubscribers.add(unsubscribe);
|
|
1339
1485
|
return () => {
|
|
@@ -1362,7 +1508,17 @@ export class ExtensionUiController {
|
|
|
1362
1508
|
this.#extensionTerminalInputUnsubscribers.clear();
|
|
1363
1509
|
}
|
|
1364
1510
|
|
|
1511
|
+
dispose(): void {
|
|
1512
|
+
this.#extensionErrorUnsubscribe?.();
|
|
1513
|
+
this.#extensionErrorUnsubscribe = undefined;
|
|
1514
|
+
this.#activeHookCustomCancel?.();
|
|
1515
|
+
this.#activeHookCustomCancel = undefined;
|
|
1516
|
+
this.clearExtensionTerminalInputListeners();
|
|
1517
|
+
this.clearHookWidgets();
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1365
1520
|
showExtensionError(extensionPath: string, error: string): void {
|
|
1521
|
+
if (this.#isStopped()) return;
|
|
1366
1522
|
const errorText = new Text(theme.fg("error", `Extension "${extensionPath}" error: ${error}`), 1, 0);
|
|
1367
1523
|
this.ctx.chatContainer.addChild(errorText);
|
|
1368
1524
|
this.ctx.ui.requestRender();
|
|
@@ -1388,6 +1544,7 @@ export class ExtensionUiController {
|
|
|
1388
1544
|
}
|
|
1389
1545
|
|
|
1390
1546
|
#sendExtensionUserMessage: SendUserMessageHandler = (content, options) => {
|
|
1547
|
+
if (this.#isStopped()) return Promise.resolve();
|
|
1391
1548
|
// Compute queued BEFORE send: prompt() may flip session.isStreaming synchronously.
|
|
1392
1549
|
const queued = Boolean(options?.deliverAs) || this.ctx.session.isStreaming;
|
|
1393
1550
|
// Call send first so the busy/queued path finds the session queue populated
|
|
@@ -1395,6 +1552,7 @@ export class ExtensionUiController {
|
|
|
1395
1552
|
const send = this.ctx.session.sendUserMessage(content, options);
|
|
1396
1553
|
applyInjectedUserSubmission(this.ctx, { content, queued });
|
|
1397
1554
|
void send.catch((err: unknown) => {
|
|
1555
|
+
if (this.#isStopped()) return;
|
|
1398
1556
|
this.ctx.showError(`Extension sendUserMessage failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1399
1557
|
});
|
|
1400
1558
|
return send;
|
|
@@ -1418,20 +1576,21 @@ export class ExtensionUiController {
|
|
|
1418
1576
|
} {
|
|
1419
1577
|
const { promise, resolve } = Promise.withResolvers<string | undefined>();
|
|
1420
1578
|
let settled = false;
|
|
1421
|
-
|
|
1422
|
-
hide();
|
|
1423
|
-
if (!settled) {
|
|
1424
|
-
settled = true;
|
|
1425
|
-
resolve(undefined);
|
|
1426
|
-
}
|
|
1427
|
-
};
|
|
1579
|
+
let unregisterStop: (() => void) | undefined;
|
|
1428
1580
|
const finish = (value: string | undefined) => {
|
|
1429
1581
|
if (settled) return;
|
|
1430
1582
|
settled = true;
|
|
1431
1583
|
signal?.removeEventListener("abort", onAbort);
|
|
1584
|
+
unregisterStop?.();
|
|
1432
1585
|
resolve(value);
|
|
1433
1586
|
};
|
|
1587
|
+
const onAbort = () => {
|
|
1588
|
+
hide();
|
|
1589
|
+
finish(undefined);
|
|
1590
|
+
};
|
|
1434
1591
|
const attachAbort = () => {
|
|
1592
|
+
unregisterStop = this.ctx.onStop?.(onAbort);
|
|
1593
|
+
if (settled) unregisterStop?.();
|
|
1435
1594
|
if (!signal) return;
|
|
1436
1595
|
if (signal.aborted) {
|
|
1437
1596
|
onAbort();
|