@code-yeongyu/senpi 2026.6.10 → 2026.6.12-2

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 (197) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/README.md +18 -18
  3. package/dist/cli/startup-ui.d.ts +10 -0
  4. package/dist/cli/startup-ui.d.ts.map +1 -1
  5. package/dist/cli/startup-ui.js +72 -1
  6. package/dist/cli/startup-ui.js.map +1 -1
  7. package/dist/core/bash-executor.d.ts.map +1 -1
  8. package/dist/core/bash-executor.js +31 -7
  9. package/dist/core/bash-executor.js.map +1 -1
  10. package/dist/core/extensions/builtin/compaction/degradation-monitor.d.ts +0 -1
  11. package/dist/core/extensions/builtin/compaction/degradation-monitor.d.ts.map +1 -1
  12. package/dist/core/extensions/builtin/compaction/degradation-monitor.js +0 -1
  13. package/dist/core/extensions/builtin/compaction/degradation-monitor.js.map +1 -1
  14. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  15. package/dist/core/extensions/builtin/compaction/index.js +4 -4
  16. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  17. package/dist/core/extensions/builtin/compaction/policy.d.ts +0 -5
  18. package/dist/core/extensions/builtin/compaction/policy.d.ts.map +1 -1
  19. package/dist/core/extensions/builtin/compaction/policy.js +0 -4
  20. package/dist/core/extensions/builtin/compaction/policy.js.map +1 -1
  21. package/dist/core/extensions/builtin/compaction/speculative.d.ts +0 -6
  22. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  23. package/dist/core/extensions/builtin/compaction/speculative.js +0 -3
  24. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  25. package/dist/core/extensions/builtin/gpt-apply-patch/extension.d.ts +1 -1
  26. package/dist/core/extensions/builtin/gpt-apply-patch/extension.d.ts.map +1 -1
  27. package/dist/core/extensions/builtin/gpt-apply-patch/extension.js +10 -2
  28. package/dist/core/extensions/builtin/gpt-apply-patch/extension.js.map +1 -1
  29. package/dist/core/extensions/builtin/permission-system/cli.d.ts +0 -3
  30. package/dist/core/extensions/builtin/permission-system/cli.d.ts.map +1 -1
  31. package/dist/core/extensions/builtin/permission-system/cli.js +0 -13
  32. package/dist/core/extensions/builtin/permission-system/cli.js.map +1 -1
  33. package/dist/core/extensions/types.d.ts +1 -0
  34. package/dist/core/extensions/types.d.ts.map +1 -1
  35. package/dist/core/extensions/types.js.map +1 -1
  36. package/dist/core/model-registry.d.ts +0 -3
  37. package/dist/core/model-registry.d.ts.map +1 -1
  38. package/dist/core/model-registry.js +1 -3
  39. package/dist/core/model-registry.js.map +1 -1
  40. package/dist/core/model-resolver.d.ts +1 -0
  41. package/dist/core/model-resolver.d.ts.map +1 -1
  42. package/dist/core/model-resolver.js +20 -5
  43. package/dist/core/model-resolver.js.map +1 -1
  44. package/dist/core/resolve-config-value.d.ts +0 -4
  45. package/dist/core/resolve-config-value.d.ts.map +1 -1
  46. package/dist/core/resolve-config-value.js +0 -15
  47. package/dist/core/resolve-config-value.js.map +1 -1
  48. package/dist/core/settings-manager.d.ts +6 -0
  49. package/dist/core/settings-manager.d.ts.map +1 -1
  50. package/dist/core/settings-manager.js +17 -0
  51. package/dist/core/settings-manager.js.map +1 -1
  52. package/dist/core/tools/edit-diff.d.ts +0 -5
  53. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  54. package/dist/core/tools/edit-diff.js +0 -7
  55. package/dist/core/tools/edit-diff.js.map +1 -1
  56. package/dist/core/tools/index.d.ts +0 -5
  57. package/dist/core/tools/index.d.ts.map +1 -1
  58. package/dist/core/tools/index.js +0 -67
  59. package/dist/core/tools/index.js.map +1 -1
  60. package/dist/core/tools/output-accumulator.d.ts +3 -5
  61. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  62. package/dist/core/tools/output-accumulator.js +38 -34
  63. package/dist/core/tools/output-accumulator.js.map +1 -1
  64. package/dist/core/tools/tail-window.d.ts +15 -0
  65. package/dist/core/tools/tail-window.d.ts.map +1 -0
  66. package/dist/core/tools/tail-window.js +75 -0
  67. package/dist/core/tools/tail-window.js.map +1 -0
  68. package/dist/main.d.ts.map +1 -1
  69. package/dist/main.js +8 -1
  70. package/dist/main.js.map +1 -1
  71. package/dist/modes/interactive/components/first-time-setup.d.ts +25 -0
  72. package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -0
  73. package/dist/modes/interactive/components/first-time-setup.js +101 -0
  74. package/dist/modes/interactive/components/first-time-setup.js.map +1 -0
  75. package/dist/modes/interactive/components/index.d.ts +1 -0
  76. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  77. package/dist/modes/interactive/components/index.js +1 -0
  78. package/dist/modes/interactive/components/index.js.map +1 -1
  79. package/dist/modes/interactive/components/tool-execution.d.ts +6 -0
  80. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  81. package/dist/modes/interactive/components/tool-execution.js +45 -0
  82. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  83. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  84. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  85. package/dist/modes/interactive/interactive-mode.js +12 -5
  86. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  87. package/dist/modes/interactive/theme/theme.d.ts +0 -4
  88. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  89. package/dist/modes/interactive/theme/theme.js +0 -7
  90. package/dist/modes/interactive/theme/theme.js.map +1 -1
  91. package/dist/modes/rpc/rpc-types.d.ts +0 -1
  92. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  93. package/dist/modes/rpc/rpc-types.js.map +1 -1
  94. package/docs/compaction-guide.md +9 -9
  95. package/docs/compaction.md +2 -2
  96. package/docs/containerization.md +22 -22
  97. package/docs/custom-provider.md +13 -13
  98. package/docs/development.md +4 -2
  99. package/docs/extensions.md +18 -19
  100. package/docs/index.md +4 -4
  101. package/docs/json.md +21 -15
  102. package/docs/keybindings.md +6 -3
  103. package/docs/models.md +6 -6
  104. package/docs/packages.md +11 -11
  105. package/docs/prompt-templates.md +2 -2
  106. package/docs/providers.md +9 -9
  107. package/docs/rpc.md +14 -13
  108. package/docs/sdk.md +13 -9
  109. package/docs/security.md +7 -7
  110. package/docs/session-format.md +4 -4
  111. package/docs/sessions.md +14 -14
  112. package/docs/settings.md +13 -11
  113. package/docs/skills.md +8 -8
  114. package/docs/terminal-setup.md +2 -2
  115. package/docs/termux.md +3 -3
  116. package/docs/tui.md +4 -4
  117. package/docs/usage.md +51 -58
  118. package/node_modules/@earendil-works/pi-agent-core/README.md +4 -3
  119. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +0 -2
  120. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  121. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js +0 -4
  122. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  123. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +1 -1
  124. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  125. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  126. package/node_modules/@earendil-works/pi-ai/README.md +2 -4
  127. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts +0 -1
  128. package/node_modules/@earendil-works/pi-ai/dist/api-registry.d.ts.map +1 -1
  129. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js +0 -3
  130. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js.map +1 -1
  131. package/node_modules/@earendil-works/pi-ai/dist/models.d.ts.map +1 -1
  132. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +104 -454
  133. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  134. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +162 -475
  135. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  136. package/node_modules/@earendil-works/pi-ai/dist/models.js +12 -5
  137. package/node_modules/@earendil-works/pi-ai/dist/models.js.map +1 -1
  138. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
  139. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +11 -2
  140. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  141. package/node_modules/@earendil-works/pi-ai/dist/providers/google-shared.d.ts +0 -4
  142. package/node_modules/@earendil-works/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  143. package/node_modules/@earendil-works/pi-ai/dist/providers/google-shared.js +0 -13
  144. package/node_modules/@earendil-works/pi-ai/dist/providers/google-shared.js.map +1 -1
  145. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.d.ts +0 -6
  146. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  147. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +0 -14
  148. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js.map +1 -1
  149. package/node_modules/@earendil-works/pi-ai/dist/utils/event-stream.d.ts +4 -1
  150. package/node_modules/@earendil-works/pi-ai/dist/utils/event-stream.d.ts.map +1 -1
  151. package/node_modules/@earendil-works/pi-ai/dist/utils/event-stream.js +54 -8
  152. package/node_modules/@earendil-works/pi-ai/dist/utils/event-stream.js.map +1 -1
  153. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts +0 -4
  154. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
  155. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +0 -6
  156. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
  157. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  158. package/node_modules/@earendil-works/pi-tui/README.md +1 -2
  159. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +2 -0
  160. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  161. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +80 -7
  162. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  163. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts +1 -0
  164. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.d.ts.map +1 -1
  165. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +72 -1
  166. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js.map +1 -1
  167. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +2 -2
  168. package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
  169. package/node_modules/@earendil-works/pi-tui/dist/index.js +2 -2
  170. package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
  171. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts +0 -1
  172. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.d.ts.map +1 -1
  173. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js +4 -7
  174. package/node_modules/@earendil-works/pi-tui/dist/terminal-image.js.map +1 -1
  175. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts +1 -4
  176. package/node_modules/@earendil-works/pi-tui/dist/utils.d.ts.map +1 -1
  177. package/node_modules/@earendil-works/pi-tui/dist/utils.js +1 -7
  178. package/node_modules/@earendil-works/pi-tui/dist/utils.js.map +1 -1
  179. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  180. package/npm-shrinkwrap.json +12 -12
  181. package/package.json +4 -4
  182. package/dist/core/experimental.d.ts +0 -2
  183. package/dist/core/experimental.d.ts.map +0 -1
  184. package/dist/core/experimental.js +0 -4
  185. package/dist/core/experimental.js.map +0 -1
  186. package/dist/core/extensions/builtin/compaction/overflow-detection.d.ts +0 -11
  187. package/dist/core/extensions/builtin/compaction/overflow-detection.d.ts.map +0 -1
  188. package/dist/core/extensions/builtin/compaction/overflow-detection.js +0 -40
  189. package/dist/core/extensions/builtin/compaction/overflow-detection.js.map +0 -1
  190. package/dist/core/extensions/builtin/system-messages.d.ts +0 -47
  191. package/dist/core/extensions/builtin/system-messages.d.ts.map +0 -1
  192. package/dist/core/extensions/builtin/system-messages.js +0 -117
  193. package/dist/core/extensions/builtin/system-messages.js.map +0 -1
  194. package/dist/core/index.d.ts +0 -13
  195. package/dist/core/index.d.ts.map +0 -1
  196. package/dist/core/index.js +0 -13
  197. package/dist/core/index.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [2026.6.12-2] - 2026-06-12
4
4
 
5
5
  ### Added
6
6
 
@@ -8,6 +8,33 @@
8
8
 
9
9
  ### Removed
10
10
 
11
+ ## [2026.6.12] - 2026-06-12
12
+
13
+ ### Added
14
+
15
+ - Added an experimental first-time setup flow behind `PI_EXPERIMENTAL=1` that asks for a dark/light theme choice (preselecting the detected appearance) and opt-in analytics data sharing on first launch with the default agent directory; opting in stores a `trackingId` in `settings.json`.
16
+ - Added local benchmark coverage for interactive assistant transcript rendering and large bash output handling.
17
+
18
+ ### Fixed
19
+
20
+ - Improved large bash output retention by using a head-indexed rolling buffer and a compact tail window for streaming output snapshots.
21
+ - Restored pending tool-call wait rendering during thinking updates and stopped stale pending animations after the interactive display clears.
22
+
23
+ ### Removed
24
+
25
+ ## [2026.6.10-2] - 2026-06-10
26
+
27
+ ### Added
28
+
29
+ ### Fixed
30
+
31
+ - `apply_patch` now activates based on the model's API (`openai-responses`, `azure-openai-responses`, `openai-codex-responses`) instead of a provider allowlist, so OpenAI-compatible custom providers serving `gpt-*` models get the freeform patch tool, and Chat Completions models (which reject freeform tools) never do.
32
+ - The `compaction.restorationEnabled` setting is now honored at runtime; previously the restoration tracker read a hardcoded default and could not be disabled.
33
+
34
+ ### Removed
35
+
36
+ - Removed dead internal modules and exports: `system-messages.ts`, compaction `overflow-detection.ts`, the `PI_EXPERIMENTAL` flag, unused tool factory functions (`createTool`, `createToolDefinition`, `createCodingToolDefinitions`, `createReadOnlyToolDefinitions`, `createAllTools`), `computeEditDiff`, `resolveHeaders`, `isLightTheme`, `RpcCommandType`, and the unwired compaction cooldown helpers.
37
+
11
38
  ## [2026.6.10] - 2026-06-10
12
39
 
13
40
  ### Added
package/README.md CHANGED
@@ -142,7 +142,7 @@ For each built-in provider, pi maintains a list of tool-capable models, updated
142
142
 
143
143
  See [docs/providers.md](docs/providers.md) for detailed setup instructions.
144
144
 
145
- **Custom providers & models:** Add providers via `~/.pi/agent/models.json` if they speak a supported API (OpenAI, Anthropic, Google). For custom APIs or OAuth, use extensions. See [docs/models.md](docs/models.md) and [docs/custom-provider.md](docs/custom-provider.md).
145
+ **Custom providers & models:** Add providers via `~/.senpi/agent/models.json` if they speak a supported API (OpenAI, Anthropic, Google). For custom APIs or OAuth, use extensions. See [docs/models.md](docs/models.md) and [docs/custom-provider.md](docs/custom-provider.md).
146
146
 
147
147
  ---
148
148
 
@@ -179,7 +179,7 @@ Type `/` in the editor to trigger commands. [Extensions](#extensions) can regist
179
179
  |---------|-------------|
180
180
  | `/login`, `/logout` | OAuth authentication |
181
181
  | `/model` | Switch models |
182
- | `/scoped-models` | Enable/disable models for Ctrl+P cycling |
182
+ | `/favorite-models` | Manage favorite models for Ctrl+P cycling |
183
183
  | `/settings` | Thinking level, theme, message delivery, transport |
184
184
  | `/resume` | Pick from previous sessions |
185
185
  | `/new` | Start a new session |
@@ -200,7 +200,7 @@ Type `/` in the editor to trigger commands. [Extensions](#extensions) can regist
200
200
 
201
201
  ### Keyboard Shortcuts
202
202
 
203
- See `/hotkeys` for the full list. Customize via `~/.pi/agent/keybindings.json`. See [docs/keybindings.md](docs/keybindings.md).
203
+ See `/hotkeys` for the full list. Customize via `~/.senpi/agent/keybindings.json`. See [docs/keybindings.md](docs/keybindings.md).
204
204
 
205
205
  **Commonly used:**
206
206
 
@@ -237,7 +237,7 @@ Sessions are stored as JSONL files with a tree structure. Each entry has an `id`
237
237
 
238
238
  ### Management
239
239
 
240
- Sessions auto-save to `~/.pi/agent/sessions/` organized by working directory.
240
+ Sessions auto-save to `~/.senpi/agent/sessions/` organized by working directory.
241
241
 
242
242
  ```bash
243
243
  pi -c # Continue most recent session
@@ -284,14 +284,14 @@ Use `/settings` to modify common options, or edit JSON files directly:
284
284
 
285
285
  | Location | Scope |
286
286
  |----------|-------|
287
- | `~/.pi/agent/settings.json` | Global (all projects) |
288
- | `.pi/settings.json` | Project (overrides global) |
287
+ | `~/.senpi/agent/settings.json` | Global (all projects) |
288
+ | `.senpi/settings.json` | Project (overrides global) |
289
289
 
290
290
  See [docs/settings.md](docs/settings.md) for all options.
291
291
 
292
292
  ### Project Trust
293
293
 
294
- On interactive startup, pi asks before trusting a project folder that contains project-local extensions or settings and has no saved decision for the folder or a parent folder in `~/.pi/agent/trust.json`. Trusting a project allows pi to load `.pi/settings.json` and `.pi` resources, install missing project packages, and execute project extensions.
294
+ On interactive startup, pi asks before trusting a project folder that contains project-local extensions or settings and has no saved decision for the folder or a parent folder in `~/.senpi/agent/trust.json`. Trusting a project allows pi to load `.senpi/settings.json` and `.senpi` resources, install missing project packages, and execute project extensions.
295
295
 
296
296
  Before the trust decision, pi loads only context files, user/global extensions, and CLI `-e` extensions so they can handle the `project_trust` event. Project-local extensions, project package-managed extensions, and project settings are loaded only after the project is trusted. This split also applies when switching to a session from a different cwd whose trust has not been resolved in the current process.
297
297
 
@@ -327,7 +327,7 @@ Disable context file loading with `--no-context-files` (or `-nc`).
327
327
 
328
328
  ### System Prompt
329
329
 
330
- Replace the default system prompt with `.pi/SYSTEM.md` (project) or `~/.pi/agent/SYSTEM.md` (global). Append without replacing via `APPEND_SYSTEM.md`.
330
+ Senpi builds its system prompt dynamically from the registered tools. Replace it with `--system-prompt <text>` or from an extension via the `before_agent_start` event; append without replacing via `--append-system-prompt <text>`. Upstream pi's `SYSTEM.md`/`APPEND_SYSTEM.md` file overrides are not supported in this fork.
331
331
 
332
332
  ---
333
333
 
@@ -338,19 +338,19 @@ Replace the default system prompt with `.pi/SYSTEM.md` (project) or `~/.pi/agent
338
338
  Reusable prompts as Markdown files. Type `/name` to expand.
339
339
 
340
340
  ```markdown
341
- <!-- ~/.pi/agent/prompts/review.md -->
341
+ <!-- ~/.senpi/agent/prompts/review.md -->
342
342
  Review this code for bugs, security issues, and performance problems.
343
343
  Focus on: {{focus}}
344
344
  ```
345
345
 
346
- Place in `~/.pi/agent/prompts/`, `.pi/prompts/`, or a [pi package](#pi-packages) to share with others. See [docs/prompt-templates.md](docs/prompt-templates.md).
346
+ Place in `~/.senpi/agent/prompts/`, `.senpi/prompts/`, or a [pi package](#pi-packages) to share with others. See [docs/prompt-templates.md](docs/prompt-templates.md).
347
347
 
348
348
  ### Skills
349
349
 
350
350
  On-demand capability packages following the [Agent Skills standard](https://agentskills.io). Invoke via `/skill:name` or let the agent load them automatically.
351
351
 
352
352
  ```markdown
353
- <!-- ~/.pi/agent/skills/my-skill/SKILL.md -->
353
+ <!-- ~/.senpi/agent/skills/my-skill/SKILL.md -->
354
354
  # My Skill
355
355
  Use this skill when the user asks about X.
356
356
 
@@ -359,7 +359,7 @@ Use this skill when the user asks about X.
359
359
  2. Then that
360
360
  ```
361
361
 
362
- Place in `~/.pi/agent/skills/`, `~/.agents/skills/`, `.pi/skills/`, or `.agents/skills/` (from `cwd` up through parent directories) or a [pi package](#pi-packages) to share with others. See [docs/skills.md](docs/skills.md).
362
+ Place in `~/.senpi/agent/skills/`, `~/.agents/skills/`, `.senpi/skills/`, or `.agents/skills/` (from `cwd` up through parent directories) or a [pi package](#pi-packages) to share with others. See [docs/skills.md](docs/skills.md).
363
363
 
364
364
  ### Extensions
365
365
 
@@ -391,13 +391,13 @@ The default export can also be `async`. pi waits for async extension factories b
391
391
  - Games while waiting (yes, Doom runs)
392
392
  - ...anything you can dream up
393
393
 
394
- Place in `~/.pi/agent/extensions/`, `.pi/extensions/`, or a [pi package](#pi-packages) to share with others. See [docs/extensions.md](docs/extensions.md) and [examples/extensions/](examples/extensions/).
394
+ Place in `~/.senpi/agent/extensions/`, `.senpi/extensions/`, or a [pi package](#pi-packages) to share with others. See [docs/extensions.md](docs/extensions.md) and [examples/extensions/](examples/extensions/).
395
395
 
396
396
  ### Themes
397
397
 
398
398
  Built-in: `dark`, `light`. Themes hot-reload: modify the active theme file and pi immediately applies changes.
399
399
 
400
- Place in `~/.pi/agent/themes/`, `.pi/themes/`, or a [pi package](#pi-packages) to share with others. See [docs/themes.md](docs/themes.md).
400
+ Place in `~/.senpi/agent/themes/`, `.senpi/themes/`, or a [pi package](#pi-packages) to share with others. See [docs/themes.md](docs/themes.md).
401
401
 
402
402
  ### Pi Packages
403
403
 
@@ -427,7 +427,7 @@ pi update npm:@foo/pi-tools # update one package
427
427
  pi config # enable/disable extensions, skills, prompts, themes
428
428
  ```
429
429
 
430
- Packages install to `~/.pi/agent/git/` (git) or `~/.pi/agent/npm/` (npm). Use `-l` for project-local installs (`.pi/git/`, `.pi/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `pi update`, so use `pi install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
430
+ Packages install to `~/.senpi/agent/git/` (git) or `~/.senpi/agent/npm/` (npm). Use `-l` for project-local installs (`.senpi/git/`, `.senpi/npm/`). Git `@ref` values are pinned tags or commits; pinned packages are skipped by `pi update`, so use `pi install git:host/user/repo@new-ref` to move an existing package to a new ref. Git packages install dependencies with `npm install --omit=dev` by default, so runtime deps must be listed under `dependencies`; when `npmCommand` is configured, git packages use plain `install` for compatibility with wrappers. If you use a Node version manager and want package installs to reuse a stable npm context, set `npmCommand` in `settings.json`, for example `["mise", "exec", "node@20", "--", "npm"]`.
431
431
 
432
432
  Create a package by adding a `pi` key to `package.json`:
433
433
 
@@ -552,7 +552,7 @@ cat README.md | pi -p "Summarize this text"
552
552
  | `--provider <name>` | Provider (anthropic, openai, google, etc.) |
553
553
  | `--model <pattern>` | Model pattern or ID (supports `provider/id` and optional `:<thinking>`) |
554
554
  | `--api-key <key>` | API key (overrides env vars) |
555
- | `--thinking <level>` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh` |
555
+ | `--thinking <level>` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` |
556
556
  | `--models <patterns>` | Comma-separated patterns for Ctrl+P cycling |
557
557
  | `--list-models [search]` | List available models |
558
558
 
@@ -658,8 +658,8 @@ pi --thinking high "Solve this complex problem"
658
658
 
659
659
  | Variable | Description |
660
660
  |----------|-------------|
661
- | `PI_CODING_AGENT_DIR` | Override config directory (default: `~/.pi/agent`) |
662
- | `PI_CODING_AGENT_SESSION_DIR` | Override session storage directory (overridden by `--session-dir`) |
661
+ | `SENPI_CODING_AGENT_DIR` | Override config directory (default: `~/.senpi/agent`) |
662
+ | `SENPI_CODING_AGENT_SESSION_DIR` | Override session storage directory (overridden by `--session-dir`) |
663
663
  | `PI_PACKAGE_DIR` | Override package directory (useful for Nix/Guix where store paths tokenize poorly) |
664
664
  | `PI_OFFLINE` | Disable startup network operations, including update checks, package update checks, and install/update telemetry |
665
665
  | `PI_SKIP_VERSION_CHECK` | Skip the Pi version update check at startup. This prevents the `pi.dev` latest-version request |
@@ -1,7 +1,17 @@
1
1
  import type { SettingsManager } from "../core/settings-manager.ts";
2
+ /**
3
+ * First-time setup runs when all of these hold:
4
+ * - this is the official Pi distribution (not a fork/rebrand)
5
+ * - experimental features are enabled (PI_EXPERIMENTAL=1)
6
+ * - the default agent directory is used (no custom agent dir override)
7
+ * - setup was not completed before (settings.json does not exist)
8
+ */
9
+ export declare function shouldRunFirstTimeSetup(settingsPath?: string): boolean;
2
10
  export declare function showStartupSelector<T>(settingsManager: SettingsManager, title: string, options: Array<{
3
11
  label: string;
4
12
  value: T;
5
13
  }>): Promise<T | undefined>;
14
+ /** Show the first-time setup dialog and persist the result */
15
+ export declare function showFirstTimeSetup(settingsManager: SettingsManager): Promise<void>;
6
16
  export declare function showStartupInput(settingsManager: SettingsManager, title: string, placeholder?: string): Promise<string | undefined>;
7
17
  //# sourceMappingURL=startup-ui.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"startup-ui.d.ts","sourceRoot":"","sources":["../../src/cli/startup-ui.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAmBnE,wBAAsB,mBAAmB,CAAC,CAAC,EAC1C,eAAe,EAAE,eAAe,EAChC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC,GACzC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CA0BxB;AAED,wBAAsB,gBAAgB,CACrC,eAAe,EAAE,eAAe,EAChC,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA6B7B"}
1
+ {"version":3,"file":"startup-ui.d.ts","sourceRoot":"","sources":["../../src/cli/startup-ui.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA6CnE;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,YAAY,GAAE,MAA0B,GAAG,OAAO,CAiBzF;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EAC1C,eAAe,EAAE,eAAe,EAChC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC,GACzC,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CA0BxB;AAED,8DAA8D;AAC9D,wBAAsB,kBAAkB,CAAC,eAAe,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAkCxF;AAED,wBAAsB,gBAAgB,CACrC,eAAe,EAAE,eAAe,EAChC,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA6B7B"}
@@ -1,8 +1,22 @@
1
1
  import { ProcessTerminal, setKeybindings, TUI } from "@earendil-works/pi-tui";
2
+ import { existsSync } from "fs";
3
+ import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, getSettingsPath, PACKAGE_NAME } from "../config.js";
2
4
  import { KeybindingsManager } from "../core/keybindings.js";
3
5
  import { ExtensionInputComponent } from "../modes/interactive/components/extension-input.js";
4
6
  import { ExtensionSelectorComponent } from "../modes/interactive/components/extension-selector.js";
5
- import { initTheme } from "../modes/interactive/theme/theme.js";
7
+ import { FirstTimeSetupComponent, } from "../modes/interactive/components/first-time-setup.js";
8
+ import { detectTerminalBackground, initTheme, setTheme } from "../modes/interactive/theme/theme.js";
9
+ const OFFICIAL_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
10
+ const OFFICIAL_APP_NAME = "pi";
11
+ const OFFICIAL_CONFIG_DIR_NAME = ".pi";
12
+ function isOfficialDistribution({ packageName, appName, configDirName }) {
13
+ return (packageName === OFFICIAL_PACKAGE_NAME &&
14
+ appName === OFFICIAL_APP_NAME &&
15
+ configDirName === OFFICIAL_CONFIG_DIR_NAME);
16
+ }
17
+ function areExperimentalFeaturesEnabled() {
18
+ return process.env.PI_EXPERIMENTAL === "1";
19
+ }
6
20
  function createStartupTui(settingsManager) {
7
21
  initTheme(settingsManager.getTheme());
8
22
  setKeybindings(KeybindingsManager.create());
@@ -15,6 +29,29 @@ async function clearStartupTui(ui) {
15
29
  ui.requestRender();
16
30
  await new Promise((resolve) => setTimeout(resolve, 25));
17
31
  }
32
+ /**
33
+ * First-time setup runs when all of these hold:
34
+ * - this is the official Pi distribution (not a fork/rebrand)
35
+ * - experimental features are enabled (PI_EXPERIMENTAL=1)
36
+ * - the default agent directory is used (no custom agent dir override)
37
+ * - setup was not completed before (settings.json does not exist)
38
+ */
39
+ export function shouldRunFirstTimeSetup(settingsPath = getSettingsPath()) {
40
+ if (!isOfficialDistribution({
41
+ packageName: PACKAGE_NAME,
42
+ appName: APP_NAME,
43
+ configDirName: CONFIG_DIR_NAME,
44
+ })) {
45
+ return false;
46
+ }
47
+ if (!areExperimentalFeaturesEnabled()) {
48
+ return false;
49
+ }
50
+ if (process.env[ENV_AGENT_DIR]) {
51
+ return false;
52
+ }
53
+ return !existsSync(settingsPath);
54
+ }
18
55
  export async function showStartupSelector(settingsManager, title, options) {
19
56
  return new Promise((resolve) => {
20
57
  const ui = createStartupTui(settingsManager);
@@ -34,6 +71,40 @@ export async function showStartupSelector(settingsManager, title, options) {
34
71
  ui.start();
35
72
  });
36
73
  }
74
+ /** Show the first-time setup dialog and persist the result */
75
+ export async function showFirstTimeSetup(settingsManager) {
76
+ return new Promise((resolve) => {
77
+ const ui = createStartupTui(settingsManager);
78
+ let settled = false;
79
+ const finish = async (result) => {
80
+ if (settled) {
81
+ return;
82
+ }
83
+ settled = true;
84
+ if (result) {
85
+ settingsManager.setTheme(result.theme);
86
+ settingsManager.setEnableAnalytics(result.shareAnalytics);
87
+ await settingsManager.flush();
88
+ }
89
+ await clearStartupTui(ui);
90
+ ui.stop();
91
+ resolve();
92
+ };
93
+ const component = new FirstTimeSetupComponent({
94
+ detectedTheme: detectTerminalBackground().theme,
95
+ onThemePreview: (themeName) => {
96
+ setTheme(themeName);
97
+ ui.invalidate();
98
+ ui.requestRender();
99
+ },
100
+ onSubmit: (result) => void finish(result),
101
+ onCancel: () => void finish(undefined),
102
+ });
103
+ ui.addChild(component);
104
+ ui.setFocus(component);
105
+ ui.start();
106
+ });
107
+ }
37
108
  export async function showStartupInput(settingsManager, title, placeholder) {
38
109
  return new Promise((resolve) => {
39
110
  const ui = createStartupTui(settingsManager);
@@ -1 +1 @@
1
- {"version":3,"file":"startup-ui.js","sourceRoot":"","sources":["../../src/cli/startup-ui.ts"],"names":[],"mappings":"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","sourcesContent":["import { ProcessTerminal, setKeybindings, TUI } from \"@earendil-works/pi-tui\";\nimport { KeybindingsManager } from \"../core/keybindings.ts\";\nimport type { SettingsManager } from \"../core/settings-manager.ts\";\nimport { ExtensionInputComponent } from \"../modes/interactive/components/extension-input.ts\";\nimport { ExtensionSelectorComponent } from \"../modes/interactive/components/extension-selector.ts\";\nimport { initTheme } from \"../modes/interactive/theme/theme.ts\";\n\nfunction createStartupTui(settingsManager: SettingsManager): TUI {\n\tinitTheme(settingsManager.getTheme());\n\tsetKeybindings(KeybindingsManager.create());\n\tconst ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());\n\tui.setClearOnShrink(settingsManager.getClearOnShrink());\n\treturn ui;\n}\n\nasync function clearStartupTui(ui: TUI): Promise<void> {\n\tui.clear();\n\tui.requestRender();\n\tawait new Promise((resolve) => setTimeout(resolve, 25));\n}\n\nexport async function showStartupSelector<T>(\n\tsettingsManager: SettingsManager,\n\ttitle: string,\n\toptions: Array<{ label: string; value: T }>,\n): Promise<T | undefined> {\n\treturn new Promise((resolve) => {\n\t\tconst ui = createStartupTui(settingsManager);\n\n\t\tlet settled = false;\n\t\tconst finish = async (result: T | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tawait clearStartupTui(ui);\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\ttitle,\n\t\t\toptions.map((option) => option.label),\n\t\t\t(option) => void finish(options.find((entry) => entry.label === option)?.value),\n\t\t\t() => void finish(undefined),\n\t\t\t{ tui: ui },\n\t\t);\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector);\n\t\tui.start();\n\t});\n}\n\nexport async function showStartupInput(\n\tsettingsManager: SettingsManager,\n\ttitle: string,\n\tplaceholder?: string,\n): Promise<string | undefined> {\n\treturn new Promise((resolve) => {\n\t\tconst ui = createStartupTui(settingsManager);\n\n\t\tlet settled = false;\n\t\tconst finish = async (result: string | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tinput.dispose();\n\t\t\tawait clearStartupTui(ui);\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst input = new ExtensionInputComponent(\n\t\t\ttitle,\n\t\t\tplaceholder,\n\t\t\t(value) => void finish(value),\n\t\t\t() => void finish(undefined),\n\t\t\t{\n\t\t\t\ttui: ui,\n\t\t\t},\n\t\t);\n\t\tui.addChild(input);\n\t\tui.setFocus(input);\n\t\tui.start();\n\t});\n}\n"]}
1
+ {"version":3,"file":"startup-ui.js","sourceRoot":"","sources":["../../src/cli/startup-ui.ts"],"names":[],"mappings":"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","sourcesContent":["import { ProcessTerminal, setKeybindings, TUI } from \"@earendil-works/pi-tui\";\nimport { existsSync } from \"fs\";\nimport { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR, getSettingsPath, PACKAGE_NAME } from \"../config.ts\";\nimport { KeybindingsManager } from \"../core/keybindings.ts\";\nimport type { SettingsManager } from \"../core/settings-manager.ts\";\nimport { ExtensionInputComponent } from \"../modes/interactive/components/extension-input.ts\";\nimport { ExtensionSelectorComponent } from \"../modes/interactive/components/extension-selector.ts\";\nimport {\n\tFirstTimeSetupComponent,\n\ttype FirstTimeSetupResult,\n} from \"../modes/interactive/components/first-time-setup.ts\";\nimport { detectTerminalBackground, initTheme, setTheme } from \"../modes/interactive/theme/theme.ts\";\n\nconst OFFICIAL_PACKAGE_NAME = \"@earendil-works/pi-coding-agent\";\nconst OFFICIAL_APP_NAME = \"pi\";\nconst OFFICIAL_CONFIG_DIR_NAME = \".pi\";\n\ninterface DistributionMetadata {\n\tpackageName: string;\n\tappName: string;\n\tconfigDirName: string;\n}\n\nfunction isOfficialDistribution({ packageName, appName, configDirName }: DistributionMetadata): boolean {\n\treturn (\n\t\tpackageName === OFFICIAL_PACKAGE_NAME &&\n\t\tappName === OFFICIAL_APP_NAME &&\n\t\tconfigDirName === OFFICIAL_CONFIG_DIR_NAME\n\t);\n}\n\nfunction areExperimentalFeaturesEnabled(): boolean {\n\treturn process.env.PI_EXPERIMENTAL === \"1\";\n}\n\nfunction createStartupTui(settingsManager: SettingsManager): TUI {\n\tinitTheme(settingsManager.getTheme());\n\tsetKeybindings(KeybindingsManager.create());\n\tconst ui = new TUI(new ProcessTerminal(), settingsManager.getShowHardwareCursor());\n\tui.setClearOnShrink(settingsManager.getClearOnShrink());\n\treturn ui;\n}\n\nasync function clearStartupTui(ui: TUI): Promise<void> {\n\tui.clear();\n\tui.requestRender();\n\tawait new Promise((resolve) => setTimeout(resolve, 25));\n}\n\n/**\n * First-time setup runs when all of these hold:\n * - this is the official Pi distribution (not a fork/rebrand)\n * - experimental features are enabled (PI_EXPERIMENTAL=1)\n * - the default agent directory is used (no custom agent dir override)\n * - setup was not completed before (settings.json does not exist)\n */\nexport function shouldRunFirstTimeSetup(settingsPath: string = getSettingsPath()): boolean {\n\tif (\n\t\t!isOfficialDistribution({\n\t\t\tpackageName: PACKAGE_NAME,\n\t\t\tappName: APP_NAME,\n\t\t\tconfigDirName: CONFIG_DIR_NAME,\n\t\t})\n\t) {\n\t\treturn false;\n\t}\n\tif (!areExperimentalFeaturesEnabled()) {\n\t\treturn false;\n\t}\n\tif (process.env[ENV_AGENT_DIR]) {\n\t\treturn false;\n\t}\n\treturn !existsSync(settingsPath);\n}\n\nexport async function showStartupSelector<T>(\n\tsettingsManager: SettingsManager,\n\ttitle: string,\n\toptions: Array<{ label: string; value: T }>,\n): Promise<T | undefined> {\n\treturn new Promise((resolve) => {\n\t\tconst ui = createStartupTui(settingsManager);\n\n\t\tlet settled = false;\n\t\tconst finish = async (result: T | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tawait clearStartupTui(ui);\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst selector = new ExtensionSelectorComponent(\n\t\t\ttitle,\n\t\t\toptions.map((option) => option.label),\n\t\t\t(option) => void finish(options.find((entry) => entry.label === option)?.value),\n\t\t\t() => void finish(undefined),\n\t\t\t{ tui: ui },\n\t\t);\n\t\tui.addChild(selector);\n\t\tui.setFocus(selector);\n\t\tui.start();\n\t});\n}\n\n/** Show the first-time setup dialog and persist the result */\nexport async function showFirstTimeSetup(settingsManager: SettingsManager): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tconst ui = createStartupTui(settingsManager);\n\n\t\tlet settled = false;\n\t\tconst finish = async (result: FirstTimeSetupResult | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tif (result) {\n\t\t\t\tsettingsManager.setTheme(result.theme);\n\t\t\t\tsettingsManager.setEnableAnalytics(result.shareAnalytics);\n\t\t\t\tawait settingsManager.flush();\n\t\t\t}\n\t\t\tawait clearStartupTui(ui);\n\t\t\tui.stop();\n\t\t\tresolve();\n\t\t};\n\n\t\tconst component = new FirstTimeSetupComponent({\n\t\t\tdetectedTheme: detectTerminalBackground().theme,\n\t\t\tonThemePreview: (themeName) => {\n\t\t\t\tsetTheme(themeName);\n\t\t\t\tui.invalidate();\n\t\t\t\tui.requestRender();\n\t\t\t},\n\t\t\tonSubmit: (result) => void finish(result),\n\t\t\tonCancel: () => void finish(undefined),\n\t\t});\n\t\tui.addChild(component);\n\t\tui.setFocus(component);\n\t\tui.start();\n\t});\n}\n\nexport async function showStartupInput(\n\tsettingsManager: SettingsManager,\n\ttitle: string,\n\tplaceholder?: string,\n): Promise<string | undefined> {\n\treturn new Promise((resolve) => {\n\t\tconst ui = createStartupTui(settingsManager);\n\n\t\tlet settled = false;\n\t\tconst finish = async (result: string | undefined) => {\n\t\t\tif (settled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tinput.dispose();\n\t\t\tawait clearStartupTui(ui);\n\t\t\tui.stop();\n\t\t\tresolve(result);\n\t\t};\n\n\t\tconst input = new ExtensionInputComponent(\n\t\t\ttitle,\n\t\t\tplaceholder,\n\t\t\t(value) => void finish(value),\n\t\t\t() => void finish(undefined),\n\t\t\t{\n\t\t\t\ttui: ui,\n\t\t\t},\n\t\t);\n\t\tui.addChild(input);\n\t\tui.setFocus(input);\n\t\tui.start();\n\t});\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAqGrB"}
1
+ {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAgIrB"}
@@ -21,11 +21,20 @@ import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
21
21
  */
22
22
  export async function executeBashWithOperations(command, cwd, operations, options) {
23
23
  const outputChunks = [];
24
+ let outputChunkStart = 0;
24
25
  let outputBytes = 0;
25
26
  const maxOutputBytes = DEFAULT_MAX_BYTES * 2;
26
27
  let tempFilePath;
27
28
  let tempFileStream;
28
29
  let totalBytes = 0;
30
+ const compactOutputChunks = () => {
31
+ if (outputChunkStart < 64) {
32
+ return;
33
+ }
34
+ outputChunks.splice(0, outputChunkStart);
35
+ outputChunkStart = 0;
36
+ };
37
+ const outputText = () => outputChunks.slice(outputChunkStart).join("");
29
38
  const ensureTempFile = () => {
30
39
  if (tempFilePath) {
31
40
  return;
@@ -33,8 +42,11 @@ export async function executeBashWithOperations(command, cwd, operations, option
33
42
  const id = randomBytes(8).toString("hex");
34
43
  tempFilePath = join(tmpdir(), `pi-bash-${id}.log`);
35
44
  tempFileStream = createWriteStream(tempFilePath);
36
- for (const chunk of outputChunks) {
37
- tempFileStream.write(chunk);
45
+ for (let i = outputChunkStart; i < outputChunks.length; i++) {
46
+ const chunk = outputChunks[i];
47
+ if (chunk !== undefined) {
48
+ tempFileStream.write(chunk);
49
+ }
38
50
  }
39
51
  };
40
52
  const decoder = new TextDecoder();
@@ -49,24 +61,35 @@ export async function executeBashWithOperations(command, cwd, operations, option
49
61
  if (tempFileStream) {
50
62
  tempFileStream.write(text);
51
63
  }
52
- // Keep rolling buffer
53
64
  outputChunks.push(text);
54
65
  outputBytes += text.length;
55
- while (outputBytes > maxOutputBytes && outputChunks.length > 1) {
56
- const removed = outputChunks.shift();
66
+ while (outputBytes > maxOutputBytes && outputChunkStart < outputChunks.length - 1) {
67
+ const removed = outputChunks[outputChunkStart];
68
+ if (removed === undefined) {
69
+ break;
70
+ }
57
71
  outputBytes -= removed.length;
72
+ outputChunkStart++;
58
73
  }
74
+ compactOutputChunks();
59
75
  // Stream to callback
60
76
  if (options?.onChunk) {
61
77
  options.onChunk(text);
62
78
  }
63
79
  };
80
+ const finishDecoder = () => {
81
+ const finalText = sanitizeBinaryOutput(stripAnsi(decoder.decode())).replace(/\r/g, "");
82
+ if (finalText.length > 0) {
83
+ onData(Buffer.from(finalText, "utf-8"));
84
+ }
85
+ };
64
86
  try {
65
87
  const result = await operations.exec(command, cwd, {
66
88
  onData,
67
89
  signal: options?.signal,
68
90
  });
69
- const fullOutput = outputChunks.join("");
91
+ finishDecoder();
92
+ const fullOutput = outputText();
70
93
  const truncationResult = truncateTail(fullOutput);
71
94
  if (truncationResult.truncated) {
72
95
  ensureTempFile();
@@ -86,7 +109,8 @@ export async function executeBashWithOperations(command, cwd, operations, option
86
109
  catch (err) {
87
110
  // Check if it was an abort
88
111
  if (options?.signal?.aborted) {
89
- const fullOutput = outputChunks.join("");
112
+ finishDecoder();
113
+ const fullOutput = outputText();
90
114
  const truncationResult = truncateTail(fullOutput);
91
115
  if (truncationResult.truncated) {
92
116
  ensureTempFile();
@@ -1 +1 @@
1
- {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stripAnsi } from \"../utils/ansi.ts\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport type { BashOperations } from \"./tools/bash.ts\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst ensureTempFile = () => {\n\t\tif (tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\tfor (const chunk of outputChunks) {\n\t\t\ttempFileStream.write(chunk);\n\t\t}\n\t};\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\tensureTempFile();\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tif (truncationResult.truncated) {\n\t\t\tensureTempFile();\n\t\t}\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\tif (truncationResult.truncated) {\n\t\t\t\tensureTempFile();\n\t\t\t}\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}
1
+ {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stripAnsi } from \"../utils/ansi.ts\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport type { BashOperations } from \"./tools/bash.ts\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputChunkStart = 0;\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst compactOutputChunks = () => {\n\t\tif (outputChunkStart < 64) {\n\t\t\treturn;\n\t\t}\n\t\toutputChunks.splice(0, outputChunkStart);\n\t\toutputChunkStart = 0;\n\t};\n\n\tconst outputText = () => outputChunks.slice(outputChunkStart).join(\"\");\n\n\tconst ensureTempFile = () => {\n\t\tif (tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\tfor (let i = outputChunkStart; i < outputChunks.length; i++) {\n\t\t\tconst chunk = outputChunks[i];\n\t\t\tif (chunk !== undefined) {\n\t\t\t\ttempFileStream.write(chunk);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\tensureTempFile();\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunkStart < outputChunks.length - 1) {\n\t\t\tconst removed = outputChunks[outputChunkStart];\n\t\t\tif (removed === undefined) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\toutputBytes -= removed.length;\n\t\t\toutputChunkStart++;\n\t\t}\n\t\tcompactOutputChunks();\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\tconst finishDecoder = () => {\n\t\tconst finalText = sanitizeBinaryOutput(stripAnsi(decoder.decode())).replace(/\\r/g, \"\");\n\t\tif (finalText.length > 0) {\n\t\t\tonData(Buffer.from(finalText, \"utf-8\"));\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tfinishDecoder();\n\t\tconst fullOutput = outputText();\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tif (truncationResult.truncated) {\n\t\t\tensureTempFile();\n\t\t}\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tfinishDecoder();\n\t\t\tconst fullOutput = outputText();\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\tif (truncationResult.truncated) {\n\t\t\t\tensureTempFile();\n\t\t\t}\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}
@@ -25,7 +25,6 @@
25
25
  export declare const POST_COMPACTION_MONITOR_COUNT = 5;
26
26
  export declare const POST_COMPACTION_NO_TEXT_THRESHOLD = 3;
27
27
  export declare const MAX_RECOVERY_ATTEMPTS = 3;
28
- export declare const RECOVERY_COMPACTION_SUPPRESSION_MS = 5000;
29
28
  export declare const RECOVERY_INSTRUCTIONS = "RECOVERY: prior compaction caused degraded responses; rebuild context";
30
29
  export declare const RECOVERY_NOTIFICATION = "Detected repeated no-text assistant responses; retried compaction recovery.";
31
30
  export interface DegradationMonitorState {
@@ -1 +1 @@
1
- {"version":3,"file":"degradation-monitor.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/degradation-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,iCAAiC,IAAI,CAAC;AACnD,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,kCAAkC,OAAQ,CAAC;AAExD,eAAO,MAAM,qBAAqB,0EAA0E,CAAC;AAC7G,eAAO,MAAM,qBAAqB,gFAAgF,CAAC;AAEnH,MAAM,WAAW,uBAAuB;IACvC,4BAA4B,EAAE,MAAM,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,0BAA0B,EAAE,OAAO,CAAC;IACpC,gBAAgB,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACzC,eAAe,EAAE,CAAC,OAAO,EAAE;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5G,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,UAAU,2BAA2B;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,UAAU,qBAAqB;IAC9B,OAAO,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,2BAA2B,EAAE,CAAC;KACvC,CAAC;CACF;AAED,wBAAgB,6BAA6B,IAAI,uBAAuB,CAOvE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAI1E;AAcD,wBAAsB,gBAAgB,CACrC,KAAK,EAAE,uBAAuB,EAC9B,KAAK,EAAE,qBAAqB,EAC5B,OAAO,EAAE,yBAAyB,GAChC,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAIlE"}
1
+ {"version":3,"file":"degradation-monitor.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/degradation-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,iCAAiC,IAAI,CAAC;AACnD,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,eAAO,MAAM,qBAAqB,0EAA0E,CAAC;AAC7G,eAAO,MAAM,qBAAqB,gFAAgF,CAAC;AAEnH,MAAM,WAAW,uBAAuB;IACvC,4BAA4B,EAAE,MAAM,CAAC;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,0BAA0B,EAAE,OAAO,CAAC;IACpC,gBAAgB,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,yBAAyB;IACzC,eAAe,EAAE,CAAC,OAAO,EAAE;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5G,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,UAAU,2BAA2B;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,UAAU,qBAAqB;IAC9B,OAAO,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,2BAA2B,EAAE,CAAC;KACvC,CAAC;CACF;AAED,wBAAgB,6BAA6B,IAAI,uBAAuB,CAOvE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAI1E;AAcD,wBAAsB,gBAAgB,CACrC,KAAK,EAAE,uBAAuB,EAC9B,KAAK,EAAE,qBAAqB,EAC5B,OAAO,EAAE,yBAAyB,GAChC,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAIlE"}
@@ -25,7 +25,6 @@
25
25
  export const POST_COMPACTION_MONITOR_COUNT = 5;
26
26
  export const POST_COMPACTION_NO_TEXT_THRESHOLD = 3;
27
27
  export const MAX_RECOVERY_ATTEMPTS = 3;
28
- export const RECOVERY_COMPACTION_SUPPRESSION_MS = 5_000;
29
28
  export const RECOVERY_INSTRUCTIONS = "RECOVERY: prior compaction caused degraded responses; rebuild context";
30
29
  export const RECOVERY_NOTIFICATION = "Detected repeated no-text assistant responses; retried compaction recovery.";
31
30
  export function createDegradationMonitorState() {
@@ -1 +1 @@
1
- {"version":3,"file":"degradation-monitor.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/degradation-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAC/C,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AACnD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AACvC,MAAM,CAAC,MAAM,kCAAkC,GAAG,KAAK,CAAC;AAExD,MAAM,CAAC,MAAM,qBAAqB,GAAG,uEAAuE,CAAC;AAC7G,MAAM,CAAC,MAAM,qBAAqB,GAAG,6EAA6E,CAAC;AA0BnH,MAAM,UAAU,6BAA6B;IAC5C,OAAO;QACN,4BAA4B,EAAE,CAAC;QAC/B,aAAa,EAAE,CAAC;QAChB,0BAA0B,EAAE,KAAK;QACjC,gBAAgB,EAAE,CAAC;KACnB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAA8B;IACnE,KAAK,CAAC,4BAA4B,GAAG,6BAA6B,CAAC;IACnE,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;IACxB,KAAK,CAAC,0BAA0B,GAAG,KAAK,CAAC;AAC1C,CAAC;AAED,SAAS,uBAAuB,CAAC,KAA4B;IAC5D,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnF,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,KAA8B,EAC9B,KAA4B,EAC5B,OAAkC;IAElC,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,EAAE,CAAC;QAC7C,OAAO;IACR,CAAC;IAED,IAAI,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;QACxB,OAAO;IACR,CAAC;IAED,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;IAEzB,IAAI,KAAK,CAAC,aAAa,GAAG,iCAAiC,EAAE,CAAC;QAC7D,OAAO;IACR,CAAC;IAED,IAAI,KAAK,CAAC,0BAA0B,EAAE,CAAC;QACtC,OAAO;IACR,CAAC;IAED,IAAI,KAAK,CAAC,gBAAgB,IAAI,qBAAqB,EAAE,CAAC;QACrD,OAAO;IACR,CAAC;IAED,KAAK,CAAC,0BAA0B,GAAG,IAAI,CAAC;IACxC,KAAK,CAAC,gBAAgB,IAAI,CAAC,CAAC;IAC5B,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;IAExB,MAAM,OAAO,CAAC,eAAe,CAAC,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAC7E,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA8B;IAC3D,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,EAAE,CAAC;QAC5C,KAAK,CAAC,4BAA4B,IAAI,CAAC,CAAC;IACzC,CAAC;AACF,CAAC","sourcesContent":["/**\n * Post-compaction degradation monitor.\n *\n * Watches the first POST_COMPACTION_MONITOR_COUNT assistant turns after a\n * compaction completes and triggers a single recovery compaction if the\n * model emits POST_COMPACTION_NO_TEXT_THRESHOLD consecutive assistant\n * messages with no text content (only step-start/step-finish parts).\n *\n * Algorithm ported from omo's preemptive-compaction-degradation-monitor:\n * `/Users/yeongyu/local-workspaces/omo/src/hooks/preemptive-compaction-degradation-monitor.ts`\n *\n * Pi extension surface differences vs omo:\n * - Recovery dispatch: speculative generation plus `ctx.applyCompaction(...)`\n * instead of omo's `client.session.summarize(...)`. The \"RECOVERY:\" prefix\n * in customInstructions is the disambiguator; CompactionReason stays\n * \"extension\" (no new variant in v1).\n * - Notification: `ctx.notify(message)` instead of omo's\n * `client.tui.showToast(...)`.\n *\n * Recovery cap: 1 per compaction cycle (gate via recoveryTriggeredThisCycle).\n * MAX_RECOVERY_ATTEMPTS = 3 is exported for future iteration but the v1 cycle\n * gate keeps the effective cap at 1 — recoveryAttempts is incremented only on\n * successful trigger, so once it hits 1 the cycle gate also fires regardless.\n */\n\nexport const POST_COMPACTION_MONITOR_COUNT = 5;\nexport const POST_COMPACTION_NO_TEXT_THRESHOLD = 3;\nexport const MAX_RECOVERY_ATTEMPTS = 3;\nexport const RECOVERY_COMPACTION_SUPPRESSION_MS = 5_000;\n\nexport const RECOVERY_INSTRUCTIONS = \"RECOVERY: prior compaction caused degraded responses; rebuild context\";\nexport const RECOVERY_NOTIFICATION = \"Detected repeated no-text assistant responses; retried compaction recovery.\";\n\nexport interface DegradationMonitorState {\n\tpostCompactionTurnsRemaining: number;\n\tnoTextCounter: number;\n\trecoveryTriggeredThisCycle: boolean;\n\trecoveryAttempts: number;\n}\n\nexport interface DegradationMonitorContext {\n\tapplyCompaction: (options: { customInstructions: string }) => Promise<{ applied: boolean; reason: string }>;\n\tnotify: (message: string) => void;\n}\n\ninterface MonitoredMessageContentPart {\n\ttype: string;\n\ttext?: string;\n}\n\ninterface MonitoredMessageEvent {\n\tmessage: {\n\t\trole: string;\n\t\tcontent: MonitoredMessageContentPart[];\n\t};\n}\n\nexport function createDegradationMonitorState(): DegradationMonitorState {\n\treturn {\n\t\tpostCompactionTurnsRemaining: 0,\n\t\tnoTextCounter: 0,\n\t\trecoveryTriggeredThisCycle: false,\n\t\trecoveryAttempts: 0,\n\t};\n}\n\n/**\n * Reset the per-cycle monitor fields when a fresh compaction completes.\n * Opens a new POST_COMPACTION_MONITOR_COUNT-turn window and clears the\n * cycle-trigger gate. recoveryAttempts is preserved across cycles so the\n * MAX_RECOVERY_ATTEMPTS ceiling remains a session-wide guard.\n */\nexport function resetOnSessionCompact(state: DegradationMonitorState): void {\n\tstate.postCompactionTurnsRemaining = POST_COMPACTION_MONITOR_COUNT;\n\tstate.noTextCounter = 0;\n\tstate.recoveryTriggeredThisCycle = false;\n}\n\nfunction assistantMessageHasText(event: MonitoredMessageEvent): boolean {\n\tif (event.message.role !== \"assistant\") {\n\t\treturn true;\n\t}\n\tfor (const part of event.message.content) {\n\t\tif (part.type === \"text\" && typeof part.text === \"string\" && part.text.length > 0) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nexport async function handleMessageEnd(\n\tstate: DegradationMonitorState,\n\tevent: MonitoredMessageEvent,\n\tcontext: DegradationMonitorContext,\n): Promise<void> {\n\tif (state.postCompactionTurnsRemaining <= 0) {\n\t\treturn;\n\t}\n\n\tif (assistantMessageHasText(event)) {\n\t\tstate.noTextCounter = 0;\n\t\treturn;\n\t}\n\n\tstate.noTextCounter += 1;\n\n\tif (state.noTextCounter < POST_COMPACTION_NO_TEXT_THRESHOLD) {\n\t\treturn;\n\t}\n\n\tif (state.recoveryTriggeredThisCycle) {\n\t\treturn;\n\t}\n\n\tif (state.recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) {\n\t\treturn;\n\t}\n\n\tstate.recoveryTriggeredThisCycle = true;\n\tstate.recoveryAttempts += 1;\n\tstate.noTextCounter = 0;\n\n\tawait context.applyCompaction({ customInstructions: RECOVERY_INSTRUCTIONS });\n\tcontext.notify(RECOVERY_NOTIFICATION);\n}\n\nexport function handleTurnEnd(state: DegradationMonitorState): void {\n\tif (state.postCompactionTurnsRemaining > 0) {\n\t\tstate.postCompactionTurnsRemaining -= 1;\n\t}\n}\n"]}
1
+ {"version":3,"file":"degradation-monitor.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/degradation-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAC/C,MAAM,CAAC,MAAM,iCAAiC,GAAG,CAAC,CAAC;AACnD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC,MAAM,CAAC,MAAM,qBAAqB,GAAG,uEAAuE,CAAC;AAC7G,MAAM,CAAC,MAAM,qBAAqB,GAAG,6EAA6E,CAAC;AA0BnH,MAAM,UAAU,6BAA6B;IAC5C,OAAO;QACN,4BAA4B,EAAE,CAAC;QAC/B,aAAa,EAAE,CAAC;QAChB,0BAA0B,EAAE,KAAK;QACjC,gBAAgB,EAAE,CAAC;KACnB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAA8B;IACnE,KAAK,CAAC,4BAA4B,GAAG,6BAA6B,CAAC;IACnE,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;IACxB,KAAK,CAAC,0BAA0B,GAAG,KAAK,CAAC;AAC1C,CAAC;AAED,SAAS,uBAAuB,CAAC,KAA4B;IAC5D,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC;IACb,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACnF,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,KAA8B,EAC9B,KAA4B,EAC5B,OAAkC;IAElC,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,EAAE,CAAC;QAC7C,OAAO;IACR,CAAC;IAED,IAAI,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;QACxB,OAAO;IACR,CAAC;IAED,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;IAEzB,IAAI,KAAK,CAAC,aAAa,GAAG,iCAAiC,EAAE,CAAC;QAC7D,OAAO;IACR,CAAC;IAED,IAAI,KAAK,CAAC,0BAA0B,EAAE,CAAC;QACtC,OAAO;IACR,CAAC;IAED,IAAI,KAAK,CAAC,gBAAgB,IAAI,qBAAqB,EAAE,CAAC;QACrD,OAAO;IACR,CAAC;IAED,KAAK,CAAC,0BAA0B,GAAG,IAAI,CAAC;IACxC,KAAK,CAAC,gBAAgB,IAAI,CAAC,CAAC;IAC5B,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;IAExB,MAAM,OAAO,CAAC,eAAe,CAAC,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAC7E,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA8B;IAC3D,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,EAAE,CAAC;QAC5C,KAAK,CAAC,4BAA4B,IAAI,CAAC,CAAC;IACzC,CAAC;AACF,CAAC","sourcesContent":["/**\n * Post-compaction degradation monitor.\n *\n * Watches the first POST_COMPACTION_MONITOR_COUNT assistant turns after a\n * compaction completes and triggers a single recovery compaction if the\n * model emits POST_COMPACTION_NO_TEXT_THRESHOLD consecutive assistant\n * messages with no text content (only step-start/step-finish parts).\n *\n * Algorithm ported from omo's preemptive-compaction-degradation-monitor:\n * `/Users/yeongyu/local-workspaces/omo/src/hooks/preemptive-compaction-degradation-monitor.ts`\n *\n * Pi extension surface differences vs omo:\n * - Recovery dispatch: speculative generation plus `ctx.applyCompaction(...)`\n * instead of omo's `client.session.summarize(...)`. The \"RECOVERY:\" prefix\n * in customInstructions is the disambiguator; CompactionReason stays\n * \"extension\" (no new variant in v1).\n * - Notification: `ctx.notify(message)` instead of omo's\n * `client.tui.showToast(...)`.\n *\n * Recovery cap: 1 per compaction cycle (gate via recoveryTriggeredThisCycle).\n * MAX_RECOVERY_ATTEMPTS = 3 is exported for future iteration but the v1 cycle\n * gate keeps the effective cap at 1 — recoveryAttempts is incremented only on\n * successful trigger, so once it hits 1 the cycle gate also fires regardless.\n */\n\nexport const POST_COMPACTION_MONITOR_COUNT = 5;\nexport const POST_COMPACTION_NO_TEXT_THRESHOLD = 3;\nexport const MAX_RECOVERY_ATTEMPTS = 3;\n\nexport const RECOVERY_INSTRUCTIONS = \"RECOVERY: prior compaction caused degraded responses; rebuild context\";\nexport const RECOVERY_NOTIFICATION = \"Detected repeated no-text assistant responses; retried compaction recovery.\";\n\nexport interface DegradationMonitorState {\n\tpostCompactionTurnsRemaining: number;\n\tnoTextCounter: number;\n\trecoveryTriggeredThisCycle: boolean;\n\trecoveryAttempts: number;\n}\n\nexport interface DegradationMonitorContext {\n\tapplyCompaction: (options: { customInstructions: string }) => Promise<{ applied: boolean; reason: string }>;\n\tnotify: (message: string) => void;\n}\n\ninterface MonitoredMessageContentPart {\n\ttype: string;\n\ttext?: string;\n}\n\ninterface MonitoredMessageEvent {\n\tmessage: {\n\t\trole: string;\n\t\tcontent: MonitoredMessageContentPart[];\n\t};\n}\n\nexport function createDegradationMonitorState(): DegradationMonitorState {\n\treturn {\n\t\tpostCompactionTurnsRemaining: 0,\n\t\tnoTextCounter: 0,\n\t\trecoveryTriggeredThisCycle: false,\n\t\trecoveryAttempts: 0,\n\t};\n}\n\n/**\n * Reset the per-cycle monitor fields when a fresh compaction completes.\n * Opens a new POST_COMPACTION_MONITOR_COUNT-turn window and clears the\n * cycle-trigger gate. recoveryAttempts is preserved across cycles so the\n * MAX_RECOVERY_ATTEMPTS ceiling remains a session-wide guard.\n */\nexport function resetOnSessionCompact(state: DegradationMonitorState): void {\n\tstate.postCompactionTurnsRemaining = POST_COMPACTION_MONITOR_COUNT;\n\tstate.noTextCounter = 0;\n\tstate.recoveryTriggeredThisCycle = false;\n}\n\nfunction assistantMessageHasText(event: MonitoredMessageEvent): boolean {\n\tif (event.message.role !== \"assistant\") {\n\t\treturn true;\n\t}\n\tfor (const part of event.message.content) {\n\t\tif (part.type === \"text\" && typeof part.text === \"string\" && part.text.length > 0) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nexport async function handleMessageEnd(\n\tstate: DegradationMonitorState,\n\tevent: MonitoredMessageEvent,\n\tcontext: DegradationMonitorContext,\n): Promise<void> {\n\tif (state.postCompactionTurnsRemaining <= 0) {\n\t\treturn;\n\t}\n\n\tif (assistantMessageHasText(event)) {\n\t\tstate.noTextCounter = 0;\n\t\treturn;\n\t}\n\n\tstate.noTextCounter += 1;\n\n\tif (state.noTextCounter < POST_COMPACTION_NO_TEXT_THRESHOLD) {\n\t\treturn;\n\t}\n\n\tif (state.recoveryTriggeredThisCycle) {\n\t\treturn;\n\t}\n\n\tif (state.recoveryAttempts >= MAX_RECOVERY_ATTEMPTS) {\n\t\treturn;\n\t}\n\n\tstate.recoveryTriggeredThisCycle = true;\n\tstate.recoveryAttempts += 1;\n\tstate.noTextCounter = 0;\n\n\tawait context.applyCompaction({ customInstructions: RECOVERY_INSTRUCTIONS });\n\tcontext.notify(RECOVERY_NOTIFICATION);\n}\n\nexport function handleTurnEnd(state: DegradationMonitorState): void {\n\tif (state.postCompactionTurnsRemaining > 0) {\n\t\tstate.postCompactionTurnsRemaining -= 1;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,YAAY,EAA+C,MAAM,gBAAgB,CAAC;AAuI9G,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAsTlE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,YAAY,EAA+C,MAAM,gBAAgB,CAAC;AAuI9G,MAAM,CAAC,OAAO,UAAU,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAuTlE"}
@@ -1,5 +1,4 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { DEFAULT_COMPACTION_SETTINGS } from "../../../compaction/index.js";
3
2
  import { convertToLlm } from "../../../messages.js";
4
3
  import * as checkpointState from "./checkpoint-state.js";
5
4
  import * as breaker from "./circuit-breaker.js";
@@ -247,15 +246,16 @@ export default function compactionExtension(pi) {
247
246
  resetOnSessionCompact(degradationState);
248
247
  todoBridge.restoreTodosIfMissing(pi, ctx);
249
248
  const usage = ctx.getContextUsage();
250
- if (DEFAULT_COMPACTION_SETTINGS.restorationEnabled) {
249
+ const settings = ctx.getCompactionSettings();
250
+ if (settings.restorationEnabled ?? true) {
251
251
  restoration.preparePendingPayload(restorationState, {
252
252
  accepted: true,
253
253
  reason: event.reason,
254
254
  compactionEntryId: event.compactionEntry.id,
255
255
  contextWindow: usage?.contextWindow ?? ctx.model?.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
256
256
  usageTokens: usage?.tokens ?? null,
257
- reserveTokens: DEFAULT_COMPACTION_SETTINGS.reserveTokens,
258
- settings: DEFAULT_COMPACTION_SETTINGS,
257
+ reserveTokens: settings.reserveTokens,
258
+ settings,
259
259
  keptMessages: keptEntries.flatMap((entry) => {
260
260
  if (entry.type !== "message")
261
261
  return [];