@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1

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 (349) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +1 -1
  3. package/dist/cli.js +4685 -4614
  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/model-registry.d.ts +3 -1
  10. package/dist/types/config/models-config-schema.d.ts +18 -6
  11. package/dist/types/config/models-config.d.ts +14 -5
  12. package/dist/types/config/provider-globals.d.ts +2 -2
  13. package/dist/types/config/service-tier.d.ts +5 -1
  14. package/dist/types/config/settings-schema.d.ts +374 -47
  15. package/dist/types/config/settings.d.ts +7 -0
  16. package/dist/types/dap/client.d.ts +20 -0
  17. package/dist/types/exec/bash-executor.d.ts +38 -0
  18. package/dist/types/exec/direnv.d.ts +33 -0
  19. package/dist/types/export/html/args.d.ts +15 -0
  20. package/dist/types/export/html/index.d.ts +11 -26
  21. package/dist/types/export/html/web-palette.d.ts +55 -116
  22. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  23. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  24. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
  25. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
  26. package/dist/types/live/attestation.d.ts +2 -0
  27. package/dist/types/live/controller.d.ts +48 -0
  28. package/dist/types/live/protocol.d.ts +85 -0
  29. package/dist/types/live/protocol.test.d.ts +1 -0
  30. package/dist/types/live/transport.d.ts +35 -0
  31. package/dist/types/live/visualizer.d.ts +30 -0
  32. package/dist/types/lsp/client.d.ts +16 -1
  33. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  34. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  35. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  36. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  37. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  38. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  39. package/dist/types/modes/components/custom-message.d.ts +1 -1
  40. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  41. package/dist/types/modes/components/message-frame.d.ts +8 -4
  42. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  43. package/dist/types/modes/components/session-account-selector.d.ts +11 -0
  44. package/dist/types/modes/components/settings-defs.d.ts +7 -1
  45. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  46. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  48. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  49. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  50. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  51. package/dist/types/modes/interactive-mode.d.ts +6 -0
  52. package/dist/types/modes/theme/theme.d.ts +1 -1
  53. package/dist/types/modes/types.d.ts +4 -1
  54. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  55. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  56. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  57. package/dist/types/registry/agent-registry.d.ts +11 -4
  58. package/dist/types/sdk.d.ts +15 -1
  59. package/dist/types/secrets/index.d.ts +15 -2
  60. package/dist/types/secrets/obfuscator.d.ts +113 -15
  61. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  62. package/dist/types/session/agent-session-events.d.ts +84 -0
  63. package/dist/types/session/agent-session-types.d.ts +321 -0
  64. package/dist/types/session/agent-session.d.ts +135 -753
  65. package/dist/types/session/async-job-delivery.d.ts +28 -0
  66. package/dist/types/session/auth-storage.d.ts +1 -1
  67. package/dist/types/session/bash-runner.d.ts +75 -0
  68. package/dist/types/session/blob-store.d.ts +11 -1
  69. package/dist/types/session/blob-store.test.d.ts +1 -0
  70. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  71. package/dist/types/session/eval-runner.d.ts +51 -0
  72. package/dist/types/session/irc-bridge.d.ts +46 -0
  73. package/dist/types/session/messages.d.ts +24 -0
  74. package/dist/types/session/model-controls.d.ts +180 -0
  75. package/dist/types/session/prewalk.d.ts +46 -0
  76. package/dist/types/session/queued-messages.d.ts +22 -0
  77. package/dist/types/session/redis-session-storage.d.ts +1 -0
  78. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  79. package/dist/types/session/role-models.d.ts +15 -0
  80. package/dist/types/session/session-advisors.d.ts +228 -0
  81. package/dist/types/session/session-entries.d.ts +8 -0
  82. package/dist/types/session/session-handoff.d.ts +70 -0
  83. package/dist/types/session/session-history-format.d.ts +6 -1
  84. package/dist/types/session/session-maintenance.d.ts +246 -0
  85. package/dist/types/session/session-manager.d.ts +41 -0
  86. package/dist/types/session/session-memory.d.ts +56 -0
  87. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  88. package/dist/types/session/session-stats.d.ts +48 -0
  89. package/dist/types/session/session-tools.d.ts +158 -0
  90. package/dist/types/session/session-workspace.d.ts +32 -0
  91. package/dist/types/session/stream-guards.d.ts +44 -0
  92. package/dist/types/session/streaming-output.d.ts +8 -0
  93. package/dist/types/session/todo-tracker.d.ts +56 -0
  94. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  95. package/dist/types/session/turn-recovery.d.ts +225 -0
  96. package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
  97. package/dist/types/stt/index.d.ts +0 -2
  98. package/dist/types/stt/stt-controller.d.ts +7 -0
  99. package/dist/types/system-prompt.d.ts +2 -0
  100. package/dist/types/task/executor.d.ts +4 -1
  101. package/dist/types/task/output-manager.d.ts +3 -1
  102. package/dist/types/tiny/title-client.d.ts +10 -0
  103. package/dist/types/tools/approval.d.ts +1 -0
  104. package/dist/types/tools/browser/launch.d.ts +5 -0
  105. package/dist/types/tools/builtin-names.d.ts +1 -1
  106. package/dist/types/tools/computer/protocol.d.ts +43 -0
  107. package/dist/types/tools/computer/supervisor.d.ts +32 -0
  108. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  109. package/dist/types/tools/computer/worker.d.ts +15 -0
  110. package/dist/types/tools/computer-renderer.d.ts +22 -0
  111. package/dist/types/tools/computer.d.ts +71 -0
  112. package/dist/types/tools/context.d.ts +2 -0
  113. package/dist/types/tools/default-renderer.d.ts +21 -0
  114. package/dist/types/tools/image-gen.d.ts +4 -4
  115. package/dist/types/tools/image-providers.d.ts +38 -0
  116. package/dist/types/tools/index.d.ts +7 -0
  117. package/dist/types/tools/path-utils.d.ts +8 -0
  118. package/dist/types/tools/todo.d.ts +10 -4
  119. package/dist/types/tools/xdev.d.ts +5 -1
  120. package/dist/types/tts/streaming-player.d.ts +10 -29
  121. package/dist/types/tts/vocalizer.d.ts +5 -0
  122. package/dist/types/utils/jj.d.ts +29 -0
  123. package/dist/types/utils/lang-from-path.d.ts +1 -0
  124. package/dist/types/utils/title-generator.d.ts +30 -0
  125. package/dist/types/utils/tools-manager.d.ts +1 -2
  126. package/dist/types/vibe/runtime.d.ts +36 -8
  127. package/dist/types/web/search/index.d.ts +1 -1
  128. package/dist/types/web/search/provider.d.ts +15 -5
  129. package/dist/types/web/search/types.d.ts +94 -0
  130. package/package.json +12 -12
  131. package/scripts/generate-share-viewer.ts +4 -6
  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 +3 -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/setup-cli.ts +2 -14
  141. package/src/cli/usage-cli.ts +10 -1
  142. package/src/cli.ts +8 -0
  143. package/src/collab/host.ts +3 -2
  144. package/src/commands/bench.ts +18 -4
  145. package/src/commands/launch.ts +4 -0
  146. package/src/config/model-registry.ts +16 -2
  147. package/src/config/models-config-schema.ts +15 -4
  148. package/src/config/provider-globals.ts +9 -9
  149. package/src/config/service-tier.ts +26 -1
  150. package/src/config/settings-schema.ts +270 -51
  151. package/src/config/settings.ts +81 -2
  152. package/src/dap/client.ts +37 -4
  153. package/src/edit/index.ts +49 -12
  154. package/src/eval/executor-base.ts +1 -0
  155. package/src/eval/js/executor.ts +2 -0
  156. package/src/eval/py/executor.ts +103 -33
  157. package/src/exec/bash-executor.ts +90 -6
  158. package/src/exec/direnv.ts +145 -0
  159. package/src/export/html/args.ts +20 -0
  160. package/src/export/html/index.ts +45 -47
  161. package/src/export/html/template.css +19 -1
  162. package/src/export/html/template.html +6 -0
  163. package/src/export/html/template.js +21 -0
  164. package/src/export/html/tool-views.generated.js +31 -31
  165. package/src/export/html/web-palette.ts +116 -132
  166. package/src/export/share.ts +249 -49
  167. package/src/extensibility/extensions/loader.ts +29 -1
  168. package/src/extensibility/extensions/runner.ts +6 -0
  169. package/src/extensibility/extensions/types.ts +33 -2
  170. package/src/extensibility/extensions/wrapper.ts +68 -12
  171. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
  172. package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
  173. package/src/hindsight/state.ts +64 -6
  174. package/src/internal-urls/local-protocol.ts +2 -1
  175. package/src/internal-urls/memory-protocol.ts +2 -2
  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/attestation.ts +91 -0
  180. package/src/live/controller.ts +517 -0
  181. package/src/live/prompts/agent-final-message.md +3 -0
  182. package/src/live/prompts/live-instructions.md +23 -0
  183. package/src/live/protocol.test.ts +140 -0
  184. package/src/live/protocol.ts +233 -0
  185. package/src/live/transport.ts +422 -0
  186. package/src/live/visualizer.ts +214 -0
  187. package/src/lsp/client.ts +29 -9
  188. package/src/lsp/index.ts +19 -4
  189. package/src/main.ts +8 -1
  190. package/src/mcp/config-writer.test.ts +43 -0
  191. package/src/mcp/config-writer.ts +109 -82
  192. package/src/mcp/oauth-discovery.ts +10 -2
  193. package/src/mcp/smithery-auth.test.ts +29 -0
  194. package/src/mcp/smithery-auth.ts +3 -2
  195. package/src/memories/index.ts +40 -20
  196. package/src/memory-backend/tool-names.ts +2 -0
  197. package/src/mnemopi/backend.ts +24 -7
  198. package/src/modes/acp/acp-agent.ts +56 -10
  199. package/src/modes/acp/acp-event-mapper.ts +8 -1
  200. package/src/modes/components/agent-hub.ts +1 -1
  201. package/src/modes/components/assistant-message.ts +7 -2
  202. package/src/modes/components/custom-message.ts +4 -1
  203. package/src/modes/components/login-dialog.ts +14 -4
  204. package/src/modes/components/message-frame.ts +14 -8
  205. package/src/modes/components/plan-review-overlay.ts +350 -35
  206. package/src/modes/components/session-account-selector.ts +62 -0
  207. package/src/modes/components/settings-defs.ts +23 -1
  208. package/src/modes/components/settings-selector.ts +169 -1
  209. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  210. package/src/modes/components/status-line/component.ts +123 -2
  211. package/src/modes/components/tool-execution.ts +28 -112
  212. package/src/modes/controllers/command-controller.ts +56 -15
  213. package/src/modes/controllers/event-controller.ts +111 -16
  214. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  215. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  216. package/src/modes/controllers/input-controller.ts +47 -39
  217. package/src/modes/controllers/live-command-controller.ts +255 -0
  218. package/src/modes/controllers/mcp-command-controller.ts +10 -1
  219. package/src/modes/controllers/omfg-controller.ts +44 -40
  220. package/src/modes/controllers/selector-controller.ts +80 -10
  221. package/src/modes/controllers/session-focus-controller.ts +6 -1
  222. package/src/modes/interactive-mode.ts +176 -38
  223. package/src/modes/runtime-init.ts +2 -0
  224. package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
  225. package/src/modes/theme/theme.ts +1 -1
  226. package/src/modes/types.ts +4 -1
  227. package/src/modes/utils/context-usage.ts +23 -7
  228. package/src/modes/utils/ui-helpers.ts +10 -5
  229. package/src/plan-mode/plan-files.ts +40 -0
  230. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  231. package/src/prompts/bench/cache-prefix.md +3 -0
  232. package/src/prompts/bench/cache-suffix.md +1 -0
  233. package/src/prompts/system/computer-safety.md +14 -0
  234. package/src/prompts/system/custom-system-prompt.md +1 -1
  235. package/src/prompts/system/project-prompt.md +8 -1
  236. package/src/prompts/system/subagent-async-pending.md +6 -0
  237. package/src/prompts/system/system-prompt.md +1 -1
  238. package/src/prompts/system/workflow-notice.md +48 -40
  239. package/src/prompts/system/xdev-mount-notice.md +4 -0
  240. package/src/prompts/tools/computer.md +26 -0
  241. package/src/prompts/tools/todo.md +3 -1
  242. package/src/registry/agent-lifecycle.ts +116 -46
  243. package/src/registry/agent-registry.ts +35 -8
  244. package/src/registry/persisted-agents.ts +26 -2
  245. package/src/sdk.ts +215 -136
  246. package/src/secrets/index.ts +127 -4
  247. package/src/secrets/obfuscator.ts +2328 -105
  248. package/src/session/acp-permission-gate.ts +165 -0
  249. package/src/session/agent-session-error-log.test.ts +1 -1
  250. package/src/session/agent-session-events.ts +66 -0
  251. package/src/session/agent-session-types.ts +343 -0
  252. package/src/session/agent-session.ts +1940 -12218
  253. package/src/session/artifacts.ts +6 -5
  254. package/src/session/async-job-delivery.ts +74 -0
  255. package/src/session/auth-storage.ts +1 -0
  256. package/src/session/bash-runner.ts +326 -0
  257. package/src/session/blob-store.test.ts +56 -0
  258. package/src/session/blob-store.ts +18 -2
  259. package/src/session/checkpoint-entries.ts +81 -0
  260. package/src/session/eval-runner.ts +217 -0
  261. package/src/session/indexed-session-storage.ts +9 -2
  262. package/src/session/irc-bridge.ts +203 -0
  263. package/src/session/messages.ts +266 -1
  264. package/src/session/model-controls.ts +714 -0
  265. package/src/session/prewalk.ts +252 -0
  266. package/src/session/queued-messages.ts +93 -0
  267. package/src/session/redis-session-storage.ts +51 -11
  268. package/src/session/retry-fallback-chains.ts +455 -0
  269. package/src/session/role-models.ts +85 -0
  270. package/src/session/session-advisors.ts +1679 -0
  271. package/src/session/session-context.ts +6 -3
  272. package/src/session/session-entries.ts +8 -0
  273. package/src/session/session-handoff.ts +308 -0
  274. package/src/session/session-history-format.ts +20 -9
  275. package/src/session/session-loader.ts +0 -46
  276. package/src/session/session-maintenance.ts +2983 -0
  277. package/src/session/session-manager.ts +390 -30
  278. package/src/session/session-memory.ts +222 -0
  279. package/src/session/session-provider-boundary.ts +307 -0
  280. package/src/session/session-stats.ts +293 -0
  281. package/src/session/session-tools.ts +980 -0
  282. package/src/session/session-workspace.ts +53 -0
  283. package/src/session/stream-guards.ts +417 -0
  284. package/src/session/streaming-output.ts +52 -5
  285. package/src/session/todo-tracker.ts +380 -0
  286. package/src/session/ttsr-coordinator.ts +496 -0
  287. package/src/session/turn-recovery.ts +1629 -0
  288. package/src/slash-commands/builtin-registry.ts +257 -20
  289. package/src/slash-commands/helpers/session-pin.ts +44 -0
  290. package/src/slash-commands/helpers/usage-report.ts +22 -3
  291. package/src/stt/downloader.ts +0 -2
  292. package/src/stt/index.ts +0 -2
  293. package/src/stt/stt-controller.ts +57 -146
  294. package/src/system-prompt.ts +45 -17
  295. package/src/task/executor.ts +285 -70
  296. package/src/task/index.ts +98 -105
  297. package/src/task/output-manager.ts +25 -3
  298. package/src/task/persisted-revive.ts +4 -3
  299. package/src/task/structured-subagent.ts +1 -0
  300. package/src/tiny/title-client.ts +22 -0
  301. package/src/tools/approval.ts +55 -10
  302. package/src/tools/bash-interactive.ts +116 -86
  303. package/src/tools/bash-skill-urls.ts +28 -2
  304. package/src/tools/bash.ts +339 -123
  305. package/src/tools/browser/launch.ts +9 -1
  306. package/src/tools/browser/tab-supervisor.ts +8 -5
  307. package/src/tools/browser.ts +94 -47
  308. package/src/tools/builtin-names.ts +1 -0
  309. package/src/tools/computer/protocol.ts +28 -0
  310. package/src/tools/computer/supervisor.ts +258 -0
  311. package/src/tools/computer/worker-entry.ts +25 -0
  312. package/src/tools/computer/worker.ts +135 -0
  313. package/src/tools/computer-renderer.ts +108 -0
  314. package/src/tools/computer.ts +433 -0
  315. package/src/tools/context.ts +2 -0
  316. package/src/tools/default-renderer.ts +139 -0
  317. package/src/tools/essential-tools.ts +1 -0
  318. package/src/tools/image-gen.ts +20 -28
  319. package/src/tools/image-providers.ts +50 -0
  320. package/src/tools/index.ts +14 -0
  321. package/src/tools/path-utils.ts +49 -2
  322. package/src/tools/read.ts +156 -88
  323. package/src/tools/renderers.ts +9 -1
  324. package/src/tools/todo.ts +101 -11
  325. package/src/tools/vibe.ts +1 -2
  326. package/src/tools/write.ts +51 -1
  327. package/src/tools/xdev.ts +121 -29
  328. package/src/tts/streaming-player.ts +82 -283
  329. package/src/tts/vocalizer.ts +18 -1
  330. package/src/utils/clipboard.ts +1 -30
  331. package/src/utils/jj.ts +125 -4
  332. package/src/utils/lang-from-path.ts +7 -0
  333. package/src/utils/mac-file-urls.applescript +37 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/utils/tool-choice.ts +14 -0
  336. package/src/utils/tools-manager.ts +1 -19
  337. package/src/vibe/runtime.ts +852 -72
  338. package/src/web/search/index.ts +3 -6
  339. package/src/web/search/provider.ts +31 -16
  340. package/src/web/search/providers/xai.ts +7 -1
  341. package/src/web/search/types.ts +3 -0
  342. package/dist/types/stt/recorder.d.ts +0 -30
  343. package/dist/types/stt/transcriber.d.ts +0 -14
  344. package/dist/types/stt/wav.d.ts +0 -29
  345. package/dist/types/tts/player.d.ts +0 -32
  346. package/src/stt/recorder.ts +0 -551
  347. package/src/stt/transcriber.ts +0 -60
  348. package/src/stt/wav.ts +0 -173
  349. package/src/tts/player.ts +0 -137
@@ -0,0 +1,222 @@
1
+ /** Session memory backend lifecycle and transcript resets. */
2
+
3
+ import type { Agent, AgentTool } from "@oh-my-pi/pi-agent-core";
4
+ import { logger } from "@oh-my-pi/pi-utils";
5
+ import type { ModelRegistry } from "../config/model-registry";
6
+ import type { Settings } from "../config/settings";
7
+ import type { HindsightSessionState } from "../hindsight/state";
8
+ import { resolveMemoryBackend } from "../memory-backend/resolve";
9
+ import type { MemoryBackendStartOptions } from "../memory-backend/types";
10
+ import type { MnemopiSessionState } from "../mnemopi/state";
11
+
12
+ /** Capabilities borrowed from the owning AgentSession. */
13
+ export interface SessionMemoryHost {
14
+ agent: Agent;
15
+ settings: Settings;
16
+ modelRegistry: ModelRegistry;
17
+ isDisposed(): boolean;
18
+ memoryBackendSession(): MemoryBackendStartOptions["session"];
19
+ getHindsightSessionState(): HindsightSessionState | undefined;
20
+ setHindsightSessionState(state: HindsightSessionState | undefined): void;
21
+ getMnemopiSessionState(): MnemopiSessionState | undefined;
22
+ takeMnemopiSessionState(): MnemopiSessionState | undefined;
23
+ setBaseSystemPrompt(prompt: string[]): void;
24
+ refreshBaseSystemPrompt(): Promise<void>;
25
+ replaceMemoryTools(tools: AgentTool[]): Promise<void>;
26
+ }
27
+
28
+ /** Owns memory backend transitions and transcript-scoped memory state. */
29
+ export class SessionMemory {
30
+ readonly #host: SessionMemoryHost;
31
+ readonly #memoryAgentDir: string | undefined;
32
+ readonly #memoryTaskDepth: number;
33
+ readonly #createMemoryTools: (() => Promise<AgentTool[]>) | undefined;
34
+ #memoryBackendTransition: Promise<void> = Promise.resolve();
35
+ #localMemoryStartupAbort: AbortController | undefined;
36
+ #baseSystemPromptBeforeMemoryPromotion: string[] | undefined;
37
+
38
+ constructor(
39
+ host: SessionMemoryHost,
40
+ options: {
41
+ memoryAgentDir?: string;
42
+ memoryTaskDepth?: number;
43
+ createMemoryTools?: () => Promise<AgentTool[]>;
44
+ },
45
+ ) {
46
+ this.#host = host;
47
+ this.#memoryAgentDir = options.memoryAgentDir;
48
+ this.#memoryTaskDepth = options.memoryTaskDepth ?? 0;
49
+ this.#createMemoryTools = options.createMemoryTools;
50
+ }
51
+
52
+ /** Current serialized backend transition, used by prompt and disposal drains. */
53
+ get transition(): Promise<void> {
54
+ return this.#memoryBackendTransition;
55
+ }
56
+
57
+ /** Base prompt captured before a per-turn memory promotion. */
58
+ get promotionSnapshot(): string[] | undefined {
59
+ return this.#baseSystemPromptBeforeMemoryPromotion;
60
+ }
61
+
62
+ /** Clears the per-turn memory promotion after a canonical prompt rebuild. */
63
+ clearPromotionSnapshot(): void {
64
+ this.#baseSystemPromptBeforeMemoryPromotion = undefined;
65
+ }
66
+
67
+ /** Captures the canonical prompt before the first per-turn memory promotion. */
68
+ capturePromotionSnapshot(prompt: string[]): void {
69
+ this.#baseSystemPromptBeforeMemoryPromotion ??= prompt;
70
+ }
71
+
72
+ /** Restores a promotion snapshot while rolling back a failed session switch. */
73
+ restorePromotionSnapshot(prompt: string[] | undefined): void {
74
+ this.#baseSystemPromptBeforeMemoryPromotion = prompt;
75
+ }
76
+ /** Rekeys every active memory backend to the current provider session. */
77
+ rekeyForCurrentSessionId(): void {
78
+ this.#rekeyHindsightMemoryForCurrentSessionId();
79
+ this.#rekeyMnemopiMemoryForCurrentSessionId();
80
+ }
81
+
82
+ #rekeyHindsightMemoryForCurrentSessionId(): void {
83
+ if (this.#host.settings.get("memory.backend") !== "hindsight") return;
84
+ const sid = this.#host.agent.sessionId;
85
+ if (!sid) return;
86
+ this.#host.getHindsightSessionState()?.setSessionId(sid);
87
+ }
88
+
89
+ #rekeyMnemopiMemoryForCurrentSessionId(): void {
90
+ if (this.#host.settings.get("memory.backend") !== "mnemopi") return;
91
+ const sid = this.#host.agent.sessionId;
92
+ if (!sid) return;
93
+ this.#host.getMnemopiSessionState()?.setSessionId(sid);
94
+ }
95
+
96
+ /** New session file: reset auto-recall / retain-threshold counters for the new transcript. */
97
+ #resetHindsightConversationTrackingIfHindsight(): boolean {
98
+ if (this.#host.settings.get("memory.backend") !== "hindsight") return false;
99
+ const state = this.#host.getHindsightSessionState();
100
+ if (!state || state.aliasOf) return false;
101
+ state.resetConversationTracking();
102
+ return true;
103
+ }
104
+
105
+ #resetMnemopiConversationTrackingIfMnemopi(): boolean {
106
+ if (this.#host.settings.get("memory.backend") !== "mnemopi") return false;
107
+ const state = this.#host.getMnemopiSessionState();
108
+ if (!state || state.aliasOf) return false;
109
+ state.resetConversationTracking();
110
+ return true;
111
+ }
112
+
113
+ /** Resets transcript-scoped memory counters and removes a promoted prompt. */
114
+ async resetContextForNewTranscript(): Promise<void> {
115
+ const hadPromotedMemoryPrompt = this.#baseSystemPromptBeforeMemoryPromotion !== undefined;
116
+ const resetHindsight = this.#resetHindsightConversationTrackingIfHindsight();
117
+ const resetMnemopi = this.#resetMnemopiConversationTrackingIfMnemopi();
118
+ if (hadPromotedMemoryPrompt) {
119
+ this.#host.setBaseSystemPrompt(this.#baseSystemPromptBeforeMemoryPromotion!);
120
+ this.#baseSystemPromptBeforeMemoryPromotion = undefined;
121
+ }
122
+ if (resetHindsight || resetMnemopi || hadPromotedMemoryPrompt) {
123
+ await this.#host.refreshBaseSystemPrompt();
124
+ }
125
+ }
126
+
127
+ /** Cancel the local rollout-memory startup owned by this session. */
128
+ cancelLocalMemoryStartup(): void {
129
+ this.#localMemoryStartupAbort?.abort();
130
+ this.#localMemoryStartupAbort = undefined;
131
+ }
132
+
133
+ /** Start a new local rollout-memory generation and cancel its predecessor. */
134
+ beginLocalMemoryStartup(): AbortSignal {
135
+ this.cancelLocalMemoryStartup();
136
+ const controller = new AbortController();
137
+ this.#localMemoryStartupAbort = controller;
138
+ return controller.signal;
139
+ }
140
+
141
+ /** Release the local startup slot if `signal` still owns it. */
142
+ endLocalMemoryStartup(signal: AbortSignal): void {
143
+ if (this.#localMemoryStartupAbort?.signal === signal) this.#localMemoryStartupAbort = undefined;
144
+ }
145
+
146
+ async #disposeMemoryBackendState(consolidateMnemopi = true): Promise<void> {
147
+ this.cancelLocalMemoryStartup();
148
+ const hindsight = this.#host.getHindsightSessionState();
149
+ if (hindsight) {
150
+ try {
151
+ await hindsight.flushRetainQueue();
152
+ } catch (error) {
153
+ logger.warn("Memory lifecycle: Hindsight flush failed", { error: String(error) });
154
+ }
155
+ this.#host.setHindsightSessionState(undefined);
156
+ hindsight.dispose();
157
+ }
158
+
159
+ const mnemopi = this.#host.takeMnemopiSessionState();
160
+ if (mnemopi) {
161
+ try {
162
+ await mnemopi.dispose({ consolidate: consolidateMnemopi });
163
+ } catch (error) {
164
+ logger.warn("Memory lifecycle: Mnemopi dispose failed", { error: String(error) });
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Apply the selected memory backend to runtime state, tools, and prompt.
171
+ * Concurrent settings changes run in order and settle before the next turn.
172
+ */
173
+ async applyMemoryBackend(): Promise<void> {
174
+ if (this.#host.isDisposed()) return;
175
+ const transition = this.#memoryBackendTransition.then(() => this.#applyMemoryBackend());
176
+ this.#memoryBackendTransition = transition.then(
177
+ () => undefined,
178
+ () => undefined,
179
+ );
180
+ await transition;
181
+ }
182
+
183
+ async #applyMemoryBackend(): Promise<void> {
184
+ if (this.#host.isDisposed()) return;
185
+ try {
186
+ await this.#disposeMemoryBackendState();
187
+ if (this.#memoryAgentDir && this.#memoryTaskDepth === 0 && !this.#host.isDisposed()) {
188
+ const backend = await resolveMemoryBackend(this.#host.settings);
189
+ await backend.start({
190
+ session: this.#host.memoryBackendSession(),
191
+ settings: this.#host.settings,
192
+ modelRegistry: this.#host.modelRegistry,
193
+ agentDir: this.#memoryAgentDir,
194
+ taskDepth: this.#memoryTaskDepth,
195
+ });
196
+ }
197
+ if (this.#host.isDisposed()) return;
198
+ await this.#refreshMemoryTools();
199
+ if (this.#host.isDisposed()) return;
200
+ await this.#host.refreshBaseSystemPrompt();
201
+ } catch (error) {
202
+ await this.#disposeMemoryBackendState(false);
203
+ if (!this.#host.isDisposed()) {
204
+ await this.#replaceMemoryTools([]).catch(refreshError => {
205
+ logger.warn("Failed to remove memory tools after backend apply error", {
206
+ error: String(refreshError),
207
+ });
208
+ });
209
+ }
210
+ throw error;
211
+ }
212
+ }
213
+
214
+ async #refreshMemoryTools(): Promise<void> {
215
+ const tools = (await this.#createMemoryTools?.()) ?? [];
216
+ await this.#replaceMemoryTools(tools);
217
+ }
218
+
219
+ #replaceMemoryTools(tools: AgentTool[]): Promise<void> {
220
+ return this.#host.replaceMemoryTools(tools);
221
+ }
222
+ }
@@ -0,0 +1,307 @@
1
+ /** Provider-facing message, image, secret, and stream normalization for a session. */
2
+
3
+ import type { Agent, AgentMessage } from "@oh-my-pi/pi-agent-core";
4
+ import type { CompactionPreparation } from "@oh-my-pi/pi-agent-core/compaction";
5
+ import type { AssistantMessage, ImageContent, Message, Model, SimpleStreamOptions, TextContent } from "@oh-my-pi/pi-ai";
6
+ import { isRecord, logger } from "@oh-my-pi/pi-utils";
7
+ import * as snapcompact from "@oh-my-pi/snapcompact";
8
+ import type { ModelRegistry } from "../config/model-registry";
9
+ import { formatModelString } from "../config/model-resolver";
10
+ import type { Settings } from "../config/settings";
11
+ import { validateProviderMaxInFlightRequests } from "../config/settings";
12
+ import type { LocalProtocolOptions } from "../internal-urls";
13
+ import {
14
+ deobfuscateSessionContext,
15
+ obfuscateMessages,
16
+ type SecretObfuscator,
17
+ stripPendingSecretPlaceholderSuffix,
18
+ } from "../secrets/obfuscator";
19
+ import { normalizeModelContextImages } from "../utils/image-loading";
20
+ import { describeAttachedImagesForTextModel } from "../utils/image-vision-fallback";
21
+ import { type CustomMessage, convertToLlm } from "./messages";
22
+ import { IMAGE_ATTACHMENT_DESCRIPTION_TYPE } from "./queued-messages";
23
+ import type { BuildSessionContextOptions, SessionContext } from "./session-context";
24
+ import type { SessionManager } from "./session-manager";
25
+
26
+ type NormalizableContentBlock = AssistantMessage["content"][number] | TextContent | ImageContent;
27
+
28
+ /** Capabilities borrowed from the owning AgentSession. */
29
+ export interface SessionProviderBoundaryHost {
30
+ agent: Agent;
31
+ sessionManager: SessionManager;
32
+ settings: Settings;
33
+ modelRegistry: ModelRegistry;
34
+ model(): Model | undefined;
35
+ sessionId(): string;
36
+ localProtocolOptions(): LocalProtocolOptions;
37
+ transformContext(messages: AgentMessage[], signal?: AbortSignal): AgentMessage[] | Promise<AgentMessage[]>;
38
+ convertToLlm(messages: AgentMessage[]): Message[] | Promise<Message[]>;
39
+ onPayload: SimpleStreamOptions["onPayload"] | undefined;
40
+ onResponse: SimpleStreamOptions["onResponse"] | undefined;
41
+ onSseEvent: SimpleStreamOptions["onSseEvent"] | undefined;
42
+ obfuscator: SecretObfuscator | undefined;
43
+ }
44
+
45
+ /** Owns the transformations at the session/provider boundary. */
46
+ export class SessionProviderBoundary {
47
+ readonly #host: SessionProviderBoundaryHost;
48
+
49
+ constructor(host: SessionProviderBoundaryHost) {
50
+ this.#host = host;
51
+ }
52
+
53
+ /** Latest image attachments addressable by tools as `Image #N` or `attachment://N`. */
54
+ getImageAttachments(): { label: string; uri: string; image: ImageContent }[] {
55
+ for (let i = this.#host.agent.state.messages.length - 1; i >= 0; i--) {
56
+ const message = this.#host.agent.state.messages[i];
57
+ if (!message || (message.role !== "user" && message.role !== "developer") || !Array.isArray(message.content)) {
58
+ continue;
59
+ }
60
+ const images = message.content.filter((part): part is ImageContent => part.type === "image");
61
+ if (images.length === 0) continue;
62
+ return images.map((image, index) => ({
63
+ label: `Image #${index + 1}`,
64
+ uri: `attachment://${index + 1}`,
65
+ image,
66
+ }));
67
+ }
68
+ return [];
69
+ }
70
+
71
+ /** Builds the current deobfuscated context for agent display and replay. */
72
+ buildDisplaySessionContext(): SessionContext {
73
+ return deobfuscateSessionContext(this.#host.sessionManager.buildSessionContext(), this.#host.obfuscator);
74
+ }
75
+
76
+ /** Builds the full display-only transcript context. */
77
+ buildTranscriptSessionContext(
78
+ options?: Pick<BuildSessionContextOptions, "collapseCompactedHistory" | "keepDanglingToolCalls">,
79
+ ): SessionContext {
80
+ return deobfuscateSessionContext(
81
+ this.#host.sessionManager.buildSessionContext({
82
+ transcript: true,
83
+ collapseCompactedHistory: options?.collapseCompactedHistory,
84
+ keepDanglingToolCalls: options?.keepDanglingToolCalls,
85
+ }),
86
+ this.#host.obfuscator,
87
+ true,
88
+ );
89
+ }
90
+
91
+ /** Obfuscates optional plaintext before a provider request. */
92
+ obfuscateText(text: string | undefined): string | undefined {
93
+ if (!text || !this.#host.obfuscator?.hasSecrets()) return text;
94
+ return this.#host.obfuscator.obfuscate(text);
95
+ }
96
+
97
+ /** Obfuscates summaries and snapcompact plaintext carried into compaction. */
98
+ obfuscateCompactionPreparation(preparation: CompactionPreparation): CompactionPreparation {
99
+ if (!this.#host.obfuscator?.hasSecrets()) return preparation;
100
+ const previousSummary = this.obfuscateText(preparation.previousSummary);
101
+ const previousPreserveData = this.#obfuscatePreservedArchiveText(preparation.previousPreserveData);
102
+ if (
103
+ previousSummary === preparation.previousSummary &&
104
+ previousPreserveData === preparation.previousPreserveData
105
+ ) {
106
+ return preparation;
107
+ }
108
+ return { ...preparation, previousSummary, previousPreserveData };
109
+ }
110
+
111
+ /** Deobfuscates provider text before exposing it to the session. */
112
+ deobfuscateText(text: string): string {
113
+ if (!this.#host.obfuscator?.hasSecrets()) return text;
114
+ return this.#host.obfuscator.deobfuscate(text);
115
+ }
116
+
117
+ /** Deobfuscates a streamed delta and removes an incomplete secret placeholder suffix. */
118
+ deobfuscateDelta(text: string): string {
119
+ const deobfuscated = this.deobfuscateText(text);
120
+ if (!this.#host.obfuscator?.hasSecrets()) return deobfuscated;
121
+ return stripPendingSecretPlaceholderSuffix(deobfuscated);
122
+ }
123
+
124
+ /** Converts side-request messages through the session's secret boundary. */
125
+ convertToLlmForSideRequest(messages: AgentMessage[]): Message[] {
126
+ const converted = convertToLlm(messages);
127
+ return this.#host.obfuscator?.hasSecrets() ? obfuscateMessages(this.#host.obfuscator, converted) : converted;
128
+ }
129
+
130
+ /** Converts session messages using the configured pre-LLM pipeline. */
131
+ async convertMessagesToLlm(messages: AgentMessage[], signal?: AbortSignal): Promise<Message[]> {
132
+ const transformedMessages = await this.#host.transformContext(messages, signal);
133
+ return await this.#host.convertToLlm(transformedMessages);
134
+ }
135
+
136
+ /** Applies session-level stream hooks and provider defaults to a side request. */
137
+ prepareSimpleStreamOptions(options: SimpleStreamOptions, provider = "anthropic"): SimpleStreamOptions {
138
+ const sessionOnPayload = this.#host.onPayload;
139
+ const sessionOnResponse = this.#host.onResponse;
140
+ const sessionMetadata = this.#host.agent.metadataForProvider(provider);
141
+ const sessionOnSseEvent = this.#host.onSseEvent;
142
+ const openrouterRoutingPreset =
143
+ provider === "openrouter" ? this.#host.settings.get("providers.openrouterVariant") : "default";
144
+ const openrouterVariant =
145
+ openrouterRoutingPreset !== "default" && options.openrouterVariant === undefined
146
+ ? openrouterRoutingPreset
147
+ : undefined;
148
+ const antigravityEndpointMode =
149
+ provider === "google-antigravity" ? this.#host.settings.get("providers.antigravityEndpoint") : undefined;
150
+
151
+ const preparedOptions: SimpleStreamOptions = {
152
+ ...options,
153
+ ...(openrouterVariant !== undefined && { openrouterVariant }),
154
+ ...(antigravityEndpointMode !== undefined && { antigravityEndpointMode }),
155
+ maxInFlightRequests: validateProviderMaxInFlightRequests(
156
+ options.maxInFlightRequests ?? this.#host.settings.get("providers.maxInFlightRequests"),
157
+ ),
158
+ loopGuard: {
159
+ enabled: this.#host.settings.get("model.loopGuard.enabled"),
160
+ checkAssistantContent: this.#host.settings.get("model.loopGuard.checkAssistantContent"),
161
+ ...options.loopGuard,
162
+ },
163
+ };
164
+
165
+ if (sessionMetadata && !options.metadata) {
166
+ preparedOptions.metadata = sessionMetadata;
167
+ }
168
+
169
+ if (sessionOnPayload) {
170
+ if (!options.onPayload) {
171
+ preparedOptions.onPayload = sessionOnPayload;
172
+ } else {
173
+ const requestOnPayload = options.onPayload;
174
+ preparedOptions.onPayload = async (payload, model) => {
175
+ const sessionPayload = await sessionOnPayload(payload, model);
176
+ const sessionResolvedPayload = sessionPayload ?? payload;
177
+ const requestPayload = await requestOnPayload(sessionResolvedPayload, model);
178
+ return requestPayload ?? sessionResolvedPayload;
179
+ };
180
+ }
181
+ }
182
+
183
+ if (sessionOnResponse) {
184
+ if (!options.onResponse) {
185
+ preparedOptions.onResponse = sessionOnResponse;
186
+ } else {
187
+ const requestOnResponse = options.onResponse;
188
+ preparedOptions.onResponse = async (response, model) => {
189
+ await sessionOnResponse(response, model);
190
+ await requestOnResponse(response, model);
191
+ };
192
+ }
193
+ }
194
+
195
+ if (sessionOnSseEvent) {
196
+ if (!options.onSseEvent) {
197
+ preparedOptions.onSseEvent = sessionOnSseEvent;
198
+ } else {
199
+ const requestOnSseEvent = options.onSseEvent;
200
+ preparedOptions.onSseEvent = (event, model) => {
201
+ sessionOnSseEvent(event, model);
202
+ requestOnSseEvent(event, model);
203
+ };
204
+ }
205
+ }
206
+
207
+ return preparedOptions;
208
+ }
209
+
210
+ /** Normalizes image payloads for the active model. */
211
+ normalizeImagesForModel(images: ImageContent[] | undefined): Promise<ImageContent[] | undefined> {
212
+ return normalizeModelContextImages(images, { model: this.#host.model() });
213
+ }
214
+
215
+ /** Builds a hidden vision-model description for attachments sent to a text-only model. */
216
+ async buildImageDescriptionNotice(
217
+ normalizedImages: ImageContent[],
218
+ signal?: AbortSignal,
219
+ ): Promise<CustomMessage | undefined> {
220
+ const model = this.#host.model();
221
+ const shouldDescribe =
222
+ !!model &&
223
+ !model.input.includes("image") &&
224
+ !this.#host.settings.get("images.blockImages") &&
225
+ this.#host.settings.get("images.describeForTextModels");
226
+ if (!shouldDescribe || !model) return undefined;
227
+
228
+ let blocks: TextContent[];
229
+ try {
230
+ blocks = await describeAttachedImagesForTextModel(
231
+ normalizedImages,
232
+ {
233
+ activeModel: model,
234
+ modelRegistry: this.#host.modelRegistry,
235
+ settings: this.#host.settings,
236
+ localProtocolOptions: this.#host.localProtocolOptions(),
237
+ activeModelString: formatModelString(model),
238
+ telemetryConfig: this.#host.agent.telemetry,
239
+ sessionId: this.#host.sessionId(),
240
+ },
241
+ signal,
242
+ );
243
+ } catch (error) {
244
+ logger.warn("image attachment vision fallback failed; image left undescribed", {
245
+ error: error instanceof Error ? error.message : String(error),
246
+ });
247
+ return undefined;
248
+ }
249
+ if (blocks.length === 0) return undefined;
250
+ return {
251
+ role: "custom",
252
+ customType: IMAGE_ATTACHMENT_DESCRIPTION_TYPE,
253
+ content: blocks,
254
+ display: false,
255
+ attribution: "user",
256
+ timestamp: Date.now(),
257
+ };
258
+ }
259
+
260
+ /** Normalizes every image embedded in an agent message. */
261
+ async normalizeAgentMessageImages<T extends AgentMessage>(message: T): Promise<T> {
262
+ if (!("content" in message)) return message;
263
+ const content = message.content;
264
+ if (typeof content !== "string" && !Array.isArray(content)) return message;
265
+ const normalized = await this.#normalizeMessageContentImages(content);
266
+ if (normalized === content) return message;
267
+ return Object.assign({}, message, { content: normalized });
268
+ }
269
+
270
+ async #normalizeMessageContentImages(
271
+ content: string | NormalizableContentBlock[],
272
+ ): Promise<string | NormalizableContentBlock[]> {
273
+ if (typeof content === "string") return content;
274
+ const images = content.filter((part): part is ImageContent => part.type === "image");
275
+ if (images.length === 0) return content;
276
+ const normalizedImages = await this.normalizeImagesForModel(images);
277
+ if (!normalizedImages) return content;
278
+ let imageIndex = 0;
279
+ return content.map(part => (part.type === "image" ? normalizedImages[imageIndex++]! : part));
280
+ }
281
+
282
+ #obfuscatePreservedArchiveText(
283
+ preserveData: Record<string, unknown> | undefined,
284
+ ): Record<string, unknown> | undefined {
285
+ const obfuscator = this.#host.obfuscator;
286
+ const slot = preserveData?.[snapcompact.PRESERVE_KEY];
287
+ if (
288
+ !obfuscator?.hasSecrets() ||
289
+ !preserveData ||
290
+ !isRecord(slot) ||
291
+ !snapcompact.getPreservedArchive(preserveData)
292
+ ) {
293
+ return preserveData;
294
+ }
295
+ const obfuscated: Record<string, unknown> = { ...slot };
296
+ let changed = false;
297
+ for (const key of ["text", "textHead", "textTail"] as const) {
298
+ const value = slot[key];
299
+ if (typeof value !== "string" || value.length === 0) continue;
300
+ const next = obfuscator.obfuscate(value);
301
+ if (next === value) continue;
302
+ obfuscated[key] = next;
303
+ changed = true;
304
+ }
305
+ return changed ? { ...preserveData, [snapcompact.PRESERVE_KEY]: obfuscated } : preserveData;
306
+ }
307
+ }