@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
@@ -1,8 +1,9 @@
1
+ import { Database } from "bun:sqlite";
2
+ import * as fs from "node:fs";
1
3
  import * as path from "node:path";
2
4
  import type { MnemopiOptions } from "@oh-my-pi/pi-mnemopi";
3
- import { getMemoriesDir } from "@oh-my-pi/pi-utils";
5
+ import { getMemoriesDir, logger } from "@oh-my-pi/pi-utils";
4
6
  import type { Settings } from "../config/settings";
5
- import * as git from "../utils/git";
6
7
 
7
8
  export type MnemopiLlmMode = "none" | "smol" | "remote";
8
9
 
@@ -42,15 +43,29 @@ export function loadMnemopiConfig(settings: Settings, agentDir: string): Mnemopi
42
43
  const configuredDbPath = settings.get("mnemopi.dbPath");
43
44
  const cwd = settings.getCwd();
44
45
  const scoping = settings.get("mnemopi.scoping");
45
- const scope = resolveBankScope(settings.get("mnemopi.bank"), cwd, scoping);
46
+ const dbPath = configuredDbPath ?? path.join(getMemoriesDir(agentDir), "mnemopi", "mnemopi.db");
47
+ const scope = computeMnemopiBankScope(settings.get("mnemopi.bank"), cwd, scoping);
48
+ const recallBanks =
49
+ scoping === "global" ? scope.recallBanks : extendRecallWithLegacyBanks(scope.recallBanks, dbPath, cwd);
46
50
  const llmMode = settings.get("mnemopi.llmMode");
51
+ const embeddingOverride = settings.get("mnemopi.embeddingModel");
52
+ const embeddingVariant = settings.get("mnemopi.embeddingVariant");
53
+ // Map the variant explicitly rather than indexing an object with the raw config
54
+ // value (which could resolve an inherited property like `__proto__`); any value
55
+ // other than the multilingual variant falls back to the English default.
56
+ const variantModel =
57
+ embeddingVariant === "multilingual" ? "intfloat/multilingual-e5-large" : "BAAI/bge-base-en-v1.5";
58
+ // Precedence: explicit `mnemopi.embeddingModel` setting > `MNEMOPI_EMBEDDING_MODEL`
59
+ // env (documented model-level override) > variant-derived default. Without the env
60
+ // term a variant default would silently shadow a user's configured env model.
61
+ const embeddingModel = embeddingOverride?.trim() || Bun.env.MNEMOPI_EMBEDDING_MODEL?.trim() || variantModel;
47
62
  return {
48
- dbPath: configuredDbPath ?? path.join(getMemoriesDir(agentDir), "mnemopi", "mnemopi.db"),
63
+ dbPath,
49
64
  baseBank: scope.baseBank,
50
65
  bank: scope.bank,
51
66
  globalBank: scope.globalBank,
52
67
  retainBank: scope.retainBank,
53
- recallBanks: scope.recallBanks,
68
+ recallBanks,
54
69
  scoping,
55
70
  autoRecall: settings.get("mnemopi.autoRecall"),
56
71
  autoRetain: settings.get("mnemopi.autoRetain"),
@@ -65,7 +80,7 @@ export function loadMnemopiConfig(settings: Settings, agentDir: string): Mnemopi
65
80
  providerOptions: {
66
81
  noEmbeddings: settings.get("mnemopi.noEmbeddings"),
67
82
  debug: settings.get("mnemopi.debug"),
68
- embeddingModel: settings.get("mnemopi.embeddingModel"),
83
+ embeddingModel,
69
84
  embeddingApiUrl: settings.get("mnemopi.embeddingApiUrl"),
70
85
  embeddingApiKey: settings.get("mnemopi.embeddingApiKey"),
71
86
  llm:
@@ -86,7 +101,11 @@ export function loadMnemopiConfig(settings: Settings, agentDir: string): Mnemopi
86
101
 
87
102
  const DEFAULT_SHARED_BANK = "default";
88
103
 
89
- interface MnemopiBankScope {
104
+ // Cap legacy-bank scanning at session start so a pathological banks/
105
+ // directory cannot dominate startup latency.
106
+ const LEGACY_BANK_SCAN_LIMIT = 64;
107
+
108
+ export interface MnemopiBankScope {
90
109
  baseBank: string;
91
110
  bank: string;
92
111
  globalBank: string;
@@ -94,9 +113,19 @@ interface MnemopiBankScope {
94
113
  recallBanks: readonly string[];
95
114
  }
96
115
 
97
- // Mnemopi does not have built-in tag-filtered recall, so `per-project-tagged`
98
- // maps to a project-local write bank plus a shared recall-visible bank.
99
- function resolveBankScope(configured: string | undefined, cwd: string, scoping: MnemopiScoping): MnemopiBankScope {
116
+ /**
117
+ * Resolve write/recall banks for a session.
118
+ *
119
+ * Mnemopi has no tag-filtered recall, so `per-project-tagged` maps to a
120
+ * project-local write bank plus a shared recall-visible bank. The project
121
+ * bank is derived purely from {@link cwd} — see {@link projectBank} for the
122
+ * stability contract.
123
+ */
124
+ export function computeMnemopiBankScope(
125
+ configured: string | undefined,
126
+ cwd: string,
127
+ scoping: MnemopiScoping,
128
+ ): MnemopiBankScope {
100
129
  const project = projectBank(configured, cwd);
101
130
  const globalBank = sharedBank(configured);
102
131
  switch (scoping) {
@@ -131,8 +160,17 @@ function sharedBank(configured: string | undefined): string {
131
160
  return sanitizeBankName(configured) ?? DEFAULT_SHARED_BANK;
132
161
  }
133
162
 
163
+ /**
164
+ * Derive the per-project bank id from `cwd` alone.
165
+ *
166
+ * Earlier versions resolved the enclosing git root before hashing, which
167
+ * made the bank id unstable: removing or adding a `.git` anywhere above the
168
+ * cwd repointed the same conversation directory to a different bank and
169
+ * fragmented memories (#2412). The git lookup is gone here; the rescue path
170
+ * for already-fragmented installs lives in {@link extendRecallWithLegacyBanks}.
171
+ */
134
172
  function projectBank(configured: string | undefined, cwd: string): string {
135
- const projectRoot = git.repo.resolveSync(cwd)?.repoRoot ?? path.resolve(cwd);
173
+ const projectRoot = path.resolve(cwd || ".");
136
174
  const project = projectBankSegment(projectRoot);
137
175
  const base = sanitizeBankName(configured);
138
176
  return limitBankName(base ? `${base}-${project}` : project);
@@ -140,7 +178,69 @@ function projectBank(configured: string | undefined, cwd: string): string {
140
178
 
141
179
  function projectBankSegment(projectRoot: string): string {
142
180
  const project = sanitizeBankName(path.basename(projectRoot)) ?? "default";
143
- return limitBankName(`${project}-${Bun.hash(path.resolve(projectRoot)).toString(36)}`);
181
+ return limitBankName(`${project}-${Bun.hash(projectRoot).toString(36)}`);
182
+ }
183
+
184
+ /**
185
+ * Discover sibling banks under `<dbDir>/banks/` whose `working_memory` rows
186
+ * all carry the active `cwd` in `metadata_json.$.cwd`, and add those safe
187
+ * single-cwd banks to the recall set. This rescues memories stranded by a
188
+ * previous, less-stable bank derivation (#2412) without recalling mixed-cwd
189
+ * legacy banks wholesale under per-project isolation.
190
+ *
191
+ * Robust by design: a missing banks directory, unreadable bank dir, or
192
+ * corrupt SQLite file is silently skipped. Scanning is capped at
193
+ * {@link LEGACY_BANK_SCAN_LIMIT} to bound startup cost.
194
+ */
195
+ export function extendRecallWithLegacyBanks(
196
+ resolved: readonly string[],
197
+ dbPath: string,
198
+ cwd: string,
199
+ ): readonly string[] {
200
+ const banksDir = path.join(path.dirname(dbPath), "banks");
201
+ const cwdAbs = path.resolve(cwd || ".");
202
+ let entries: fs.Dirent[];
203
+ try {
204
+ entries = fs.readdirSync(banksDir, { withFileTypes: true });
205
+ } catch {
206
+ return resolved;
207
+ }
208
+ const have = new Set(resolved);
209
+ const extras: string[] = [];
210
+ let scanned = 0;
211
+ for (const entry of entries) {
212
+ if (!entry.isDirectory() || have.has(entry.name)) continue;
213
+ if (scanned >= LEGACY_BANK_SCAN_LIMIT) break;
214
+ scanned++;
215
+ const candidate = path.join(banksDir, entry.name, "mnemopi.db");
216
+ if (bankOnlyHasCwd(candidate, cwdAbs)) extras.push(entry.name);
217
+ }
218
+ return extras.length === 0 ? resolved : [...resolved, ...extras];
219
+ }
220
+
221
+ function bankOnlyHasCwd(dbPath: string, cwd: string): boolean {
222
+ let db: Database | undefined;
223
+ try {
224
+ db = new Database(dbPath, { readonly: true });
225
+ const row = db
226
+ .prepare<{ matching: number; unsafe: number }, [string, string]>(`
227
+ SELECT
228
+ SUM(CASE WHEN json_extract(metadata_json, '$.cwd') = ? THEN 1 ELSE 0 END) AS matching,
229
+ SUM(CASE WHEN json_extract(metadata_json, '$.cwd') IS NULL OR json_extract(metadata_json, '$.cwd') <> ? THEN 1 ELSE 0 END) AS unsafe
230
+ FROM working_memory
231
+ `)
232
+ .get(cwd, cwd);
233
+ return (row?.matching ?? 0) > 0 && (row?.unsafe ?? 0) === 0;
234
+ } catch (error) {
235
+ logger.debug("Mnemopi: legacy bank probe failed", { dbPath, error: String(error) });
236
+ return false;
237
+ } finally {
238
+ try {
239
+ db?.close();
240
+ } catch {
241
+ // nothing to do — read-only handle.
242
+ }
243
+ }
144
244
  }
145
245
 
146
246
  function sanitizeBankName(value: string | undefined): string | undefined {
@@ -31,14 +31,11 @@ import {
31
31
  type ResumeSessionResponse,
32
32
  type SessionConfigOption,
33
33
  type SessionInfo,
34
- type SessionModelState,
35
34
  type SessionModeState,
36
35
  type SessionNotification,
37
36
  type SessionUpdate,
38
37
  type SetSessionConfigOptionRequest,
39
38
  type SetSessionConfigOptionResponse,
40
- type SetSessionModelRequest,
41
- type SetSessionModelResponse,
42
39
  type SetSessionModeRequest,
43
40
  type SetSessionModeResponse,
44
41
  type Usage,
@@ -66,17 +63,16 @@ import { theme } from "../../modes/theme/theme";
66
63
  import { type PlanApprovalDetails, resolveApprovedPlan } from "../../plan-mode/approved-plan";
67
64
  import type { AgentSession, AgentSessionEvent } from "../../session/agent-session";
68
65
  import { isSilentAbort, SKILL_PROMPT_MESSAGE_TYPE } from "../../session/messages";
69
- import {
70
- SessionManager,
71
- type SessionInfo as StoredSessionInfo,
72
- type UsageStatistics,
73
- } from "../../session/session-manager";
66
+ import type { UsageStatistics } from "../../session/session-entries";
67
+ import type { SessionInfo as StoredSessionInfo } from "../../session/session-listing";
68
+ import { SessionManager } from "../../session/session-manager";
74
69
  import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
75
70
  import { buildAvailableSlashCommands, toAcpAvailableCommands } from "../../slash-commands/available-commands";
76
71
  import { AUTO_THINKING, parseConfiguredThinkingLevel } from "../../thinking";
77
72
  import { normalizeLocalScheme } from "../../tools/path-utils";
78
73
  import { runResolveInvocation } from "../../tools/resolve";
79
74
  import { ToolError } from "../../tools/tool-errors";
75
+ import { getVisibleThinkingText } from "../../utils/thinking-display";
80
76
  import { createAcpClientBridge } from "./acp-client-bridge";
81
77
  import {
82
78
  buildToolCallStartUpdate,
@@ -121,7 +117,6 @@ type PromptQueueState = {
121
117
  type PromptLifecycleError = Error & { readonly code: "ACP_SESSION_CLOSED" };
122
118
 
123
119
  type PromptTurnState = {
124
- userMessageId: string;
125
120
  cancelRequested: boolean;
126
121
  settled: boolean;
127
122
  /**
@@ -465,7 +460,6 @@ export class AcpAgent implements Agent {
465
460
  const response: NewSessionResponse = {
466
461
  sessionId: record.session.sessionId,
467
462
  configOptions: this.#buildConfigOptions(record.session),
468
- models: this.#buildModelState(record.session),
469
463
  modes: this.#buildModeState(record.session),
470
464
  };
471
465
  this.#scheduleBootstrapUpdates(record.session.sessionId);
@@ -478,7 +472,6 @@ export class AcpAgent implements Agent {
478
472
  await this.#replaySessionHistory(record);
479
473
  const response: LoadSessionResponse = {
480
474
  configOptions: this.#buildConfigOptions(record.session),
481
- models: this.#buildModelState(record.session),
482
475
  modes: this.#buildModeState(record.session),
483
476
  };
484
477
  this.#scheduleBootstrapUpdates(record.session.sessionId);
@@ -507,7 +500,6 @@ export class AcpAgent implements Agent {
507
500
  const record = await this.#resumeManagedSession(params.sessionId, params.cwd, params.mcpServers ?? []);
508
501
  const response: ResumeSessionResponse = {
509
502
  configOptions: this.#buildConfigOptions(record.session),
510
- models: this.#buildModelState(record.session),
511
503
  modes: this.#buildModeState(record.session),
512
504
  };
513
505
  this.#scheduleBootstrapUpdates(record.session.sessionId);
@@ -520,7 +512,6 @@ export class AcpAgent implements Agent {
520
512
  const response: ForkSessionResponse = {
521
513
  sessionId: record.session.sessionId,
522
514
  configOptions: this.#buildConfigOptions(record.session),
523
- models: this.#buildModelState(record.session),
524
515
  modes: this.#buildModeState(record.session),
525
516
  };
526
517
  this.#scheduleBootstrapUpdates(record.session.sessionId);
@@ -588,13 +579,6 @@ export class AcpAgent implements Agent {
588
579
  return { configOptions: this.#buildConfigOptions(record.session) };
589
580
  }
590
581
 
591
- async unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse> {
592
- const record = this.#getSessionRecord(params.sessionId);
593
- await this.#setModelById(record.session, params.modelId);
594
- await this.#pushConfigOptionUpdate(record);
595
- return {};
596
- }
597
-
598
582
  async prompt(params: PromptRequest): Promise<PromptResponse> {
599
583
  const record = this.#getSessionRecord(params.sessionId);
600
584
  const activeTurn = record.promptTurn;
@@ -633,7 +617,6 @@ export class AcpAgent implements Agent {
633
617
  const converted = this.#convertPromptBlocks(params.prompt);
634
618
  const pendingPrompt = Promise.withResolvers<PromptResponse>();
635
619
  record.promptTurn = {
636
- userMessageId: params.messageId ?? crypto.randomUUID(),
637
620
  cancelRequested: false,
638
621
  settled: false,
639
622
  cleanup: undefined,
@@ -766,7 +749,6 @@ export class AcpAgent implements Agent {
766
749
  this.#cloneUsageStatistics(record.session.sessionManager.getUsageStatistics()),
767
750
  record.session.sessionManager.getUsageStatistics(),
768
751
  ),
769
- userMessageId: promptTurn?.userMessageId,
770
752
  });
771
753
  return;
772
754
  }
@@ -844,7 +826,6 @@ export class AcpAgent implements Agent {
844
826
  this.#finishPrompt(record, {
845
827
  stopReason: "cancelled",
846
828
  usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
847
- userMessageId: promptTurn.userMessageId,
848
829
  });
849
830
  return cleanup;
850
831
  }
@@ -1162,7 +1143,6 @@ export class AcpAgent implements Agent {
1162
1143
  this.#finishPrompt(record, {
1163
1144
  stopReason: this.#resolveStopReason(event, promptTurn.cancelRequested),
1164
1145
  usage: this.#buildTurnUsage(promptTurn.usageBaseline, record.session.sessionManager.getUsageStatistics()),
1165
- userMessageId: promptTurn.userMessageId,
1166
1146
  });
1167
1147
  }
1168
1148
  }
@@ -1385,28 +1365,6 @@ export class AcpAgent implements Agent {
1385
1365
  return configOptions;
1386
1366
  }
1387
1367
 
1388
- #buildModelState(session: AgentSession): SessionModelState | undefined {
1389
- const models = session.getAvailableModels();
1390
- if (models.length === 0) {
1391
- return undefined;
1392
- }
1393
-
1394
- const availableModels = models.map(model => ({
1395
- modelId: this.#toModelId(model),
1396
- name: model.name,
1397
- description: `${model.provider}/${model.id}`,
1398
- }));
1399
- const currentModelId = session.model ? this.#toModelId(session.model) : availableModels[0]?.modelId;
1400
- if (!currentModelId) {
1401
- return undefined;
1402
- }
1403
-
1404
- return {
1405
- availableModels,
1406
- currentModelId,
1407
- };
1408
- }
1409
-
1410
1368
  #buildThinkingOptions(session: AgentSession): Array<{ value: string; name: string; description?: string }> {
1411
1369
  return [
1412
1370
  { value: THINKING_OFF, name: "Off" },
@@ -1948,17 +1906,14 @@ export class AcpAgent implements Agent {
1948
1906
  });
1949
1907
  continue;
1950
1908
  }
1951
- if (
1952
- item.type === "thinking" &&
1953
- "thinking" in item &&
1954
- typeof item.thinking === "string" &&
1955
- item.thinking.length > 0
1956
- ) {
1909
+ if (item.type === "thinking" && "thinking" in item && typeof item.thinking === "string") {
1910
+ const thinking = getVisibleThinkingText(item);
1911
+ if (thinking.length === 0) continue;
1957
1912
  notifications.push({
1958
1913
  sessionId,
1959
1914
  update: {
1960
1915
  sessionUpdate: "agent_thought_chunk",
1961
- content: { type: "text", text: item.thinking },
1916
+ content: { type: "text", text: thinking },
1962
1917
  messageId,
1963
1918
  },
1964
1919
  });
@@ -9,6 +9,7 @@ import type {
9
9
  import type { AgentSessionEvent } from "../../session/agent-session";
10
10
  import { resolveToCwd } from "../../tools/path-utils";
11
11
  import type { TodoStatus } from "../../tools/todo";
12
+ import { hasVisibleThinking } from "../../utils/thinking-display";
12
13
 
13
14
  interface MessageProgress {
14
15
  textEmitted: boolean;
@@ -256,13 +257,16 @@ function mapAssistantMessageUpdate(
256
257
  progress.textEmitted = true;
257
258
  }
258
259
  break;
259
- case "thinking_delta":
260
+ case "thinking_delta": {
261
+ const block = event.assistantMessageEvent.partial?.content?.[event.assistantMessageEvent.contentIndex];
262
+ if (block?.type === "thinking" && !hasVisibleThinking(block)) return [];
260
263
  sessionUpdate = "agent_thought_chunk";
261
264
  text = event.assistantMessageEvent.delta;
262
265
  if (text.length > 0 && progress) {
263
266
  progress.thoughtEmitted = true;
264
267
  }
265
268
  break;
269
+ }
266
270
  case "done":
267
271
  if (progress?.textEmitted) {
268
272
  return [];
@@ -16,6 +16,7 @@
16
16
  import * as fs from "node:fs";
17
17
  import * as path from "node:path";
18
18
  import type { AgentMessage, AgentTool } from "@oh-my-pi/pi-agent-core";
19
+ import type { Usage } from "@oh-my-pi/pi-ai";
19
20
  import { Container, Editor, matchesKey, ScrollView, Text, type TUI } from "@oh-my-pi/pi-tui";
20
21
  import { formatAge, formatBytes, formatDuration, formatNumber, getProjectDir, logger } from "@oh-my-pi/pi-utils";
21
22
  import { COLLAB_PROMPT_MESSAGE_TYPE, type CollabPromptDetails } from "../../collab/protocol";
@@ -35,10 +36,11 @@ import {
35
36
  type SkillPromptDetails,
36
37
  USER_INTERRUPT_LABEL,
37
38
  } from "../../session/messages";
38
- import type { SessionMessageEntry } from "../../session/session-manager";
39
- import { parseSessionEntries } from "../../session/session-manager";
39
+ import type { SessionMessageEntry } from "../../session/session-entries";
40
+ import { parseSessionEntries } from "../../session/session-loader";
40
41
  import { createIrcMessageCard } from "../../tools/irc";
41
42
  import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
43
+ import { hasVisibleThinking } from "../../utils/thinking-display";
42
44
  import type { ObservableSession, SessionObserverRegistry } from "../session-observer-registry";
43
45
  import { getEditorTheme, theme } from "../theme/theme";
44
46
  import { matchesSelectDown, matchesSelectUp } from "../utils/keybinding-matchers";
@@ -46,7 +48,7 @@ import { AssistantMessageComponent } from "./assistant-message";
46
48
  import { BashExecutionComponent } from "./bash-execution";
47
49
  import { BranchSummaryMessageComponent } from "./branch-summary-message";
48
50
  import { CollabPromptMessageComponent } from "./collab-prompt-message";
49
- import { CompactionSummaryMessageComponent } from "./compaction-summary-message";
51
+ import { CompactionSummaryMessageComponent, createHandoffSummaryMessageComponent } from "./compaction-summary-message";
50
52
  import { CustomMessageComponent } from "./custom-message";
51
53
  import { DynamicBorder } from "./dynamic-border";
52
54
  import { EvalExecutionComponent } from "./eval-execution";
@@ -56,6 +58,7 @@ import { SkillMessageComponent } from "./skill-message";
56
58
  import { formatContextUsage } from "./status-line/context-thresholds";
57
59
  import { ToolExecutionComponent } from "./tool-execution";
58
60
  import { TranscriptBlock, TranscriptContainer } from "./transcript-container";
61
+ import { createUsageRowBlock } from "./usage-row";
59
62
  import { UserMessageComponent } from "./user-message";
60
63
 
61
64
  /** Lines per page for PageUp/PageDown */
@@ -212,6 +215,7 @@ export class AgentHubOverlayComponent extends Container {
212
215
  #chatPendingTools = new Map<string, ToolExecutionComponent | ReadToolGroupComponent>();
213
216
  #chatReadArgs = new Map<string, Record<string, unknown>>();
214
217
  #chatReadGroup: ReadToolGroupComponent | null = null;
218
+ #pendingUsage: Usage | undefined;
215
219
  #chatWaitingPoll: ToolExecutionComponent | null = null;
216
220
  #chatExpandables: Array<{ setExpanded(expanded: boolean): void }> = [];
217
221
  #chatExpanded = false;
@@ -263,6 +267,15 @@ export class AgentHubOverlayComponent extends Container {
263
267
  this.#refreshRows();
264
268
  }
265
269
 
270
+ /**
271
+ * Whether the table view has no agents to show (every registered agent except
272
+ * Main, after the persisted-subagent scan in the constructor). The double-←
273
+ * gesture reads this to stay inert when there is nothing to open.
274
+ */
275
+ get isEmpty(): boolean {
276
+ return this.#rows.length === 0;
277
+ }
278
+
266
279
  /** Tear down every subscription and timer. Called by the overlay owner on close. */
267
280
  dispose(): void {
268
281
  for (const unsubscribe of this.#unsubscribers.splice(0)) unsubscribe();
@@ -437,6 +450,7 @@ export class AgentHubOverlayComponent extends Container {
437
450
  #renderRow(ref: AgentRef, selected: boolean, width: number): string {
438
451
  const cursor = selected ? theme.fg("accent", theme.nav.cursor) : " ";
439
452
  const parts: string[] = [statusBadge(ref.status), theme.bold(replaceTabs(ref.id))];
453
+ parts.push(theme.fg("dim", replaceTabs(ref.displayName)));
440
454
  parts.push(theme.fg("dim", ref.parentId ? `${ref.kind} · of ${ref.parentId}` : ref.kind));
441
455
  const observed = this.#observableFor(ref.id);
442
456
  const task = observed?.description ?? observed?.progress?.task;
@@ -849,6 +863,7 @@ export class AgentHubOverlayComponent extends Container {
849
863
  this.#chatPendingTools.clear();
850
864
  this.#chatReadArgs.clear();
851
865
  this.#chatReadGroup = null;
866
+ this.#pendingUsage = undefined;
852
867
  this.#chatWaitingPoll = null;
853
868
  this.#chatExpandables = [];
854
869
  this.#chatLog.dispose();
@@ -868,6 +883,13 @@ export class AgentHubOverlayComponent extends Container {
868
883
  this.#appendChatMessage(entries[i].message);
869
884
  }
870
885
  this.#chatBuiltCount = entries.length;
886
+ // Flush the trailing turn's usage row only once its tools are materialized.
887
+ // A read (or any tool) whose toolResult lands in a later debounced sync stays
888
+ // pending in #chatReadArgs / #chatPendingTools; flushing now would emit the
889
+ // row above it. The sync that drains the maps flushes it below the tools.
890
+ if (this.#chatReadArgs.size === 0 && this.#chatPendingTools.size === 0) {
891
+ this.#flushPendingUsage();
892
+ }
871
893
  }
872
894
 
873
895
  #trackExpandable(component: { setExpanded(expanded: boolean): void }): void {
@@ -897,7 +919,21 @@ export class AgentHubOverlayComponent extends Container {
897
919
  return this.#chatReadGroup;
898
920
  }
899
921
 
922
+ // The per-turn token-usage row must land below the turn's tool blocks, but
923
+ // normal `read` calls only materialize their group in #appendToolResult. Defer
924
+ // the row: stash it on the assistant message and flush once the turn's tools
925
+ // are placed — before the next non-toolResult message and at the end of each
926
+ // sync pass — sealing the read run so the row sits under it.
927
+ #flushPendingUsage(): void {
928
+ if (!this.#pendingUsage) return;
929
+ this.#chatReadGroup?.seal();
930
+ this.#chatReadGroup = null;
931
+ this.#chatLog.addChild(createUsageRowBlock(this.#pendingUsage));
932
+ this.#pendingUsage = undefined;
933
+ }
934
+
900
935
  #appendChatMessage(message: AgentMessage): void {
936
+ if (message.role !== "toolResult") this.#flushPendingUsage();
901
937
  switch (message.role) {
902
938
  case "assistant":
903
939
  this.#appendAssistantMessage(message);
@@ -986,13 +1022,12 @@ export class AgentHubOverlayComponent extends Container {
986
1022
  const assistantComponent = new AssistantMessageComponent(message, this.#hideThinkingBlock?.() ?? false, () =>
987
1023
  this.#requestRender(),
988
1024
  );
989
- assistantComponent.setUsageInfo(message.usage);
990
1025
  this.#chatLog.addChild(assistantComponent);
991
1026
 
992
1027
  const hasVisibleAssistantContent = message.content.some(
993
1028
  content =>
994
1029
  (content.type === "text" && content.text.trim().length > 0) ||
995
- (content.type === "thinking" && content.thinking.trim().length > 0),
1030
+ (content.type === "thinking" && hasVisibleThinking(content)),
996
1031
  );
997
1032
  if (hasVisibleAssistantContent) {
998
1033
  // New visible turn content closes the current read run (mirrors rebuild).
@@ -1065,6 +1100,8 @@ export class AgentHubOverlayComponent extends Container {
1065
1100
  this.#chatPendingTools.set(content.id, component);
1066
1101
  }
1067
1102
  }
1103
+
1104
+ this.#pendingUsage = settings.get("display.showTokenUsage") ? message.usage : undefined;
1068
1105
  }
1069
1106
 
1070
1107
  #appendToolResult(message: Extract<AgentMessage, { role: "toolResult" }>): void {
@@ -1178,6 +1215,15 @@ export class AgentHubOverlayComponent extends Container {
1178
1215
  this.#chatLog.addChild(card);
1179
1216
  return;
1180
1217
  }
1218
+ const handoffComponent = createHandoffSummaryMessageComponent(
1219
+ message as CustomMessage<unknown>,
1220
+ this.#chatExpanded,
1221
+ );
1222
+ if (handoffComponent) {
1223
+ this.#trackExpandable(handoffComponent);
1224
+ this.#chatLog.addChild(handoffComponent);
1225
+ return;
1226
+ }
1181
1227
  const component = new CustomMessageComponent(
1182
1228
  message as CustomMessage<unknown>,
1183
1229
  this.#getMessageRenderer?.(message.customType),
@@ -1,11 +1,10 @@
1
- import type { AssistantMessage, ImageContent, Usage } from "@oh-my-pi/pi-ai";
1
+ import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
2
2
  import { Container, Image, type ImageBudget, ImageProtocol, Markdown, Spacer, TERMINAL, Text } from "@oh-my-pi/pi-tui";
3
- import { formatNumber } from "@oh-my-pi/pi-utils";
4
- import { settings } from "../../config/settings";
5
3
  import type { AssistantThinkingRenderer } from "../../extensibility/extensions/types";
6
4
  import { getMarkdownTheme, theme } from "../../modes/theme/theme";
7
5
  import { resolveAbortLabel, shouldRenderAbortReason } from "../../session/messages";
8
6
  import { getPreviewLines, resolveImageOptions, TRUNCATE_LENGTHS } from "../../tools/render-utils";
7
+ import { getVisibleThinkingText, hasVisibleThinking } from "../../utils/thinking-display";
9
8
 
10
9
  /**
11
10
  * Max lines of a turn-ending provider error rendered inline in the transcript.
@@ -23,7 +22,6 @@ export class AssistantMessageComponent extends Container {
23
22
  #contentContainer: Container;
24
23
  #lastMessage?: AssistantMessage;
25
24
  #toolImagesByCallId = new Map<string, ImageContent[]>();
26
- #usageInfo?: Usage;
27
25
  #convertedKittyImages = new Map<string, ImageContent>();
28
26
  #kittyConversionsInFlight = new Set<string>();
29
27
  #transcriptBlockFinalized: boolean;
@@ -39,11 +37,9 @@ export class AssistantMessageComponent extends Container {
39
37
  /**
40
38
  * Monotonic content version reported to the transcript container via
41
39
  * {@link getTranscriptBlockVersion}. Bumped by {@link updateContent} — the
42
- * choke point every mutator funnels through, including the post-finalize
43
- * ones: `setErrorPinned(false)` restoring the inline error at the next
44
- * turn's `agent_start`, late tool-result images, async Kitty conversions,
45
- * and `setUsageInfo`. Without it, the container's committed-scrollback
46
- * bypass would replay this block's pre-mutation bytes forever.
40
+ * choke point every mutator funnels through, including post-finalize changes
41
+ * such as `setErrorPinned(false)` restoring the inline error at the next
42
+ * turn's `agent_start`, late tool-result images, and async Kitty conversions.
47
43
  */
48
44
  #blockVersion = 0;
49
45
  /** Whether the last updateContent carried an in-flight streaming partial; such
@@ -184,13 +180,6 @@ export class AssistantMessageComponent extends Container {
184
180
  }
185
181
  }
186
182
 
187
- setUsageInfo(usage: Usage): void {
188
- this.#usageInfo = usage;
189
- if (this.#lastMessage) {
190
- this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
191
- }
192
- }
193
-
194
183
  #renderToolImages(): void {
195
184
  const imageEntries = Array.from(this.#toolImagesByCallId.entries()).flatMap(([toolCallId, images]) =>
196
185
  images.map((image, index) => ({ image, key: `${toolCallId}:${index}` })),
@@ -245,7 +234,7 @@ export class AssistantMessageComponent extends Container {
245
234
  if (content.type === "text") {
246
235
  parts.push(content.text.trim() ? "T1" : "T0");
247
236
  } else if (content.type === "thinking") {
248
- if (!content.thinking.trim()) parts.push("K0");
237
+ if (!hasVisibleThinking(content)) parts.push("K0");
249
238
  else if (this.hideThinkingBlock) parts.push("KH");
250
239
  else parts.push("KV");
251
240
  } else {
@@ -255,12 +244,6 @@ export class AssistantMessageComponent extends Container {
255
244
  parts.push(`O:${content.type}`);
256
245
  }
257
246
  }
258
- if (settings.get("display.showTokenUsage") && this.#usageInfo) {
259
- const u = this.#usageInfo;
260
- parts.push(`u:${u.input + u.cacheWrite}:${u.output}:${u.cacheRead}`);
261
- } else {
262
- parts.push("u:");
263
- }
264
247
  return parts.join("|");
265
248
  }
266
249
 
@@ -284,7 +267,7 @@ export class AssistantMessageComponent extends Container {
284
267
  for (const item of this.#fastPathItems) {
285
268
  if (item.blockType === "thinking") {
286
269
  const content = message.content[item.contentIndex];
287
- if (content?.type === "thinking" && content.thinking.trim() !== item.lastText) return false;
270
+ if (content?.type === "thinking" && getVisibleThinkingText(content) !== item.lastText) return false;
288
271
  }
289
272
  }
290
273
  }
@@ -312,7 +295,7 @@ export class AssistantMessageComponent extends Container {
312
295
  if (item.blockType === "text" && content?.type === "text") {
313
296
  newText = content.text.trim();
314
297
  } else if (item.blockType === "thinking" && content?.type === "thinking") {
315
- newText = content.thinking.trim();
298
+ newText = getVisibleThinkingText(content);
316
299
  } else {
317
300
  // Block at this index is gone or changed type (index shift) — fail closed.
318
301
  this.#fastPathKey = undefined;
@@ -347,7 +330,7 @@ export class AssistantMessageComponent extends Container {
347
330
  const hasVisibleContent = message.content.some(
348
331
  c =>
349
332
  (c.type === "text" && c.text.trim()) ||
350
- (!this.hideThinkingBlock && c.type === "thinking" && c.thinking.trim()),
333
+ (!this.hideThinkingBlock && c.type === "thinking" && hasVisibleThinking(c)),
351
334
  );
352
335
 
353
336
  // Render content in order
@@ -362,7 +345,8 @@ export class AssistantMessageComponent extends Container {
362
345
  md.transientRenderCache = this.#lastUpdateTransient;
363
346
  this.#contentContainer.addChild(md);
364
347
  captureItems?.push({ md, contentIndex: i, blockType: "text", lastText: trimmed });
365
- } else if (content.type === "thinking" && content.thinking.trim()) {
348
+ } else if (content.type === "thinking" && hasVisibleThinking(content)) {
349
+ const thinkingText = getVisibleThinkingText(content);
366
350
  if (this.hideThinkingBlock) {
367
351
  thinkingIndex += 1;
368
352
  continue;
@@ -371,9 +355,8 @@ export class AssistantMessageComponent extends Container {
371
355
  // This avoids a superfluous blank line before separately-rendered tool execution blocks.
372
356
  const hasVisibleContentAfter = message.content
373
357
  .slice(i + 1)
374
- .some(c => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()));
358
+ .some(c => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && hasVisibleThinking(c)));
375
359
 
376
- const thinkingText = content.thinking.trim();
377
360
  // Thinking traces in thinkingText color, italic
378
361
  const md = new Markdown(thinkingText, 1, 0, getMarkdownTheme(), {
379
362
  color: (text: string) => theme.fg("thinkingText", text),
@@ -415,21 +398,6 @@ export class AssistantMessageComponent extends Container {
415
398
  ) {
416
399
  this.#appendErrorBlock(message.errorMessage);
417
400
  }
418
-
419
- // Token usage metadata
420
- if (settings.get("display.showTokenUsage") && this.#usageInfo) {
421
- const usage = this.#usageInfo;
422
- const totalInput = usage.input + usage.cacheWrite;
423
- const parts: string[] = [];
424
- parts.push(`${theme.icon.input} ${formatNumber(totalInput)}`);
425
- parts.push(`${theme.icon.output} ${formatNumber(usage.output)}`);
426
- if (usage.cacheRead > 0) {
427
- parts.push(`cache: ${formatNumber(usage.cacheRead)}`);
428
- }
429
- this.#contentContainer.addChild(new Spacer(1));
430
- this.#contentContainer.addChild(new Text(theme.fg("dim", parts.join(" ")), 1, 0));
431
- }
432
-
433
401
  // Store fast-path state for next call
434
402
  if (shouldCapture) {
435
403
  this.#fastPathItems = captureItems;