apex-code 0.5.2 → 0.6.1
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 +16 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +7 -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/bash-execution.d.ts +13 -2
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +56 -1
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/error-summary.d.ts +2 -0
- package/dist/modes/interactive/components/error-summary.d.ts.map +1 -1
- package/dist/modes/interactive/components/error-summary.js +10 -0
- package/dist/modes/interactive/components/error-summary.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 +6 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +38 -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/tool-execution.d.ts +9 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +81 -8
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-panel.d.ts +7 -0
- package/dist/modes/interactive/components/tool-panel.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-panel.js +35 -0
- package/dist/modes/interactive/components/tool-panel.js.map +1 -1
- 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 +10 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +191 -22
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/keybindings.md +21 -2
- 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,15 @@ 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.tools.expandLatest", () => this.toggleLatestOperation());
|
|
2632
|
+
this.defaultEditor.onAction("app.tasks.toggle", () => this.toggleTaskPanel());
|
|
2608
2633
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
2609
2634
|
this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
|
|
2610
2635
|
this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true, preferSelection: true }));
|
|
@@ -2690,6 +2715,11 @@ export class InteractiveMode {
|
|
|
2690
2715
|
await this.showConfigurationIndex();
|
|
2691
2716
|
return;
|
|
2692
2717
|
}
|
|
2718
|
+
if (text === "/tasks") {
|
|
2719
|
+
this.editor.setText("");
|
|
2720
|
+
this.toggleTaskPanel();
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2693
2723
|
if (text === "/settings") {
|
|
2694
2724
|
this.editor.setText("");
|
|
2695
2725
|
await this.showSettingsSelector();
|
|
@@ -2907,6 +2937,7 @@ export class InteractiveMode {
|
|
|
2907
2937
|
await this.init();
|
|
2908
2938
|
}
|
|
2909
2939
|
this.footer.invalidate();
|
|
2940
|
+
this.taskPanel?.handleEvent(event);
|
|
2910
2941
|
switch (event.type) {
|
|
2911
2942
|
case "agent_start":
|
|
2912
2943
|
this.clearPendingTools();
|
|
@@ -2921,6 +2952,7 @@ export class InteractiveMode {
|
|
|
2921
2952
|
}
|
|
2922
2953
|
break;
|
|
2923
2954
|
case "turn_start":
|
|
2955
|
+
this.turnStartedAt ??= Date.now();
|
|
2924
2956
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2925
2957
|
this.ui.terminal.setProgress(true);
|
|
2926
2958
|
}
|
|
@@ -3108,6 +3140,8 @@ export class InteractiveMode {
|
|
|
3108
3140
|
break;
|
|
3109
3141
|
}
|
|
3110
3142
|
case "tool_execution_end": {
|
|
3143
|
+
if (event.toolName === "plan_present" && !event.isError)
|
|
3144
|
+
await this.refreshFooterPermissionMode();
|
|
3111
3145
|
const component = this.pendingTools.get(event.toolCallId);
|
|
3112
3146
|
if (component) {
|
|
3113
3147
|
component.updateResult({ ...event.result, isError: event.isError });
|
|
@@ -3127,9 +3161,20 @@ export class InteractiveMode {
|
|
|
3127
3161
|
this.streamingMessage = undefined;
|
|
3128
3162
|
}
|
|
3129
3163
|
this.clearPendingTools();
|
|
3164
|
+
if (!event.willRetry) {
|
|
3165
|
+
const kind = event.stopReason?.kind ??
|
|
3166
|
+
[...event.messages].reverse().find((message) => message.role === "assistant")?.stopReason;
|
|
3167
|
+
this.finishTurnSummary(kind === "aborted"
|
|
3168
|
+
? "aborted"
|
|
3169
|
+
: kind === "completed" || kind === "stop" || kind === undefined
|
|
3170
|
+
? "completed"
|
|
3171
|
+
: undefined);
|
|
3172
|
+
}
|
|
3130
3173
|
this.ui.requestRender();
|
|
3131
3174
|
break;
|
|
3132
3175
|
case "agent_settled":
|
|
3176
|
+
this.turnStartedAt = undefined;
|
|
3177
|
+
this.ui.requestRender();
|
|
3133
3178
|
await this.checkShutdownRequested();
|
|
3134
3179
|
break;
|
|
3135
3180
|
case "compaction_start": {
|
|
@@ -3229,6 +3274,8 @@ export class InteractiveMode {
|
|
|
3229
3274
|
}
|
|
3230
3275
|
this.retriedAttempt = undefined;
|
|
3231
3276
|
this.retryCancelled = false;
|
|
3277
|
+
if (event.aborted)
|
|
3278
|
+
this.finishTurnSummary("aborted");
|
|
3232
3279
|
this.ui.requestRender();
|
|
3233
3280
|
break;
|
|
3234
3281
|
}
|
|
@@ -3256,6 +3303,16 @@ export class InteractiveMode {
|
|
|
3256
3303
|
}
|
|
3257
3304
|
}
|
|
3258
3305
|
}
|
|
3306
|
+
finishTurnSummary(outcome) {
|
|
3307
|
+
const startedAt = this.turnStartedAt;
|
|
3308
|
+
this.turnStartedAt = undefined;
|
|
3309
|
+
if (startedAt === undefined || outcome === undefined)
|
|
3310
|
+
return;
|
|
3311
|
+
const elapsedMs = Date.now() - startedAt;
|
|
3312
|
+
if (elapsedMs < 1000)
|
|
3313
|
+
return;
|
|
3314
|
+
this.chatContainer.addChild(new TurnSummaryComponent(elapsedMs, outcome));
|
|
3315
|
+
}
|
|
3259
3316
|
/** Extract text content from a user message */
|
|
3260
3317
|
getUserMessageText(message) {
|
|
3261
3318
|
if (message.role !== "user")
|
|
@@ -3323,7 +3380,8 @@ export class InteractiveMode {
|
|
|
3323
3380
|
addMessageToChat(message, options) {
|
|
3324
3381
|
switch (message.role) {
|
|
3325
3382
|
case "bashExecution": {
|
|
3326
|
-
const component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext);
|
|
3383
|
+
const component = new BashExecutionComponent(message.command, this.ui, message.excludeFromContext, this.settingsManager.getSymbolPreset());
|
|
3384
|
+
this.adoptChatDetail(component);
|
|
3327
3385
|
if (message.output) {
|
|
3328
3386
|
component.appendOutput(message.output);
|
|
3329
3387
|
}
|
|
@@ -3905,11 +3963,26 @@ export class InteractiveMode {
|
|
|
3905
3963
|
* remembering to set every axis.
|
|
3906
3964
|
*/
|
|
3907
3965
|
adoptChatDetail(component) {
|
|
3966
|
+
if (component instanceof ToolExecutionComponent || component instanceof BashExecutionComponent) {
|
|
3967
|
+
component.setChatDetail(this.chatDetail);
|
|
3968
|
+
return;
|
|
3969
|
+
}
|
|
3908
3970
|
if (isExpandable(component))
|
|
3909
3971
|
component.setExpanded(this.toolOutputExpanded);
|
|
3910
3972
|
if (hasEditDiffsExpansion(component))
|
|
3911
3973
|
component.setEditDiffsExpanded(this.editDiffsExpanded);
|
|
3912
3974
|
}
|
|
3975
|
+
toggleLatestOperation() {
|
|
3976
|
+
const pending = this.pendingMessagesContainer?.children ?? [];
|
|
3977
|
+
const operations = [...this.chatContainer.children, ...pending];
|
|
3978
|
+
const latest = operations
|
|
3979
|
+
.reverse()
|
|
3980
|
+
.find((child) => child instanceof ToolExecutionComponent || child instanceof BashExecutionComponent);
|
|
3981
|
+
if (latest instanceof ToolExecutionComponent || latest instanceof BashExecutionComponent) {
|
|
3982
|
+
latest.toggleExpanded();
|
|
3983
|
+
this.ui.requestRender();
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3913
3986
|
/** The expand key. A level the user picks is saved, so the next session opens at it. */
|
|
3914
3987
|
cycleChatDetail() {
|
|
3915
3988
|
this.chooseChatDetail(nextChatDetail(this.chatDetail));
|
|
@@ -3927,6 +4000,7 @@ export class InteractiveMode {
|
|
|
3927
4000
|
this.chatDetail = detail;
|
|
3928
4001
|
this.toolOutputExpanded = view.toolOutputExpanded;
|
|
3929
4002
|
this.editDiffsExpanded = view.editDiffsExpanded;
|
|
4003
|
+
this.footer?.setChatDetail(detail);
|
|
3930
4004
|
}
|
|
3931
4005
|
/**
|
|
3932
4006
|
* Presentation only. This never rewrites messages or the session transcript,
|
|
@@ -3938,7 +4012,6 @@ export class InteractiveMode {
|
|
|
3938
4012
|
return;
|
|
3939
4013
|
this.assignChatDetail(detail);
|
|
3940
4014
|
this.applyChatDetail();
|
|
3941
|
-
this.showStatus(`Conversation detail: ${detail}`);
|
|
3942
4015
|
}
|
|
3943
4016
|
/** True when thinking blocks are hidden right now, setting and detail level combined. */
|
|
3944
4017
|
isThinkingHidden() {
|
|
@@ -3950,17 +4023,14 @@ export class InteractiveMode {
|
|
|
3950
4023
|
if (isExpandable(activeHeader)) {
|
|
3951
4024
|
activeHeader.setExpanded(this.toolOutputExpanded);
|
|
3952
4025
|
}
|
|
3953
|
-
for (const container of [this.loadedResourcesContainer, this.chatContainer]) {
|
|
4026
|
+
for (const container of [this.loadedResourcesContainer, this.chatContainer, this.pendingMessagesContainer]) {
|
|
4027
|
+
if (!container)
|
|
4028
|
+
continue;
|
|
3954
4029
|
for (const child of container.children) {
|
|
3955
4030
|
if (child instanceof AssistantMessageComponent) {
|
|
3956
4031
|
child.setHideThinkingBlock(this.isThinkingHidden());
|
|
3957
4032
|
}
|
|
3958
|
-
|
|
3959
|
-
child.setExpanded(this.toolOutputExpanded);
|
|
3960
|
-
}
|
|
3961
|
-
if (hasEditDiffsExpansion(child)) {
|
|
3962
|
-
child.setEditDiffsExpanded(this.editDiffsExpanded);
|
|
3963
|
-
}
|
|
4033
|
+
this.adoptChatDetail(child);
|
|
3964
4034
|
}
|
|
3965
4035
|
}
|
|
3966
4036
|
this.ui.requestRender();
|
|
@@ -4278,25 +4348,61 @@ export class InteractiveMode {
|
|
|
4278
4348
|
* would under-report bypassPermissions, which is the one direction that matters.
|
|
4279
4349
|
*/
|
|
4280
4350
|
async refreshFooterPermissionMode() {
|
|
4281
|
-
const
|
|
4282
|
-
|
|
4351
|
+
const session = this.session;
|
|
4352
|
+
const resolution = await session.getPermissionMode();
|
|
4353
|
+
if (this.session !== session)
|
|
4354
|
+
return;
|
|
4355
|
+
this.footer.setPermissionMode(resolution?.mode ?? "default", resolution?.origin);
|
|
4283
4356
|
if (this.isInitialized) {
|
|
4284
4357
|
this.ui.requestRender();
|
|
4285
4358
|
}
|
|
4286
4359
|
}
|
|
4360
|
+
cyclePermissionMode() {
|
|
4361
|
+
const session = this.session;
|
|
4362
|
+
const generation = this.permissionModeCycleGeneration ?? 0;
|
|
4363
|
+
const isCurrent = () => this.session === session && (this.permissionModeCycleGeneration ?? 0) === generation;
|
|
4364
|
+
const cycle = (this.pendingPermissionModeCycle ?? Promise.resolve())
|
|
4365
|
+
.then(async () => {
|
|
4366
|
+
if (!isCurrent())
|
|
4367
|
+
return;
|
|
4368
|
+
const modes = await session.getInteractivePermissionModeCycle();
|
|
4369
|
+
if (!isCurrent() || modes.length === 0)
|
|
4370
|
+
return;
|
|
4371
|
+
const current = await session.getPermissionMode();
|
|
4372
|
+
if (!isCurrent())
|
|
4373
|
+
return;
|
|
4374
|
+
const index = modes.indexOf(current?.mode ?? "default");
|
|
4375
|
+
const resolution = await session.setInteractivePermissionMode(modes[(index + 1) % modes.length]);
|
|
4376
|
+
if (!isCurrent() || !resolution)
|
|
4377
|
+
return;
|
|
4378
|
+
this.footer.setPermissionMode(resolution.mode, resolution.origin);
|
|
4379
|
+
this.offerFirstUseHint("permission-mode-cycle", `${keyDisplayText("app.permissionMode.cycle")} now cycles permission modes for this session; ${keyDisplayText("app.thinking.cycle")} cycles thinking level.`);
|
|
4380
|
+
this.ui.requestRender();
|
|
4381
|
+
})
|
|
4382
|
+
.catch((error) => {
|
|
4383
|
+
if (!isCurrent())
|
|
4384
|
+
return;
|
|
4385
|
+
this.showError(`Permission mode could not be changed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4386
|
+
});
|
|
4387
|
+
this.pendingPermissionModeCycle = cycle;
|
|
4388
|
+
return cycle;
|
|
4389
|
+
}
|
|
4287
4390
|
/**
|
|
4288
4391
|
* Writes the mode to user scope, then reports what is actually in force. The
|
|
4289
4392
|
* write can be outranked by `--permission-mode` or a project file, and a
|
|
4290
4393
|
* settings row that silently does nothing is worse than no row at all.
|
|
4291
4394
|
*/
|
|
4292
4395
|
async applyPermissionMode(mode) {
|
|
4396
|
+
const session = this.session;
|
|
4293
4397
|
let resolution;
|
|
4294
4398
|
try {
|
|
4295
|
-
const write =
|
|
4399
|
+
const write = session.setPermissionMode(mode);
|
|
4296
4400
|
this.pendingPermissionModeWrite = write.then(() => { }, () => { });
|
|
4297
4401
|
resolution = await write;
|
|
4298
4402
|
}
|
|
4299
4403
|
catch (error) {
|
|
4404
|
+
if (this.session !== session)
|
|
4405
|
+
return;
|
|
4300
4406
|
// Never swallow this. A permission mode the user believes they set, and
|
|
4301
4407
|
// did not, is the one outcome worse than the prompt they were escaping.
|
|
4302
4408
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -4304,17 +4410,27 @@ export class InteractiveMode {
|
|
|
4304
4410
|
this.ui.requestRender();
|
|
4305
4411
|
return;
|
|
4306
4412
|
}
|
|
4307
|
-
if (!resolution)
|
|
4413
|
+
if (this.session !== session || !resolution)
|
|
4308
4414
|
return;
|
|
4309
|
-
const outranked = resolution.mode !== mode;
|
|
4415
|
+
const outranked = resolution.mode !== mode || resolution.origin === "interactive";
|
|
4310
4416
|
const source = PERMISSION_MODE_OVERRIDE_HINTS[resolution.origin] ?? resolution.origin;
|
|
4311
4417
|
const line = outranked
|
|
4312
4418
|
? `Permission mode saved as ${mode}, but ${source} sets ${resolution.mode} for this session.`
|
|
4313
4419
|
: `Permission mode set to ${mode}.`;
|
|
4314
4420
|
this.chatContainer.addChild(new Text(theme.fg(outranked ? "warning" : "muted", line), 1, 0));
|
|
4315
|
-
this.footer.setPermissionMode(resolution.mode);
|
|
4421
|
+
this.footer.setPermissionMode(resolution.mode, resolution.origin);
|
|
4316
4422
|
this.ui.requestRender();
|
|
4317
4423
|
}
|
|
4424
|
+
setTaskListToolDefault(enabled) {
|
|
4425
|
+
if (this.settingsManager.getProjectSettings().defaultTools !== undefined)
|
|
4426
|
+
return;
|
|
4427
|
+
const names = this.settingsManager.getDefaultTools() ?? this.session.getImplicitDefaultToolNames();
|
|
4428
|
+
this.settingsManager.setDefaultTools(enabled
|
|
4429
|
+
? names.includes("todo_write")
|
|
4430
|
+
? names
|
|
4431
|
+
: [...names, "todo_write"]
|
|
4432
|
+
: names.filter((name) => name !== "todo_write"));
|
|
4433
|
+
}
|
|
4318
4434
|
async showSettingsSelector() {
|
|
4319
4435
|
const permissionMode = await this.session.getPermissionMode();
|
|
4320
4436
|
this.showSelector((done) => {
|
|
@@ -4324,6 +4440,10 @@ export class InteractiveMode {
|
|
|
4324
4440
|
const defaultModel = defaultProvider && defaultModelId ? `${defaultProvider}/${defaultModelId}` : "not set";
|
|
4325
4441
|
selector = new SettingsSelectorComponent({
|
|
4326
4442
|
autoCompact: this.session.autoCompactionEnabled,
|
|
4443
|
+
taskListTool: {
|
|
4444
|
+
enabled: (this.settingsManager.getDefaultTools() ?? this.session.getImplicitDefaultToolNames()).includes("todo_write"),
|
|
4445
|
+
projectControlled: this.settingsManager.getProjectSettings().defaultTools !== undefined,
|
|
4446
|
+
},
|
|
4327
4447
|
defaultModel,
|
|
4328
4448
|
currentModel: this.session.model,
|
|
4329
4449
|
availableDefaultModels: this.session.modelRuntime.getAvailableSnapshot(),
|
|
@@ -4366,6 +4486,7 @@ export class InteractiveMode {
|
|
|
4366
4486
|
fullscreenCopyOnSelect: this.settingsManager.getFullscreenCopyOnSelect(),
|
|
4367
4487
|
warnings: this.settingsManager.getWarnings(),
|
|
4368
4488
|
}, {
|
|
4489
|
+
onTaskListToolChange: (enabled) => this.setTaskListToolDefault(enabled),
|
|
4369
4490
|
onAutoCompactChange: (enabled) => {
|
|
4370
4491
|
this.session.setAutoCompactionEnabled(enabled);
|
|
4371
4492
|
this.footer.setAutoCompactEnabled(enabled);
|
|
@@ -4982,6 +5103,29 @@ export class InteractiveMode {
|
|
|
4982
5103
|
this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
|
|
4983
5104
|
return;
|
|
4984
5105
|
}
|
|
5106
|
+
const workspacePreview = await this.session.previewTreeWorkspace(entryId);
|
|
5107
|
+
if (this.session.isCompacting) {
|
|
5108
|
+
this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
|
|
5109
|
+
return;
|
|
5110
|
+
}
|
|
5111
|
+
let workspacePolicy = "keep";
|
|
5112
|
+
if (workspacePreview.state === "differs") {
|
|
5113
|
+
const choice = await this.showExtensionSelector("Restore workspace files?", [
|
|
5114
|
+
"Keep current files",
|
|
5115
|
+
"Restore files to this point",
|
|
5116
|
+
"Cancel",
|
|
5117
|
+
]);
|
|
5118
|
+
if (choice === undefined || choice === "Cancel") {
|
|
5119
|
+
this.showStatus("Navigation cancelled");
|
|
5120
|
+
return;
|
|
5121
|
+
}
|
|
5122
|
+
if (choice === "Restore files to this point")
|
|
5123
|
+
workspacePolicy = "restore";
|
|
5124
|
+
}
|
|
5125
|
+
if (this.session.isCompacting) {
|
|
5126
|
+
this.showError("Wait for the current compaction or tree navigation to finish before navigating the session tree.");
|
|
5127
|
+
return;
|
|
5128
|
+
}
|
|
4985
5129
|
// Set up escape handler and status indicator if summarizing
|
|
4986
5130
|
let showingSummaryIndicator = false;
|
|
4987
5131
|
const originalOnEscape = this.defaultEditor.onEscape;
|
|
@@ -4998,6 +5142,7 @@ export class InteractiveMode {
|
|
|
4998
5142
|
const result = await this.session.navigateTree(entryId, {
|
|
4999
5143
|
summarize: wantsSummary,
|
|
5000
5144
|
customInstructions,
|
|
5145
|
+
workspacePolicy,
|
|
5001
5146
|
});
|
|
5002
5147
|
if (result.aborted) {
|
|
5003
5148
|
// Summarization aborted - re-show tree selector with same selection
|
|
@@ -5015,7 +5160,22 @@ export class InteractiveMode {
|
|
|
5015
5160
|
if (result.editorText && !this.editor.getText().trim()) {
|
|
5016
5161
|
this.editor.setText(result.editorText);
|
|
5017
5162
|
}
|
|
5018
|
-
|
|
5163
|
+
const workspace = result.workspace;
|
|
5164
|
+
if (workspace?.outcome === "restored") {
|
|
5165
|
+
this.showStatus(workspace.preRestoreCheckpoint
|
|
5166
|
+
? "Restored files · pre-restore checkpoint saved"
|
|
5167
|
+
: "Restored files");
|
|
5168
|
+
}
|
|
5169
|
+
else if (workspace?.outcome === "failed") {
|
|
5170
|
+
this.showStatus(workspace.warnings.join(" · ") || "Workspace restore failed");
|
|
5171
|
+
}
|
|
5172
|
+
else if (workspace?.outcome === "missing-checkpoint" ||
|
|
5173
|
+
workspacePreview.state === "no-checkpoint") {
|
|
5174
|
+
this.showStatus("Files unchanged · no checkpoint for this point");
|
|
5175
|
+
}
|
|
5176
|
+
else {
|
|
5177
|
+
this.showStatus("Files unchanged");
|
|
5178
|
+
}
|
|
5019
5179
|
void this.flushCompactionQueue({ willRetry: false });
|
|
5020
5180
|
}
|
|
5021
5181
|
catch (error) {
|
|
@@ -5600,6 +5760,8 @@ export class InteractiveMode {
|
|
|
5600
5760
|
this.showWarning("Wait for compaction to finish before reloading.");
|
|
5601
5761
|
return;
|
|
5602
5762
|
}
|
|
5763
|
+
this.permissionModeCycleGeneration = (this.permissionModeCycleGeneration ?? 0) + 1;
|
|
5764
|
+
this.pendingPermissionModeCycle = undefined;
|
|
5603
5765
|
this.resetExtensionUI();
|
|
5604
5766
|
const reloadBox = new Container();
|
|
5605
5767
|
reloadBox.addChild(new DynamicBorder());
|
|
@@ -5665,6 +5827,7 @@ export class InteractiveMode {
|
|
|
5665
5827
|
dismissReloadBox(previousEditor);
|
|
5666
5828
|
}
|
|
5667
5829
|
this.showError(`Reload failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5830
|
+
await this.refreshFooterPermissionMode();
|
|
5668
5831
|
}
|
|
5669
5832
|
}
|
|
5670
5833
|
async handleExportCommand(text) {
|
|
@@ -6030,9 +6193,11 @@ export class InteractiveMode {
|
|
|
6030
6193
|
const exit = this.getAppKeyDisplay("app.exit");
|
|
6031
6194
|
const suspend = this.getAppKeyDisplay("app.suspend");
|
|
6032
6195
|
const cycleThinkingLevel = this.getAppKeyDisplay("app.thinking.cycle");
|
|
6196
|
+
const cyclePermissionMode = this.getAppKeyDisplay("app.permissionMode.cycle");
|
|
6033
6197
|
const cycleModelForward = this.getAppKeyDisplay("app.model.cycleForward");
|
|
6034
6198
|
const selectModel = this.getAppKeyDisplay("app.model.select");
|
|
6035
6199
|
const expandTools = this.getAppKeyDisplay("app.tools.expand");
|
|
6200
|
+
const expandLatest = this.getAppKeyDisplay("app.tools.expandLatest");
|
|
6036
6201
|
const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
|
|
6037
6202
|
const externalEditor = this.getAppKeyDisplay("app.editor.external");
|
|
6038
6203
|
const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
|
|
@@ -6074,9 +6239,11 @@ export class InteractiveMode {
|
|
|
6074
6239
|
| \`${exit}\` | Exit (when editor is empty) |
|
|
6075
6240
|
| \`${suspend}\` | Suspend to background |
|
|
6076
6241
|
| \`${cycleThinkingLevel}\` | Cycle thinking level |
|
|
6242
|
+
| \`${cyclePermissionMode}\` | Cycle permission mode for this session |
|
|
6077
6243
|
| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
|
|
6078
6244
|
| \`${selectModel}\` | Open model selector |
|
|
6079
6245
|
| \`${expandTools}\` | Cycle conversation detail (overview / details / all) |
|
|
6246
|
+
| \`${expandLatest}\` | Toggle latest operation details |
|
|
6080
6247
|
| \`${toggleThinking}\` | Toggle thinking block visibility |
|
|
6081
6248
|
| \`${externalEditor}\` | Edit message in external editor |
|
|
6082
6249
|
| \`${copyMessage}\` | Copy selection or last assistant message |
|
|
@@ -6227,7 +6394,8 @@ export class InteractiveMode {
|
|
|
6227
6394
|
if (eventResult?.result) {
|
|
6228
6395
|
const result = eventResult.result;
|
|
6229
6396
|
// Create UI component for display
|
|
6230
|
-
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
|
|
6397
|
+
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext, this.settingsManager.getSymbolPreset());
|
|
6398
|
+
this.adoptChatDetail(this.bashComponent);
|
|
6231
6399
|
if (this.session.isStreaming) {
|
|
6232
6400
|
this.pendingMessagesContainer.addChild(this.bashComponent);
|
|
6233
6401
|
this.pendingBashComponents.push(this.bashComponent);
|
|
@@ -6248,7 +6416,8 @@ export class InteractiveMode {
|
|
|
6248
6416
|
}
|
|
6249
6417
|
// Normal execution path (possibly with custom operations)
|
|
6250
6418
|
const isDeferred = this.session.isStreaming;
|
|
6251
|
-
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext);
|
|
6419
|
+
this.bashComponent = new BashExecutionComponent(command, this.ui, excludeFromContext, this.settingsManager.getSymbolPreset());
|
|
6420
|
+
this.adoptChatDetail(this.bashComponent);
|
|
6252
6421
|
if (isDeferred) {
|
|
6253
6422
|
// Show in pending area when agent is streaming
|
|
6254
6423
|
this.pendingMessagesContainer.addChild(this.bashComponent);
|