apex-code 0.2.0 → 0.3.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 (49) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +1 -1
  3. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  4. package/dist/core/export-html/tool-renderer.js +3 -0
  5. package/dist/core/export-html/tool-renderer.js.map +1 -1
  6. package/dist/core/extensions/types.d.ts +7 -0
  7. package/dist/core/extensions/types.d.ts.map +1 -1
  8. package/dist/core/extensions/types.js.map +1 -1
  9. package/dist/core/keybindings.d.ts +1 -1
  10. package/dist/core/keybindings.d.ts.map +1 -1
  11. package/dist/core/keybindings.js +1 -1
  12. package/dist/core/keybindings.js.map +1 -1
  13. package/dist/core/tools/bash.d.ts +6 -0
  14. package/dist/core/tools/bash.d.ts.map +1 -1
  15. package/dist/core/tools/bash.js +71 -24
  16. package/dist/core/tools/bash.js.map +1 -1
  17. package/dist/core/tools/edit.d.ts.map +1 -1
  18. package/dist/core/tools/edit.js +47 -7
  19. package/dist/core/tools/edit.js.map +1 -1
  20. package/dist/core/tools/shell-operation.d.ts +34 -0
  21. package/dist/core/tools/shell-operation.d.ts.map +1 -0
  22. package/dist/core/tools/shell-operation.js +69 -0
  23. package/dist/core/tools/shell-operation.js.map +1 -0
  24. package/dist/modes/interactive/components/keybinding-hints.d.ts +7 -1
  25. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  26. package/dist/modes/interactive/components/keybinding-hints.js +3 -2
  27. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  28. package/dist/modes/interactive/components/model-row.d.ts +84 -0
  29. package/dist/modes/interactive/components/model-row.d.ts.map +1 -0
  30. package/dist/modes/interactive/components/model-row.js +206 -0
  31. package/dist/modes/interactive/components/model-row.js.map +1 -0
  32. package/dist/modes/interactive/components/model-selector.d.ts +39 -4
  33. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  34. package/dist/modes/interactive/components/model-selector.js +158 -27
  35. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  36. package/dist/modes/interactive/components/tool-execution.d.ts +2 -0
  37. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  38. package/dist/modes/interactive/components/tool-execution.js +8 -0
  39. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  40. package/dist/modes/interactive/interactive-mode.d.ts +53 -1
  41. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  42. package/dist/modes/interactive/interactive-mode.js +99 -26
  43. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  44. package/dist/modes/interactive/theme/theme.d.ts +16 -0
  45. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  46. package/dist/modes/interactive/theme/theme.js +19 -1
  47. package/dist/modes/interactive/theme/theme.js.map +1 -1
  48. package/npm-shrinkwrap.json +5 -5
  49. package/package.json +2 -2
@@ -82,9 +82,28 @@ import { refreshModelCatalogs } from "./model-catalog-refresh.js";
82
82
  import { getModelSearchText } from "./model-search.js";
83
83
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, onThemeChange, setRegisteredThemes, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
84
84
  import { InteractiveThemeController } from "./theme/theme-controller.js";
85
+ const CHAT_DETAIL_ORDER = ["overview", "details", "all"];
86
+ /** The next rung, wrapping from `all` back to `overview`. */
87
+ export function nextChatDetail(detail) {
88
+ return CHAT_DETAIL_ORDER[(CHAT_DETAIL_ORDER.indexOf(detail) + 1) % CHAT_DETAIL_ORDER.length] ?? "details";
89
+ }
90
+ /** The single place the three rungs turn into rendering flags. */
91
+ export function chatDetailView(detail) {
92
+ return {
93
+ toolOutputExpanded: detail === "all",
94
+ editDiffsExpanded: detail !== "overview",
95
+ revealThinking: detail === "all",
96
+ };
97
+ }
85
98
  function isExpandable(obj) {
86
99
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
87
100
  }
101
+ function hasEditDiffsExpansion(obj) {
102
+ return (typeof obj === "object" &&
103
+ obj !== null &&
104
+ "setEditDiffsExpanded" in obj &&
105
+ typeof obj.setEditDiffsExpanded === "function");
106
+ }
88
107
  class ExpandableText extends Text {
89
108
  getCollapsedText;
90
109
  getExpandedText;
@@ -318,7 +337,9 @@ export class InteractiveMode {
318
337
  // Tool execution tracking: toolCallId -> component
319
338
  pendingTools = new Map();
320
339
  // Tool output expansion state
340
+ chatDetail = "details";
321
341
  toolOutputExpanded = false;
342
+ editDiffsExpanded = true;
322
343
  // Thinking block visibility state
323
344
  hideThinkingBlock = false;
324
345
  outputPad = 1;
@@ -2153,7 +2174,7 @@ export class InteractiveMode {
2153
2174
  }, {
2154
2175
  tui: this.ui,
2155
2176
  timeout: opts?.timeout,
2156
- onToggleToolsExpanded: () => this.toggleToolOutputExpansion(),
2177
+ onToggleToolsExpanded: () => this.cycleChatDetail(),
2157
2178
  enableSearch,
2158
2179
  preamble: opts?.preview ? renderPermissionPreview(opts.preview) : undefined,
2159
2180
  });
@@ -2520,7 +2541,7 @@ export class InteractiveMode {
2520
2541
  // Global debug handler on TUI (works regardless of focus)
2521
2542
  this.ui.onDebug = () => this.handleDebugCommand();
2522
2543
  this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
2523
- this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
2544
+ this.defaultEditor.onAction("app.tools.expand", () => this.cycleChatDetail());
2524
2545
  this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
2525
2546
  this.defaultEditor.onAction("app.editor.external", () => void this.handleOpenExternalEditor());
2526
2547
  this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand({ flashConfirmation: true, preferSelection: true }));
@@ -2874,7 +2895,7 @@ export class InteractiveMode {
2874
2895
  this.ui.requestRender();
2875
2896
  }
2876
2897
  else if (event.message.role === "assistant") {
2877
- this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
2898
+ this.streamingComponent = new AssistantMessageComponent(undefined, this.isThinkingHidden(), this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
2878
2899
  this.streamingMessage = event.message;
2879
2900
  this.chatContainer.addChild(this.streamingComponent);
2880
2901
  this.streamingComponent.updateContent(this.streamingMessage, true);
@@ -2893,7 +2914,7 @@ export class InteractiveMode {
2893
2914
  showImages: this.settingsManager.getShowImages(),
2894
2915
  imageWidthCells: this.settingsManager.getImageWidthCells(),
2895
2916
  }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
2896
- component.setExpanded(this.toolOutputExpanded);
2917
+ this.adoptChatDetail(component);
2897
2918
  this.chatContainer.addChild(component);
2898
2919
  this.pendingTools.set(content.id, component);
2899
2920
  this.offerFirstUseHint("tool-expand");
@@ -2960,7 +2981,7 @@ export class InteractiveMode {
2960
2981
  showImages: this.settingsManager.getShowImages(),
2961
2982
  imageWidthCells: this.settingsManager.getImageWidthCells(),
2962
2983
  }, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
2963
- component.setExpanded(this.toolOutputExpanded);
2984
+ this.adoptChatDetail(component);
2964
2985
  this.chatContainer.addChild(component);
2965
2986
  this.pendingTools.set(event.toolCallId, component);
2966
2987
  }
@@ -3161,7 +3182,7 @@ export class InteractiveMode {
3161
3182
  return;
3162
3183
  }
3163
3184
  const component = new CustomEntryComponent(entry, renderer);
3164
- component.setExpanded(this.toolOutputExpanded);
3185
+ this.adoptChatDetail(component);
3165
3186
  if (!component.hasContent()) {
3166
3187
  return;
3167
3188
  }
@@ -3189,7 +3210,7 @@ export class InteractiveMode {
3189
3210
  if (message.display) {
3190
3211
  const renderer = this.session.extensionRunner.getMessageRenderer(message.customType);
3191
3212
  const component = new CustomMessageComponent(message, renderer, this.getMarkdownThemeWithSettings(), this.outputPad);
3192
- component.setExpanded(this.toolOutputExpanded);
3213
+ this.adoptChatDetail(component);
3193
3214
  this.chatContainer.addChild(component);
3194
3215
  }
3195
3216
  break;
@@ -3197,14 +3218,14 @@ export class InteractiveMode {
3197
3218
  case "compactionSummary": {
3198
3219
  this.chatContainer.addChild(new Spacer(1));
3199
3220
  const component = new CompactionSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
3200
- component.setExpanded(this.toolOutputExpanded);
3221
+ this.adoptChatDetail(component);
3201
3222
  this.chatContainer.addChild(component);
3202
3223
  break;
3203
3224
  }
3204
3225
  case "branchSummary": {
3205
3226
  this.chatContainer.addChild(new Spacer(1));
3206
3227
  const component = new BranchSummaryMessageComponent(message, this.getMarkdownThemeWithSettings());
3207
- component.setExpanded(this.toolOutputExpanded);
3228
+ this.adoptChatDetail(component);
3208
3229
  this.chatContainer.addChild(component);
3209
3230
  break;
3210
3231
  }
@@ -3218,7 +3239,7 @@ export class InteractiveMode {
3218
3239
  if (skillBlock) {
3219
3240
  // Render skill block (collapsible)
3220
3241
  const component = new SkillInvocationMessageComponent(skillBlock, this.getMarkdownThemeWithSettings());
3221
- component.setExpanded(this.toolOutputExpanded);
3242
+ this.adoptChatDetail(component);
3222
3243
  this.chatContainer.addChild(component);
3223
3244
  // Render user message separately if present
3224
3245
  if (skillBlock.userMessage) {
@@ -3238,7 +3259,7 @@ export class InteractiveMode {
3238
3259
  break;
3239
3260
  }
3240
3261
  case "assistant": {
3241
- const assistantComponent = new AssistantMessageComponent(message, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
3262
+ const assistantComponent = new AssistantMessageComponent(message, this.isThinkingHidden(), this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel, this.outputPad, this.getMarkdownTransformers());
3242
3263
  this.chatContainer.addChild(assistantComponent);
3243
3264
  break;
3244
3265
  }
@@ -3283,7 +3304,7 @@ export class InteractiveMode {
3283
3304
  showImages: this.settingsManager.getShowImages(),
3284
3305
  imageWidthCells: this.settingsManager.getImageWidthCells(),
3285
3306
  }, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
3286
- component.setExpanded(this.toolOutputExpanded);
3307
+ this.adoptChatDetail(component);
3287
3308
  this.chatContainer.addChild(component);
3288
3309
  if (message.stopReason === "aborted" || message.stopReason === "error") {
3289
3310
  let errorMessage;
@@ -3694,31 +3715,76 @@ export class InteractiveMode {
3694
3715
  this.showError(error instanceof Error ? error.message : String(error));
3695
3716
  }
3696
3717
  }
3697
- toggleToolOutputExpansion() {
3698
- this.setToolsExpanded(!this.toolOutputExpanded);
3718
+ /**
3719
+ * Bring a freshly created transcript component up to the current detail level.
3720
+ *
3721
+ * Components are constructed collapsed and adopted here, so a new component
3722
+ * type joins the cycle by having the methods rather than by its call site
3723
+ * remembering to set every axis.
3724
+ */
3725
+ adoptChatDetail(component) {
3726
+ if (isExpandable(component))
3727
+ component.setExpanded(this.toolOutputExpanded);
3728
+ if (hasEditDiffsExpansion(component))
3729
+ component.setEditDiffsExpanded(this.editDiffsExpanded);
3730
+ }
3731
+ cycleChatDetail() {
3732
+ this.setChatDetail(nextChatDetail(this.chatDetail));
3699
3733
  }
3700
- setToolsExpanded(expanded) {
3701
- if (expanded === this.toolOutputExpanded)
3734
+ /**
3735
+ * Presentation only. This never rewrites messages, settings, or the session
3736
+ * transcript, which is why it may override the persisted thinking preference
3737
+ * at `all` without saving that override.
3738
+ */
3739
+ setChatDetail(detail) {
3740
+ if (detail === this.chatDetail)
3702
3741
  return;
3703
- this.toolOutputExpanded = expanded;
3742
+ this.chatDetail = detail;
3743
+ const view = chatDetailView(detail);
3744
+ this.toolOutputExpanded = view.toolOutputExpanded;
3745
+ this.editDiffsExpanded = view.editDiffsExpanded;
3746
+ this.applyChatDetail();
3747
+ this.showStatus(`Conversation detail: ${detail}`);
3748
+ }
3749
+ /** True when thinking blocks are hidden right now, setting and detail level combined. */
3750
+ isThinkingHidden() {
3751
+ return this.hideThinkingBlock && !chatDetailView(this.chatDetail).revealThinking;
3752
+ }
3753
+ applyChatDetail() {
3704
3754
  const activeHeader = this.customHeader ?? this.builtInHeader;
3705
3755
  if (isExpandable(activeHeader)) {
3706
- activeHeader.setExpanded(expanded);
3756
+ activeHeader.setExpanded(this.toolOutputExpanded);
3707
3757
  }
3708
3758
  for (const container of [this.loadedResourcesContainer, this.chatContainer]) {
3709
3759
  for (const child of container.children) {
3760
+ if (child instanceof AssistantMessageComponent) {
3761
+ child.setHideThinkingBlock(this.isThinkingHidden());
3762
+ }
3710
3763
  if (isExpandable(child)) {
3711
- child.setExpanded(expanded);
3764
+ child.setExpanded(this.toolOutputExpanded);
3765
+ }
3766
+ if (hasEditDiffsExpansion(child)) {
3767
+ child.setEditDiffsExpanded(this.editDiffsExpanded);
3712
3768
  }
3713
3769
  }
3714
3770
  }
3715
- this.showStatus(`Tool output: ${expanded ? "expanded" : "collapsed"}`);
3771
+ this.ui.requestRender();
3772
+ }
3773
+ /**
3774
+ * The extension-facing boolean, which names one axis of three.
3775
+ *
3776
+ * Collapsing maps to `details` rather than `overview`, because an extension
3777
+ * asking to collapse tool output has not asked to hide edit diffs, and
3778
+ * `details` is the level where only tool output is collapsed.
3779
+ */
3780
+ setToolsExpanded(expanded) {
3781
+ this.setChatDetail(expanded ? "all" : "details");
3716
3782
  }
3717
3783
  /** Update rendered assistant messages without rebuilding live tool components. */
3718
3784
  updateThinkingBlockVisibility() {
3719
3785
  for (const child of this.chatContainer.children) {
3720
3786
  if (child instanceof AssistantMessageComponent) {
3721
- child.setHideThinkingBlock(this.hideThinkingBlock);
3787
+ child.setHideThinkingBlock(this.isThinkingHidden());
3722
3788
  }
3723
3789
  }
3724
3790
  this.ui.requestRender();
@@ -4456,14 +4522,21 @@ export class InteractiveMode {
4456
4522
  }
4457
4523
  showModelSelector(initialSearchInput) {
4458
4524
  this.showSelector((done) => {
4459
- const selectModel = async (model, persist) => {
4525
+ const selectModel = async (model, persist, thinkingLevel) => {
4460
4526
  try {
4461
4527
  await this.session.setModel(model, { persist });
4528
+ // setModel already applied the level this model switches to, so this
4529
+ // only overrides it when the picker's effort was actually dialled. The
4530
+ // cast is the session's own: it stores and clamps "off" but types the
4531
+ // field as ThinkingLevel, which does not name it.
4532
+ if (thinkingLevel !== undefined)
4533
+ this.session.setThinkingLevel(thinkingLevel);
4462
4534
  this.updateAvailableProviderCount();
4463
4535
  this.footer.invalidate();
4464
4536
  this.updateEditorBorderColor();
4465
4537
  done();
4466
- this.showStatus(persist ? `Default model: ${model.provider}/${model.id}` : `Model: ${model.id}`);
4538
+ const effort = thinkingLevel === undefined ? "" : ` · ${thinkingLevel}`;
4539
+ this.showStatus(persist ? `Default model: ${model.provider}/${model.id}${effort}` : `Model: ${model.id}${effort}`);
4467
4540
  void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
4468
4541
  this.checkDaxnutsEasterEgg(model);
4469
4542
  }
@@ -4474,10 +4547,10 @@ export class InteractiveMode {
4474
4547
  };
4475
4548
  const defaultProvider = this.settingsManager.getDefaultProvider();
4476
4549
  const defaultModel = this.settingsManager.getDefaultModel();
4477
- const selector = new ModelSelectorComponent(this.ui, this.session.model, this.session.modelRuntime, this.session.scopedModels, (model) => selectModel(model, false), () => {
4550
+ const selector = new ModelSelectorComponent(this.ui, this.session.model, this.session.modelRuntime, this.session.scopedModels, (model, thinkingLevel) => selectModel(model, false, thinkingLevel), () => {
4478
4551
  done();
4479
4552
  this.ui.requestRender();
4480
- }, initialSearchInput, (model) => selectModel(model, true), defaultProvider && defaultModel ? { provider: defaultProvider, id: defaultModel } : undefined);
4553
+ }, initialSearchInput, (model, thinkingLevel) => selectModel(model, true, thinkingLevel), defaultProvider && defaultModel ? { provider: defaultProvider, id: defaultModel } : undefined, { thinkingLevel: this.session.thinkingLevel });
4481
4554
  return { component: selector, focus: selector, dispose: () => selector.dispose() };
4482
4555
  });
4483
4556
  }
@@ -5745,7 +5818,7 @@ export class InteractiveMode {
5745
5818
  | \`${cycleThinkingLevel}\` | Cycle thinking level |
5746
5819
  | \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
5747
5820
  | \`${selectModel}\` | Open model selector |
5748
- | \`${expandTools}\` | Toggle tool output expansion |
5821
+ | \`${expandTools}\` | Cycle conversation detail (overview / details / all) |
5749
5822
  | \`${toggleThinking}\` | Toggle thinking block visibility |
5750
5823
  | \`${externalEditor}\` | Edit message in external editor |
5751
5824
  | \`${copyMessage}\` | Copy last assistant message |