@oh-my-pi/pi-coding-agent 15.12.3 → 15.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (457) hide show
  1. package/CHANGELOG.md +347 -7
  2. package/dist/cli.js +1615 -1231
  3. package/dist/types/async/job-manager.d.ts +15 -0
  4. package/dist/types/autolearn/controller.d.ts +25 -0
  5. package/dist/types/autolearn/managed-skills.d.ts +45 -0
  6. package/dist/types/autoresearch/state.d.ts +1 -1
  7. package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
  8. package/dist/types/autoresearch/tools/log-experiment.d.ts +1 -1
  9. package/dist/types/autoresearch/tools/run-experiment.d.ts +1 -1
  10. package/dist/types/autoresearch/tools/update-notes.d.ts +1 -1
  11. package/dist/types/autoresearch/types.d.ts +1 -1
  12. package/dist/types/cli/args.d.ts +19 -2
  13. package/dist/types/cli/models-cli.d.ts +49 -0
  14. package/dist/types/cli/session-picker.d.ts +1 -1
  15. package/dist/types/cli/setup-cli.d.ts +1 -1
  16. package/dist/types/cli/setup-model-picker.d.ts +14 -0
  17. package/dist/types/collab/protocol.d.ts +1 -1
  18. package/dist/types/commands/launch.d.ts +0 -3
  19. package/dist/types/commands/models.d.ts +33 -0
  20. package/dist/types/commands/say.d.ts +24 -0
  21. package/dist/types/commands/token.d.ts +25 -0
  22. package/dist/types/commit/agentic/tools/analyze-file.d.ts +1 -1
  23. package/dist/types/commit/agentic/tools/git-file-diff.d.ts +1 -1
  24. package/dist/types/commit/agentic/tools/git-hunk.d.ts +1 -1
  25. package/dist/types/commit/agentic/tools/git-overview.d.ts +1 -1
  26. package/dist/types/commit/agentic/tools/propose-changelog.d.ts +1 -1
  27. package/dist/types/commit/agentic/tools/propose-commit.d.ts +1 -1
  28. package/dist/types/commit/agentic/tools/recent-commits.d.ts +1 -1
  29. package/dist/types/commit/agentic/tools/schemas.d.ts +1 -1
  30. package/dist/types/commit/agentic/tools/split-commit.d.ts +1 -1
  31. package/dist/types/commit/changelog/generate.d.ts +1 -1
  32. package/dist/types/commit/shared-llm.d.ts +1 -1
  33. package/dist/types/config/keybindings.d.ts +3 -3
  34. package/dist/types/config/model-registry.d.ts +17 -0
  35. package/dist/types/config/models-config-schema.d.ts +13 -1
  36. package/dist/types/config/models-config.d.ts +8 -2
  37. package/dist/types/config/settings-schema.d.ts +281 -58
  38. package/dist/types/edit/hashline/params.d.ts +1 -1
  39. package/dist/types/edit/modes/apply-patch.d.ts +1 -1
  40. package/dist/types/edit/modes/patch.d.ts +1 -1
  41. package/dist/types/edit/modes/replace.d.ts +1 -1
  42. package/dist/types/export/html/index.d.ts +2 -1
  43. package/dist/types/extensibility/custom-commands/types.d.ts +2 -2
  44. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  45. package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
  46. package/dist/types/extensibility/extensions/runner.d.ts +3 -1
  47. package/dist/types/extensibility/extensions/types.d.ts +49 -3
  48. package/dist/types/extensibility/hooks/index.d.ts +2 -1
  49. package/dist/types/extensibility/hooks/types.d.ts +2 -2
  50. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +9 -0
  51. package/dist/types/extensibility/plugins/loader.d.ts +11 -0
  52. package/dist/types/extensibility/shared-events.d.ts +1 -1
  53. package/dist/types/extensibility/skills.d.ts +10 -0
  54. package/dist/types/goals/guided-setup.d.ts +18 -0
  55. package/dist/types/goals/state.d.ts +1 -1
  56. package/dist/types/goals/tools/goal-tool.d.ts +1 -1
  57. package/dist/types/hindsight/transcript.d.ts +1 -1
  58. package/dist/types/index.d.ts +5 -0
  59. package/dist/types/internal-urls/local-protocol.d.ts +4 -2
  60. package/dist/types/lsp/types.d.ts +1 -1
  61. package/dist/types/main.d.ts +4 -3
  62. package/dist/types/mcp/manager.d.ts +8 -0
  63. package/dist/types/mcp/startup-events.d.ts +11 -0
  64. package/dist/types/memories/index.d.ts +7 -0
  65. package/dist/types/memory-backend/local-backend.d.ts +4 -3
  66. package/dist/types/mnemopi/config.d.ts +28 -0
  67. package/dist/types/modes/acp/acp-agent.d.ts +1 -2
  68. package/dist/types/modes/components/agent-hub.d.ts +6 -0
  69. package/dist/types/modes/components/assistant-message.d.ts +1 -2
  70. package/dist/types/modes/components/compaction-summary-message.d.ts +15 -1
  71. package/dist/types/modes/components/custom-editor.d.ts +39 -1
  72. package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
  73. package/dist/types/modes/components/index.d.ts +1 -0
  74. package/dist/types/modes/components/logout-account-selector.d.ts +8 -0
  75. package/dist/types/modes/components/session-selector.d.ts +1 -1
  76. package/dist/types/modes/components/status-line/component.d.ts +9 -5
  77. package/dist/types/modes/components/status-line/types.d.ts +2 -1
  78. package/dist/types/modes/components/tool-execution.d.ts +26 -16
  79. package/dist/types/modes/components/transcript-container.d.ts +23 -2
  80. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  81. package/dist/types/modes/components/usage-row.d.ts +3 -0
  82. package/dist/types/modes/controllers/command-controller.d.ts +2 -2
  83. package/dist/types/modes/controllers/event-controller.d.ts +0 -17
  84. package/dist/types/modes/controllers/input-controller.d.ts +14 -0
  85. package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
  86. package/dist/types/modes/gradient-highlight.d.ts +9 -4
  87. package/dist/types/modes/image-references.d.ts +6 -0
  88. package/dist/types/modes/interactive-mode.d.ts +27 -6
  89. package/dist/types/modes/magic-keywords.d.ts +13 -1
  90. package/dist/types/modes/rpc/rpc-mode.d.ts +35 -1
  91. package/dist/types/modes/rpc/rpc-types.d.ts +9 -1
  92. package/dist/types/modes/runtime-init.d.ts +4 -0
  93. package/dist/types/modes/theme/theme.d.ts +13 -2
  94. package/dist/types/modes/types.d.ts +8 -7
  95. package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
  96. package/dist/types/registry/agent-registry.d.ts +17 -0
  97. package/dist/types/secrets/obfuscator.d.ts +1 -1
  98. package/dist/types/session/agent-session.d.ts +28 -35
  99. package/dist/types/session/agent-storage.d.ts +2 -1
  100. package/dist/types/session/indexed-session-storage.d.ts +3 -3
  101. package/dist/types/session/messages.d.ts +8 -10
  102. package/dist/types/session/session-context.d.ts +39 -0
  103. package/dist/types/session/session-entries.d.ts +159 -0
  104. package/dist/types/session/session-listing.d.ts +69 -0
  105. package/dist/types/session/session-loader.d.ts +16 -0
  106. package/dist/types/session/session-manager.d.ts +85 -462
  107. package/dist/types/session/session-migrations.d.ts +12 -0
  108. package/dist/types/session/session-paths.d.ts +25 -0
  109. package/dist/types/session/session-persistence.d.ts +8 -0
  110. package/dist/types/session/session-storage.d.ts +11 -7
  111. package/dist/types/session/snapcompact-inline.d.ts +12 -1
  112. package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
  113. package/dist/types/session/tool-choice-queue.d.ts +6 -6
  114. package/dist/types/slash-commands/helpers/logout.d.ts +15 -0
  115. package/dist/types/stt/asr-client.d.ts +90 -0
  116. package/dist/types/stt/asr-protocol.d.ts +97 -0
  117. package/dist/types/stt/asr-worker.d.ts +2 -0
  118. package/dist/types/stt/downloader.d.ts +38 -0
  119. package/dist/types/stt/endpointer.d.ts +59 -0
  120. package/dist/types/stt/index.d.ts +5 -1
  121. package/dist/types/stt/models.d.ts +120 -0
  122. package/dist/types/stt/recorder.d.ts +17 -0
  123. package/dist/types/stt/stt-controller.d.ts +6 -0
  124. package/dist/types/stt/transcriber.d.ts +5 -7
  125. package/dist/types/stt/wav.d.ts +29 -0
  126. package/dist/types/system-prompt.d.ts +4 -0
  127. package/dist/types/task/executor.d.ts +2 -0
  128. package/dist/types/task/index.d.ts +9 -1
  129. package/dist/types/task/types.d.ts +37 -1
  130. package/dist/types/tools/ask.d.ts +1 -1
  131. package/dist/types/tools/ast-edit.d.ts +1 -1
  132. package/dist/types/tools/ast-grep.d.ts +1 -1
  133. package/dist/types/tools/bash.d.ts +3 -3
  134. package/dist/types/tools/browser/cmux/cmux-tab.d.ts +202 -0
  135. package/dist/types/tools/browser/cmux/rpc.d.ts +70 -0
  136. package/dist/types/tools/browser/cmux/socket-client.d.ts +19 -0
  137. package/dist/types/tools/browser/registry.d.ts +16 -3
  138. package/dist/types/tools/browser/render.d.ts +2 -0
  139. package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
  140. package/dist/types/tools/browser/tab-supervisor.d.ts +16 -4
  141. package/dist/types/tools/browser.d.ts +3 -1
  142. package/dist/types/tools/checkpoint.d.ts +1 -1
  143. package/dist/types/tools/debug.d.ts +1 -1
  144. package/dist/types/tools/eval-render.d.ts +1 -1
  145. package/dist/types/tools/eval.d.ts +1 -1
  146. package/dist/types/tools/find.d.ts +1 -1
  147. package/dist/types/tools/gh.d.ts +1 -1
  148. package/dist/types/tools/image-gen.d.ts +1 -1
  149. package/dist/types/tools/index.d.ts +14 -2
  150. package/dist/types/tools/inspect-image.d.ts +1 -1
  151. package/dist/types/tools/irc.d.ts +2 -1
  152. package/dist/types/tools/job.d.ts +1 -1
  153. package/dist/types/tools/learn.d.ts +51 -0
  154. package/dist/types/tools/manage-skill.d.ts +40 -0
  155. package/dist/types/tools/memory-edit.d.ts +1 -1
  156. package/dist/types/tools/memory-recall.d.ts +1 -1
  157. package/dist/types/tools/memory-reflect.d.ts +1 -1
  158. package/dist/types/tools/memory-retain.d.ts +1 -1
  159. package/dist/types/tools/plan-mode-guard.d.ts +10 -0
  160. package/dist/types/tools/read.d.ts +1 -1
  161. package/dist/types/tools/render-mermaid.d.ts +1 -1
  162. package/dist/types/tools/renderers.d.ts +7 -11
  163. package/dist/types/tools/resolve.d.ts +1 -1
  164. package/dist/types/tools/review.d.ts +1 -1
  165. package/dist/types/tools/search-tool-bm25.d.ts +1 -1
  166. package/dist/types/tools/search.d.ts +1 -1
  167. package/dist/types/tools/ssh.d.ts +2 -2
  168. package/dist/types/tools/todo.d.ts +2 -2
  169. package/dist/types/tools/tts.d.ts +26 -1
  170. package/dist/types/tools/write.d.ts +2 -2
  171. package/dist/types/tts/downloader.d.ts +20 -0
  172. package/dist/types/tts/index.d.ts +8 -0
  173. package/dist/types/tts/models.d.ts +82 -0
  174. package/dist/types/tts/player.d.ts +32 -0
  175. package/dist/types/tts/runtime.d.ts +6 -0
  176. package/dist/types/tts/streaming-player.d.ts +41 -0
  177. package/dist/types/tts/tts-client.d.ts +93 -0
  178. package/dist/types/tts/tts-protocol.d.ts +95 -0
  179. package/dist/types/tts/tts-worker.d.ts +2 -0
  180. package/dist/types/tts/vocalizer.d.ts +41 -0
  181. package/dist/types/tts/wav.d.ts +8 -0
  182. package/dist/types/utils/clipboard.d.ts +4 -3
  183. package/dist/types/utils/image-loading.d.ts +18 -1
  184. package/dist/types/utils/thinking-display.d.ts +17 -0
  185. package/dist/types/utils/tool-choice.d.ts +8 -0
  186. package/dist/types/utils/tools-manager.d.ts +2 -1
  187. package/dist/types/utils/tools-manager.test.d.ts +1 -0
  188. package/dist/types/web/scrapers/github.d.ts +1 -1
  189. package/dist/types/web/search/index.d.ts +1 -1
  190. package/package.json +17 -16
  191. package/src/async/job-manager.ts +49 -0
  192. package/src/autolearn/controller.ts +139 -0
  193. package/src/autolearn/managed-skills.ts +257 -0
  194. package/src/autoresearch/state.ts +1 -1
  195. package/src/autoresearch/storage.ts +2 -1
  196. package/src/autoresearch/tools/init-experiment.ts +1 -1
  197. package/src/autoresearch/tools/log-experiment.ts +1 -1
  198. package/src/autoresearch/tools/run-experiment.ts +1 -1
  199. package/src/autoresearch/tools/update-notes.ts +1 -1
  200. package/src/autoresearch/types.ts +1 -1
  201. package/src/cli/args.ts +56 -10
  202. package/src/cli/auth-gateway-cli.ts +1 -1
  203. package/src/cli/bench-cli.ts +1 -1
  204. package/src/cli/dry-balance-cli.ts +1 -1
  205. package/src/cli/models-cli.ts +427 -0
  206. package/src/cli/session-picker.ts +2 -1
  207. package/src/cli/setup-cli.ts +148 -47
  208. package/src/cli/setup-model-picker.ts +43 -0
  209. package/src/cli-commands.ts +3 -0
  210. package/src/cli.ts +45 -13
  211. package/src/collab/host.ts +10 -13
  212. package/src/collab/protocol.ts +1 -1
  213. package/src/commands/launch.ts +0 -3
  214. package/src/commands/models.ts +61 -0
  215. package/src/commands/say.ts +102 -0
  216. package/src/commands/setup.ts +1 -1
  217. package/src/commands/token.ts +89 -0
  218. package/src/commit/agentic/tools/analyze-file.ts +4 -1
  219. package/src/commit/agentic/tools/git-file-diff.ts +1 -1
  220. package/src/commit/agentic/tools/git-hunk.ts +1 -1
  221. package/src/commit/agentic/tools/git-overview.ts +1 -1
  222. package/src/commit/agentic/tools/propose-changelog.ts +1 -1
  223. package/src/commit/agentic/tools/propose-commit.ts +1 -1
  224. package/src/commit/agentic/tools/recent-commits.ts +1 -1
  225. package/src/commit/agentic/tools/schemas.ts +1 -1
  226. package/src/commit/agentic/tools/split-commit.ts +1 -1
  227. package/src/commit/analysis/summary.ts +1 -1
  228. package/src/commit/changelog/generate.ts +1 -1
  229. package/src/commit/shared-llm.ts +1 -1
  230. package/src/config/keybindings.ts +2 -2
  231. package/src/config/model-discovery.ts +11 -5
  232. package/src/config/model-registry.ts +79 -21
  233. package/src/config/model-resolver.ts +2 -2
  234. package/src/config/models-config-schema.ts +5 -2
  235. package/src/config/models-config.ts +2 -1
  236. package/src/config/settings-schema.ts +266 -32
  237. package/src/config/settings.ts +10 -0
  238. package/src/discovery/builtin.ts +23 -1
  239. package/src/discovery/claude-plugins.ts +44 -5
  240. package/src/discovery/helpers.ts +41 -1
  241. package/src/edit/hashline/params.ts +1 -1
  242. package/src/edit/modes/apply-patch.ts +1 -1
  243. package/src/edit/modes/patch.ts +1 -1
  244. package/src/edit/modes/replace.ts +1 -1
  245. package/src/eval/__tests__/budget-bridge.test.ts +1 -1
  246. package/src/eval/agent-bridge.ts +1 -1
  247. package/src/eval/completion-bridge.ts +1 -1
  248. package/src/eval/js/shared/prelude.txt +69 -17
  249. package/src/export/html/index.ts +3 -6
  250. package/src/export/html/template.js +24 -2
  251. package/src/export/html/tool-views.generated.js +2 -2
  252. package/src/extensibility/custom-commands/loader.ts +1 -1
  253. package/src/extensibility/custom-commands/types.ts +2 -2
  254. package/src/extensibility/custom-tools/loader.ts +1 -1
  255. package/src/extensibility/custom-tools/types.ts +2 -2
  256. package/src/extensibility/extensions/loader.ts +2 -2
  257. package/src/extensibility/extensions/model-api.ts +41 -0
  258. package/src/extensibility/extensions/runner.ts +4 -0
  259. package/src/extensibility/extensions/types.ts +54 -3
  260. package/src/extensibility/extensions/wrapper.ts +41 -5
  261. package/src/extensibility/hooks/index.ts +2 -1
  262. package/src/extensibility/hooks/loader.ts +1 -1
  263. package/src/extensibility/hooks/types.ts +2 -2
  264. package/src/extensibility/plugins/legacy-pi-compat.ts +43 -13
  265. package/src/extensibility/plugins/loader.ts +30 -19
  266. package/src/extensibility/plugins/manager.ts +221 -90
  267. package/src/extensibility/shared-events.ts +1 -1
  268. package/src/extensibility/skills.ts +101 -5
  269. package/src/goals/guided-setup.ts +133 -0
  270. package/src/goals/state.ts +1 -1
  271. package/src/goals/tools/goal-tool.ts +1 -1
  272. package/src/hindsight/transcript.ts +1 -1
  273. package/src/index.ts +5 -0
  274. package/src/internal-urls/docs-index.generated.ts +13 -10
  275. package/src/internal-urls/history-protocol.ts +1 -1
  276. package/src/internal-urls/local-protocol.ts +29 -7
  277. package/src/lsp/types.ts +1 -1
  278. package/src/main.ts +27 -32
  279. package/src/mcp/config-writer.ts +7 -3
  280. package/src/mcp/manager.ts +11 -0
  281. package/src/mcp/startup-events.ts +21 -0
  282. package/src/mcp/transports/stdio.ts +2 -1
  283. package/src/memories/index.ts +149 -12
  284. package/src/memories/storage.ts +2 -1
  285. package/src/memory-backend/local-backend.ts +11 -5
  286. package/src/mnemopi/backend.ts +1 -0
  287. package/src/mnemopi/config.ts +112 -12
  288. package/src/modes/acp/acp-agent.ts +8 -53
  289. package/src/modes/acp/acp-event-mapper.ts +5 -1
  290. package/src/modes/components/agent-hub.ts +51 -5
  291. package/src/modes/components/assistant-message.ts +12 -44
  292. package/src/modes/components/compaction-summary-message.ts +125 -26
  293. package/src/modes/components/custom-editor.test.ts +96 -0
  294. package/src/modes/components/custom-editor.ts +164 -8
  295. package/src/modes/components/index.ts +1 -0
  296. package/src/modes/components/logout-account-selector.ts +130 -0
  297. package/src/modes/components/mcp-add-wizard.ts +1 -1
  298. package/src/modes/components/model-selector.ts +2 -2
  299. package/src/modes/components/session-selector.ts +1 -1
  300. package/src/modes/components/settings-defs.ts +7 -0
  301. package/src/modes/components/status-line/component.ts +54 -157
  302. package/src/modes/components/status-line/segments.ts +1 -1
  303. package/src/modes/components/status-line/types.ts +2 -1
  304. package/src/modes/components/tool-execution.ts +82 -43
  305. package/src/modes/components/transcript-container.ts +70 -1
  306. package/src/modes/components/tree-selector.ts +1 -1
  307. package/src/modes/components/usage-row.ts +18 -0
  308. package/src/modes/components/user-message.ts +4 -2
  309. package/src/modes/controllers/command-controller.ts +14 -16
  310. package/src/modes/controllers/event-controller.ts +101 -73
  311. package/src/modes/controllers/extension-ui-controller.ts +6 -0
  312. package/src/modes/controllers/input-controller.ts +311 -57
  313. package/src/modes/controllers/mcp-command-controller.ts +44 -3
  314. package/src/modes/controllers/selector-controller.ts +68 -12
  315. package/src/modes/controllers/streaming-reveal.ts +4 -3
  316. package/src/modes/gradient-highlight.ts +21 -9
  317. package/src/modes/image-references.ts +20 -0
  318. package/src/modes/interactive-mode.ts +288 -48
  319. package/src/modes/magic-keywords.ts +27 -5
  320. package/src/modes/rpc/rpc-mode.ts +146 -14
  321. package/src/modes/rpc/rpc-subagents.ts +2 -2
  322. package/src/modes/rpc/rpc-types.ts +8 -2
  323. package/src/modes/runtime-init.ts +28 -3
  324. package/src/modes/theme/theme.ts +99 -51
  325. package/src/modes/types.ts +6 -7
  326. package/src/modes/utils/hotkeys-markdown.ts +1 -1
  327. package/src/modes/utils/ui-helpers.ts +36 -7
  328. package/src/priority.json +5 -1
  329. package/src/prompts/agents/task.md +1 -0
  330. package/src/prompts/goals/guided-goal-interview.md +8 -0
  331. package/src/prompts/goals/guided-goal-system.md +12 -0
  332. package/src/prompts/memories/read-path.md +6 -0
  333. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  334. package/src/prompts/system/autolearn-guidance.md +7 -0
  335. package/src/prompts/system/autolearn-nudge.md +3 -0
  336. package/src/prompts/system/eager-task.md +7 -0
  337. package/src/prompts/system/eager-todo.md +11 -6
  338. package/src/prompts/system/empty-stop-retry.md +4 -6
  339. package/src/prompts/system/subagent-system-prompt.md +4 -0
  340. package/src/prompts/system/system-prompt.md +10 -5
  341. package/src/prompts/system/title-marker-instruction.md +1 -0
  342. package/src/prompts/system/title-system-marker.md +16 -0
  343. package/src/prompts/tools/job.md +1 -0
  344. package/src/prompts/tools/learn.md +7 -0
  345. package/src/prompts/tools/manage-skill.md +9 -0
  346. package/src/prompts/tools/task.md +3 -0
  347. package/src/registry/agent-registry.ts +30 -0
  348. package/src/sdk.ts +103 -43
  349. package/src/secrets/obfuscator.ts +1 -1
  350. package/src/session/agent-session.ts +331 -318
  351. package/src/session/agent-storage.ts +18 -9
  352. package/src/session/history-storage.ts +3 -2
  353. package/src/session/indexed-session-storage.ts +7 -10
  354. package/src/session/messages.ts +9 -11
  355. package/src/session/session-context.ts +352 -0
  356. package/src/session/session-dump-format.ts +4 -2
  357. package/src/session/session-entries.ts +194 -0
  358. package/src/session/session-listing.ts +588 -0
  359. package/src/session/session-loader.ts +106 -0
  360. package/src/session/session-manager.ts +968 -3064
  361. package/src/session/session-migrations.ts +78 -0
  362. package/src/session/session-paths.ts +193 -0
  363. package/src/session/session-persistence.ts +131 -0
  364. package/src/session/session-storage.ts +91 -30
  365. package/src/session/snapcompact-inline.ts +21 -1
  366. package/src/session/snapcompact-savings-journal.ts +113 -0
  367. package/src/session/tool-choice-queue.ts +23 -11
  368. package/src/slash-commands/builtin-registry.ts +40 -4
  369. package/src/slash-commands/helpers/logout.ts +88 -0
  370. package/src/stt/asr-client.ts +520 -0
  371. package/src/stt/asr-protocol.ts +65 -0
  372. package/src/stt/asr-worker.ts +790 -0
  373. package/src/stt/downloader.ts +107 -47
  374. package/src/stt/endpointer.ts +259 -0
  375. package/src/stt/index.ts +5 -1
  376. package/src/stt/models.ts +150 -0
  377. package/src/stt/recorder.ts +247 -60
  378. package/src/stt/stt-controller.ts +201 -22
  379. package/src/stt/transcriber.ts +37 -68
  380. package/src/stt/wav.ts +173 -0
  381. package/src/system-prompt.ts +8 -0
  382. package/src/task/agents.ts +1 -2
  383. package/src/task/executor.ts +49 -15
  384. package/src/task/index.ts +60 -6
  385. package/src/task/render.ts +83 -8
  386. package/src/task/types.ts +54 -1
  387. package/src/tools/ask.ts +9 -1
  388. package/src/tools/ast-edit.ts +1 -1
  389. package/src/tools/ast-grep.ts +1 -1
  390. package/src/tools/bash.ts +5 -4
  391. package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
  392. package/src/tools/browser/cmux/rpc.ts +156 -0
  393. package/src/tools/browser/cmux/socket-client.ts +309 -0
  394. package/src/tools/browser/registry.ts +37 -3
  395. package/src/tools/browser/render.ts +6 -1
  396. package/src/tools/browser/tab-protocol.ts +2 -0
  397. package/src/tools/browser/tab-supervisor.ts +189 -18
  398. package/src/tools/browser/tab-worker.ts +1 -1
  399. package/src/tools/browser.ts +16 -1
  400. package/src/tools/checkpoint.ts +1 -1
  401. package/src/tools/debug.ts +1 -1
  402. package/src/tools/eval-render.ts +4 -3
  403. package/src/tools/eval.ts +11 -6
  404. package/src/tools/fetch.ts +13 -2
  405. package/src/tools/find.ts +1 -1
  406. package/src/tools/gh.ts +1 -1
  407. package/src/tools/github-cache.ts +2 -1
  408. package/src/tools/image-gen.ts +1 -1
  409. package/src/tools/index.ts +43 -5
  410. package/src/tools/inspect-image.ts +3 -1
  411. package/src/tools/irc.ts +11 -3
  412. package/src/tools/job.ts +15 -3
  413. package/src/tools/learn.ts +144 -0
  414. package/src/tools/manage-skill.ts +104 -0
  415. package/src/tools/memory-edit.ts +1 -1
  416. package/src/tools/memory-recall.ts +1 -1
  417. package/src/tools/memory-reflect.ts +1 -1
  418. package/src/tools/memory-retain.ts +1 -1
  419. package/src/tools/plan-mode-guard.ts +53 -19
  420. package/src/tools/read.ts +8 -2
  421. package/src/tools/render-mermaid.ts +1 -1
  422. package/src/tools/renderers.ts +7 -11
  423. package/src/tools/report-tool-issue.ts +3 -2
  424. package/src/tools/resolve.ts +1 -1
  425. package/src/tools/review.ts +1 -1
  426. package/src/tools/search-tool-bm25.ts +1 -1
  427. package/src/tools/search.ts +1 -1
  428. package/src/tools/ssh.ts +5 -4
  429. package/src/tools/todo.ts +2 -2
  430. package/src/tools/tts.ts +204 -93
  431. package/src/tools/write.ts +19 -3
  432. package/src/tts/downloader.ts +64 -0
  433. package/src/tts/index.ts +8 -0
  434. package/src/tts/models.ts +137 -0
  435. package/src/tts/player.ts +137 -0
  436. package/src/tts/runtime.ts +21 -0
  437. package/src/tts/streaming-player.ts +266 -0
  438. package/src/tts/tts-client.ts +647 -0
  439. package/src/tts/tts-protocol.ts +60 -0
  440. package/src/tts/tts-worker.ts +497 -0
  441. package/src/tts/vocalizer.ts +162 -0
  442. package/src/tts/wav.ts +58 -0
  443. package/src/utils/clipboard.ts +35 -18
  444. package/src/utils/image-loading.ts +35 -4
  445. package/src/utils/thinking-display.ts +37 -0
  446. package/src/utils/title-generator.ts +48 -5
  447. package/src/utils/tool-choice.ts +16 -0
  448. package/src/utils/tools-manager.test.ts +25 -0
  449. package/src/utils/tools-manager.ts +19 -1
  450. package/src/web/scrapers/github.ts +96 -0
  451. package/src/web/search/index.ts +14 -1
  452. package/src/web/search/providers/searxng.ts +13 -1
  453. package/dist/types/cli/list-models.d.ts +0 -30
  454. package/dist/types/stt/setup.d.ts +0 -18
  455. package/src/cli/list-models.ts +0 -194
  456. package/src/stt/setup.ts +0 -52
  457. package/src/stt/transcribe.py +0 -70
@@ -4,6 +4,7 @@ import * as path from "node:path";
4
4
  import {
5
5
  type AuthCredential,
6
6
  type AuthCredentialStore,
7
+ isSqliteBusyError,
7
8
  SqliteAuthCredentialStore,
8
9
  type StoredAuthCredential,
9
10
  } from "@oh-my-pi/pi-ai";
@@ -78,10 +79,14 @@ export class AgentStorage {
78
79
  * AuthCredentialStore handles auth_credentials and cache tables.
79
80
  */
80
81
  #initializeSchema(): void {
82
+ // Install the busy handler BEFORE any lock-taking statement (incl.
83
+ // `PRAGMA journal_mode=WAL`, which acquires an exclusive lock during WAL
84
+ // recovery). Without this, concurrent omp startups can crash here with
85
+ // `SQLITE_BUSY` / `SQLITE_BUSY_RECOVERY`. See issue #2421.
86
+ this.#db.run("PRAGMA busy_timeout = 5000");
81
87
  this.#db.run(`
82
88
  PRAGMA journal_mode=WAL;
83
89
  PRAGMA synchronous=NORMAL;
84
- PRAGMA busy_timeout=5000;
85
90
 
86
91
  CREATE TABLE IF NOT EXISTS model_usage (
87
92
  model_key TEXT PRIMARY KEY,
@@ -208,7 +213,8 @@ FROM model_usage_legacy
208
213
 
209
214
  /**
210
215
  * Returns singleton instance for the given database path, creating if needed.
211
- * Retries on SQLITE_BUSY with exponential backoff.
216
+ * Retries on the `SQLITE_BUSY` family (including `SQLITE_BUSY_RECOVERY`) with
217
+ * exponential backoff. See issue #2421.
212
218
  * @param dbPath - Path to the SQLite database file (defaults to config path)
213
219
  * @returns AgentStorage instance for the given path
214
220
  */
@@ -216,7 +222,7 @@ FROM model_usage_legacy
216
222
  const existing = instances.get(dbPath);
217
223
  if (existing) return existing;
218
224
 
219
- const maxRetries = 3;
225
+ const maxRetries = 4;
220
226
  const baseDelayMs = 100;
221
227
  let lastError: Error | undefined;
222
228
 
@@ -226,17 +232,20 @@ FROM model_usage_legacy
226
232
  instances.set(dbPath, storage);
227
233
  return storage;
228
234
  } catch (err) {
229
- const isSqliteBusy = err && typeof err === "object" && (err as { code?: string }).code === "SQLITE_BUSY";
230
- if (!isSqliteBusy) {
235
+ if (!isSqliteBusyError(err)) {
231
236
  throw err;
232
237
  }
233
- lastError = err as Error;
234
- const delayMs = baseDelayMs * 2 ** attempt;
235
- await Bun.sleep(delayMs);
238
+ lastError = err instanceof Error ? err : new Error(String(err));
239
+ if (attempt < maxRetries - 1) {
240
+ await Bun.sleep(baseDelayMs * 2 ** attempt);
241
+ }
236
242
  }
237
243
  }
238
244
 
239
- throw lastError ?? new Error("Failed to open database after retries");
245
+ throw new Error(
246
+ `Failed to open agent database at '${dbPath}' after ${maxRetries} attempts: ${lastError?.message}`,
247
+ { cause: lastError },
248
+ );
240
249
  }
241
250
 
242
251
  /**
@@ -84,12 +84,13 @@ export class HistoryStorage {
84
84
 
85
85
  this.#db = new Database(dbPath);
86
86
 
87
- const hasFts = this.#db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='history_fts'").get();
87
+ // Install the busy handler BEFORE any lock-taking statement. See #2421.
88
+ this.#db.run("PRAGMA busy_timeout = 5000");
88
89
 
90
+ const hasFts = this.#db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='history_fts'").get();
89
91
  this.#db.run(`
90
92
  PRAGMA journal_mode=WAL;
91
93
  PRAGMA synchronous=NORMAL;
92
- PRAGMA busy_timeout=5000;
93
94
 
94
95
  CREATE TABLE IF NOT EXISTS history (
95
96
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -173,6 +173,10 @@ export class IndexedSessionStorage implements SessionStorage {
173
173
  }
174
174
  }
175
175
 
176
+ writeTextAtomic(path: string, content: string): Promise<void> {
177
+ return this.writeText(path, content);
178
+ }
179
+
176
180
  async rename(src: string, dst: string): Promise<void> {
177
181
  await this.#awaitPath(src);
178
182
  await this.#awaitPath(dst);
@@ -390,14 +394,7 @@ class IndexedSessionStorageWriter implements SessionStorageWriter {
390
394
  return next;
391
395
  }
392
396
 
393
- writeLineSync(line: string): void {
394
- if (this.#closed) throw new Error("Writer closed");
395
- if (this.#error) throw this.#error;
396
- const mtimeMs = this.#storage._appendForWriter(this.#path, line);
397
- this.#trackPromise(this.#storage._queueAppend(this.#path, line, mtimeMs, () => this.#error));
398
- }
399
-
400
- async writeLine(line: string): Promise<void> {
397
+ async append(line: string): Promise<void> {
401
398
  if (this.#closed) throw new Error("Writer closed");
402
399
  if (this.#error) throw this.#error;
403
400
  const mtimeMs = this.#storage._appendForWriter(this.#path, line);
@@ -410,8 +407,8 @@ class IndexedSessionStorageWriter implements SessionStorageWriter {
410
407
  if (this.#error) throw this.#error;
411
408
  }
412
409
 
413
- async fsync(): Promise<void> {
414
- await this.flush();
410
+ isOpen(): boolean {
411
+ return !this.#closed;
415
412
  }
416
413
 
417
414
  async close(): Promise<void> {
@@ -40,13 +40,11 @@ export interface SkillPromptDetails {
40
40
  path: string;
41
41
  args?: string;
42
42
  lineCount: number;
43
- /** Internal: tag used by AgentSession to remove the pending-display chip
44
- * from `#steeringMessages` / `#followUpMessages` when the agent consumes
45
- * this message. Not surfaced to renderers; the `__` prefix signals
46
- * "private". Optional — non-streaming skill prompts never set it. Stripped
47
- * from persisted `details` by `SessionManager.appendCustomMessageEntry`
48
- * via the `INTERNAL_DETAILS_FIELDS` allowlist below. */
49
- __pendingDisplayTag?: string;
43
+ /** Internal: compact label shown for a queued custom message. Optional —
44
+ * non-streaming skill prompts never set it. Stripped from persisted
45
+ * `details` by `SessionManager.appendCustomMessageEntry` via the
46
+ * `INTERNAL_DETAILS_FIELDS` allowlist below. */
47
+ __queueChipText?: string;
50
48
  }
51
49
 
52
50
  /** Sentinel value for `AssistantMessage.errorMessage` indicating that the abort
@@ -104,12 +102,12 @@ export function resolveAbortLabel(errorMessage: string | undefined, retryAttempt
104
102
  return "Operation aborted";
105
103
  }
106
104
 
107
- /** Extract the optional `__pendingDisplayTag` field from a CustomMessage's
105
+ /** Extract the optional `__queueChipText` field from a CustomMessage's
108
106
  * `details` blob. Safe over `unknown`; returns undefined when the field is
109
107
  * absent or non-string. */
110
- export function readPendingDisplayTag(details: unknown): string | undefined {
108
+ export function readQueueChipText(details: unknown): string | undefined {
111
109
  if (typeof details !== "object" || details === null) return undefined;
112
- const candidate = (details as { __pendingDisplayTag?: unknown }).__pendingDisplayTag;
110
+ const candidate = (details as { __queueChipText?: unknown }).__queueChipText;
113
111
  return typeof candidate === "string" ? candidate : undefined;
114
112
  }
115
113
 
@@ -118,7 +116,7 @@ export function readPendingDisplayTag(details: unknown): string | undefined {
118
116
  * the CustomMessageEntry to disk. Scoped intentionally narrow: only fields
119
117
  * declared here are stripped. Adding a new entry is a deliberate, reviewed
120
118
  * change — unrelated future payload fields are never silently dropped. */
121
- export const INTERNAL_DETAILS_FIELDS = ["__pendingDisplayTag"] as const;
119
+ export const INTERNAL_DETAILS_FIELDS = ["__queueChipText"] as const;
122
120
 
123
121
  /** Return a `details` copy with every key in `INTERNAL_DETAILS_FIELDS`
124
122
  * removed. Returns the input unchanged when there is nothing to strip
@@ -0,0 +1,352 @@
1
+ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
+ import type { ProviderPayload, ServiceTier } from "@oh-my-pi/pi-ai";
3
+ import * as snapcompact from "@oh-my-pi/snapcompact";
4
+ import { createBranchSummaryMessage, createCompactionSummaryMessage, createCustomMessage } from "./messages";
5
+ import { type CompactionEntry, EPHEMERAL_MODEL_CHANGE_ROLE, type SessionEntry } from "./session-entries";
6
+
7
+ export interface SessionContext {
8
+ messages: AgentMessage[];
9
+ thinkingLevel?: string;
10
+ serviceTier?: ServiceTier;
11
+ /** Model roles: { default: "provider/modelId", small: "provider/modelId", ... } */
12
+ models: Record<string, string>;
13
+ /** Names of TTSR rules that have been injected this session */
14
+ injectedTtsrRules: string[];
15
+ /** MCP tool names selected through discovery for this session branch. */
16
+ selectedMCPToolNames: string[];
17
+ /** Whether this branch contains an explicit persisted MCP selection entry. */
18
+ hasPersistedMCPToolSelection: boolean;
19
+ /** Active mode (e.g. "plan") or "none" if no special mode is active */
20
+ mode: string;
21
+ /** Mode-specific data from the last mode_change entry */
22
+ modeData?: Record<string, unknown>;
23
+ }
24
+
25
+ /** Lists session model strings to try when restoring, in fallback order. */
26
+ export function getRestorableSessionModels(
27
+ models: Readonly<Record<string, string>>,
28
+ lastModelChangeRole: string | undefined,
29
+ ): string[] {
30
+ const defaultModel = models.default;
31
+ if (
32
+ !lastModelChangeRole ||
33
+ lastModelChangeRole === "default" ||
34
+ lastModelChangeRole === EPHEMERAL_MODEL_CHANGE_ROLE
35
+ ) {
36
+ return defaultModel ? [defaultModel] : [];
37
+ }
38
+
39
+ const roleModel = models[lastModelChangeRole];
40
+ if (!roleModel) return defaultModel ? [defaultModel] : [];
41
+ if (!defaultModel || roleModel === defaultModel) return [roleModel];
42
+ return [roleModel, defaultModel];
43
+ }
44
+
45
+ export function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEntry | null {
46
+ for (let i = entries.length - 1; i >= 0; i--) {
47
+ if (entries[i].type === "compaction") {
48
+ return entries[i] as CompactionEntry;
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ export interface BuildSessionContextOptions {
55
+ /**
56
+ * Build the full-history display transcript instead of the LLM context:
57
+ * every path entry in chronological order, with each compaction emitted
58
+ * inline as a `compactionSummary` message at the position it fired rather
59
+ * than replacing the history before it. Display-only — never send the
60
+ * result to a provider.
61
+ */
62
+ transcript?: boolean;
63
+ }
64
+
65
+ /**
66
+ * Build the session context from entries using tree traversal.
67
+ * If leafId is provided, walks from that entry to root.
68
+ * Handles compaction and branch summaries along the path.
69
+ */
70
+ export function buildSessionContext(
71
+ entries: SessionEntry[],
72
+ leafId?: string | null,
73
+ byId?: Map<string, SessionEntry>,
74
+ options?: BuildSessionContextOptions,
75
+ ): SessionContext {
76
+ // Build uuid index if not available
77
+ if (!byId) {
78
+ byId = new Map<string, SessionEntry>();
79
+ for (const entry of entries) {
80
+ byId.set(entry.id, entry);
81
+ }
82
+ }
83
+
84
+ // Find leaf
85
+ let leaf: SessionEntry | undefined;
86
+ if (leafId === null) {
87
+ // Explicitly null - return no messages (navigated to before first entry)
88
+ return {
89
+ messages: [],
90
+ thinkingLevel: "off",
91
+ serviceTier: undefined,
92
+ models: {},
93
+ injectedTtsrRules: [],
94
+ selectedMCPToolNames: [],
95
+ hasPersistedMCPToolSelection: false,
96
+ mode: "none",
97
+ };
98
+ }
99
+ if (leafId) {
100
+ leaf = byId.get(leafId);
101
+ }
102
+ if (!leaf) {
103
+ // Fallback to last entry (when leafId is undefined)
104
+ leaf = entries[entries.length - 1];
105
+ }
106
+
107
+ if (!leaf) {
108
+ return {
109
+ messages: [],
110
+ thinkingLevel: "off",
111
+ serviceTier: undefined,
112
+ models: {},
113
+ injectedTtsrRules: [],
114
+ selectedMCPToolNames: [],
115
+ hasPersistedMCPToolSelection: false,
116
+ mode: "none",
117
+ };
118
+ }
119
+
120
+ // Walk from leaf to root, collecting path
121
+ const path: SessionEntry[] = [];
122
+ let current: SessionEntry | undefined = leaf;
123
+ while (current) {
124
+ path.unshift(current);
125
+ current = current.parentId ? byId.get(current.parentId) : undefined;
126
+ }
127
+
128
+ // Extract settings and find compaction
129
+ let thinkingLevel: string | undefined = "off";
130
+ let serviceTier: ServiceTier | undefined;
131
+ const models: Record<string, string> = {};
132
+ let compaction: CompactionEntry | null = null;
133
+ const injectedTtsrRulesSet = new Set<string>();
134
+ let selectedMCPToolNames: string[] = [];
135
+ let hasPersistedMCPToolSelection = false;
136
+ let mode = "none";
137
+ let modeData: Record<string, unknown> | undefined;
138
+ // Track whether an explicit `model_change` with role="default" has been
139
+ // seen on this path. Once a user (or the agent itself) records an
140
+ // explicit default, later assistant-message inference must NOT overwrite
141
+ // it: temporary fallbacks (retry fallback, context promotion) and
142
+ // server-side model downgrades both produce assistant messages tagged
143
+ // with the wrong model id, which previously clobbered the user's pick on
144
+ // resume (issue #849).
145
+ let hasExplicitDefaultModel = false;
146
+
147
+ for (const entry of path) {
148
+ if (entry.type === "thinking_level_change") {
149
+ thinkingLevel = entry.thinkingLevel ?? "off";
150
+ } else if (entry.type === "model_change") {
151
+ // New format: { model: "provider/id", role?: string }
152
+ if (entry.model) {
153
+ const role = entry.role ?? "default";
154
+ models[role] = entry.model;
155
+ if (role === "default") {
156
+ hasExplicitDefaultModel = true;
157
+ }
158
+ }
159
+ } else if (entry.type === "service_tier_change") {
160
+ serviceTier = entry.serviceTier ?? undefined;
161
+ } else if (entry.type === "message" && entry.message.role === "assistant") {
162
+ // Legacy fallback: infer default model from assistant messages only
163
+ // when no explicit `model_change` (role=default) entry has been
164
+ // recorded yet. Newer sessions always record an explicit default
165
+ // model_change at the start of the conversation, so this branch is
166
+ // only used to keep pre-model_change sessions working.
167
+ if (!hasExplicitDefaultModel) {
168
+ models.default = `${entry.message.provider}/${entry.message.model}`;
169
+ }
170
+ } else if (entry.type === "compaction") {
171
+ compaction = entry;
172
+ } else if (entry.type === "ttsr_injection") {
173
+ // Collect injected TTSR rule names
174
+ for (const ruleName of entry.injectedRules) {
175
+ injectedTtsrRulesSet.add(ruleName);
176
+ }
177
+ } else if (entry.type === "mcp_tool_selection") {
178
+ selectedMCPToolNames = [...entry.selectedToolNames];
179
+ hasPersistedMCPToolSelection = true;
180
+ } else if (entry.type === "mode_change") {
181
+ mode = entry.mode;
182
+ modeData = entry.data;
183
+ }
184
+ }
185
+
186
+ const injectedTtsrRules = Array.from(injectedTtsrRulesSet);
187
+
188
+ // Build messages and collect corresponding entries
189
+ // When there's a compaction, we need to:
190
+ // 1. Emit summary first (entry = compaction)
191
+ // 2. Emit kept messages (from firstKeptEntryId up to compaction)
192
+ // 3. Emit messages after compaction
193
+ const messages: AgentMessage[] = [];
194
+
195
+ const appendMessage = (entry: SessionEntry) => {
196
+ if (entry.type === "message") {
197
+ messages.push(entry.message);
198
+ } else if (entry.type === "custom_message") {
199
+ messages.push(
200
+ createCustomMessage(
201
+ entry.customType,
202
+ entry.content,
203
+ entry.display,
204
+ entry.details,
205
+ entry.timestamp,
206
+ entry.attribution,
207
+ ),
208
+ );
209
+ } else if (entry.type === "branch_summary" && entry.summary) {
210
+ messages.push(createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp));
211
+ }
212
+ };
213
+
214
+ if (options?.transcript) {
215
+ // Display transcript: every entry in chronological order. Compactions do
216
+ // not erase prior history here — each renders inline (as a divider in the
217
+ // TUI) at the point it fired, with any snapcompact frames re-attached so
218
+ // the component can report them.
219
+ for (const entry of path) {
220
+ if (entry.type === "compaction") {
221
+ const snapcompactArchive = snapcompact.getPreservedArchive(entry.preserveData);
222
+ messages.push(
223
+ createCompactionSummaryMessage(
224
+ entry.summary,
225
+ entry.tokensBefore,
226
+ entry.timestamp,
227
+ entry.shortSummary,
228
+ undefined,
229
+ snapcompactArchive ? snapcompact.images(snapcompactArchive) : undefined,
230
+ ),
231
+ );
232
+ } else {
233
+ appendMessage(entry);
234
+ }
235
+ }
236
+ } else if (compaction) {
237
+ const providerPayload: ProviderPayload | undefined = (() => {
238
+ const candidate = compaction.preserveData?.openaiRemoteCompaction;
239
+ if (!candidate || typeof candidate !== "object") return undefined;
240
+ const remote = candidate as { provider?: unknown; replacementHistory?: unknown };
241
+ if (typeof remote.provider !== "string" || remote.provider.length === 0) return undefined;
242
+ if (!Array.isArray(remote.replacementHistory)) return undefined;
243
+ return {
244
+ type: "openaiResponsesHistory",
245
+ provider: remote.provider,
246
+ items: remote.replacementHistory as Array<Record<string, unknown>>,
247
+ };
248
+ })();
249
+ const remoteReplacementHistory = providerPayload?.items;
250
+
251
+ // Emit summary first; re-attach any archived snapcompact frames so the
252
+ // model can keep reading the archived history after every context rebuild.
253
+ const snapcompactArchive = snapcompact.getPreservedArchive(compaction.preserveData);
254
+ messages.push(
255
+ createCompactionSummaryMessage(
256
+ compaction.summary,
257
+ compaction.tokensBefore,
258
+ compaction.timestamp,
259
+ compaction.shortSummary,
260
+ providerPayload,
261
+ snapcompactArchive ? snapcompact.images(snapcompactArchive) : undefined,
262
+ ),
263
+ );
264
+
265
+ // Find compaction index in path
266
+ const compactionIdx = path.findIndex(e => e.type === "compaction" && e.id === compaction.id);
267
+
268
+ if (!remoteReplacementHistory) {
269
+ // Emit kept messages (before compaction, starting from firstKeptEntryId)
270
+ let foundFirstKept = false;
271
+ for (let i = 0; i < compactionIdx; i++) {
272
+ const entry = path[i];
273
+ if (entry.id === compaction.firstKeptEntryId) {
274
+ foundFirstKept = true;
275
+ }
276
+ if (foundFirstKept) {
277
+ appendMessage(entry);
278
+ }
279
+ }
280
+ }
281
+
282
+ // Emit messages after compaction
283
+ for (let i = compactionIdx + 1; i < path.length; i++) {
284
+ const entry = path[i];
285
+ appendMessage(entry);
286
+ }
287
+ } else {
288
+ // No compaction - emit all messages, handle branch summaries and custom messages
289
+ for (const entry of path) {
290
+ appendMessage(entry);
291
+ }
292
+ }
293
+
294
+ // Strip dangling tool_use blocks — a tool_use with no matching tool_result on the
295
+ // resolved leaf→root path — from ANY assistant turn, not just the trailing one.
296
+ // This happens whenever the leaf (or a branch point) lands such that an assistant
297
+ // turn's tool results are off the selected path: its result children live on a
298
+ // sibling branch, or it is the leaf itself (results are children below it). Left
299
+ // in place, `transformMessages` fabricates one synthetic "aborted"/"No result
300
+ // provided" result per dangling call, which render as phantom failed calls and
301
+ // re-inject the failed batch into the model's
302
+ // context — the rewind/restore loop.
303
+ //
304
+ // Stripping is necessary but not sufficient: a *modified* assistant turn that still
305
+ // carries signed `thinking`/`redacted_thinking` is rejected by Anthropic — "thinking
306
+ // blocks in the latest assistant message cannot be modified", and signed thinking
307
+ // replayed out of its original turn shape can also fail signature validation (this
308
+ // bites the handoff/branch-summary request). So when we rewrite a turn we also
309
+ // neutralize its protected reasoning: drop `redactedThinking` (encrypted, no
310
+ // plaintext to keep) and clear `thinking` signatures so the provider encoder
311
+ // downgrades them to plain text (verified accepted by the live API), preserving the
312
+ // visible reasoning while removing the immutability/invalid-signature hazard. Drop a
313
+ // turn left with no content. (Live turns never qualify: their results are persisted
314
+ // on the same path before any context rebuild.)
315
+ const pairedToolResultIds = new Set<string>();
316
+ for (const message of messages) {
317
+ if (message.role === "toolResult") pairedToolResultIds.add(message.toolCallId);
318
+ }
319
+ for (let i = messages.length - 1; i >= 0; i--) {
320
+ const message = messages[i];
321
+ if (message.role !== "assistant") continue;
322
+ const hasDangling = message.content.some(
323
+ block => block.type === "toolCall" && !pairedToolResultIds.has(block.id),
324
+ );
325
+ if (!hasDangling) continue;
326
+ const normalized = message.content
327
+ .filter(
328
+ block =>
329
+ !(block.type === "toolCall" && !pairedToolResultIds.has(block.id)) && block.type !== "redactedThinking",
330
+ )
331
+ .map(block =>
332
+ block.type === "thinking" && block.thinkingSignature ? { ...block, thinkingSignature: undefined } : block,
333
+ );
334
+ if (normalized.length === 0) {
335
+ messages.splice(i, 1);
336
+ } else {
337
+ messages[i] = { ...message, content: normalized };
338
+ }
339
+ }
340
+
341
+ return {
342
+ messages,
343
+ thinkingLevel,
344
+ serviceTier,
345
+ models,
346
+ injectedTtsrRules,
347
+ selectedMCPToolNames,
348
+ hasPersistedMCPToolSelection,
349
+ mode,
350
+ modeData,
351
+ };
352
+ }
@@ -5,6 +5,7 @@ import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
5
5
  import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
6
6
  import type { AssistantMessage, Model } from "@oh-my-pi/pi-ai";
7
7
  import { isZodSchema, zodToWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
8
+ import { getVisibleThinkingText } from "../utils/thinking-display";
8
9
  import {
9
10
  type BashExecutionMessage,
10
11
  type BranchSummaryMessage,
@@ -126,9 +127,10 @@ export function formatSessionDumpText(options: FormatSessionDumpTextOptions): st
126
127
  if (c.type === "text") {
127
128
  lines.push(c.text);
128
129
  } else if (c.type === "thinking") {
129
- if (c.thinking.trim().length === 0) continue;
130
+ const thinking = getVisibleThinkingText(c);
131
+ if (thinking.length === 0) continue;
130
132
  lines.push("<thinking>");
131
- lines.push(c.thinking);
133
+ lines.push(thinking);
132
134
  lines.push("</thinking>\n");
133
135
  } else if (c.type === "toolCall") {
134
136
  lines.push(`<invoke name="${c.name}">`);