apex-code 0.5.1 → 0.6.0
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 +24 -0
- package/README.md +41 -2
- package/dist/core/agent-session.d.ts +37 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +264 -60
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/default-tool-names.d.ts +7 -0
- package/dist/core/default-tool-names.d.ts.map +1 -0
- package/dist/core/default-tool-names.js +14 -0
- package/dist/core/default-tool-names.js.map +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +2 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +3 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/keybindings.d.ts +11 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +6 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/permissions/startup.d.ts +1 -1
- package/dist/core/permissions/startup.d.ts.map +1 -1
- package/dist/core/permissions/startup.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +13 -9
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +13 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/contract.d.ts +1 -0
- package/dist/core/tools/contract.d.ts.map +1 -1
- package/dist/core/tools/contract.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/plan-present.d.ts +13 -12
- package/dist/core/tools/plan-present.d.ts.map +1 -1
- package/dist/core/tools/plan-present.js +32 -19
- package/dist/core/tools/plan-present.js.map +1 -1
- package/dist/core/tools/renderers/index.d.ts +1 -1
- package/dist/core/tools/renderers/index.d.ts.map +1 -1
- package/dist/core/tools/renderers/index.js +2 -0
- package/dist/core/tools/renderers/index.js.map +1 -1
- package/dist/core/tools/renderers/todo-write.d.ts +7 -0
- package/dist/core/tools/renderers/todo-write.d.ts.map +1 -0
- package/dist/core/tools/renderers/todo-write.js +58 -0
- package/dist/core/tools/renderers/todo-write.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/first-use-hints.d.ts +1 -1
- package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-use-hints.js +1 -0
- package/dist/modes/interactive/components/first-use-hints.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +3 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +25 -11
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +5 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +20 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/task-panel.d.ts +27 -0
- package/dist/modes/interactive/components/task-panel.d.ts.map +1 -0
- package/dist/modes/interactive/components/task-panel.js +92 -0
- package/dist/modes/interactive/components/task-panel.js.map +1 -0
- package/dist/modes/interactive/components/turn-summary.d.ts +6 -0
- package/dist/modes/interactive/components/turn-summary.d.ts.map +1 -0
- package/dist/modes/interactive/components/turn-summary.js +12 -0
- package/dist/modes/interactive/components/turn-summary.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +9 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +162 -11
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +31 -72
- package/dist/package-manager-cli.js.map +1 -1
- package/docs/keybindings.md +19 -1
- package/docs/settings.md +8 -0
- package/npm-shrinkwrap.json +5 -5
- package/package.json +2 -2
|
@@ -12,7 +12,7 @@ import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities,
|
|
|
12
12
|
import chalk from "chalk";
|
|
13
13
|
import { spawn, spawnSync } from "child_process";
|
|
14
14
|
import { APP_NAME, APP_TITLE, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
15
|
-
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
15
|
+
import { parseSkillBlock, } from "../../core/agent-session.js";
|
|
16
16
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
17
17
|
import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
|
|
18
18
|
import { formatCacheWarmingStatus, formatCacheWarmingUsage } from "../../core/cache-warmer.js";
|
|
@@ -81,10 +81,12 @@ import { PERMISSION_MODE_OVERRIDE_HINTS, SettingsSelectorComponent } from "./com
|
|
|
81
81
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
82
82
|
import { ApexSplashHeader } from "./components/splash-header.js";
|
|
83
83
|
import { BranchSummaryStatusIndicator, CompactionStatusIndicator, IdleStatus, RetryStatusIndicator, WorkingStatusIndicator, } from "./components/status-indicator.js";
|
|
84
|
+
import { TaskPanelComponent } from "./components/task-panel.js";
|
|
84
85
|
import { ThinkingSelectorComponent } from "./components/thinking-selector.js";
|
|
85
86
|
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
86
87
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
87
88
|
import { TrustSelectorComponent } from "./components/trust-selector.js";
|
|
89
|
+
import { TurnSummaryComponent } from "./components/turn-summary.js";
|
|
88
90
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
89
91
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
90
92
|
import { editInExternalEditor } from "./external-editor.js";
|
|
@@ -273,6 +275,8 @@ export class InteractiveMode {
|
|
|
273
275
|
runtimeHost;
|
|
274
276
|
/** Settled once the most recent /settings permission-mode write has hit disk. */
|
|
275
277
|
pendingPermissionModeWrite;
|
|
278
|
+
pendingPermissionModeCycle;
|
|
279
|
+
permissionModeCycleGeneration = 0;
|
|
276
280
|
renderer;
|
|
277
281
|
ui;
|
|
278
282
|
mainScreenRenderState;
|
|
@@ -372,6 +376,7 @@ export class InteractiveMode {
|
|
|
372
376
|
retriedAttempt;
|
|
373
377
|
/** Set when the user presses Escape during a retry wait, which is the only way to cancel one. */
|
|
374
378
|
retryCancelled = false;
|
|
379
|
+
turnStartedAt;
|
|
375
380
|
// Messages queued while compaction is running
|
|
376
381
|
compactionQueuedMessages = [];
|
|
377
382
|
// Shutdown state
|
|
@@ -387,6 +392,8 @@ export class InteractiveMode {
|
|
|
387
392
|
extensionWidgetsAbove = new Map();
|
|
388
393
|
extensionWidgetsBelow = new Map();
|
|
389
394
|
widgetContainerAbove;
|
|
395
|
+
extensionWidgetContainerAbove;
|
|
396
|
+
taskPanel;
|
|
390
397
|
widgetContainerBelow;
|
|
391
398
|
// Custom footer from extension (undefined = use built-in footer)
|
|
392
399
|
customFooter = undefined;
|
|
@@ -415,13 +422,13 @@ export class InteractiveMode {
|
|
|
415
422
|
get settingsManager() {
|
|
416
423
|
return this.session.settingsManager;
|
|
417
424
|
}
|
|
418
|
-
offerFirstUseHint(id) {
|
|
425
|
+
offerFirstUseHint(id, text) {
|
|
419
426
|
this.firstUseHints ??= new FirstUseHints(this.settingsManager.getFirstUseHints());
|
|
420
427
|
const hint = this.firstUseHints.offer(id);
|
|
421
428
|
if (!hint)
|
|
422
429
|
return;
|
|
423
430
|
this.settingsManager.setFirstUseHints(this.firstUseHints.getSeen());
|
|
424
|
-
this.chatContainer.addChild(new Text(theme.fg("dim", hint), this.outputPad, 0));
|
|
431
|
+
this.chatContainer.addChild(new Text(theme.fg("dim", text ?? hint), this.outputPad, 0));
|
|
425
432
|
}
|
|
426
433
|
constructor(runtimeHost, options = {}) {
|
|
427
434
|
this.runtimeHost = runtimeHost;
|
|
@@ -457,6 +464,13 @@ export class InteractiveMode {
|
|
|
457
464
|
this.pendingMessagesContainer = new Container();
|
|
458
465
|
this.statusContainer = new Container();
|
|
459
466
|
this.widgetContainerAbove = new Container();
|
|
467
|
+
this.extensionWidgetContainerAbove = new Container();
|
|
468
|
+
this.taskPanel = new TaskPanelComponent({
|
|
469
|
+
getSession: () => this.session,
|
|
470
|
+
getSettings: () => this.settingsManager,
|
|
471
|
+
});
|
|
472
|
+
this.widgetContainerAbove.addChild(this.taskPanel);
|
|
473
|
+
this.widgetContainerAbove.addChild(this.extensionWidgetContainerAbove);
|
|
460
474
|
this.widgetContainerBelow = new Container();
|
|
461
475
|
this.keybindings = KeybindingsManager.create();
|
|
462
476
|
setKeybindings(this.keybindings);
|
|
@@ -1619,6 +1633,7 @@ export class InteractiveMode {
|
|
|
1619
1633
|
customInstructions: options?.customInstructions,
|
|
1620
1634
|
replaceInstructions: options?.replaceInstructions,
|
|
1621
1635
|
label: options?.label,
|
|
1636
|
+
workspacePolicy: options?.workspacePolicy,
|
|
1622
1637
|
});
|
|
1623
1638
|
if (result.cancelled) {
|
|
1624
1639
|
return { cancelled: true };
|
|
@@ -1688,10 +1703,13 @@ export class InteractiveMode {
|
|
|
1688
1703
|
}
|
|
1689
1704
|
}
|
|
1690
1705
|
async rebindCurrentSession(options = {}) {
|
|
1706
|
+
this.permissionModeCycleGeneration = (this.permissionModeCycleGeneration ?? 0) + 1;
|
|
1707
|
+
this.pendingPermissionModeCycle = undefined;
|
|
1691
1708
|
const session = this.session;
|
|
1692
1709
|
this.unsubscribe?.();
|
|
1693
1710
|
this.unsubscribe = undefined;
|
|
1694
1711
|
this.applyRuntimeSettings();
|
|
1712
|
+
this.taskPanel?.rebind();
|
|
1695
1713
|
if (options.renderBeforeBind) {
|
|
1696
1714
|
this.renderCurrentSessionState();
|
|
1697
1715
|
this.subscribeToAgent();
|
|
@@ -2027,13 +2045,17 @@ export class InteractiveMode {
|
|
|
2027
2045
|
}
|
|
2028
2046
|
// Maximum total widget lines to prevent viewport overflow
|
|
2029
2047
|
static MAX_WIDGET_LINES = 10;
|
|
2048
|
+
toggleTaskPanel() {
|
|
2049
|
+
this.taskPanel.toggle();
|
|
2050
|
+
this.ui.requestRender();
|
|
2051
|
+
}
|
|
2030
2052
|
/**
|
|
2031
2053
|
* Render all extension widgets to the widget container.
|
|
2032
2054
|
*/
|
|
2033
2055
|
renderWidgets() {
|
|
2034
2056
|
if (!this.widgetContainerAbove || !this.widgetContainerBelow)
|
|
2035
2057
|
return;
|
|
2036
|
-
this.renderWidgetContainer(this.widgetContainerAbove, this.extensionWidgetsAbove, true, true);
|
|
2058
|
+
this.renderWidgetContainer(this.extensionWidgetContainerAbove ?? this.widgetContainerAbove, this.extensionWidgetsAbove, true, true);
|
|
2037
2059
|
this.renderWidgetContainer(this.widgetContainerBelow, this.extensionWidgetsBelow, false, false);
|
|
2038
2060
|
this.ui.requestRender();
|
|
2039
2061
|
}
|
|
@@ -2599,12 +2621,14 @@ export class InteractiveMode {
|
|
|
2599
2621
|
this.defaultEditor.onCtrlD = () => this.handleCtrlD();
|
|
2600
2622
|
this.defaultEditor.onAction("app.suspend", () => this.handleCtrlZ());
|
|
2601
2623
|
this.defaultEditor.onAction("app.thinking.cycle", () => this.cycleThinkingLevel());
|
|
2624
|
+
this.defaultEditor.onAction("app.permissionMode.cycle", () => this.cyclePermissionMode());
|
|
2602
2625
|
this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
|
|
2603
2626
|
this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
|
|
2604
2627
|
// Global debug handler on TUI (works regardless of focus)
|
|
2605
2628
|
this.ui.onDebug = () => this.handleDebugCommand();
|
|
2606
2629
|
this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
|
|
2607
2630
|
this.defaultEditor.onAction("app.tools.expand", () => this.cycleChatDetail());
|
|
2631
|
+
this.defaultEditor.onAction("app.tasks.toggle", () => this.toggleTaskPanel());
|
|
2608
2632
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2609
2633
|
this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
|
|
2610
2634
|
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true, preferSelection: true }));
|
|
@@ -2690,6 +2714,11 @@ export class InteractiveMode {
|
|
|
2690
2714
|
await this.showConfigurationIndex();
|
|
2691
2715
|
return;
|
|
2692
2716
|
}
|
|
2717
|
+
if (text === "/tasks") {
|
|
2718
|
+
this.editor.setText("");
|
|
2719
|
+
this.toggleTaskPanel();
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2693
2722
|
if (text === "/settings") {
|
|
2694
2723
|
this.editor.setText("");
|
|
2695
2724
|
await this.showSettingsSelector();
|
|
@@ -2907,6 +2936,7 @@ export class InteractiveMode {
|
|
|
2907
2936
|
await this.init();
|
|
2908
2937
|
}
|
|
2909
2938
|
this.footer.invalidate();
|
|
2939
|
+
this.taskPanel?.handleEvent(event);
|
|
2910
2940
|
switch (event.type) {
|
|
2911
2941
|
case "agent_start":
|
|
2912
2942
|
this.clearPendingTools();
|
|
@@ -2921,6 +2951,7 @@ export class InteractiveMode {
|
|
|
2921
2951
|
}
|
|
2922
2952
|
break;
|
|
2923
2953
|
case "turn_start":
|
|
2954
|
+
this.turnStartedAt ??= Date.now();
|
|
2924
2955
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2925
2956
|
this.ui.terminal.setProgress(true);
|
|
2926
2957
|
}
|
|
@@ -3108,6 +3139,8 @@ export class InteractiveMode {
|
|
|
3108
3139
|
break;
|
|
3109
3140
|
}
|
|
3110
3141
|
case "tool_execution_end": {
|
|
3142
|
+
if (event.toolName === "plan_present" && !event.isError)
|
|
3143
|
+
await this.refreshFooterPermissionMode();
|
|
3111
3144
|
const component = this.pendingTools.get(event.toolCallId);
|
|
3112
3145
|
if (component) {
|
|
3113
3146
|
component.updateResult({ ...event.result, isError: event.isError });
|
|
@@ -3127,9 +3160,20 @@ export class InteractiveMode {
|
|
|
3127
3160
|
this.streamingMessage = undefined;
|
|
3128
3161
|
}
|
|
3129
3162
|
this.clearPendingTools();
|
|
3163
|
+
if (!event.willRetry) {
|
|
3164
|
+
const kind = event.stopReason?.kind ??
|
|
3165
|
+
[...event.messages].reverse().find((message) => message.role === "assistant")?.stopReason;
|
|
3166
|
+
this.finishTurnSummary(kind === "aborted"
|
|
3167
|
+
? "aborted"
|
|
3168
|
+
: kind === "completed" || kind === "stop" || kind === undefined
|
|
3169
|
+
? "completed"
|
|
3170
|
+
: undefined);
|
|
3171
|
+
}
|
|
3130
3172
|
this.ui.requestRender();
|
|
3131
3173
|
break;
|
|
3132
3174
|
case "agent_settled":
|
|
3175
|
+
this.turnStartedAt = undefined;
|
|
3176
|
+
this.ui.requestRender();
|
|
3133
3177
|
await this.checkShutdownRequested();
|
|
3134
3178
|
break;
|
|
3135
3179
|
case "compaction_start": {
|
|
@@ -3229,6 +3273,8 @@ export class InteractiveMode {
|
|
|
3229
3273
|
}
|
|
3230
3274
|
this.retriedAttempt = undefined;
|
|
3231
3275
|
this.retryCancelled = false;
|
|
3276
|
+
if (event.aborted)
|
|
3277
|
+
this.finishTurnSummary("aborted");
|
|
3232
3278
|
this.ui.requestRender();
|
|
3233
3279
|
break;
|
|
3234
3280
|
}
|
|
@@ -3256,6 +3302,16 @@ export class InteractiveMode {
|
|
|
3256
3302
|
}
|
|
3257
3303
|
}
|
|
3258
3304
|
}
|
|
3305
|
+
finishTurnSummary(outcome) {
|
|
3306
|
+
const startedAt = this.turnStartedAt;
|
|
3307
|
+
this.turnStartedAt = undefined;
|
|
3308
|
+
if (startedAt === undefined || outcome === undefined)
|
|
3309
|
+
return;
|
|
3310
|
+
const elapsedMs = Date.now() - startedAt;
|
|
3311
|
+
if (elapsedMs < 1000)
|
|
3312
|
+
return;
|
|
3313
|
+
this.chatContainer.addChild(new TurnSummaryComponent(elapsedMs, outcome));
|
|
3314
|
+
}
|
|
3259
3315
|
/** Extract text content from a user message */
|
|
3260
3316
|
getUserMessageText(message) {
|
|
3261
3317
|
if (message.role !== "user")
|
|
@@ -4278,25 +4334,61 @@ export class InteractiveMode {
|
|
|
4278
4334
|
* would under-report bypassPermissions, which is the one direction that matters.
|
|
4279
4335
|
*/
|
|
4280
4336
|
async refreshFooterPermissionMode() {
|
|
4281
|
-
const
|
|
4282
|
-
|
|
4337
|
+
const session = this.session;
|
|
4338
|
+
const resolution = await session.getPermissionMode();
|
|
4339
|
+
if (this.session !== session)
|
|
4340
|
+
return;
|
|
4341
|
+
this.footer.setPermissionMode(resolution?.mode ?? "default", resolution?.origin);
|
|
4283
4342
|
if (this.isInitialized) {
|
|
4284
4343
|
this.ui.requestRender();
|
|
4285
4344
|
}
|
|
4286
4345
|
}
|
|
4346
|
+
cyclePermissionMode() {
|
|
4347
|
+
const session = this.session;
|
|
4348
|
+
const generation = this.permissionModeCycleGeneration ?? 0;
|
|
4349
|
+
const isCurrent = () => this.session === session && (this.permissionModeCycleGeneration ?? 0) === generation;
|
|
4350
|
+
const cycle = (this.pendingPermissionModeCycle ?? Promise.resolve())
|
|
4351
|
+
.then(async () => {
|
|
4352
|
+
if (!isCurrent())
|
|
4353
|
+
return;
|
|
4354
|
+
const modes = await session.getInteractivePermissionModeCycle();
|
|
4355
|
+
if (!isCurrent() || modes.length === 0)
|
|
4356
|
+
return;
|
|
4357
|
+
const current = await session.getPermissionMode();
|
|
4358
|
+
if (!isCurrent())
|
|
4359
|
+
return;
|
|
4360
|
+
const index = modes.indexOf(current?.mode ?? "default");
|
|
4361
|
+
const resolution = await session.setInteractivePermissionMode(modes[(index + 1) % modes.length]);
|
|
4362
|
+
if (!isCurrent() || !resolution)
|
|
4363
|
+
return;
|
|
4364
|
+
this.footer.setPermissionMode(resolution.mode, resolution.origin);
|
|
4365
|
+
this.offerFirstUseHint("permission-mode-cycle", `${keyDisplayText("app.permissionMode.cycle")} now cycles permission modes for this session; ${keyDisplayText("app.thinking.cycle")} cycles thinking level.`);
|
|
4366
|
+
this.ui.requestRender();
|
|
4367
|
+
})
|
|
4368
|
+
.catch((error) => {
|
|
4369
|
+
if (!isCurrent())
|
|
4370
|
+
return;
|
|
4371
|
+
this.showError(`Permission mode could not be changed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4372
|
+
});
|
|
4373
|
+
this.pendingPermissionModeCycle = cycle;
|
|
4374
|
+
return cycle;
|
|
4375
|
+
}
|
|
4287
4376
|
/**
|
|
4288
4377
|
* Writes the mode to user scope, then reports what is actually in force. The
|
|
4289
4378
|
* write can be outranked by `--permission-mode` or a project file, and a
|
|
4290
4379
|
* settings row that silently does nothing is worse than no row at all.
|
|
4291
4380
|
*/
|
|
4292
4381
|
async applyPermissionMode(mode) {
|
|
4382
|
+
const session = this.session;
|
|
4293
4383
|
let resolution;
|
|
4294
4384
|
try {
|
|
4295
|
-
const write =
|
|
4385
|
+
const write = session.setPermissionMode(mode);
|
|
4296
4386
|
this.pendingPermissionModeWrite = write.then(() => { }, () => { });
|
|
4297
4387
|
resolution = await write;
|
|
4298
4388
|
}
|
|
4299
4389
|
catch (error) {
|
|
4390
|
+
if (this.session !== session)
|
|
4391
|
+
return;
|
|
4300
4392
|
// Never swallow this. A permission mode the user believes they set, and
|
|
4301
4393
|
// did not, is the one outcome worse than the prompt they were escaping.
|
|
4302
4394
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -4304,17 +4396,27 @@ export class InteractiveMode {
|
|
|
4304
4396
|
this.ui.requestRender();
|
|
4305
4397
|
return;
|
|
4306
4398
|
}
|
|
4307
|
-
if (!resolution)
|
|
4399
|
+
if (this.session !== session || !resolution)
|
|
4308
4400
|
return;
|
|
4309
|
-
const outranked = resolution.mode !== mode;
|
|
4401
|
+
const outranked = resolution.mode !== mode || resolution.origin === "interactive";
|
|
4310
4402
|
const source = PERMISSION_MODE_OVERRIDE_HINTS[resolution.origin] ?? resolution.origin;
|
|
4311
4403
|
const line = outranked
|
|
4312
4404
|
? `Permission mode saved as ${mode}, but ${source} sets ${resolution.mode} for this session.`
|
|
4313
4405
|
: `Permission mode set to ${mode}.`;
|
|
4314
4406
|
this.chatContainer.addChild(new Text(theme.fg(outranked ? "warning" : "muted", line), 1, 0));
|
|
4315
|
-
this.footer.setPermissionMode(resolution.mode);
|
|
4407
|
+
this.footer.setPermissionMode(resolution.mode, resolution.origin);
|
|
4316
4408
|
this.ui.requestRender();
|
|
4317
4409
|
}
|
|
4410
|
+
setTaskListToolDefault(enabled) {
|
|
4411
|
+
if (this.settingsManager.getProjectSettings().defaultTools !== undefined)
|
|
4412
|
+
return;
|
|
4413
|
+
const names = this.settingsManager.getDefaultTools() ?? this.session.getImplicitDefaultToolNames();
|
|
4414
|
+
this.settingsManager.setDefaultTools(enabled
|
|
4415
|
+
? names.includes("todo_write")
|
|
4416
|
+
? names
|
|
4417
|
+
: [...names, "todo_write"]
|
|
4418
|
+
: names.filter((name) => name !== "todo_write"));
|
|
4419
|
+
}
|
|
4318
4420
|
async showSettingsSelector() {
|
|
4319
4421
|
const permissionMode = await this.session.getPermissionMode();
|
|
4320
4422
|
this.showSelector((done) => {
|
|
@@ -4324,6 +4426,10 @@ export class InteractiveMode {
|
|
|
4324
4426
|
const defaultModel = defaultProvider && defaultModelId ? `${defaultProvider}/${defaultModelId}` : "not set";
|
|
4325
4427
|
selector = new SettingsSelectorComponent({
|
|
4326
4428
|
autoCompact: this.session.autoCompactionEnabled,
|
|
4429
|
+
taskListTool: {
|
|
4430
|
+
enabled: (this.settingsManager.getDefaultTools() ?? this.session.getImplicitDefaultToolNames()).includes("todo_write"),
|
|
4431
|
+
projectControlled: this.settingsManager.getProjectSettings().defaultTools !== undefined,
|
|
4432
|
+
},
|
|
4327
4433
|
defaultModel,
|
|
4328
4434
|
currentModel: this.session.model,
|
|
4329
4435
|
availableDefaultModels: this.session.modelRuntime.getAvailableSnapshot(),
|
|
@@ -4366,6 +4472,7 @@ export class InteractiveMode {
|
|
|
4366
4472
|
fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
|
|
4367
4473
|
warnings: this.settingsManager.getWarnings(),
|
|
4368
4474
|
}, {
|
|
4475
|
+
onTaskListToolChange: (enabled) => this.setTaskListToolDefault(enabled),
|
|
4369
4476
|
onAutoCompactChange: (enabled) => {
|
|
4370
4477
|
this.session.setAutoCompactionEnabled(enabled);
|
|
4371
4478
|
this.footer.setAutoCompactEnabled(enabled);
|
|
@@ -4982,6 +5089,29 @@ export class InteractiveMode {
|
|
|
4982
5089
|
this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
|
|
4983
5090
|
return;
|
|
4984
5091
|
}
|
|
5092
|
+
const workspacePreview = await this.session.previewTreeWorkspace(entryId);
|
|
5093
|
+
if (this.session.isCompacting) {
|
|
5094
|
+
this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
|
|
5095
|
+
return;
|
|
5096
|
+
}
|
|
5097
|
+
let workspacePolicy = "keep";
|
|
5098
|
+
if (workspacePreview.state === "differs") {
|
|
5099
|
+
const choice = await this.showExtensionSelector("Restore workspace files?", [
|
|
5100
|
+
"Keep current files",
|
|
5101
|
+
"Restore files to this point",
|
|
5102
|
+
"Cancel",
|
|
5103
|
+
]);
|
|
5104
|
+
if (choice === undefined || choice === "Cancel") {
|
|
5105
|
+
this.showStatus("Navigation cancelled");
|
|
5106
|
+
return;
|
|
5107
|
+
}
|
|
5108
|
+
if (choice === "Restore files to this point")
|
|
5109
|
+
workspacePolicy = "restore";
|
|
5110
|
+
}
|
|
5111
|
+
if (this.session.isCompacting) {
|
|
5112
|
+
this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
|
|
5113
|
+
return;
|
|
5114
|
+
}
|
|
4985
5115
|
// Set up escape handler and status indicator if summarizing
|
|
4986
5116
|
let showingSummaryIndicator = false;
|
|
4987
5117
|
const originalOnEscape = this.defaultEditor.onEscape;
|
|
@@ -4998,6 +5128,7 @@ export class InteractiveMode {
|
|
|
4998
5128
|
const result = await this.session.navigateTree(entryId, {
|
|
4999
5129
|
summarize: wantsSummary,
|
|
5000
5130
|
customInstructions,
|
|
5131
|
+
workspacePolicy,
|
|
5001
5132
|
});
|
|
5002
5133
|
if (result.aborted) {
|
|
5003
5134
|
// Summarization aborted - re-show tree selector with same selection
|
|
@@ -5015,7 +5146,22 @@ export class InteractiveMode {
|
|
|
5015
5146
|
if (result.editorText && !this.editor.getText().trim()) {
|
|
5016
5147
|
this.editor.setText(result.editorText);
|
|
5017
5148
|
}
|
|
5018
|
-
|
|
5149
|
+
const workspace = result.workspace;
|
|
5150
|
+
if (workspace?.outcome === "restored") {
|
|
5151
|
+
this.showStatus(workspace.preRestoreCheckpoint
|
|
5152
|
+
? "Restored files · pre-restore checkpoint saved"
|
|
5153
|
+
: "Restored files");
|
|
5154
|
+
}
|
|
5155
|
+
else if (workspace?.outcome === "failed") {
|
|
5156
|
+
this.showStatus(workspace.warnings.join(" · ") || "Workspace restore failed");
|
|
5157
|
+
}
|
|
5158
|
+
else if (workspace?.outcome === "missing-checkpoint" ||
|
|
5159
|
+
workspacePreview.state === "no-checkpoint") {
|
|
5160
|
+
this.showStatus("Files unchanged · no checkpoint for this point");
|
|
5161
|
+
}
|
|
5162
|
+
else {
|
|
5163
|
+
this.showStatus("Files unchanged");
|
|
5164
|
+
}
|
|
5019
5165
|
void this.flushCompactionQueue({ willRetry: false });
|
|
5020
5166
|
}
|
|
5021
5167
|
catch (error) {
|
|
@@ -5600,6 +5746,8 @@ export class InteractiveMode {
|
|
|
5600
5746
|
this.showWarning("Wait for compaction to finish before reloading.");
|
|
5601
5747
|
return;
|
|
5602
5748
|
}
|
|
5749
|
+
this.permissionModeCycleGeneration = (this.permissionModeCycleGeneration ?? 0) + 1;
|
|
5750
|
+
this.pendingPermissionModeCycle = undefined;
|
|
5603
5751
|
this.resetExtensionUI();
|
|
5604
5752
|
const reloadBox = new Container();
|
|
5605
5753
|
reloadBox.addChild(new DynamicBorder());
|
|
@@ -5665,6 +5813,7 @@ export class InteractiveMode {
|
|
|
5665
5813
|
dismissReloadBox(previousEditor);
|
|
5666
5814
|
}
|
|
5667
5815
|
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5816
|
+
await this.refreshFooterPermissionMode();
|
|
5668
5817
|
}
|
|
5669
5818
|
}
|
|
5670
5819
|
async handleExportCommand(text) {
|
|
@@ -6030,6 +6179,7 @@ export class InteractiveMode {
|
|
|
6030
6179
|
const exit = this.getAppKeyDisplay("app.exit");
|
|
6031
6180
|
const suspend = this.getAppKeyDisplay("app.suspend");
|
|
6032
6181
|
const cycleThinkingLevel = this.getAppKeyDisplay("app.thinking.cycle");
|
|
6182
|
+
const cyclePermissionMode = this.getAppKeyDisplay("app.permissionMode.cycle");
|
|
6033
6183
|
const cycleModelForward = this.getAppKeyDisplay("app.model.cycleForward");
|
|
6034
6184
|
const selectModel = this.getAppKeyDisplay("app.model.select");
|
|
6035
6185
|
const expandTools = this.getAppKeyDisplay("app.tools.expand");
|
|
@@ -6074,6 +6224,7 @@ export class InteractiveMode {
|
|
|
6074
6224
|
| \`${exit}\` | Exit (when editor is empty) |
|
|
6075
6225
|
| \`${suspend}\` | Suspend to background |
|
|
6076
6226
|
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
6227
|
+
| \`${cyclePermissionMode}\` | Cycle permission mode for this session |
|
|
6077
6228
|
| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
|
|
6078
6229
|
| \`${selectModel}\` | Open model selector |
|
|
6079
6230
|
| \`${expandTools}\` | Cycle conversation detail (overview / details / all) |
|