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.
Files changed (86) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +41 -2
  3. package/dist/core/agent-session.d.ts +37 -0
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +264 -60
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/default-tool-names.d.ts +7 -0
  8. package/dist/core/default-tool-names.d.ts.map +1 -0
  9. package/dist/core/default-tool-names.js +14 -0
  10. package/dist/core/default-tool-names.js.map +1 -0
  11. package/dist/core/extensions/runner.d.ts.map +1 -1
  12. package/dist/core/extensions/runner.js +2 -0
  13. package/dist/core/extensions/runner.js.map +1 -1
  14. package/dist/core/extensions/types.d.ts +3 -0
  15. package/dist/core/extensions/types.d.ts.map +1 -1
  16. package/dist/core/extensions/types.js.map +1 -1
  17. package/dist/core/keybindings.d.ts +11 -1
  18. package/dist/core/keybindings.d.ts.map +1 -1
  19. package/dist/core/keybindings.js +6 -1
  20. package/dist/core/keybindings.js.map +1 -1
  21. package/dist/core/permissions/startup.d.ts +1 -1
  22. package/dist/core/permissions/startup.d.ts.map +1 -1
  23. package/dist/core/permissions/startup.js.map +1 -1
  24. package/dist/core/sdk.d.ts.map +1 -1
  25. package/dist/core/sdk.js +13 -9
  26. package/dist/core/sdk.js.map +1 -1
  27. package/dist/core/settings-manager.d.ts +4 -0
  28. package/dist/core/settings-manager.d.ts.map +1 -1
  29. package/dist/core/settings-manager.js +13 -0
  30. package/dist/core/settings-manager.js.map +1 -1
  31. package/dist/core/slash-commands.d.ts.map +1 -1
  32. package/dist/core/slash-commands.js +1 -0
  33. package/dist/core/slash-commands.js.map +1 -1
  34. package/dist/core/tools/contract.d.ts +1 -0
  35. package/dist/core/tools/contract.d.ts.map +1 -1
  36. package/dist/core/tools/contract.js.map +1 -1
  37. package/dist/core/tools/index.d.ts +1 -1
  38. package/dist/core/tools/index.d.ts.map +1 -1
  39. package/dist/core/tools/index.js.map +1 -1
  40. package/dist/core/tools/plan-present.d.ts +13 -12
  41. package/dist/core/tools/plan-present.d.ts.map +1 -1
  42. package/dist/core/tools/plan-present.js +32 -19
  43. package/dist/core/tools/plan-present.js.map +1 -1
  44. package/dist/core/tools/renderers/index.d.ts +1 -1
  45. package/dist/core/tools/renderers/index.d.ts.map +1 -1
  46. package/dist/core/tools/renderers/index.js +2 -0
  47. package/dist/core/tools/renderers/index.js.map +1 -1
  48. package/dist/core/tools/renderers/todo-write.d.ts +7 -0
  49. package/dist/core/tools/renderers/todo-write.d.ts.map +1 -0
  50. package/dist/core/tools/renderers/todo-write.js +58 -0
  51. package/dist/core/tools/renderers/todo-write.js.map +1 -0
  52. package/dist/index.d.ts +2 -2
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/modes/interactive/components/first-use-hints.d.ts +1 -1
  57. package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -1
  58. package/dist/modes/interactive/components/first-use-hints.js +1 -0
  59. package/dist/modes/interactive/components/first-use-hints.js.map +1 -1
  60. package/dist/modes/interactive/components/footer.d.ts +3 -1
  61. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/footer.js +25 -11
  63. package/dist/modes/interactive/components/footer.js.map +1 -1
  64. package/dist/modes/interactive/components/settings-selector.d.ts +5 -0
  65. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  66. package/dist/modes/interactive/components/settings-selector.js +20 -1
  67. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  68. package/dist/modes/interactive/components/task-panel.d.ts +27 -0
  69. package/dist/modes/interactive/components/task-panel.d.ts.map +1 -0
  70. package/dist/modes/interactive/components/task-panel.js +92 -0
  71. package/dist/modes/interactive/components/task-panel.js.map +1 -0
  72. package/dist/modes/interactive/components/turn-summary.d.ts +6 -0
  73. package/dist/modes/interactive/components/turn-summary.d.ts.map +1 -0
  74. package/dist/modes/interactive/components/turn-summary.js +12 -0
  75. package/dist/modes/interactive/components/turn-summary.js.map +1 -0
  76. package/dist/modes/interactive/interactive-mode.d.ts +9 -0
  77. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  78. package/dist/modes/interactive/interactive-mode.js +162 -11
  79. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  80. package/dist/package-manager-cli.d.ts.map +1 -1
  81. package/dist/package-manager-cli.js +31 -72
  82. package/dist/package-manager-cli.js.map +1 -1
  83. package/docs/keybindings.md +19 -1
  84. package/docs/settings.md +8 -0
  85. package/npm-shrinkwrap.json +5 -5
  86. 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 resolution = await this.session.getPermissionMode();
4282
- this.footer.setPermissionMode(resolution?.mode ?? "default");
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 = this.session.setPermissionMode(mode);
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
- this.showStatus("Navigated to selected point");
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) |