@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.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 (340) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/cli.js +5495 -5021
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
  10. package/dist/types/config/model-registry.d.ts +30 -1
  11. package/dist/types/config/models-config-schema.d.ts +18 -6
  12. package/dist/types/config/models-config.d.ts +14 -5
  13. package/dist/types/config/provider-globals.d.ts +2 -2
  14. package/dist/types/config/service-tier.d.ts +5 -1
  15. package/dist/types/config/settings-schema.d.ts +350 -50
  16. package/dist/types/config/settings.d.ts +7 -0
  17. package/dist/types/dap/client.d.ts +20 -0
  18. package/dist/types/exec/bash-executor.d.ts +38 -0
  19. package/dist/types/exec/direnv.d.ts +33 -0
  20. package/dist/types/export/html/args.d.ts +15 -0
  21. package/dist/types/export/html/index.d.ts +11 -26
  22. package/dist/types/export/html/web-palette.d.ts +55 -116
  23. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  24. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  25. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
  26. package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
  27. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
  28. package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
  29. package/dist/types/live/controller.d.ts +40 -0
  30. package/dist/types/live/protocol.d.ts +85 -0
  31. package/dist/types/live/protocol.test.d.ts +1 -0
  32. package/dist/types/live/transport.d.ts +48 -0
  33. package/dist/types/live/visualizer.d.ts +33 -0
  34. package/dist/types/lsp/client.d.ts +18 -1
  35. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  36. package/dist/types/mcp/manager.d.ts +6 -2
  37. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  38. package/dist/types/mcp/render.d.ts +2 -2
  39. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  40. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  41. package/dist/types/mcp/tool-bridge.d.ts +7 -3
  42. package/dist/types/mcp/types.d.ts +6 -0
  43. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  44. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  45. package/dist/types/modes/components/oauth-selector.d.ts +8 -0
  46. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  47. package/dist/types/modes/components/settings-defs.d.ts +8 -1
  48. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  49. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  50. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  51. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  52. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  53. package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
  54. package/dist/types/modes/interactive-mode.d.ts +5 -0
  55. package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
  56. package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
  57. package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
  58. package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
  59. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  60. package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
  61. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  62. package/dist/types/modes/theme/theme.d.ts +1 -1
  63. package/dist/types/modes/types.d.ts +3 -1
  64. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  65. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  66. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  67. package/dist/types/registry/agent-registry.d.ts +11 -4
  68. package/dist/types/sdk.d.ts +15 -1
  69. package/dist/types/secrets/index.d.ts +15 -2
  70. package/dist/types/secrets/obfuscator.d.ts +113 -15
  71. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  72. package/dist/types/session/agent-session-events.d.ts +84 -0
  73. package/dist/types/session/agent-session-types.d.ts +314 -0
  74. package/dist/types/session/agent-session.d.ts +120 -750
  75. package/dist/types/session/async-job-delivery.d.ts +28 -0
  76. package/dist/types/session/bash-runner.d.ts +75 -0
  77. package/dist/types/session/blob-store.d.ts +11 -1
  78. package/dist/types/session/blob-store.test.d.ts +1 -0
  79. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  80. package/dist/types/session/eval-runner.d.ts +49 -0
  81. package/dist/types/session/irc-bridge.d.ts +46 -0
  82. package/dist/types/session/messages.d.ts +22 -0
  83. package/dist/types/session/model-controls.d.ts +180 -0
  84. package/dist/types/session/prewalk.d.ts +46 -0
  85. package/dist/types/session/queued-messages.d.ts +22 -0
  86. package/dist/types/session/redis-session-storage.d.ts +1 -0
  87. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  88. package/dist/types/session/role-models.d.ts +15 -0
  89. package/dist/types/session/session-advisors.d.ts +228 -0
  90. package/dist/types/session/session-entries.d.ts +8 -0
  91. package/dist/types/session/session-handoff.d.ts +70 -0
  92. package/dist/types/session/session-history-format.d.ts +6 -1
  93. package/dist/types/session/session-loader.d.ts +6 -4
  94. package/dist/types/session/session-maintenance.d.ts +246 -0
  95. package/dist/types/session/session-manager.d.ts +41 -0
  96. package/dist/types/session/session-memory.d.ts +56 -0
  97. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  98. package/dist/types/session/session-stats.d.ts +48 -0
  99. package/dist/types/session/session-tools.d.ts +143 -0
  100. package/dist/types/session/session-workspace.d.ts +32 -0
  101. package/dist/types/session/stream-guards.d.ts +44 -0
  102. package/dist/types/session/todo-tracker.d.ts +56 -0
  103. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  104. package/dist/types/session/turn-recovery.d.ts +225 -0
  105. package/dist/types/system-prompt.d.ts +2 -0
  106. package/dist/types/task/executor.d.ts +4 -1
  107. package/dist/types/task/output-manager.d.ts +3 -1
  108. package/dist/types/task/types.d.ts +14 -0
  109. package/dist/types/tools/approval.d.ts +1 -0
  110. package/dist/types/tools/browser/launch.d.ts +5 -0
  111. package/dist/types/tools/hub/jobs.d.ts +1 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -4
  113. package/dist/types/tools/image-providers.d.ts +38 -0
  114. package/dist/types/tools/index.d.ts +8 -0
  115. package/dist/types/tools/path-utils.d.ts +8 -0
  116. package/dist/types/tools/report-tool-issue.d.ts +24 -9
  117. package/dist/types/tools/todo.d.ts +10 -4
  118. package/dist/types/tools/xdev.d.ts +5 -1
  119. package/dist/types/tts/streaming-player.d.ts +14 -0
  120. package/dist/types/tts/vocalizer.d.ts +5 -0
  121. package/dist/types/utils/jj.d.ts +29 -0
  122. package/dist/types/utils/lang-from-path.d.ts +1 -0
  123. package/dist/types/utils/title-generator.d.ts +30 -0
  124. package/dist/types/vibe/runtime.d.ts +36 -8
  125. package/dist/types/web/search/index.d.ts +1 -1
  126. package/dist/types/web/search/provider.d.ts +15 -5
  127. package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
  128. package/dist/types/web/search/types.d.ts +95 -1
  129. package/package.json +13 -12
  130. package/scripts/generate-share-viewer.ts +4 -6
  131. package/scripts/legacy-pi-virtual-module.ts +1 -1
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +2 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/grievances-cli.ts +2 -2
  141. package/src/cli/usage-cli.ts +11 -2
  142. package/src/collab/host.ts +3 -2
  143. package/src/commands/bench.ts +18 -4
  144. package/src/commands/launch.ts +4 -0
  145. package/src/config/__tests__/model-registry.test.ts +147 -0
  146. package/src/config/model-registry.ts +50 -2
  147. package/src/config/model-resolver.ts +0 -1
  148. package/src/config/models-config-schema.ts +15 -4
  149. package/src/config/provider-globals.ts +9 -9
  150. package/src/config/service-tier.ts +26 -1
  151. package/src/config/settings-schema.ts +250 -54
  152. package/src/config/settings.ts +81 -2
  153. package/src/dap/client.ts +37 -4
  154. package/src/edit/index.ts +49 -12
  155. package/src/eval/py/executor.ts +103 -33
  156. package/src/exec/bash-executor.ts +89 -6
  157. package/src/exec/direnv.ts +145 -0
  158. package/src/export/html/args.ts +20 -0
  159. package/src/export/html/index.ts +45 -47
  160. package/src/export/html/template.css +19 -1
  161. package/src/export/html/template.html +6 -0
  162. package/src/export/html/template.js +21 -0
  163. package/src/export/html/tool-views.generated.js +31 -31
  164. package/src/export/html/web-palette.ts +116 -132
  165. package/src/export/share.ts +249 -49
  166. package/src/extensibility/extensions/loader.ts +29 -1
  167. package/src/extensibility/extensions/runner.ts +6 -0
  168. package/src/extensibility/extensions/types.ts +33 -2
  169. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
  170. package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
  171. package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
  172. package/src/hindsight/state.ts +64 -6
  173. package/src/internal-urls/local-protocol.ts +2 -1
  174. package/src/internal-urls/memory-protocol.ts +2 -2
  175. package/src/internal-urls/registry-helpers.ts +40 -0
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/audio-worklet.txt +59 -0
  180. package/src/live/browser-runtime.txt +221 -0
  181. package/src/live/controller.ts +464 -0
  182. package/src/live/prompts/agent-final-message.md +3 -0
  183. package/src/live/prompts/live-instructions.md +23 -0
  184. package/src/live/protocol.test.ts +140 -0
  185. package/src/live/protocol.ts +233 -0
  186. package/src/live/transport.ts +490 -0
  187. package/src/live/visualizer.ts +234 -0
  188. package/src/lsp/client.ts +52 -9
  189. package/src/lsp/clients/biome-client.ts +3 -4
  190. package/src/lsp/index.ts +44 -11
  191. package/src/main.ts +9 -1
  192. package/src/mcp/config-writer.test.ts +43 -0
  193. package/src/mcp/config-writer.ts +109 -82
  194. package/src/mcp/manager.ts +39 -8
  195. package/src/mcp/oauth-discovery.ts +10 -2
  196. package/src/mcp/render.ts +94 -35
  197. package/src/mcp/smithery-auth.test.ts +29 -0
  198. package/src/mcp/smithery-auth.ts +3 -2
  199. package/src/mcp/tool-bridge.ts +107 -11
  200. package/src/mcp/types.ts +7 -0
  201. package/src/memories/index.ts +40 -20
  202. package/src/memory-backend/tool-names.ts +2 -0
  203. package/src/mnemopi/backend.ts +24 -7
  204. package/src/modes/acp/acp-agent.ts +56 -10
  205. package/src/modes/acp/acp-event-mapper.ts +8 -1
  206. package/src/modes/components/agent-hub.ts +27 -10
  207. package/src/modes/components/login-dialog.ts +14 -4
  208. package/src/modes/components/oauth-selector.ts +24 -7
  209. package/src/modes/components/plan-review-overlay.ts +350 -35
  210. package/src/modes/components/settings-defs.ts +28 -3
  211. package/src/modes/components/settings-selector.ts +178 -6
  212. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  213. package/src/modes/components/status-line/component.ts +123 -2
  214. package/src/modes/components/tool-execution.test.ts +63 -2
  215. package/src/modes/components/tool-execution.ts +11 -2
  216. package/src/modes/controllers/command-controller.ts +70 -22
  217. package/src/modes/controllers/event-controller.ts +111 -16
  218. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  219. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  220. package/src/modes/controllers/live-command-controller.ts +178 -0
  221. package/src/modes/controllers/mcp-command-controller.ts +80 -41
  222. package/src/modes/controllers/omfg-controller.ts +44 -40
  223. package/src/modes/controllers/selector-controller.ts +18 -10
  224. package/src/modes/controllers/session-focus-controller.ts +6 -1
  225. package/src/modes/interactive-mode.ts +160 -38
  226. package/src/modes/rpc/rpc-client.ts +94 -3
  227. package/src/modes/rpc/rpc-frame.ts +164 -4
  228. package/src/modes/rpc/rpc-messages.ts +127 -0
  229. package/src/modes/rpc/rpc-mode.ts +79 -7
  230. package/src/modes/rpc/rpc-types.ts +34 -2
  231. package/src/modes/runtime-init.ts +2 -0
  232. package/src/modes/setup-wizard/scenes/model.ts +5 -7
  233. package/src/modes/setup-wizard/scenes/providers.ts +3 -2
  234. package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
  235. package/src/modes/setup-wizard/scenes/theme.ts +13 -3
  236. package/src/modes/setup-wizard/scenes/types.ts +9 -1
  237. package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
  238. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  239. package/src/modes/theme/theme.ts +1 -1
  240. package/src/modes/types.ts +3 -1
  241. package/src/modes/utils/context-usage.ts +23 -7
  242. package/src/modes/utils/ui-helpers.ts +10 -5
  243. package/src/plan-mode/plan-files.ts +40 -0
  244. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  245. package/src/prompts/bench/cache-prefix.md +3 -0
  246. package/src/prompts/bench/cache-suffix.md +1 -0
  247. package/src/prompts/goals/guided-goal-system.md +24 -3
  248. package/src/prompts/system/custom-system-prompt.md +1 -1
  249. package/src/prompts/system/project-prompt.md +8 -1
  250. package/src/prompts/system/subagent-async-pending.md +6 -0
  251. package/src/prompts/system/system-prompt.md +1 -1
  252. package/src/prompts/system/workflow-notice.md +48 -40
  253. package/src/prompts/system/xdev-mount-notice.md +4 -0
  254. package/src/prompts/tools/task.md +12 -2
  255. package/src/prompts/tools/todo.md +3 -1
  256. package/src/registry/agent-lifecycle.ts +116 -46
  257. package/src/registry/agent-registry.ts +35 -8
  258. package/src/registry/persisted-agents.ts +26 -2
  259. package/src/sdk.ts +255 -139
  260. package/src/secrets/index.ts +127 -4
  261. package/src/secrets/obfuscator.ts +2328 -105
  262. package/src/session/acp-permission-gate.ts +165 -0
  263. package/src/session/agent-session-error-log.test.ts +1 -1
  264. package/src/session/agent-session-events.ts +66 -0
  265. package/src/session/agent-session-types.ts +334 -0
  266. package/src/session/agent-session.ts +1910 -12213
  267. package/src/session/artifacts.ts +6 -5
  268. package/src/session/async-job-delivery.ts +74 -0
  269. package/src/session/bash-runner.ts +326 -0
  270. package/src/session/blob-store.test.ts +56 -0
  271. package/src/session/blob-store.ts +18 -2
  272. package/src/session/checkpoint-entries.ts +81 -0
  273. package/src/session/eval-runner.ts +212 -0
  274. package/src/session/indexed-session-storage.ts +9 -2
  275. package/src/session/irc-bridge.ts +203 -0
  276. package/src/session/messages.ts +263 -1
  277. package/src/session/model-controls.ts +714 -0
  278. package/src/session/prewalk.ts +252 -0
  279. package/src/session/queued-messages.ts +93 -0
  280. package/src/session/redis-session-storage.ts +51 -11
  281. package/src/session/retry-fallback-chains.ts +455 -0
  282. package/src/session/role-models.ts +85 -0
  283. package/src/session/session-advisors.ts +1679 -0
  284. package/src/session/session-context.test.ts +224 -1
  285. package/src/session/session-context.ts +47 -5
  286. package/src/session/session-entries.ts +8 -0
  287. package/src/session/session-handoff.ts +308 -0
  288. package/src/session/session-history-format.ts +20 -9
  289. package/src/session/session-loader.ts +10 -51
  290. package/src/session/session-maintenance.ts +2983 -0
  291. package/src/session/session-manager.ts +390 -30
  292. package/src/session/session-memory.ts +222 -0
  293. package/src/session/session-provider-boundary.ts +307 -0
  294. package/src/session/session-stats.ts +293 -0
  295. package/src/session/session-tools.ts +943 -0
  296. package/src/session/session-workspace.ts +53 -0
  297. package/src/session/stream-guards.ts +417 -0
  298. package/src/session/todo-tracker.ts +380 -0
  299. package/src/session/ttsr-coordinator.ts +496 -0
  300. package/src/session/turn-recovery.ts +1629 -0
  301. package/src/slash-commands/builtin-registry.ts +92 -11
  302. package/src/slash-commands/helpers/usage-report.ts +25 -4
  303. package/src/system-prompt.ts +41 -17
  304. package/src/task/executor.ts +288 -70
  305. package/src/task/index.ts +148 -111
  306. package/src/task/output-manager.ts +25 -3
  307. package/src/task/persisted-revive.ts +4 -3
  308. package/src/task/structured-subagent.ts +4 -1
  309. package/src/task/types.ts +16 -0
  310. package/src/tools/approval.ts +55 -10
  311. package/src/tools/bash-interactive.ts +26 -0
  312. package/src/tools/bash-skill-urls.ts +28 -2
  313. package/src/tools/bash.ts +339 -123
  314. package/src/tools/browser/launch.ts +9 -1
  315. package/src/tools/browser/tab-supervisor.ts +8 -5
  316. package/src/tools/browser.ts +94 -47
  317. package/src/tools/hub/index.ts +1 -1
  318. package/src/tools/hub/jobs.ts +67 -8
  319. package/src/tools/image-gen.ts +20 -28
  320. package/src/tools/image-providers.ts +50 -0
  321. package/src/tools/index.ts +12 -0
  322. package/src/tools/path-utils.ts +49 -2
  323. package/src/tools/read.ts +156 -88
  324. package/src/tools/renderers.ts +7 -1
  325. package/src/tools/report-tool-issue.ts +79 -28
  326. package/src/tools/todo.ts +101 -11
  327. package/src/tools/vibe.ts +1 -2
  328. package/src/tools/write.ts +51 -1
  329. package/src/tools/xdev.ts +67 -3
  330. package/src/tts/streaming-player.ts +62 -4
  331. package/src/tts/vocalizer.ts +18 -1
  332. package/src/utils/jj.ts +125 -4
  333. package/src/utils/lang-from-path.ts +7 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/vibe/runtime.ts +852 -72
  336. package/src/web/search/index.ts +3 -6
  337. package/src/web/search/provider.ts +31 -16
  338. package/src/web/search/providers/firecrawl.ts +46 -13
  339. package/src/web/search/providers/xai.ts +7 -1
  340. package/src/web/search/types.ts +8 -1
@@ -0,0 +1,252 @@
1
+ import type { Agent, AgentMessage, AgentToolResult, AgentTurnEndContext } from "@oh-my-pi/pi-agent-core";
2
+ import { invalidateMessageCache } from "@oh-my-pi/pi-agent-core/compaction";
3
+ import type { Model } from "@oh-my-pi/pi-ai";
4
+ import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
5
+ import { prompt } from "@oh-my-pi/pi-utils";
6
+ import type { LocalProtocolOptions } from "../internal-urls";
7
+ import { resolveApprovedPlan } from "../plan-mode/approved-plan";
8
+ import { listPlanFiles, readPlanFile } from "../plan-mode/plan-files";
9
+ import type { PlanModeState } from "../plan-mode/state";
10
+ import planYoloHandoffPrompt from "../prompts/system/plan-yolo-handoff.md" with { type: "text" };
11
+ import prewalkChecklistPrompt from "../prompts/system/prewalk-checklist.md" with { type: "text" };
12
+ import prewalkContinuePrompt from "../prompts/system/prewalk-continue.md" with { type: "text" };
13
+ import prewalkPlanPrompt from "../prompts/system/prewalk-plan.md" with { type: "text" };
14
+ import type { ConfiguredThinkingLevel } from "../thinking";
15
+ import type { PlanProposalHandler } from "../tools/resolve";
16
+ import { ToolError } from "../tools/tool-errors";
17
+ import type { PlanYolo, Prewalk } from "./agent-session-types";
18
+ import type { SessionManager } from "./session-manager";
19
+
20
+ const PREWALK_PLAN_MESSAGE_TYPE = "prewalk-plan";
21
+ const PREWALK_CONTINUE_MESSAGE_TYPE = "prewalk-continue";
22
+ const PREWALK_CHECKLIST_MESSAGE_TYPE = "prewalk-checklist";
23
+ const PREWALK_ACTION_TOOLS: Record<string, true> = {
24
+ edit: true,
25
+ write: true,
26
+ };
27
+ const PLAN_YOLO_HANDOFF_MESSAGE_TYPE = "plan-yolo-handoff";
28
+
29
+ /** Capabilities the prewalk coordinator borrows from its owning session. */
30
+ export interface PrewalkCoordinatorHost {
31
+ agent: Agent;
32
+ sessionManager: SessionManager;
33
+ model(): Model | undefined;
34
+ emitNotice(level: "info" | "warning" | "error", message: string, source?: string): void;
35
+ setModelTemporary(
36
+ model: Model,
37
+ thinkingLevel?: ConfiguredThinkingLevel,
38
+ options?: { ephemeral?: boolean },
39
+ ): Promise<void>;
40
+ setActiveToolsByName(names: string[]): Promise<void>;
41
+ getActiveToolNames(): string[];
42
+ getEnabledToolNames(): string[];
43
+ hasBuiltInTool(name: string): boolean;
44
+ getPlanModeState(): PlanModeState | undefined;
45
+ setPlanModeState(state: PlanModeState | undefined): void;
46
+ getPlanReferencePath(): string;
47
+ setPlanProposalHandler(handler: PlanProposalHandler | null): void;
48
+ waitForSessionMessagePersistence(message: AgentMessage): Promise<void>;
49
+ localProtocolOptions(): LocalProtocolOptions;
50
+ }
51
+
52
+ /** Initial state for prewalk and plan-yolo startup flows. */
53
+ export interface PrewalkCoordinatorOptions {
54
+ prewalk?: Prewalk;
55
+ planYolo?: PlanYolo;
56
+ }
57
+
58
+ /** Coordinates one-way model prewalks and automatic plan-yolo handoffs. */
59
+ export class PrewalkCoordinator {
60
+ readonly #host: PrewalkCoordinatorHost;
61
+ #prewalk: Prewalk | undefined;
62
+ #planInjected = false;
63
+ #continuePending = false;
64
+ #todoSeen = false;
65
+ #planYolo: PlanYolo | undefined;
66
+ #planYoloPreviousTools: string[] | undefined;
67
+ #planYoloArmed = false;
68
+
69
+ constructor(host: PrewalkCoordinatorHost, options: PrewalkCoordinatorOptions = {}) {
70
+ this.#host = host;
71
+ this.#prewalk = options.prewalk;
72
+ this.#planYolo = options.planYolo;
73
+ }
74
+
75
+ /** Current prewalk target, if the one-way switch remains armed. */
76
+ get state(): Prewalk | undefined {
77
+ return this.#prewalk;
78
+ }
79
+
80
+ /** Advances the one-way prewalk switch at a completed assistant-turn boundary. */
81
+ async advanceAtTurnEnd(liveMessages: AgentMessage[], context: AgentTurnEndContext | undefined): Promise<void> {
82
+ const prewalk = this.#prewalk;
83
+ if (!prewalk || context?.message.role !== "assistant") return;
84
+ if (context.toolResults.some(result => result.toolName === "todo" && !result.isError)) this.#todoSeen = true;
85
+
86
+ const hasToolResults = context.toolResults.length > 0;
87
+ if (this.#planInjected && hasToolResults) {
88
+ this.#continuePending = true;
89
+ } else if (this.#continuePending) {
90
+ this.#continuePending = false;
91
+ this.#host.agent.steer({
92
+ role: "custom",
93
+ customType: PREWALK_CONTINUE_MESSAGE_TYPE,
94
+ content: prewalkContinuePrompt,
95
+ attribution: "agent",
96
+ display: false,
97
+ timestamp: Date.now(),
98
+ });
99
+ }
100
+
101
+ const todoGateOpen = this.#todoSeen || !this.#host.getActiveToolNames().includes("todo");
102
+ const action = todoGateOpen
103
+ ? context.toolResults.find(result => PREWALK_ACTION_TOOLS[result.toolName])
104
+ : undefined;
105
+ if (!action) {
106
+ if (!this.#planInjected) {
107
+ this.#planInjected = true;
108
+ this.#continuePending = true;
109
+ this.#host.agent.steer({
110
+ role: "custom",
111
+ customType: PREWALK_PLAN_MESSAGE_TYPE,
112
+ content: prewalkPlanPrompt,
113
+ display: false,
114
+ attribution: "agent",
115
+ timestamp: Date.now(),
116
+ });
117
+ this.#host.emitNotice("info", "Prewalk: injected deep-plan nudge.", "prewalk");
118
+ }
119
+ return;
120
+ }
121
+
122
+ await this.#host.waitForSessionMessagePersistence(context.message);
123
+ for (const toolResult of context.toolResults) {
124
+ await this.#host.waitForSessionMessagePersistence(toolResult);
125
+ }
126
+ this.#scrubPlanNudge(liveMessages);
127
+ const target = prewalk.target;
128
+ const currentModel = this.#host.model();
129
+ if (currentModel && modelsAreEqual(currentModel, target)) {
130
+ this.#prewalk = undefined;
131
+ return;
132
+ }
133
+ await this.#host.setModelTemporary(target, prewalk.thinkingLevel, { ephemeral: true });
134
+ this.#prewalk = undefined;
135
+ this.#host.emitNotice(
136
+ "info",
137
+ `Prewalk: switched to ${target.provider}/${target.id} after first ${action.toolName} call.`,
138
+ "prewalk",
139
+ );
140
+ this.#host.agent.steer({
141
+ role: "custom",
142
+ customType: PREWALK_CHECKLIST_MESSAGE_TYPE,
143
+ content: prewalkChecklistPrompt,
144
+ attribution: "agent",
145
+ display: false,
146
+ timestamp: Date.now(),
147
+ });
148
+ }
149
+
150
+ /** Arms a prewalk immediately for an explicit slash-command request. */
151
+ arm(target: Model, thinkingLevel?: ConfiguredThinkingLevel): void {
152
+ if (this.#prewalk) {
153
+ this.#host.emitNotice(
154
+ "info",
155
+ `Prewalk: already armed for ${this.#prewalk.target.provider}/${this.#prewalk.target.id}, waiting for the first edit/write.`,
156
+ "prewalk",
157
+ );
158
+ return;
159
+ }
160
+ this.#prewalk = { target, thinkingLevel };
161
+ this.#planInjected = true;
162
+ this.#continuePending = true;
163
+ this.#host.agent.steer({
164
+ role: "custom",
165
+ customType: PREWALK_PLAN_MESSAGE_TYPE,
166
+ content: prewalkPlanPrompt,
167
+ display: false,
168
+ attribution: "agent",
169
+ timestamp: Date.now(),
170
+ });
171
+ this.#host.emitNotice(
172
+ "info",
173
+ `Prewalk: armed for ${target.provider}/${target.id} — will switch at the first edit/write once the todo list exists.`,
174
+ "prewalk",
175
+ );
176
+ }
177
+
178
+ /** Lazily enables plan-yolo's plan phase before the first prompt is built. */
179
+ async armPlanYoloIfNeeded(): Promise<void> {
180
+ if (!this.#planYolo || this.#planYoloArmed) return;
181
+ this.#planYoloArmed = true;
182
+ const previousTools = this.#host.getEnabledToolNames();
183
+ const augmentations = this.#host.hasBuiltInTool("write") ? ["write"] : [];
184
+ await this.#host.setActiveToolsByName([...new Set([...previousTools, ...augmentations])]);
185
+ this.#planYoloPreviousTools = previousTools;
186
+ this.#host.setPlanModeState({
187
+ enabled: true,
188
+ planFilePath: this.#host.getPlanReferencePath() || "local://PLAN.md",
189
+ workflow: "parallel",
190
+ });
191
+ this.#host.setPlanProposalHandler(title => this.#finalizePlanYoloProposal(title));
192
+ }
193
+
194
+ #scrubPlanNudge(liveMessages: AgentMessage[]): void {
195
+ if (!this.#planInjected) return;
196
+ const isPlanNudge = (message: AgentMessage): boolean =>
197
+ message.role === "custom" && message.customType === PREWALK_PLAN_MESSAGE_TYPE;
198
+ for (let index = liveMessages.length - 1; index >= 0; index--) {
199
+ if (!isPlanNudge(liveMessages[index])) continue;
200
+ invalidateMessageCache(liveMessages[index]);
201
+ liveMessages.splice(index, 1);
202
+ }
203
+ const stateMessages = this.#host.agent.state.messages;
204
+ const filtered = stateMessages.filter(message => !isPlanNudge(message));
205
+ if (filtered.length !== stateMessages.length) this.#host.agent.replaceMessages(filtered);
206
+ }
207
+
208
+ async #finalizePlanYoloProposal(title: string): Promise<AgentToolResult<unknown>> {
209
+ const planYolo = this.#planYolo;
210
+ const state = this.#host.getPlanModeState();
211
+ if (!planYolo || !state?.enabled) throw new ToolError("Plan mode is not active.");
212
+ const { planFilePath, title: resolvedTitle } = await resolveApprovedPlan({
213
+ suppliedTitle: title,
214
+ statePlanFilePath: state.planFilePath,
215
+ readPlan: url =>
216
+ readPlanFile(url, {
217
+ localProtocolOptions: this.#host.localProtocolOptions(),
218
+ cwd: this.#host.sessionManager.getCwd(),
219
+ }),
220
+ listPlanFiles: () => listPlanFiles({ localProtocolOptions: this.#host.localProtocolOptions() }),
221
+ });
222
+ this.#host.setPlanModeState(undefined);
223
+ const previousTools = this.#planYoloPreviousTools;
224
+ try {
225
+ if (previousTools) await this.#host.setActiveToolsByName(previousTools);
226
+ } catch (error) {
227
+ this.#host.setPlanModeState(state);
228
+ throw error;
229
+ }
230
+ this.#host.setPlanProposalHandler(null);
231
+ this.#planYolo = undefined;
232
+ this.#planYoloPreviousTools = undefined;
233
+ await this.#host.setModelTemporary(planYolo.target, planYolo.thinkingLevel, { ephemeral: true });
234
+ this.#host.emitNotice(
235
+ "info",
236
+ `Plan-yolo: plan approved, switched to ${planYolo.target.provider}/${planYolo.target.id} to implement "${resolvedTitle}".`,
237
+ "plan-yolo",
238
+ );
239
+ this.#host.agent.steer({
240
+ role: "custom",
241
+ customType: PLAN_YOLO_HANDOFF_MESSAGE_TYPE,
242
+ content: prompt.render(planYoloHandoffPrompt, { planFilePath, title: resolvedTitle }),
243
+ attribution: "agent",
244
+ display: false,
245
+ timestamp: Date.now(),
246
+ });
247
+ return {
248
+ content: [{ type: "text", text: `Plan approved. Implementing now with ${planYolo.target.id}.` }],
249
+ details: { planFilePath, title: resolvedTitle, planExists: true },
250
+ };
251
+ }
252
+ }
@@ -0,0 +1,93 @@
1
+ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
2
+ import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
3
+ import type { RestoredQueuedMessage } from "./agent-session-types";
4
+ import { type CustomMessage, readQueueChipText } from "./messages";
5
+
6
+ function queuedTextContent(message: AgentMessage): string | undefined {
7
+ if (!("content" in message)) return undefined;
8
+ const content = message.content;
9
+ if (typeof content === "string") return content;
10
+ for (const part of content) {
11
+ if (part.type === "text") return part.text;
12
+ }
13
+ return undefined;
14
+ }
15
+
16
+ function queuedImageContent(message: AgentMessage): ImageContent[] | undefined {
17
+ if (!("content" in message) || typeof message.content === "string") return undefined;
18
+ const images: ImageContent[] = [];
19
+ for (const part of message.content) {
20
+ if (part.type === "image" && typeof part.data === "string" && typeof part.mimeType === "string") {
21
+ images.push(part);
22
+ }
23
+ }
24
+ return images.length > 0 ? images : undefined;
25
+ }
26
+
27
+ /** Whether a queued message should render in the queue UI. */
28
+ export function isDisplayableQueuedMessage(message: AgentMessage): boolean {
29
+ return !(message.role === "custom" && message.display === false);
30
+ }
31
+
32
+ /** Whether a queued message is an advisor card. */
33
+ export function isAdvisorCard(message: AgentMessage): message is CustomMessage {
34
+ return message.role === "custom" && message.customType === "advisor";
35
+ }
36
+
37
+ /** Whether a message is a terminal assistant answer containing text and no tools. */
38
+ export function isTerminalTextAssistantAnswer(message: AgentMessage | undefined): message is AssistantMessage {
39
+ if (message?.role !== "assistant" || message.stopReason !== "stop") return false;
40
+ let hasText = false;
41
+ for (const part of message.content) {
42
+ if (part.type === "toolCall") return false;
43
+ if (part.type === "text") {
44
+ if (part.text.trim().length > 0) hasText = true;
45
+ continue;
46
+ }
47
+ if (part.type === "thinking" || part.type === "redactedThinking" || part.type === "fallback") continue;
48
+ return false;
49
+ }
50
+ return hasText;
51
+ }
52
+
53
+ /** Whether queued content was authored by the user and can be restored to the editor. */
54
+ export function isUserQueuedMessage(message: AgentMessage): boolean {
55
+ if (message.role === "user") return true;
56
+ return message.role === "custom" && message.attribution === "user" && message.display !== false;
57
+ }
58
+
59
+ /** Hidden magic-keyword notices queued alongside a user prompt. */
60
+ export const MAGIC_KEYWORD_NOTICE_TYPES: Record<string, true> = {
61
+ "ultrathink-notice": true,
62
+ "orchestrate-notice": true,
63
+ "workflow-notice": true,
64
+ };
65
+
66
+ /** Hidden companion carrying vision descriptions for a text-only model. */
67
+ export const IMAGE_ATTACHMENT_DESCRIPTION_TYPE = "image-attachment-description";
68
+
69
+ /** Whether a hidden queued message is a companion of an adjacent user prompt. */
70
+ export function isHiddenUserCompanion(message: AgentMessage): boolean {
71
+ return (
72
+ message.role === "custom" &&
73
+ message.attribution === "user" &&
74
+ message.display === false &&
75
+ (MAGIC_KEYWORD_NOTICE_TYPES[message.customType] === true ||
76
+ message.customType === IMAGE_ATTACHMENT_DESCRIPTION_TYPE)
77
+ );
78
+ }
79
+
80
+ /** Human-readable text shown for a queued-message chip. */
81
+ export function queueChipText(message: AgentMessage): string {
82
+ if (message.role === "custom") {
83
+ return readQueueChipText(message.details) ?? queuedTextContent(message) ?? "";
84
+ }
85
+ const text = queuedTextContent(message) ?? "";
86
+ if (text) return text;
87
+ return queuedImageContent(message) ? "[Image]" : "";
88
+ }
89
+
90
+ /** Converts a queued user message to editor-restorable content. */
91
+ export function toRestoredQueuedMessage(message: AgentMessage): RestoredQueuedMessage {
92
+ return { text: queueChipText(message), images: queuedImageContent(message) };
93
+ }
@@ -13,6 +13,7 @@ import type { SessionTitleUpdate } from "./session-title-slot";
13
13
  * without dragging the entire Bun typings into this module.
14
14
  */
15
15
  export interface RedisSessionStorageClient {
16
+ send(command: string, args: string[]): Promise<unknown>;
16
17
  get(key: string): Promise<string | null>;
17
18
  getrange(key: string, start: number, end: number): Promise<string>;
18
19
  strlen(key: string): Promise<number>;
@@ -45,6 +46,26 @@ export interface RedisSessionStorageOptions {
45
46
  const DEFAULT_PREFIX = "omp:sessions:";
46
47
  const DEFAULT_SCAN_COUNT = 500;
47
48
 
49
+ const WRITE_FULL_SCRIPT = `-- OMP_WRITE_FULL
50
+ redis.call("SET", KEYS[1], ARGV[1])
51
+ redis.call("HSET", KEYS[2], ARGV[2], ARGV[3])
52
+ if ARGV[4] == "1" then
53
+ redis.call("HSET", KEYS[3], ARGV[2], ARGV[5])
54
+ else
55
+ redis.call("HDEL", KEYS[3], ARGV[2])
56
+ end
57
+ return 1`;
58
+
59
+ const APPEND_SCRIPT = `-- OMP_APPEND
60
+ local size = redis.call("APPEND", KEYS[1], ARGV[1])
61
+ redis.call("HSET", KEYS[2], ARGV[2], ARGV[3])
62
+ return size`;
63
+
64
+ const UPDATE_TITLE_SCRIPT = `-- OMP_UPDATE_TITLE
65
+ redis.call("HSET", KEYS[1], ARGV[1], ARGV[2])
66
+ redis.call("HSET", KEYS[2], ARGV[1], ARGV[3])
67
+ return 1`;
68
+
48
69
  function encodeTitleMeta(title: SessionTitleUpdate): string {
49
70
  return JSON.stringify(title);
50
71
  }
@@ -155,23 +176,42 @@ class RedisSessionStorageBackend implements SessionStorageBackend {
155
176
  }
156
177
 
157
178
  async writeFull(path: string, content: string, mtimeMs: number, title?: SessionTitleUpdate): Promise<void> {
158
- await this.#client.set(this.#fileKey(path), content);
159
- await this.#client.hset(this.#metaKey(), path, String(mtimeMs));
160
- if (title) {
161
- await this.#client.hset(this.#titleMetaKey(), path, encodeTitleMeta(title));
162
- } else {
163
- await this.#client.hdel(this.#titleMetaKey(), path);
164
- }
179
+ await this.#client.send("EVAL", [
180
+ WRITE_FULL_SCRIPT,
181
+ "3",
182
+ this.#fileKey(path),
183
+ this.#metaKey(),
184
+ this.#titleMetaKey(),
185
+ content,
186
+ path,
187
+ String(mtimeMs),
188
+ title ? "1" : "0",
189
+ title ? encodeTitleMeta(title) : "",
190
+ ]);
165
191
  }
166
192
 
167
193
  async append(path: string, line: string, mtimeMs: number): Promise<void> {
168
- await this.#client.append(this.#fileKey(path), line);
169
- await this.#client.hset(this.#metaKey(), path, String(mtimeMs));
194
+ await this.#client.send("EVAL", [
195
+ APPEND_SCRIPT,
196
+ "2",
197
+ this.#fileKey(path),
198
+ this.#metaKey(),
199
+ line,
200
+ path,
201
+ String(mtimeMs),
202
+ ]);
170
203
  }
171
204
 
172
205
  async updateSessionTitle(path: string, title: SessionTitleUpdate, mtimeMs: number): Promise<void> {
173
- await this.#client.hset(this.#metaKey(), path, String(mtimeMs));
174
- await this.#client.hset(this.#titleMetaKey(), path, encodeTitleMeta(title));
206
+ await this.#client.send("EVAL", [
207
+ UPDATE_TITLE_SCRIPT,
208
+ "2",
209
+ this.#metaKey(),
210
+ this.#titleMetaKey(),
211
+ path,
212
+ String(mtimeMs),
213
+ encodeTitleMeta(title),
214
+ ]);
175
215
  }
176
216
 
177
217
  async truncate(path: string, mtimeMs: number): Promise<void> {