lsd-pi 1.3.2 → 1.3.7

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 (205) hide show
  1. package/dist/cli.js +2 -1
  2. package/dist/lsd-settings-manager.d.ts +2 -0
  3. package/dist/lsd-settings-manager.js +5 -0
  4. package/dist/resource-loader.js +33 -3
  5. package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
  6. package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
  7. package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
  8. package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
  9. package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
  10. package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
  11. package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
  12. package/dist/resources/extensions/browser-tools/utils.js +1 -1
  13. package/dist/resources/extensions/cache-timer/index.js +3 -2
  14. package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
  15. package/dist/resources/extensions/slash-commands/fast.js +73 -0
  16. package/dist/resources/extensions/slash-commands/index.js +2 -0
  17. package/dist/resources/extensions/slash-commands/plan.js +37 -12
  18. package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
  19. package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
  20. package/dist/resources/extensions/subagent/index.js +278 -626
  21. package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
  22. package/dist/resources/extensions/voice/index.js +96 -36
  23. package/dist/resources/extensions/voice/push-to-talk.js +26 -0
  24. package/dist/welcome-screen.js +2 -2
  25. package/package.json +1 -1
  26. package/packages/pi-agent-core/dist/agent.d.ts +19 -0
  27. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  28. package/packages/pi-agent-core/dist/agent.js +16 -0
  29. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  30. package/packages/pi-agent-core/src/agent.ts +32 -2
  31. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
  32. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
  34. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  35. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
  36. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
  37. package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
  38. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  39. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
  40. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
  41. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
  42. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
  43. package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
  44. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  45. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/providers/simple-options.js +2 -0
  47. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  48. package/packages/pi-ai/dist/types.d.ts +5 -0
  49. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  50. package/packages/pi-ai/dist/types.js.map +1 -1
  51. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
  52. package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
  53. package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
  54. package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
  55. package/packages/pi-ai/src/providers/simple-options.ts +2 -0
  56. package/packages/pi-ai/src/types.ts +5 -0
  57. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  58. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  60. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  62. package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
  63. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts +2 -0
  65. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts.map +1 -0
  66. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js +35 -0
  67. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js.map +1 -0
  68. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +12 -0
  69. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
  71. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
  72. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
  73. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
  74. package/packages/pi-coding-agent/dist/core/settings-manager.js +24 -0
  75. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  78. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
  81. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  82. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
  83. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
  85. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
  87. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
  89. package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +5 -0
  91. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +21 -0
  93. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +16 -1
  95. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +34 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +7 -5
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +86 -28
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +4 -0
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +23 -10
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +52 -6
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +19 -0
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +127 -14
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +14 -0
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +93 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +328 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +123 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +4 -0
  141. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +9 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +103 -23
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
  149. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -4
  151. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  152. package/packages/pi-coding-agent/package.json +1 -1
  153. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  154. package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
  155. package/packages/pi-coding-agent/src/core/settings-manager.collapse-tool-calls.test.ts +46 -0
  156. package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
  157. package/packages/pi-coding-agent/src/core/settings-manager.ts +36 -0
  158. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  159. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
  160. package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
  161. package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
  162. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +20 -0
  163. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +26 -0
  164. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +41 -0
  165. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
  166. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
  167. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +105 -28
  168. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +21 -6
  169. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +63 -6
  170. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1262 -1138
  171. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +120 -0
  172. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +396 -0
  173. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +530 -398
  174. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
  175. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  176. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +4 -0
  177. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +109 -23
  178. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
  179. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -4
  180. package/packages/pi-tui/dist/components/editor.js +3 -3
  181. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  182. package/packages/pi-tui/src/components/editor.ts +3 -3
  183. package/pkg/dist/modes/interactive/theme/themes.js +4 -4
  184. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  185. package/pkg/package.json +1 -1
  186. package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
  187. package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
  188. package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
  189. package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
  190. package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
  191. package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
  192. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
  193. package/src/resources/extensions/browser-tools/utils.ts +1 -1
  194. package/src/resources/extensions/cache-timer/index.ts +3 -2
  195. package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
  196. package/src/resources/extensions/slash-commands/fast.ts +89 -0
  197. package/src/resources/extensions/slash-commands/index.ts +2 -0
  198. package/src/resources/extensions/slash-commands/plan.ts +42 -12
  199. package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
  200. package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
  201. package/src/resources/extensions/subagent/index.ts +489 -799
  202. package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
  203. package/src/resources/extensions/voice/index.ts +308 -238
  204. package/src/resources/extensions/voice/push-to-talk.ts +42 -0
  205. package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
@@ -0,0 +1,46 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { describe, it } from "node:test";
6
+
7
+ import { SettingsManager } from "./settings-manager.js";
8
+
9
+ describe("SettingsManager fastMode", () => {
10
+ it("defaults fastMode to false", () => {
11
+ const manager = SettingsManager.inMemory();
12
+ assert.equal(manager.getFastMode(), false);
13
+ });
14
+
15
+ it("persists fastMode changes to settings.json", async () => {
16
+ const root = mkdtempSync(join(tmpdir(), "pi-fast-mode-settings-"));
17
+ const cwd = join(root, "project");
18
+ const agentDir = join(root, "agent");
19
+
20
+ try {
21
+ const manager = SettingsManager.create(cwd, agentDir);
22
+ assert.equal(manager.getFastMode(), false);
23
+
24
+ manager.setFastMode(true);
25
+ await manager.flush();
26
+
27
+ const rawEnabled = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8")) as {
28
+ fastMode?: boolean;
29
+ };
30
+ assert.equal(rawEnabled.fastMode, true);
31
+
32
+ const reloaded = SettingsManager.create(cwd, agentDir);
33
+ assert.equal(reloaded.getFastMode(), true);
34
+
35
+ reloaded.setFastMode(false);
36
+ await reloaded.flush();
37
+
38
+ const rawDisabled = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8")) as {
39
+ fastMode?: boolean;
40
+ };
41
+ assert.equal(rawDisabled.fastMode, false);
42
+ } finally {
43
+ rmSync(root, { recursive: true, force: true });
44
+ }
45
+ });
46
+ });
@@ -159,7 +159,9 @@ export interface Settings {
159
159
  images?: ImageSettings;
160
160
  enabledModels?: string[]; // Model patterns for cycling (same format as --models CLI flag)
161
161
  codexRotate?: boolean; // Enable the bundled codex-rotate extension (default: false)
162
+ fastMode?: boolean; // Enable OpenAI/Codex fast tier routing (priority service tier where supported)
162
163
  cacheTimer?: boolean; // Show elapsed time since last response in the footer (default: true)
164
+ verboseFooter?: boolean; // Show extra footer usage stats like input/output/cache tokens (default: false)
163
165
  pinLastPrompt?: boolean; // Pin last sent prompt above the editor as a reminder (default: false)
164
166
  doubleEscapeAction?: "fork" | "tree" | "none"; // Action for double-escape with empty editor (default: "tree")
165
167
  treeFilterMode?: "default" | "no-tools" | "user-only" | "labeled-only" | "all"; // Default filter when opening /tree
@@ -180,12 +182,14 @@ export interface Settings {
180
182
  editMode?: "standard" | "hashline"; // Edit tool mode: "standard" (text match) or "hashline" (LINE#ID anchors). Default: "standard"
181
183
  timestampFormat?: "date-time-iso" | "date-time-us"; // Timestamp display format for messages. Default: "date-time-iso"
182
184
  toolOutputMode?: "minimal" | "normal"; // Collapsed tool rendering mode. "minimal" hides previews until expanded.
185
+ collapseToolCalls?: boolean; // default: false — group low-priority tool calls into collapsed summary lines
183
186
  lspAutoInstall?: boolean; // default: false — whether to auto-install missing language servers during onboarding
184
187
  lspInstalledServers?: string[]; // list of server names installed via the onboarding wizard
185
188
  rtk?: boolean; // default: false — enable RTK shell-command compression (requires restart)
186
189
  editorScheme?: "auto" | "vscode" | "cursor" | "zed" | "jetbrains" | "sublime" | "file"; // URI scheme for Cmd+click file links (default: "auto")
187
190
  autoDream?: boolean; // default: false — enable automatic memory consolidation (dream) after sessions
188
191
  autoMemory?: boolean; // default: false — enable automatic memory extraction from session transcripts
192
+ notificationSound?: boolean; // default: false — play terminal bell when agent finishes responding
189
193
  telegramLiveRelayAutoConnect?: boolean; // default: false — auto-run /lsd telegram connect on startup
190
194
  }
191
195
 
@@ -1049,6 +1053,14 @@ export class SettingsManager {
1049
1053
  this.setGlobalSetting("toolOutputMode", mode);
1050
1054
  }
1051
1055
 
1056
+ getCollapseToolCalls(): boolean {
1057
+ return this.settings.collapseToolCalls ?? false;
1058
+ }
1059
+
1060
+ setCollapseToolCalls(enabled: boolean): void {
1061
+ this.setGlobalSetting("collapseToolCalls", enabled);
1062
+ }
1063
+
1052
1064
  getPackages(): PackageSource[] {
1053
1065
  return [...(this.settings.packages ?? [])];
1054
1066
  }
@@ -1187,6 +1199,14 @@ export class SettingsManager {
1187
1199
  this.setGlobalSetting("codexRotate", enabled);
1188
1200
  }
1189
1201
 
1202
+ getFastMode(): boolean {
1203
+ return this.settings.fastMode ?? false;
1204
+ }
1205
+
1206
+ setFastMode(enabled: boolean): void {
1207
+ this.setGlobalSetting("fastMode", enabled);
1208
+ }
1209
+
1190
1210
  getCacheTimer(): boolean {
1191
1211
  return this.settings.cacheTimer ?? true;
1192
1212
  }
@@ -1195,6 +1215,14 @@ export class SettingsManager {
1195
1215
  this.setGlobalSetting("cacheTimer", enabled);
1196
1216
  }
1197
1217
 
1218
+ getVerboseFooter(): boolean {
1219
+ return this.settings.verboseFooter ?? false;
1220
+ }
1221
+
1222
+ setVerboseFooter(enabled: boolean): void {
1223
+ this.setGlobalSetting("verboseFooter", enabled);
1224
+ }
1225
+
1198
1226
  getPinLastPrompt(): boolean {
1199
1227
  return this.settings.pinLastPrompt ?? false;
1200
1228
  }
@@ -1445,6 +1473,14 @@ export class SettingsManager {
1445
1473
  this.setGlobalSetting("autoMemory", enabled);
1446
1474
  }
1447
1475
 
1476
+ getNotificationSound(): boolean {
1477
+ return this.settings.notificationSound ?? false;
1478
+ }
1479
+
1480
+ setNotificationSound(enabled: boolean): void {
1481
+ this.setGlobalSetting("notificationSound", enabled);
1482
+ }
1483
+
1448
1484
  getTelegramLiveRelayAutoConnect(): boolean {
1449
1485
  return this.settings.telegramLiveRelayAutoConnect ?? false;
1450
1486
  }
@@ -27,6 +27,7 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
27
27
  { name: "session", description: "Show session info and stats" },
28
28
  { name: "changelog", description: "Show changelog entries" },
29
29
  { name: "hotkeys", description: "Show all keyboard shortcuts" },
30
+ { name: "btw", description: "Ask a side question without interrupting the current task" },
30
31
  { name: "fork", description: "Create a new fork from a previous message" },
31
32
  { name: "tree", description: "Navigate session tree (switch branches)" },
32
33
  { name: "provider", description: "Manage provider configuration" },
@@ -149,7 +149,12 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
149
149
  const hasLsp = tools.includes("lsp");
150
150
 
151
151
  // Priority-ordered compact guidelines
152
- addGuideline("Be concise. Prefer short, direct answers over preamble.");
152
+ addGuideline("Terse output. All technical substance stays. Only fluff dies.");
153
+ addGuideline("Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging (might be worth/could consider).");
154
+ addGuideline("Fragments OK. Short synonyms (big not extensive, fix not implement a solution for). Pattern: [thing] [action] [reason]. [next step].");
155
+ addGuideline("Never announce what you're about to do — just do it. Never summarize what you just did unless the user asks.");
156
+ addGuideline("Code blocks, error messages, file paths, and technical terms stay exact. Only compress prose.");
157
+ addGuideline("For security warnings, irreversible actions, or multi-step sequences where fragments risk misread — use full clear sentences. Resume terse after.");
153
158
  addGuideline("For conceptual, product, or UX questions, answer first; inspect code only if implementation detail is needed.");
154
159
 
155
160
  const hasSubagent = tools.includes("subagent");
@@ -0,0 +1,30 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+
4
+ import { getToolPriority, shouldCollapse } from "./tool-priority.js";
5
+
6
+ describe("tool priority", () => {
7
+ it("keeps edit and write always visible", () => {
8
+ assert.equal(getToolPriority("edit"), "always");
9
+ assert.equal(getToolPriority("write"), "always");
10
+ assert.equal(shouldCollapse("edit", false), false);
11
+ assert.equal(shouldCollapse("write", true), false);
12
+ });
13
+
14
+ it("collapses bash and bg_shell only on success", () => {
15
+ assert.equal(getToolPriority("bash"), "on-error");
16
+ assert.equal(getToolPriority("bg_shell"), "on-error");
17
+ assert.equal(shouldCollapse("bash", false), true);
18
+ assert.equal(shouldCollapse("bash", true), false);
19
+ assert.equal(shouldCollapse("bg_shell", false), true);
20
+ assert.equal(shouldCollapse("bg_shell", true), false);
21
+ });
22
+
23
+ it("collapses read, grep, and unknown tools", () => {
24
+ assert.equal(getToolPriority("read"), "collapse");
25
+ assert.equal(getToolPriority("grep"), "collapse");
26
+ assert.equal(getToolPriority("unknown-tool"), "collapse");
27
+ assert.equal(shouldCollapse("read", false), true);
28
+ assert.equal(shouldCollapse("unknown-tool", true), true);
29
+ });
30
+ });
@@ -0,0 +1,17 @@
1
+ export type ToolPriority = "always" | "on-error" | "collapse";
2
+
3
+ const ALWAYS_VISIBLE = new Set(["edit", "write"]);
4
+ const ON_ERROR = new Set(["bash", "bg_shell"]);
5
+
6
+ export function getToolPriority(toolName: string): ToolPriority {
7
+ if (ALWAYS_VISIBLE.has(toolName)) return "always";
8
+ if (ON_ERROR.has(toolName)) return "on-error";
9
+ return "collapse";
10
+ }
11
+
12
+ export function shouldCollapse(toolName: string, isError: boolean): boolean {
13
+ const priority = getToolPriority(toolName);
14
+ if (priority === "always") return false;
15
+ if (priority === "on-error") return !isError;
16
+ return true;
17
+ }
@@ -6,6 +6,7 @@ import { describe, it } from "node:test";
6
6
 
7
7
  import {
8
8
  computeEditDiff,
9
+ computeWriteDiff,
9
10
  fuzzyFindText,
10
11
  generateDiffString,
11
12
  normalizeForFuzzyMatch,
@@ -60,6 +61,25 @@ describe("edit-diff", () => {
60
61
  assert.match(result.diff, /CHANGED/);
61
62
  });
62
63
 
64
+ it("computes diffs for write preview against existing files", async (t) => {
65
+ const dir = mkdtempSync(join(tmpdir(), "write-diff-test-"));
66
+ t.after(() => {
67
+ rmSync(dir, { recursive: true, force: true });
68
+ });
69
+
70
+ const file = join(dir, "sample.ts");
71
+ writeFileSync(file, "const title = \"Hello\";\n", "utf-8");
72
+
73
+ const result = await computeWriteDiff(file, "const title = \"Hi\";\n", dir);
74
+
75
+ assert.ok(!("error" in result), "expected a diff result");
76
+ if (!("error" in result)) {
77
+ assert.equal(result.firstChangedLine, 1);
78
+ assert.match(result.diff, /-1 const title = "Hello";/);
79
+ assert.match(result.diff, /\+1 const title = "Hi";/);
80
+ }
81
+ });
82
+
63
83
  it("computes diffs for preview without native helpers", async (t) => {
64
84
  const dir = mkdtempSync(join(tmpdir(), "edit-diff-test-"));
65
85
  t.after(() => {
@@ -329,6 +329,32 @@ function buildLineDiffLinear(oldLines: string[], newLines: string[]): LineDiffOp
329
329
  return ops;
330
330
  }
331
331
 
332
+ /**
333
+ * Compute the diff for a write operation without applying it.
334
+ * Used for preview rendering in the TUI before the tool executes.
335
+ */
336
+ export async function computeWriteDiff(
337
+ path: string,
338
+ newContent: string,
339
+ cwd: string,
340
+ ): Promise<EditDiffResult | EditDiffError> {
341
+ const absolutePath = resolveToCwd(path, cwd);
342
+
343
+ try {
344
+ let oldContent = "";
345
+ try {
346
+ await access(absolutePath, constants.R_OK);
347
+ oldContent = await readFile(absolutePath, "utf-8");
348
+ } catch {
349
+ oldContent = "";
350
+ }
351
+
352
+ return generateDiffString(normalizeToLF(stripBom(oldContent).text), normalizeToLF(newContent));
353
+ } catch (err) {
354
+ return { error: err instanceof Error ? err.message : String(err) };
355
+ }
356
+ }
357
+
332
358
  /**
333
359
  * Compute the diff for an edit operation without applying it.
334
360
  * Used for preview rendering in the TUI before the tool executes.
@@ -0,0 +1,41 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import stripAnsi from "strip-ansi";
4
+
5
+ import { ToolSummaryLine } from "../tool-summary-line.js";
6
+ import { initTheme } from "../../theme/theme.js";
7
+
8
+ initTheme("dark");
9
+
10
+ describe("ToolSummaryLine", () => {
11
+ it("renders action-based summaries for grouped identical tools", () => {
12
+ const summary = new ToolSummaryLine();
13
+ summary.addTool("read", 600);
14
+ summary.addTool("read", 150);
15
+
16
+ const rendered = stripAnsi(summary.render(160).join("\n"));
17
+ assert.match(rendered, /^ ● /);
18
+ assert.ok(rendered.includes("reading 2 files · 0.8s"));
19
+ assert.equal(summary.canGroupWith("read"), true);
20
+ assert.equal(summary.canGroupWith("find"), false);
21
+ assert.equal(rendered.includes("collapsed tools"), false);
22
+ assert.equal(rendered.includes("⎯"), false);
23
+ });
24
+
25
+ it("keeps fallback format for unknown tools", () => {
26
+ const summary = new ToolSummaryLine();
27
+ summary.addTool("custom_tool", 100);
28
+
29
+ const rendered = stripAnsi(summary.render(160).join("\n"));
30
+ assert.ok(rendered.includes("custom_tool · 0.1s"));
31
+ });
32
+
33
+ it("renders nothing when empty or hidden", () => {
34
+ const summary = new ToolSummaryLine();
35
+ assert.deepEqual(summary.render(80), []);
36
+
37
+ summary.addTool("grep", 100);
38
+ summary.setHidden(true);
39
+ assert.deepEqual(summary.render(80), []);
40
+ });
41
+ });
@@ -0,0 +1,172 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import stripAnsi from "strip-ansi";
4
+ import type { Context, Model, SimpleStreamOptions, Message } from "@gsd/pi-ai";
5
+ import type { TUI } from "@gsd/pi-tui";
6
+ import { BtwOverlayComponent } from "./btw-overlay.js";
7
+ import { getMarkdownTheme, initTheme } from "../theme/theme.js";
8
+
9
+ initTheme();
10
+
11
+ function makeUi(rows = 40): TUI {
12
+ return { terminal: { rows } } as unknown as TUI;
13
+ }
14
+
15
+ function makeModel(): Model<any> {
16
+ return {
17
+ id: "test-model",
18
+ name: "Test Model",
19
+ api: "openai-completions",
20
+ provider: "test",
21
+ baseUrl: "",
22
+ reasoning: false,
23
+ input: ["text"],
24
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
25
+ contextWindow: 4096,
26
+ maxTokens: 1024,
27
+ } as unknown as Model<any>;
28
+ }
29
+
30
+ function makeMessages(): Message[] {
31
+ return [
32
+ {
33
+ role: "user",
34
+ content: [{ type: "text", text: "hello" }],
35
+ timestamp: Date.now(),
36
+ },
37
+ ];
38
+ }
39
+
40
+ function renderText(component: BtwOverlayComponent): string {
41
+ return stripAnsi(component.render(80).join("\n"));
42
+ }
43
+
44
+ async function flushTurns(count = 3): Promise<void> {
45
+ for (let i = 0; i < count; i++) {
46
+ await Promise.resolve();
47
+ await new Promise((resolve) => setTimeout(resolve, 0));
48
+ }
49
+ }
50
+
51
+ test("BtwOverlayComponent renders initial question and placeholder before response", async () => {
52
+ const component = new BtwOverlayComponent(
53
+ "What is btw?",
54
+ makeModel(),
55
+ "system",
56
+ makeMessages(),
57
+ getMarkdownTheme(),
58
+ makeUi(),
59
+ () => undefined,
60
+ () => undefined,
61
+ () => ({
62
+ async *[Symbol.asyncIterator]() {
63
+ await new Promise(() => undefined);
64
+ },
65
+ }) as any,
66
+ );
67
+
68
+ await flushTurns();
69
+ const text = renderText(component);
70
+ assert.match(text, /btw/);
71
+ assert.match(text, /You/);
72
+ assert.match(text, /What is btw\?/);
73
+ assert.match(text, /Awaiting response/);
74
+ assert.match(text, /Ask follow-up/);
75
+ component.dispose();
76
+ });
77
+
78
+ test("BtwOverlayComponent supports follow-up turns and reuses overlay-local history", async () => {
79
+ const calls: Array<{ context: Context; options?: SimpleStreamOptions }> = [];
80
+ const responses = [
81
+ [
82
+ { type: "text_delta", delta: "First answer" },
83
+ { type: "done" },
84
+ ],
85
+ [
86
+ { type: "text_delta", delta: "Second answer" },
87
+ { type: "done" },
88
+ ],
89
+ ];
90
+
91
+ const streamFn = ((_: Model<any>, context: Context, options?: SimpleStreamOptions) => {
92
+ calls.push({ context, options });
93
+ const response = responses[calls.length - 1] ?? [];
94
+ return {
95
+ async *[Symbol.asyncIterator]() {
96
+ for (const event of response) {
97
+ yield event;
98
+ }
99
+ },
100
+ };
101
+ }) as any;
102
+
103
+ const component = new BtwOverlayComponent(
104
+ "What is btw?",
105
+ makeModel(),
106
+ "system",
107
+ makeMessages(),
108
+ getMarkdownTheme(),
109
+ makeUi(),
110
+ () => undefined,
111
+ () => undefined,
112
+ streamFn,
113
+ { apiKey: "test-key", sessionId: "session-1" },
114
+ );
115
+
116
+ await flushTurns();
117
+ for (const ch of "next") {
118
+ component.handleInput(ch);
119
+ }
120
+ component.handleInput("\n");
121
+ await flushTurns();
122
+
123
+ assert.equal(calls.length, 2);
124
+ assert.equal(calls[0]?.options?.apiKey, "test-key");
125
+ assert.equal(calls[1]?.options?.sessionId, "session-1");
126
+ assert.equal(calls[0]?.context.messages.length, 2);
127
+ assert.equal(calls[1]?.context.messages.length, 4);
128
+ assert.equal(calls[1]?.context.messages[1]?.role, "user");
129
+ assert.equal(calls[1]?.context.messages[2]?.role, "assistant");
130
+ assert.deepEqual(calls[1]?.context.messages[3]?.content, [{ type: "text", text: "next" }]);
131
+
132
+ const text = renderText(component);
133
+ assert.match(text, /First answer/);
134
+ assert.match(text, /Second answer/);
135
+ component.dispose();
136
+ });
137
+
138
+ test("BtwOverlayComponent aborts active stream on Escape", async () => {
139
+ let capturedSignal: AbortSignal | undefined;
140
+ let dismissed = false;
141
+ const streamFn = ((_model: any, _context: any, options?: { signal?: AbortSignal }) => {
142
+ capturedSignal = options?.signal;
143
+ return {
144
+ async *[Symbol.asyncIterator]() {
145
+ await new Promise<void>((resolve) => {
146
+ capturedSignal?.addEventListener("abort", () => resolve(), { once: true });
147
+ });
148
+ },
149
+ };
150
+ }) as any;
151
+
152
+ const component = new BtwOverlayComponent(
153
+ "What is btw?",
154
+ makeModel(),
155
+ "system",
156
+ makeMessages(),
157
+ getMarkdownTheme(),
158
+ makeUi(),
159
+ () => {
160
+ dismissed = true;
161
+ },
162
+ () => undefined,
163
+ streamFn,
164
+ );
165
+
166
+ await flushTurns();
167
+ component.handleInput("\u001b");
168
+ await flushTurns();
169
+
170
+ assert.equal(dismissed, true);
171
+ assert.equal(capturedSignal?.aborted, true);
172
+ });