@ashx-j/lunr 0.2.8 → 0.2.10

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 (182) hide show
  1. package/dist/builtin-extensions/ashxj-spinners.d.ts +4 -0
  2. package/dist/builtin-extensions/ashxj-spinners.d.ts.map +1 -1
  3. package/dist/builtin-extensions/ashxj-spinners.js +14 -3
  4. package/dist/builtin-extensions/ashxj-spinners.js.map +1 -1
  5. package/dist/builtin-extensions/ashxj-tui.d.ts +4 -0
  6. package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
  7. package/dist/builtin-extensions/ashxj-tui.js +49 -4
  8. package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
  9. package/dist/builtin-extensions/lunr-behavior.d.ts +2 -1
  10. package/dist/builtin-extensions/lunr-behavior.d.ts.map +1 -1
  11. package/dist/builtin-extensions/lunr-behavior.js +26 -14
  12. package/dist/builtin-extensions/lunr-behavior.js.map +1 -1
  13. package/dist/builtin-extensions/lunr-todos.d.ts +4 -7
  14. package/dist/builtin-extensions/lunr-todos.d.ts.map +1 -1
  15. package/dist/builtin-extensions/lunr-todos.js +18 -10
  16. package/dist/builtin-extensions/lunr-todos.js.map +1 -1
  17. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.d.ts.map +1 -1
  18. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js +3 -3
  19. package/dist/builtin-extensions/pi-subagents/src/agents/agent-management.js.map +1 -1
  20. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.d.ts.map +1 -1
  21. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js +4 -4
  22. package/dist/builtin-extensions/pi-subagents/src/extension/schemas.js.map +1 -1
  23. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts +2 -2
  24. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.d.ts.map +1 -1
  25. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js +4 -5
  26. package/dist/builtin-extensions/pi-subagents/src/extension/tool-description.js.map +1 -1
  27. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
  28. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +5 -1
  29. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
  30. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  31. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +6 -18
  32. package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
  33. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
  34. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +13 -5
  35. package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
  36. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +2 -2
  37. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
  38. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js +2 -2
  39. package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
  40. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.d.ts +21 -0
  41. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.d.ts.map +1 -0
  42. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.js +22 -0
  43. package/dist/builtin-extensions/pi-subagents/src/shared/lunr-child-context.js.map +1 -0
  44. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +2 -2
  45. package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
  46. package/dist/builtin-extensions/pi-subagents/src/shared/types.js +2 -2
  47. package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
  48. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
  49. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +2 -1
  50. package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
  51. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
  52. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +10 -18
  53. package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
  54. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +4 -6
  55. package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
  56. package/dist/builtin-extensions/pi-subagents/src/tui/render.js +26 -18
  57. package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
  58. package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
  59. package/dist/builtin-extensions/pi-web-access/index.js +3 -1
  60. package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
  61. package/dist/builtin-extensions/pi-web-access/render-search-error.d.ts.map +1 -1
  62. package/dist/builtin-extensions/pi-web-access/render-search-error.js +1 -1
  63. package/dist/builtin-extensions/pi-web-access/render-search-error.js.map +1 -1
  64. package/dist/catalog/models.json +1 -1
  65. package/dist/catalog/providers/nvidia.json +1 -1
  66. package/dist/catalog/providers/opencode-go.json +1 -1
  67. package/dist/catalog/providers/opencode.json +1 -1
  68. package/dist/catalog/providers/openrouter.json +1 -1
  69. package/dist/catalog/providers/qwen-cloud-cn.json +1 -1
  70. package/dist/catalog/providers/vercel-ai-gateway.json +1 -1
  71. package/dist/catalog/providers/xai.json +1 -1
  72. package/dist/catalog/publication.json +2 -2
  73. package/dist/cli/update-cli.d.ts +16 -0
  74. package/dist/cli/update-cli.d.ts.map +1 -0
  75. package/dist/cli/update-cli.js +83 -0
  76. package/dist/cli/update-cli.js.map +1 -0
  77. package/dist/cli.d.ts.map +1 -1
  78. package/dist/cli.js +6 -0
  79. package/dist/cli.js.map +1 -1
  80. package/dist/core/agent-session.d.ts +2 -0
  81. package/dist/core/agent-session.d.ts.map +1 -1
  82. package/dist/core/agent-session.js +9 -1
  83. package/dist/core/agent-session.js.map +1 -1
  84. package/dist/core/behavior-preset.d.ts +60 -0
  85. package/dist/core/behavior-preset.d.ts.map +1 -0
  86. package/dist/core/behavior-preset.js +257 -0
  87. package/dist/core/behavior-preset.js.map +1 -0
  88. package/dist/core/customize.d.ts +4 -0
  89. package/dist/core/customize.d.ts.map +1 -1
  90. package/dist/core/customize.js +12 -0
  91. package/dist/core/customize.js.map +1 -1
  92. package/dist/core/footer-data-provider.d.ts +12 -1
  93. package/dist/core/footer-data-provider.d.ts.map +1 -1
  94. package/dist/core/footer-data-provider.js +70 -5
  95. package/dist/core/footer-data-provider.js.map +1 -1
  96. package/dist/core/keybindings.d.ts +2 -2
  97. package/dist/core/keybindings.d.ts.map +1 -1
  98. package/dist/core/keybindings.js +1 -1
  99. package/dist/core/keybindings.js.map +1 -1
  100. package/dist/core/model-tiers.d.ts +9 -0
  101. package/dist/core/model-tiers.d.ts.map +1 -1
  102. package/dist/core/model-tiers.js +16 -0
  103. package/dist/core/model-tiers.js.map +1 -1
  104. package/dist/core/settings-manager.d.ts +26 -0
  105. package/dist/core/settings-manager.d.ts.map +1 -1
  106. package/dist/core/settings-manager.js +74 -0
  107. package/dist/core/settings-manager.js.map +1 -1
  108. package/dist/core/tools/bash.d.ts.map +1 -1
  109. package/dist/core/tools/bash.js +3 -4
  110. package/dist/core/tools/bash.js.map +1 -1
  111. package/dist/core/tools/edit.d.ts.map +1 -1
  112. package/dist/core/tools/edit.js +9 -6
  113. package/dist/core/tools/edit.js.map +1 -1
  114. package/dist/core/tools/find.d.ts.map +1 -1
  115. package/dist/core/tools/find.js +2 -1
  116. package/dist/core/tools/find.js.map +1 -1
  117. package/dist/core/tools/grep.d.ts.map +1 -1
  118. package/dist/core/tools/grep.js +2 -1
  119. package/dist/core/tools/grep.js.map +1 -1
  120. package/dist/core/tools/ls.d.ts.map +1 -1
  121. package/dist/core/tools/ls.js +2 -1
  122. package/dist/core/tools/ls.js.map +1 -1
  123. package/dist/core/tools/read.d.ts.map +1 -1
  124. package/dist/core/tools/read.js +2 -1
  125. package/dist/core/tools/read.js.map +1 -1
  126. package/dist/core/tools/render-utils.d.ts +13 -3
  127. package/dist/core/tools/render-utils.d.ts.map +1 -1
  128. package/dist/core/tools/render-utils.js +10 -2
  129. package/dist/core/tools/render-utils.js.map +1 -1
  130. package/dist/core/tools/write.d.ts.map +1 -1
  131. package/dist/core/tools/write.js +5 -3
  132. package/dist/core/tools/write.js.map +1 -1
  133. package/dist/core/update-check.d.ts +32 -0
  134. package/dist/core/update-check.d.ts.map +1 -0
  135. package/dist/core/update-check.js +112 -0
  136. package/dist/core/update-check.js.map +1 -0
  137. package/dist/core/usage-service.d.ts +24 -1
  138. package/dist/core/usage-service.d.ts.map +1 -1
  139. package/dist/core/usage-service.js +91 -14
  140. package/dist/core/usage-service.js.map +1 -1
  141. package/dist/gateway/agent-bridge.d.ts +1 -0
  142. package/dist/gateway/agent-bridge.d.ts.map +1 -1
  143. package/dist/gateway/agent-bridge.js +5 -1
  144. package/dist/gateway/agent-bridge.js.map +1 -1
  145. package/dist/gateway/cron.d.ts.map +1 -1
  146. package/dist/gateway/cron.js +5 -1
  147. package/dist/gateway/cron.js.map +1 -1
  148. package/dist/main.d.ts.map +1 -1
  149. package/dist/main.js +13 -1
  150. package/dist/main.js.map +1 -1
  151. package/dist/modes/interactive/components/assistant-message.d.ts +4 -0
  152. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  153. package/dist/modes/interactive/components/assistant-message.js +68 -10
  154. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  155. package/dist/modes/interactive/components/settings-selector.d.ts +15 -0
  156. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  157. package/dist/modes/interactive/components/settings-selector.js +168 -11
  158. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  159. package/dist/modes/interactive/components/thinking-tail.d.ts +1 -0
  160. package/dist/modes/interactive/components/thinking-tail.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/thinking-tail.js +2 -6
  162. package/dist/modes/interactive/components/thinking-tail.js.map +1 -1
  163. package/dist/modes/interactive/components/tool-execution.d.ts +7 -0
  164. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/tool-execution.js +84 -16
  166. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  167. package/dist/modes/interactive/interactive-mode.d.ts +19 -2
  168. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  169. package/dist/modes/interactive/interactive-mode.js +221 -37
  170. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  171. package/docs/keybindings.md +1 -1
  172. package/docs/terminal-setup.md +2 -2
  173. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  174. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  175. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  176. package/examples/extensions/gondolin/package-lock.json +2 -2
  177. package/examples/extensions/gondolin/package.json +1 -1
  178. package/examples/extensions/sandbox/package-lock.json +2 -2
  179. package/examples/extensions/sandbox/package.json +1 -1
  180. package/examples/extensions/with-deps/package-lock.json +2 -2
  181. package/examples/extensions/with-deps/package.json +1 -1
  182. package/package.json +4 -4
@@ -6,12 +6,14 @@ import * as crypto from "node:crypto";
6
6
  import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
- import { CombinedAutocompleteProvider, Container, fuzzyFilter, isFocusable, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@ashx-j/lunr-tui";
9
+ import { getSupportedThinkingLevels, } from "@ashx-j/lunr-ai/compat";
10
+ import { collectImageMarkerIds, CombinedAutocompleteProvider, Container, formatImageMarker, fuzzyFilter, isFocusable, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@ashx-j/lunr-tui";
10
11
  import chalk from "chalk";
11
12
  import { spawn, spawnSync } from "child_process";
12
13
  import { APP_NAME, APP_TITLE, appendDebugLog, CONFIG_DIR_NAME, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
13
14
  import { parseSkillBlock } from "../../core/agent-session.js";
14
15
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
16
+ import { applyBehaviorPreset, reconcileBehaviorPreset } from "../../core/behavior-preset.js";
15
17
  import { CACHE_TTL_MS, collectCacheMisses, computeCacheWaste, detectCacheMiss, } from "../../core/cache-stats.js";
16
18
  import { formatCatalogRefreshSummary } from "../../core/catalog-merge.js";
17
19
  import { computeContextBreakdown } from "../../core/context-breakdown.js";
@@ -26,6 +28,8 @@ import { isValidProviderBaseUrl, parseModelIds, slugifyProviderId, upsertCustomP
26
28
  import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
27
29
  import { refreshModelCandidatesForInit } from "../../core/model-runtime.js";
28
30
  import { getModelTiersBridge } from "../../core/model-tiers.js";
31
+ import { checkForUpdate, markUpdateNotified } from "../../core/update-check.js";
32
+ import { getUsageServiceBridge } from "../../core/usage-service.js";
29
33
  import { registerPermissionModeBridge } from "../../core/permission-mode.js";
30
34
  import { nextModeForGoal, registerPermissionModeControlBridge } from "../../core/permission-mode-control.js";
31
35
  import { AUTO_MODE_ADDENDUM, getPermissionMode, nextPermissionMode, registerApprovalHandler, resetPermissions, restorePermissionModeAfterPlan, setPermissionMode, } from "../../core/permissions.js";
@@ -45,6 +49,7 @@ import { modelToUserEntry } from "../../core/user-models.js";
45
49
  import { copyToClipboard, readClipboardText } from "../../utils/clipboard.js";
46
50
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
47
51
  import { parseGitUrl } from "../../utils/git.js";
52
+ import { processImage } from "../../utils/image-process.js";
48
53
  import { getCwdRelativePath } from "../../utils/paths.js";
49
54
  import { killTrackedDetachedChildren } from "../../utils/shell.js";
50
55
  import { ensureTool, getToolPath } from "../../utils/tools-manager.js";
@@ -232,6 +237,8 @@ export class InteractiveMode {
232
237
  isInitialized = false;
233
238
  onInputCallback;
234
239
  pendingUserInputs = [];
240
+ stagedSubmitImages;
241
+ fallbackImageAttachments = new Map();
235
242
  activeStatusIndicator = undefined;
236
243
  idleStatus = new IdleStatus();
237
244
  workingMessage = undefined;
@@ -589,6 +596,8 @@ export class InteractiveMode {
589
596
  this.ui.start();
590
597
  this.isInitialized = true;
591
598
  time("ui.start");
599
+ void this.maybeNotifyCliUpdate();
600
+ this.startPlanUsagePolling();
592
601
  // lunr: register the permission approval dialog handler so manual mode can prompt.
593
602
  registerApprovalHandler(async (req) => {
594
603
  // lunr: auto-activated agent swarms get their own dialog; on approval the
@@ -599,7 +608,7 @@ export class InteractiveMode {
599
608
  const decision = typeof resp === "string" ? resp : resp.decision;
600
609
  if (decision !== "reject") {
601
610
  this.swarmMode = true;
602
- this.setExtensionStatus("swarm", "swarm ● active");
611
+ this.setExtensionStatus("swarm", "swarm");
603
612
  }
604
613
  return resp;
605
614
  }
@@ -665,6 +674,38 @@ export class InteractiveMode {
665
674
  await this.deferredBuiltinAttachPromise;
666
675
  }
667
676
  }
677
+ planUsageTimer;
678
+ startPlanUsagePolling() {
679
+ const bridge = getUsageServiceBridge();
680
+ if (!bridge)
681
+ return;
682
+ bridge.setOnUpdate(() => this.ui.requestRender());
683
+ const tick = () => {
684
+ const provider = this.session.model?.provider;
685
+ if (provider)
686
+ bridge.prefetch(provider);
687
+ };
688
+ tick();
689
+ this.planUsageTimer = setInterval(tick, 60_000);
690
+ this.planUsageTimer.unref?.();
691
+ }
692
+ /** Background npm version check. Never blocks first paint. Workspace installs skip. */
693
+ async maybeNotifyCliUpdate() {
694
+ try {
695
+ const result = await checkForUpdate({
696
+ currentVersion: VERSION,
697
+ agentDir: getAgentDir(),
698
+ offline: process.env.PI_OFFLINE === "1",
699
+ });
700
+ if (!result?.notice)
701
+ return;
702
+ this.showStatus(result.notice);
703
+ markUpdateNotified(getAgentDir(), result.latest);
704
+ }
705
+ catch {
706
+ // Version check is best-effort.
707
+ }
708
+ }
668
709
  /**
669
710
  * Update terminal title with session name and cwd.
670
711
  */
@@ -730,7 +771,7 @@ export class InteractiveMode {
730
771
  while (true) {
731
772
  const userInput = await this.getUserInput();
732
773
  try {
733
- await this.promptAfterDeferredBuiltins(userInput);
774
+ await this.promptAfterDeferredBuiltins(userInput.text, { images: userInput.images });
734
775
  }
735
776
  catch (error) {
736
777
  const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
@@ -1304,7 +1345,7 @@ export class InteractiveMode {
1304
1345
  this.chatContainer.clear();
1305
1346
  this.renderInitialMessages();
1306
1347
  if (result.editorText && !this.editor.getText().trim()) {
1307
- this.editor.setText(result.editorText);
1348
+ this.restoreEditorFromTreeResult(result);
1308
1349
  }
1309
1350
  this.showStatus("Navigated to selected point");
1310
1351
  void this.flushCompactionQueue({ willRetry: false });
@@ -2117,7 +2158,7 @@ export class InteractiveMode {
2117
2158
  // Global debug handler on TUI (works regardless of focus)
2118
2159
  this.ui.onDebug = () => this.handleDebugCommand();
2119
2160
  this.defaultEditor.onAction("app.model.select", () => void this.showModelSelector());
2120
- this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
2161
+ // lunr: tool/thinking expand is per-item click; ctrl+o is not bound (tree filter still uses ctrl+o).
2121
2162
  this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
2122
2163
  this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
2123
2164
  this.defaultEditor.onAction("app.message.copy", () => void this.handleCopyCommand());
@@ -2135,8 +2176,8 @@ export class InteractiveMode {
2135
2176
  }
2136
2177
  };
2137
2178
  // Handle clipboard paste (keybinding: app.clipboard.pasteImage, Alt+V on Windows —
2138
- // Ctrl+V is owned by the terminal and never reaches us). Images are attached by path;
2139
- // otherwise, paste plain text from the system clipboard.
2179
+ // Ctrl+V is owned by the terminal and never reaches us). Images become `[image_n]`
2180
+ // chips; otherwise, paste plain text from the system clipboard.
2140
2181
  this.defaultEditor.onPasteImage = () => {
2141
2182
  void this.handleClipboardPaste();
2142
2183
  };
@@ -2171,18 +2212,104 @@ export class InteractiveMode {
2171
2212
  }
2172
2213
  });
2173
2214
  }
2215
+ writeClipboardImageFile(image) {
2216
+ const ext = extensionForImageMimeType(image.mimeType) ?? "png";
2217
+ const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
2218
+ const filePath = path.join(os.tmpdir(), fileName);
2219
+ fs.writeFileSync(filePath, Buffer.from(image.bytes));
2220
+ return { path: filePath, mimeType: image.mimeType };
2221
+ }
2222
+ insertImageChip(attachment) {
2223
+ if (this.editor.insertImageMarker) {
2224
+ return this.editor.insertImageMarker(attachment);
2225
+ }
2226
+ const existing = collectImageMarkerIds(this.editor.getText());
2227
+ const used = new Set([...existing, ...this.fallbackImageAttachments.keys()]);
2228
+ let id = 1;
2229
+ while (used.has(id))
2230
+ id++;
2231
+ this.fallbackImageAttachments.set(id, { id, path: attachment.path, mimeType: attachment.mimeType });
2232
+ this.editor.insertTextAtCursor?.(formatImageMarker(id));
2233
+ return id;
2234
+ }
2235
+ takeSubmittedImages(submittedText) {
2236
+ if (this.editor.takePendingImages) {
2237
+ this.fallbackImageAttachments.clear();
2238
+ return this.editor.takePendingImages();
2239
+ }
2240
+ if (this.editor.getPendingImages) {
2241
+ this.fallbackImageAttachments.clear();
2242
+ return this.editor.getPendingImages();
2243
+ }
2244
+ const pending = [];
2245
+ for (const id of collectImageMarkerIds(submittedText ?? this.editor.getText())) {
2246
+ const attachment = this.fallbackImageAttachments.get(id);
2247
+ if (attachment)
2248
+ pending.push(attachment);
2249
+ }
2250
+ this.fallbackImageAttachments.clear();
2251
+ return pending;
2252
+ }
2253
+ consumeStagedSubmitImages() {
2254
+ const staged = this.stagedSubmitImages;
2255
+ this.stagedSubmitImages = undefined;
2256
+ return staged;
2257
+ }
2258
+ async loadImageAttachments(attachments) {
2259
+ if (attachments.length === 0)
2260
+ return undefined;
2261
+ const images = [];
2262
+ const autoResizeImages = this.settingsManager.getImageAutoResize();
2263
+ for (const attachment of attachments) {
2264
+ try {
2265
+ const bytes = fs.readFileSync(attachment.path);
2266
+ const processed = await processImage(bytes, attachment.mimeType, { autoResizeImages });
2267
+ if (!processed.ok) {
2268
+ this.showStatus(processed.message);
2269
+ continue;
2270
+ }
2271
+ images.push({
2272
+ type: "image",
2273
+ mimeType: processed.mimeType,
2274
+ data: processed.data,
2275
+ });
2276
+ }
2277
+ catch (err) {
2278
+ this.showStatus(`Could not attach ${formatImageMarker(attachment.id)}: ${err instanceof Error ? err.message : String(err)}`);
2279
+ }
2280
+ }
2281
+ return images.length > 0 ? images : undefined;
2282
+ }
2283
+ restoreEditorDraft(text, images) {
2284
+ this.editor.setText(text);
2285
+ if (!images || images.length === 0)
2286
+ return;
2287
+ const ids = collectImageMarkerIds(text);
2288
+ if (ids.length === 0 || !this.editor.restoreImageMarkers)
2289
+ return;
2290
+ const attachments = [];
2291
+ for (let i = 0; i < Math.min(ids.length, images.length); i++) {
2292
+ const id = ids[i];
2293
+ const image = images[i];
2294
+ const saved = this.writeClipboardImageFile({
2295
+ bytes: Buffer.from(image.data, "base64"),
2296
+ mimeType: image.mimeType,
2297
+ });
2298
+ attachments.push({ id, path: saved.path, mimeType: saved.mimeType });
2299
+ }
2300
+ this.editor.restoreImageMarkers(attachments);
2301
+ }
2302
+ restoreEditorFromTreeResult(result) {
2303
+ this.restoreEditorDraft(result.editorText ?? "", result.editorImages);
2304
+ }
2174
2305
  async handleClipboardPaste() {
2175
2306
  try {
2176
2307
  const image = await readClipboardImage();
2177
2308
  if (image) {
2178
- const tmpDir = os.tmpdir();
2179
- const ext = extensionForImageMimeType(image.mimeType) ?? "png";
2180
- const fileName = `pi-clipboard-${crypto.randomUUID()}.${ext}`;
2181
- const filePath = path.join(tmpDir, fileName);
2182
- fs.writeFileSync(filePath, Buffer.from(image.bytes));
2183
- this.editor.insertTextAtCursor?.(filePath);
2309
+ const saved = this.writeClipboardImageFile(image);
2310
+ const id = this.insertImageChip(saved);
2184
2311
  this.ui.requestRender();
2185
- this.showStatus(`Pasted clipboard image → ${fileName}`);
2312
+ this.showStatus(`Pasted ${formatImageMarker(id)}`);
2186
2313
  return;
2187
2314
  }
2188
2315
  const text = await readClipboardText();
@@ -2200,8 +2327,11 @@ export class InteractiveMode {
2200
2327
  setupEditorSubmitHandler() {
2201
2328
  this.defaultEditor.onSubmit = async (text) => {
2202
2329
  text = text.trim();
2203
- if (!text)
2330
+ if (!text) {
2331
+ this.takeSubmittedImages(text);
2204
2332
  return;
2333
+ }
2334
+ const images = this.consumeStagedSubmitImages() ?? (await this.loadImageAttachments(this.takeSubmittedImages(text)));
2205
2335
  this.ui.setChatScroll(0);
2206
2336
  // Handle commands
2207
2337
  if (text === "/settings") {
@@ -2422,7 +2552,7 @@ export class InteractiveMode {
2422
2552
  await this.promptAfterDeferredBuiltins(text);
2423
2553
  }
2424
2554
  else {
2425
- this.queueCompactionMessage(text, "steer");
2555
+ this.queueCompactionMessage(text, "steer", images);
2426
2556
  }
2427
2557
  return;
2428
2558
  }
@@ -2431,7 +2561,7 @@ export class InteractiveMode {
2431
2561
  if (this.session.isStreaming) {
2432
2562
  this.editor.addToHistory?.(text);
2433
2563
  this.editor.setText("");
2434
- await this.promptAfterDeferredBuiltins(text, { streamingBehavior: "steer" });
2564
+ await this.promptAfterDeferredBuiltins(text, { streamingBehavior: "steer", images });
2435
2565
  this.updatePendingMessagesDisplay();
2436
2566
  this.ui.requestRender();
2437
2567
  return;
@@ -2440,10 +2570,10 @@ export class InteractiveMode {
2440
2570
  // First, move any pending bash components to chat
2441
2571
  this.flushPendingBashComponents();
2442
2572
  if (this.onInputCallback) {
2443
- this.onInputCallback(text);
2573
+ this.onInputCallback({ text, images });
2444
2574
  }
2445
2575
  else {
2446
- this.pendingUserInputs.push(text);
2576
+ this.pendingUserInputs.push({ text, images });
2447
2577
  }
2448
2578
  this.editor.addToHistory?.(text);
2449
2579
  };
@@ -3156,9 +3286,9 @@ export class InteractiveMode {
3156
3286
  return queuedInput;
3157
3287
  }
3158
3288
  return new Promise((resolve) => {
3159
- this.onInputCallback = (text) => {
3289
+ this.onInputCallback = (input) => {
3160
3290
  this.onInputCallback = undefined;
3161
- resolve(text);
3291
+ resolve(input);
3162
3292
  };
3163
3293
  });
3164
3294
  }
@@ -3194,6 +3324,11 @@ export class InteractiveMode {
3194
3324
  return;
3195
3325
  this.isShuttingDown = true;
3196
3326
  this.stopSmoothStreaming();
3327
+ if (this.planUsageTimer) {
3328
+ clearInterval(this.planUsageTimer);
3329
+ this.planUsageTimer = undefined;
3330
+ }
3331
+ getUsageServiceBridge()?.setOnUpdate(undefined);
3197
3332
  // Keep signal handlers registered until terminal cleanup has completed.
3198
3333
  // `signal-exit` checks the listener list during the same SIGTERM/SIGHUP
3199
3334
  // dispatch and re-sends the signal if only its own listeners remain.
@@ -3386,21 +3521,25 @@ export class InteractiveMode {
3386
3521
  await this.promptAfterDeferredBuiltins(text);
3387
3522
  }
3388
3523
  else {
3389
- this.queueCompactionMessage(text, "followUp");
3524
+ const images = await this.loadImageAttachments(this.takeSubmittedImages(text));
3525
+ this.queueCompactionMessage(text, "followUp", images);
3390
3526
  }
3391
3527
  return;
3392
3528
  }
3393
3529
  // Alt+Enter queues a follow-up message (waits until agent finishes)
3394
3530
  // This handles extension commands (execute immediately), prompt template expansion, and queueing
3395
3531
  if (this.session.isStreaming) {
3532
+ const images = await this.loadImageAttachments(this.takeSubmittedImages(text));
3396
3533
  this.editor.addToHistory?.(text);
3397
3534
  this.editor.setText("");
3398
- await this.promptAfterDeferredBuiltins(text, { streamingBehavior: "followUp" });
3535
+ await this.promptAfterDeferredBuiltins(text, { streamingBehavior: "followUp", images });
3399
3536
  this.updatePendingMessagesDisplay();
3400
3537
  this.ui.requestRender();
3401
3538
  }
3402
3539
  // If not streaming, Alt+Enter acts like regular Enter (trigger onSubmit)
3403
3540
  else if (this.editor.onSubmit) {
3541
+ const images = await this.loadImageAttachments(this.takeSubmittedImages(text));
3542
+ this.stagedSubmitImages = images;
3404
3543
  this.editor.setText("");
3405
3544
  this.editor.onSubmit(text);
3406
3545
  }
@@ -3652,8 +3791,8 @@ export class InteractiveMode {
3652
3791
  }
3653
3792
  return allQueued.length;
3654
3793
  }
3655
- queueCompactionMessage(text, mode) {
3656
- this.compactionQueuedMessages.push({ text, mode });
3794
+ queueCompactionMessage(text, mode, images) {
3795
+ this.compactionQueuedMessages.push({ text, mode, images });
3657
3796
  this.editor.addToHistory?.(text);
3658
3797
  this.editor.setText("");
3659
3798
  this.updatePendingMessagesDisplay();
@@ -3688,10 +3827,10 @@ export class InteractiveMode {
3688
3827
  await this.promptAfterDeferredBuiltins(message.text);
3689
3828
  }
3690
3829
  else if (message.mode === "followUp") {
3691
- await this.session.followUp(message.text);
3830
+ await this.session.followUp(message.text, message.images);
3692
3831
  }
3693
3832
  else {
3694
- await this.session.steer(message.text);
3833
+ await this.session.steer(message.text, message.images);
3695
3834
  }
3696
3835
  }
3697
3836
  this.updatePendingMessagesDisplay();
@@ -3714,7 +3853,9 @@ export class InteractiveMode {
3714
3853
  await this.promptAfterDeferredBuiltins(message.text);
3715
3854
  }
3716
3855
  // Send first prompt (starts streaming)
3717
- const promptPromise = this.promptAfterDeferredBuiltins(firstPrompt.text).catch((error) => {
3856
+ const promptPromise = this.promptAfterDeferredBuiltins(firstPrompt.text, {
3857
+ images: firstPrompt.images,
3858
+ }).catch((error) => {
3718
3859
  restoreQueue(error);
3719
3860
  });
3720
3861
  // Queue remaining messages
@@ -3723,10 +3864,10 @@ export class InteractiveMode {
3723
3864
  await this.promptAfterDeferredBuiltins(message.text);
3724
3865
  }
3725
3866
  else if (message.mode === "followUp") {
3726
- await this.session.followUp(message.text);
3867
+ await this.session.followUp(message.text, message.images);
3727
3868
  }
3728
3869
  else {
3729
- await this.session.steer(message.text);
3870
+ await this.session.steer(message.text, message.images);
3730
3871
  }
3731
3872
  }
3732
3873
  this.updatePendingMessagesDisplay();
@@ -3801,6 +3942,7 @@ export class InteractiveMode {
3801
3942
  showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
3802
3943
  modelTiers: this.settingsManager.getModelTiers(),
3803
3944
  memoryCharCap: this.settingsManager.getMemoryCharCap(),
3945
+ behaviorPreset: reconcileBehaviorPreset(this.settingsManager),
3804
3946
  searchCurator: getSearchCuratorSetting(),
3805
3947
  // lunr: TUI customize settings
3806
3948
  gutterRail: this.settingsManager.getGutterRail(),
@@ -3809,7 +3951,12 @@ export class InteractiveMode {
3809
3951
  footerLsp: this.settingsManager.getFooterLsp(),
3810
3952
  footerContext: this.settingsManager.getFooterContext(),
3811
3953
  footerTokens: this.settingsManager.getFooterTokens(),
3954
+ footerTps: this.settingsManager.getFooterTps(),
3812
3955
  footerStatuses: this.settingsManager.getFooterStatuses(),
3956
+ footerGit: this.settingsManager.getFooterGit(),
3957
+ footerPlan: this.settingsManager.getFooterPlan(),
3958
+ footerPlanBar: this.settingsManager.getFooterPlanBar(),
3959
+ planUsageWindow: this.settingsManager.getPlanUsageWindow(),
3813
3960
  defaultPermissionMode: this.settingsManager.getDefaultPermissionMode(),
3814
3961
  rollbackEnabled: this.settingsManager.getRollbackEnabled(),
3815
3962
  rollbackTurns: this.settingsManager.getRollbackTurns(),
@@ -3973,9 +4120,28 @@ export class InteractiveMode {
3973
4120
  onModelTierModelChange: (tier, model) => {
3974
4121
  this.settingsManager.setTierModel(tier, model);
3975
4122
  },
4123
+ onModelTierThinkingChange: (tier, level) => {
4124
+ this.settingsManager.setTierThinking(tier, level);
4125
+ },
4126
+ getTierThinkingLevels: (tier) => {
4127
+ const model = this.resolveModelReference(this.settingsManager.getTierModel(tier));
4128
+ if (model)
4129
+ return getSupportedThinkingLevels(model);
4130
+ return this.session.getAvailableThinkingLevels();
4131
+ },
3976
4132
  onMemoryCharCapChange: (cap) => {
3977
4133
  this.settingsManager.setMemoryCharCap(cap);
3978
4134
  },
4135
+ onBehaviorPresetChange: (preset) => {
4136
+ const result = applyBehaviorPreset(this.settingsManager, preset, getAgentDir(), {
4137
+ overwrite: true,
4138
+ });
4139
+ if (result.ok) {
4140
+ this.showStatus(preset === "custom"
4141
+ ? "Behavior preset: custom (file is source of truth)"
4142
+ : `Behavior preset: ${preset}`);
4143
+ }
4144
+ },
3979
4145
  onSearchCuratorChange: (setting) => {
3980
4146
  if (!setSearchCuratorSetting(setting)) {
3981
4147
  this.showError("pi-web-access is not loaded; curator setting unavailable.");
@@ -4000,9 +4166,24 @@ export class InteractiveMode {
4000
4166
  onFooterTokensChange: (enabled) => {
4001
4167
  this.settingsManager.setFooterTokens(enabled);
4002
4168
  },
4169
+ onFooterTpsChange: (enabled) => {
4170
+ this.settingsManager.setFooterTps(enabled);
4171
+ },
4003
4172
  onFooterStatusesChange: (enabled) => {
4004
4173
  this.settingsManager.setFooterStatuses(enabled);
4005
4174
  },
4175
+ onFooterGitChange: (enabled) => {
4176
+ this.settingsManager.setFooterGit(enabled);
4177
+ },
4178
+ onFooterPlanChange: (enabled) => {
4179
+ this.settingsManager.setFooterPlan(enabled);
4180
+ },
4181
+ onFooterPlanBarChange: (enabled) => {
4182
+ this.settingsManager.setFooterPlanBar(enabled);
4183
+ },
4184
+ onPlanUsageWindowChange: (window) => {
4185
+ this.settingsManager.setPlanUsageWindow(window);
4186
+ },
4006
4187
  onDefaultPermissionModeChange: (mode) => {
4007
4188
  this.settingsManager.setDefaultPermissionMode(mode);
4008
4189
  },
@@ -4606,7 +4787,7 @@ export class InteractiveMode {
4606
4787
  this.chatContainer.clear();
4607
4788
  this.renderInitialMessages();
4608
4789
  if (result.editorText && !this.editor.getText().trim()) {
4609
- this.editor.setText(result.editorText);
4790
+ this.restoreEditorFromTreeResult(result);
4610
4791
  }
4611
4792
  this.showStatus("Navigated to selected point");
4612
4793
  void this.flushCompactionQueue({ willRetry: false });
@@ -5425,7 +5606,7 @@ export class InteractiveMode {
5425
5606
  return;
5426
5607
  }
5427
5608
  this.swarmMode = true;
5428
- this.setExtensionStatus("swarm", "swarm ● active");
5609
+ this.setExtensionStatus("swarm", "swarm");
5429
5610
  await this.sendUserMessageAfterDeferredBuiltins(buildSwarmPrompt(task));
5430
5611
  }
5431
5612
  async handleResearchCommand(args) {
@@ -5523,7 +5704,7 @@ export class InteractiveMode {
5523
5704
  this.chatContainer.clear();
5524
5705
  this.renderInitialMessages();
5525
5706
  void this.flushCompactionQueue({ willRetry: false });
5526
- return { editorText: result.editorText };
5707
+ return { editorText: result.editorText, editorImages: result.editorImages };
5527
5708
  }
5528
5709
  catch (error) {
5529
5710
  this.showError(error instanceof Error ? error.message : String(error));
@@ -5540,7 +5721,10 @@ export class InteractiveMode {
5540
5721
  const result = await this.rewindLastTurn("edit");
5541
5722
  if (!result)
5542
5723
  return;
5543
- this.editor.setText(result.editorText ?? "");
5724
+ this.restoreEditorFromTreeResult({
5725
+ editorText: result.editorText ?? "",
5726
+ editorImages: result.editorImages,
5727
+ });
5544
5728
  this.showStatus("Editing last message. /redo to restore.");
5545
5729
  }
5546
5730
  async handleRedoCommand() {
@@ -5563,7 +5747,7 @@ export class InteractiveMode {
5563
5747
  this.chatContainer.clear();
5564
5748
  this.renderInitialMessages();
5565
5749
  if (result.editorText && !this.editor.getText().trim()) {
5566
- this.editor.setText(result.editorText);
5750
+ this.restoreEditorFromTreeResult(result);
5567
5751
  }
5568
5752
  this.showStatus("Redone");
5569
5753
  void this.flushCompactionQueue({ willRetry: false });
@@ -6419,7 +6603,7 @@ export class InteractiveMode {
6419
6603
  const cyclePermissionMode = this.getAppKeyDisplay("app.mode.cycle");
6420
6604
  const cycleModelForward = this.getAppKeyDisplay("app.model.cycleForward");
6421
6605
  const selectModel = this.getAppKeyDisplay("app.model.select");
6422
- const expandTools = this.getAppKeyDisplay("app.tools.expand");
6606
+ const expandTools = "click";
6423
6607
  const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
6424
6608
  const externalEditor = this.getAppKeyDisplay("app.editor.external");
6425
6609
  const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
@@ -6464,7 +6648,7 @@ export class InteractiveMode {
6464
6648
  | \`${cyclePermissionMode}\` | Cycle permission mode |
6465
6649
  ${cycleThinkingLevel ? `| \`${cycleThinkingLevel}\` | Cycle thinking level |\n` : ""}| \`${cycleModelForward}\` / \`${cycleModelBackward}\` | Cycle models |
6466
6650
  | \`${selectModel}\` | Open model selector |
6467
- | \`${expandTools}\` | Toggle tool output expansion |
6651
+ | \`${expandTools}\` | Expand or collapse a thinking/tool card |
6468
6652
  | \`${toggleThinking}\` | Toggle thinking block visibility |
6469
6653
  | \`${externalEditor}\` | Edit message in external editor |
6470
6654
  | \`${copyMessage}\` | Copy last assistant message |