@gajae-code/coding-agent 0.15.2 → 0.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -1
- package/dist/types/config/autorouting-tier-map.d.ts +0 -9
- package/dist/types/config/file-lock.d.ts +7 -1
- package/dist/types/config/keybindings.d.ts +13 -3
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/model-resolver.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +13 -1
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +42 -2
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +12 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/modes/action-registry.d.ts +1 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +16 -1
- package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +42 -0
- package/dist/types/modes/interactive-mode.d.ts +7 -0
- package/dist/types/modes/shared/oauth-url-copy.d.ts +9 -0
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/ui-language.d.ts +2 -1
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
- package/dist/types/sdk/broker/session-index.d.ts +18 -0
- package/dist/types/sdk/bus/index.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/cli/session-cli.d.ts +1 -1
- package/dist/types/sdk/lifecycle/service.d.ts +32 -5
- package/dist/types/sdk/session.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +4 -0
- package/dist/types/session/fallback-chain-controller.d.ts +8 -0
- package/dist/types/slash-commands/helpers/aside.d.ts +54 -0
- package/dist/types/tools/ask.d.ts +8 -0
- package/dist/types/utils/clipboard.d.ts +9 -1
- package/package.json +7 -7
- package/scripts/generate-sdk-adapter-parity-manifest.ts +5 -2
- package/scripts/generate-sdk-operation-inventory.ts +3 -0
- package/src/commands/sdk.ts +2 -2
- package/src/config/autorouting-tier-map.ts +0 -9
- package/src/config/file-lock.ts +15 -1
- package/src/config/keybindings.ts +13 -3
- package/src/config/model-registry.ts +132 -21
- package/src/config/model-resolver.ts +2 -1
- package/src/config/models-config-schema.ts +2 -2
- package/src/config/settings-schema.ts +13 -1
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +235 -4
- package/src/coordinator-mcp/server.ts +402 -2
- package/src/dap/client.ts +19 -8
- package/src/gjc-runtime/deep-interview-stage.ts +20 -1
- package/src/gjc-runtime/deep-interview-state.ts +3 -1
- package/src/gjc-runtime/session-state-lock.ts +11 -2
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/lsp/client.ts +39 -4
- package/src/main.ts +42 -11
- package/src/modes/action-registry.ts +7 -0
- package/src/modes/components/iterm-pet-transport.ts +4 -2
- package/src/modes/components/runtime-mcp-add-wizard.ts +38 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/tool-status-header.ts +149 -41
- package/src/modes/components/welcome.ts +8 -0
- package/src/modes/controllers/goal-mode-controller.ts +7 -2
- package/src/modes/controllers/input-controller.ts +141 -7
- package/src/modes/controllers/plan-mode-controller.ts +12 -4
- package/src/modes/controllers/runtime-mcp-command-controller.ts +164 -20
- package/src/modes/controllers/selector-controller.ts +11 -2
- package/src/modes/interactive-mode.ts +88 -1
- package/src/modes/shared/oauth-url-copy.ts +33 -0
- package/src/modes/types.ts +3 -0
- package/src/modes/ui-language.ts +44 -3
- package/src/runtime-mcp/oauth-flow.ts +19 -8
- package/src/runtime-mcp/transports/stdio.ts +27 -13
- package/src/sdk/acp/adapter.ts +29 -2
- package/src/sdk/broker/broker.ts +26 -6
- package/src/sdk/broker/lifecycle-ledger.ts +28 -1
- package/src/sdk/broker/lifecycle.ts +664 -5
- package/src/sdk/broker/session-index.ts +246 -0
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/index.ts +135 -38
- package/src/sdk/bus/natives-augment.d.ts +20 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +3 -0
- package/src/sdk/cli/session-cli.ts +34 -7
- package/src/sdk/lifecycle/service.ts +166 -11
- package/src/sdk/mcp/server.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +40 -0
- package/src/sdk/protocol/operation-registry.ts +2 -1
- package/src/sdk/session.ts +91 -7
- package/src/session/agent-session.ts +403 -56
- package/src/session/fallback-chain-controller.ts +17 -0
- package/src/setup/frictionless-onboarding.ts +4 -4
- package/src/slash-commands/aside-command.test.ts +366 -0
- package/src/slash-commands/builtin-registry.ts +19 -0
- package/src/slash-commands/helpers/aside.ts +299 -0
- package/src/tools/ask.ts +8 -0
- package/src/utils/clipboard.ts +37 -28
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type ComposerSubmissionOptions,
|
|
16
16
|
canApplyComposerSubmission,
|
|
17
17
|
type InteractiveModeContext,
|
|
18
|
+
type SubmittedUserInput,
|
|
18
19
|
stopInteractiveActivityIndicator,
|
|
19
20
|
} from "../../modes/types";
|
|
20
21
|
import type { AgentSessionEvent, QueuedMessageEditEntry } from "../../session/agent-session";
|
|
@@ -38,6 +39,7 @@ import { CommandPalette, type CommandPaletteAction, type CommandPaletteEntry } f
|
|
|
38
39
|
import type { PasteTextContext } from "../components/custom-editor";
|
|
39
40
|
import { QueuePaneComponent } from "../components/queue-pane";
|
|
40
41
|
import { type QueuedMessageMoveDirection, QueuedMessageSelectorComponent } from "../components/queued-message-selector";
|
|
42
|
+
import { matchesAppInterrupt } from "../utils/keybinding-matchers";
|
|
41
43
|
|
|
42
44
|
const QUEUE_SELECTOR_NAVIGATION_ACTIONS = [
|
|
43
45
|
"tui.select.up",
|
|
@@ -46,6 +48,23 @@ const QUEUE_SELECTOR_NAVIGATION_ACTIONS = [
|
|
|
46
48
|
"tui.select.pageDown",
|
|
47
49
|
] as const;
|
|
48
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Navigation actions that already carry a registry callback and an availability
|
|
53
|
+
* predicate but shipped with no curated command-palette entry and no key
|
|
54
|
+
* dispatch, leaving them unreachable from the palette. They opt into
|
|
55
|
+
* availability gating so an unavailable id is not listed, and each gets a remap
|
|
56
|
+
* dispatch loop while keeping `defaultKeys: []` -- inert at defaults, but a user
|
|
57
|
+
* binding now dispatches instead of only being rendered by the selector.
|
|
58
|
+
*/
|
|
59
|
+
export const AVAILABILITY_GATED_NAV_PALETTE_ACTIONS = [
|
|
60
|
+
"app.session.dashboard",
|
|
61
|
+
"app.transcript.browse",
|
|
62
|
+
"app.transcript.prevTurn",
|
|
63
|
+
"app.transcript.nextTurn",
|
|
64
|
+
"app.queue.togglePane",
|
|
65
|
+
"app.message.sendNow",
|
|
66
|
+
] as const satisfies readonly AppKeybinding[];
|
|
67
|
+
|
|
49
68
|
interface Expandable {
|
|
50
69
|
setExpanded(expanded: boolean): void;
|
|
51
70
|
setManuallyExpanded?(expanded: boolean): void;
|
|
@@ -75,6 +94,11 @@ function isExpandable(obj: unknown): obj is Expandable {
|
|
|
75
94
|
export class InputController {
|
|
76
95
|
readonly actionRegistry: ActionRegistry<void>;
|
|
77
96
|
readonly #loadPastedImageBatch: typeof loadPastedImageBatch;
|
|
97
|
+
#deferredSubmission?: {
|
|
98
|
+
text: string;
|
|
99
|
+
images?: InteractiveModeContext["pendingImages"];
|
|
100
|
+
composer: ComposerSubmissionOptions;
|
|
101
|
+
};
|
|
78
102
|
#imagePlaceholderDeletionUndo?: {
|
|
79
103
|
beforeText: string;
|
|
80
104
|
afterText: string;
|
|
@@ -91,6 +115,7 @@ export class InputController {
|
|
|
91
115
|
showError: actionId => this.ctx.showError(actionId),
|
|
92
116
|
});
|
|
93
117
|
this.#registerActions();
|
|
118
|
+
this.#installOAuthCopyListener();
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
#registerActions(): void {
|
|
@@ -122,6 +147,7 @@ export class InputController {
|
|
|
122
147
|
},
|
|
123
148
|
"app.clipboard.copyLine": () => this.handleCopyCurrentLine(),
|
|
124
149
|
"app.clipboard.copyPrompt": () => this.handleCopyPrompt(),
|
|
150
|
+
"app.clipboard.copyOAuthUrl": () => this.ctx.copyOAuthUrl(),
|
|
125
151
|
"app.session.new": async () => {
|
|
126
152
|
await this.ctx.handleClearCommand();
|
|
127
153
|
},
|
|
@@ -142,6 +168,7 @@ export class InputController {
|
|
|
142
168
|
"app.transcript.prevTurn": () => this.#jumpTranscriptTurn(-1),
|
|
143
169
|
"app.transcript.nextTurn": () => this.#jumpTranscriptTurn(1),
|
|
144
170
|
"app.tasks.toggle": () => this.ctx.showTasksPane(),
|
|
171
|
+
"app.todo.toggle": () => this.ctx.toggleTodoExpansion(),
|
|
145
172
|
"app.queue.togglePane": () => this.toggleQueuePane(),
|
|
146
173
|
"app.message.sendNow": () => this.sendNow(),
|
|
147
174
|
};
|
|
@@ -166,7 +193,7 @@ export class InputController {
|
|
|
166
193
|
case "app.thinking.toggle":
|
|
167
194
|
return Boolean(this.ctx.session.model?.reasoning);
|
|
168
195
|
case "app.commandPalette.open":
|
|
169
|
-
return
|
|
196
|
+
return true;
|
|
170
197
|
case "app.model.cycleForward":
|
|
171
198
|
case "app.model.cycleBackward":
|
|
172
199
|
return this.ctx.session.getRoleModelCycleCandidateCount() > 1;
|
|
@@ -197,6 +224,8 @@ export class InputController {
|
|
|
197
224
|
);
|
|
198
225
|
case "app.clipboard.copyPrompt":
|
|
199
226
|
return this.ctx.editor.getText().length > 0;
|
|
227
|
+
case "app.clipboard.copyOAuthUrl":
|
|
228
|
+
return this.ctx.hasOAuthUrlForCopy();
|
|
200
229
|
case "app.session.tree":
|
|
201
230
|
case "app.session.fork":
|
|
202
231
|
return this.ctx.session.messages.length > 0;
|
|
@@ -217,6 +246,8 @@ export class InputController {
|
|
|
217
246
|
!this.ctx.goalModeController.enabled &&
|
|
218
247
|
!this.ctx.goalModeController.paused
|
|
219
248
|
);
|
|
249
|
+
case "app.todo.toggle":
|
|
250
|
+
return this.ctx.todoPhases.some(phase => phase.tasks.length > 0);
|
|
220
251
|
case "app.queue.togglePane":
|
|
221
252
|
return true;
|
|
222
253
|
case "app.message.sendNow":
|
|
@@ -234,7 +265,7 @@ export class InputController {
|
|
|
234
265
|
}
|
|
235
266
|
|
|
236
267
|
#executeAction(id: (typeof APP_ACTION_METADATA)[number]["id"]): void {
|
|
237
|
-
void this.actionRegistry.
|
|
268
|
+
void this.actionRegistry.executeFresh(id);
|
|
238
269
|
}
|
|
239
270
|
|
|
240
271
|
#transcriptTurnAnchorIds: readonly string[] = [];
|
|
@@ -281,17 +312,30 @@ export class InputController {
|
|
|
281
312
|
* so abort cleanup going idle cannot turn the second Esc into an idle action. */
|
|
282
313
|
#steerConsumePending = false;
|
|
283
314
|
#commandPaletteActions = new Map<AppKeybinding, CommandPaletteAction>();
|
|
315
|
+
/** Opt-in subset of `#commandPaletteActions` whose listing is filtered by the
|
|
316
|
+
* action registry's availability predicate. Deliberately a sibling set rather
|
|
317
|
+
* than a blanket filter over every curated entry: filtering all of them would
|
|
318
|
+
* change visible palette contents for pre-existing thinking, model-cycle,
|
|
319
|
+
* editor, copy, queue, and session actions, which is a separate reviewed
|
|
320
|
+
* migration and not this change. */
|
|
321
|
+
readonly #availabilityGatedPaletteActions = new Set<AppKeybinding>();
|
|
284
322
|
#paletteCommandInFlight = false;
|
|
285
323
|
|
|
286
|
-
#registerCommandPaletteAction(
|
|
324
|
+
#registerCommandPaletteAction(
|
|
325
|
+
action: AppKeybinding,
|
|
326
|
+
handler: () => void | Promise<void>,
|
|
327
|
+
availabilityGated = false,
|
|
328
|
+
): void {
|
|
287
329
|
this.#commandPaletteActions.set(action, {
|
|
288
330
|
id: action,
|
|
289
331
|
label: KEYBINDINGS[action].description,
|
|
290
332
|
handler,
|
|
291
333
|
});
|
|
334
|
+
if (availabilityGated) this.#availabilityGatedPaletteActions.add(action);
|
|
292
335
|
}
|
|
293
336
|
|
|
294
337
|
#globalInterruptUnsubscribe: (() => void) | undefined;
|
|
338
|
+
#oauthCopyUnsubscribe: (() => void) | undefined;
|
|
295
339
|
#draftClearEscapeText: string | undefined;
|
|
296
340
|
|
|
297
341
|
#resetEscapeGestures(): void {
|
|
@@ -300,6 +344,21 @@ export class InputController {
|
|
|
300
344
|
this.#draftClearEscapeText = undefined;
|
|
301
345
|
}
|
|
302
346
|
|
|
347
|
+
#installOAuthCopyListener(): void {
|
|
348
|
+
if (typeof this.ctx.ui?.addInputListener !== "function") return;
|
|
349
|
+
this.#oauthCopyUnsubscribe?.();
|
|
350
|
+
this.#oauthCopyUnsubscribe = this.ctx.ui.addInputListener(data => {
|
|
351
|
+
if (this.ctx.hasOAuthUrlForCopy?.() !== true) return;
|
|
352
|
+
// Focused overlays own cancellation. In particular, a user remap of
|
|
353
|
+
// copyOAuthUrl to Ctrl+C/Escape must not steal the wizard's interrupt.
|
|
354
|
+
if (data === "\x03" || matchesAppInterrupt(data)) return;
|
|
355
|
+
if (this.ctx.keybindings.getKeys("app.clipboard.copyOAuthUrl").some(key => matchesKey(data, key))) {
|
|
356
|
+
this.#executeAction("app.clipboard.copyOAuthUrl");
|
|
357
|
+
return { consume: true };
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
303
362
|
#armDraftClearEscape(text: string, now: number): void {
|
|
304
363
|
this.ctx.lastEscapeTime = 0;
|
|
305
364
|
this.ctx.lastComposerClearEscapeTime = now;
|
|
@@ -548,6 +607,7 @@ export class InputController {
|
|
|
548
607
|
return bypassAutocomplete;
|
|
549
608
|
};
|
|
550
609
|
this.#installGlobalInterruptListener();
|
|
610
|
+
this.#installOAuthCopyListener();
|
|
551
611
|
|
|
552
612
|
// An open btw panel must stay dismissable with Esc even while another
|
|
553
613
|
// controller (auto-compaction, auto-retry, manual compaction, etc.) has
|
|
@@ -695,6 +755,7 @@ export class InputController {
|
|
|
695
755
|
);
|
|
696
756
|
this.ctx.editor.onCopyPrompt = () => this.#executeAction("app.clipboard.copyPrompt");
|
|
697
757
|
this.#registerCommandPaletteAction("app.clipboard.copyPrompt", copyPrompt);
|
|
758
|
+
this.#registerCommandPaletteAction("app.clipboard.copyOAuthUrl", () => this.ctx.copyOAuthUrl(), true);
|
|
698
759
|
this.ctx.editor.onPasteText = (text, context) => this.handleTextPaste(text, context);
|
|
699
760
|
this.ctx.editor.onPastePendingInputCleared = (reason, restoredInputCount) => {
|
|
700
761
|
const reasonText = reason === "timeout" ? "timed out" : "exceeded the input queue limit";
|
|
@@ -710,10 +771,10 @@ export class InputController {
|
|
|
710
771
|
this.ctx.editor.setActionKeys("app.message.dequeue", this.ctx.keybindings.getKeys("app.message.dequeue"));
|
|
711
772
|
this.ctx.editor.onDequeue = () => this.#executeAction("app.message.dequeue");
|
|
712
773
|
this.#registerCommandPaletteAction("app.message.dequeue", dequeue);
|
|
713
|
-
const queue = () => this.
|
|
774
|
+
const queue = () => this.#executeAction("app.message.queue");
|
|
714
775
|
this.ctx.editor.setActionKeys("app.message.queue", this.ctx.keybindings.getKeys("app.message.queue"));
|
|
715
776
|
this.ctx.editor.onQueue = () => this.#executeAction("app.message.queue");
|
|
716
|
-
this.#registerCommandPaletteAction("app.message.queue", queue);
|
|
777
|
+
this.#registerCommandPaletteAction("app.message.queue", queue, true);
|
|
717
778
|
|
|
718
779
|
this.ctx.editor.onViewportPageScroll = direction => this.ctx.ui.scrollViewportPages(direction);
|
|
719
780
|
this.ctx.editor.onViewportFollowLive = () => {
|
|
@@ -788,6 +849,21 @@ export class InputController {
|
|
|
788
849
|
return true;
|
|
789
850
|
});
|
|
790
851
|
}
|
|
852
|
+
// Six navigation actions that shipped with a registry callback and an
|
|
853
|
+
// availability predicate but no curated palette entry and no key dispatch.
|
|
854
|
+
// The palette handler routes through `#executeAction` so the registry's
|
|
855
|
+
// availability check is enforced on selection as well as on listing, and
|
|
856
|
+
// `defaultKeys` stays `[]` per the settled no-new-defaults boundary.
|
|
857
|
+
for (const id of AVAILABILITY_GATED_NAV_PALETTE_ACTIONS) {
|
|
858
|
+
this.#registerCommandPaletteAction(id, () => this.#executeAction(id), true);
|
|
859
|
+
for (const key of this.ctx.keybindings.getKeys(id)) {
|
|
860
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
861
|
+
if (!this.actionRegistry.isAvailable(id)) return false;
|
|
862
|
+
this.#executeAction(id);
|
|
863
|
+
return true;
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
}
|
|
791
867
|
for (const key of this.ctx.keybindings.getKeys("app.message.followUp")) {
|
|
792
868
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
793
869
|
if (!this.actionRegistry.isAvailable("app.message.followUp")) return false;
|
|
@@ -822,6 +898,17 @@ export class InputController {
|
|
|
822
898
|
return true;
|
|
823
899
|
});
|
|
824
900
|
}
|
|
901
|
+
// The todo HUD renders a `+N more` row but had no way to expand it:
|
|
902
|
+
// `toggleTodoExpansion()` had no caller anywhere in src/. The palette entry
|
|
903
|
+
// opts into availability gating so it is not offered with an empty model.
|
|
904
|
+
this.#registerCommandPaletteAction("app.todo.toggle", () => this.#executeAction("app.todo.toggle"), true);
|
|
905
|
+
for (const key of this.ctx.keybindings.getKeys("app.todo.toggle")) {
|
|
906
|
+
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
907
|
+
if (!this.actionRegistry.isAvailable("app.todo.toggle")) return false;
|
|
908
|
+
this.#executeAction("app.todo.toggle");
|
|
909
|
+
return true;
|
|
910
|
+
});
|
|
911
|
+
}
|
|
825
912
|
for (const key of this.ctx.keybindings.getKeys("app.tool.backgroundFold")) {
|
|
826
913
|
this.ctx.editor.setCustomKeyHandler(key, () => {
|
|
827
914
|
if (!this.actionRegistry.isAvailable("app.tool.backgroundFold")) return false;
|
|
@@ -1104,12 +1191,48 @@ export class InputController {
|
|
|
1104
1191
|
const submission = this.ctx.startPendingSubmission({ text, images }, composer);
|
|
1105
1192
|
|
|
1106
1193
|
this.ctx.onInputCallback(submission);
|
|
1194
|
+
} else if (!this.#deferredSubmission) {
|
|
1195
|
+
const images = inputImages && inputImages.length > 0 ? [...inputImages] : undefined;
|
|
1196
|
+
this.#deferredSubmission = { text, images, composer };
|
|
1197
|
+
if (this.#canModifyComposer(composer)) {
|
|
1198
|
+
this.ctx.editor.setText("");
|
|
1199
|
+
this.#clearPendingImagesIfOwnedBy(pendingImages, composer);
|
|
1200
|
+
this.ctx.editor.addToHistory(text);
|
|
1201
|
+
}
|
|
1202
|
+
return;
|
|
1203
|
+
} else {
|
|
1204
|
+
this.ctx.showStatus("Your previous message is waiting to be sent. Keep this draft and send it again shortly.");
|
|
1205
|
+
return;
|
|
1107
1206
|
}
|
|
1108
1207
|
if (this.#canModifyComposer(composer)) {
|
|
1109
1208
|
this.ctx.editor.addToHistory(text);
|
|
1110
1209
|
}
|
|
1111
1210
|
}
|
|
1112
1211
|
|
|
1212
|
+
takeDeferredSubmission(): SubmittedUserInput | undefined {
|
|
1213
|
+
const deferredSubmission = this.#deferredSubmission;
|
|
1214
|
+
if (!deferredSubmission) return undefined;
|
|
1215
|
+
this.#deferredSubmission = undefined;
|
|
1216
|
+
return this.ctx.startPendingSubmission(
|
|
1217
|
+
{ text: deferredSubmission.text, images: deferredSubmission.images },
|
|
1218
|
+
{ ownsComposer: false, editor: deferredSubmission.composer.editor },
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
takeDeferredSubmissionForShutdown(): { text: string; images?: InteractiveModeContext["pendingImages"] } | undefined {
|
|
1223
|
+
const deferredSubmission = this.#deferredSubmission;
|
|
1224
|
+
if (!deferredSubmission) return undefined;
|
|
1225
|
+
this.#deferredSubmission = undefined;
|
|
1226
|
+
return {
|
|
1227
|
+
text: deferredSubmission.text,
|
|
1228
|
+
images: deferredSubmission.images,
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
discardDeferredSubmission(): void {
|
|
1233
|
+
this.#deferredSubmission = undefined;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1113
1236
|
handleCtrlC(): void {
|
|
1114
1237
|
const now = Date.now();
|
|
1115
1238
|
if (now - this.ctx.lastSigintTime < 500) {
|
|
@@ -2109,7 +2232,18 @@ export class InputController {
|
|
|
2109
2232
|
return;
|
|
2110
2233
|
}
|
|
2111
2234
|
|
|
2112
|
-
const actions = [...this.#commandPaletteActions.
|
|
2235
|
+
const actions = [...this.#commandPaletteActions.entries()]
|
|
2236
|
+
.filter(([id]) => !this.#availabilityGatedPaletteActions.has(id) || this.actionRegistry.isAvailable(id))
|
|
2237
|
+
.map(([id, action]) =>
|
|
2238
|
+
this.#availabilityGatedPaletteActions.has(id)
|
|
2239
|
+
? {
|
|
2240
|
+
...action,
|
|
2241
|
+
handler: async () => {
|
|
2242
|
+
await this.actionRegistry.executeFresh(id);
|
|
2243
|
+
},
|
|
2244
|
+
}
|
|
2245
|
+
: action,
|
|
2246
|
+
);
|
|
2113
2247
|
const slashCommands = [...(this.ctx.getSlashCommands?.() ?? this.#slashCommands)];
|
|
2114
2248
|
if (!this.ctx.showCommandPalette) {
|
|
2115
2249
|
let overlayHandle: ReturnType<typeof this.ctx.ui.showOverlay> | undefined;
|
|
@@ -2129,7 +2263,7 @@ export class InputController {
|
|
|
2129
2263
|
keybinding: action.bindingId
|
|
2130
2264
|
? this.ctx.keybindings.getKeys(action.bindingId).join(", ") || undefined
|
|
2131
2265
|
: undefined,
|
|
2132
|
-
handler: () => this.actionRegistry.
|
|
2266
|
+
handler: () => this.actionRegistry.executeFresh(action.id),
|
|
2133
2267
|
})),
|
|
2134
2268
|
...slashCommands.map(command => ({
|
|
2135
2269
|
id: `slash:/${command.name}`,
|
|
@@ -98,6 +98,7 @@ export class PlanModeController {
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
async restoreFromSession(sessionContext: SessionContext): Promise<void> {
|
|
101
|
+
if (this.ctx.session.isExplicitEmptyToolSelection()) return;
|
|
101
102
|
if (!this.ctx.session.settings.get("plan.enabled")) {
|
|
102
103
|
if (sessionContext.mode === "plan" || sessionContext.mode === "plan_paused")
|
|
103
104
|
this.ctx.sessionManager.appendModeChange("none");
|
|
@@ -115,6 +116,10 @@ export class PlanModeController {
|
|
|
115
116
|
|
|
116
117
|
async enter(options?: { planFilePath?: string; workflow?: "parallel" | "iterative" }): Promise<void> {
|
|
117
118
|
if (this.#enabled) return;
|
|
119
|
+
if (this.ctx.session.isExplicitEmptyToolSelection()) {
|
|
120
|
+
this.ctx.showWarning("Plan mode requires built-in tools and is unavailable in a --no-tools session.");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
118
123
|
if (!this.ctx.modeGate.enter("plan")) return this.ctx.showWarning("Exit goal mode first.");
|
|
119
124
|
this.#paused = false;
|
|
120
125
|
const planFilePath = options?.planFilePath ?? "local://PLAN.md";
|
|
@@ -122,9 +127,12 @@ export class PlanModeController {
|
|
|
122
127
|
this.#previousTools = previousTools;
|
|
123
128
|
this.#planFilePath = planFilePath;
|
|
124
129
|
this.#enabled = true;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
const planTools = this.ctx.session.isExplicitEmptyToolSelection()
|
|
131
|
+
? previousTools
|
|
132
|
+
: this.ctx.session.getToolByName("resolve")
|
|
133
|
+
? [...new Set([...previousTools, "resolve"])]
|
|
134
|
+
: previousTools;
|
|
135
|
+
await this.ctx.session.setActiveToolsByName(planTools);
|
|
128
136
|
this.ctx.session.setPlanModeState({
|
|
129
137
|
enabled: true,
|
|
130
138
|
planFilePath,
|
|
@@ -143,7 +151,7 @@ export class PlanModeController {
|
|
|
143
151
|
async exit(options?: { silent?: boolean; paused?: boolean }): Promise<void> {
|
|
144
152
|
if (!this.#enabled) return;
|
|
145
153
|
await this.ctx.session.abort({ timeoutMs: ABORT_TIMEOUT_MS });
|
|
146
|
-
if (this.#previousTools
|
|
154
|
+
if (this.#previousTools !== undefined) await this.ctx.session.setActiveToolsByName(this.#previousTools);
|
|
147
155
|
if (this.#providerSessionScope && !this.ctx.session.isStreaming) {
|
|
148
156
|
if (await this.ctx.session.restoreTemporaryProviderSessionScope(this.#providerSessionScope))
|
|
149
157
|
this.#providerSessionScope = undefined;
|