@selesai/code 0.13.0 → 0.13.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 (461) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/core/agent-session.d.ts +1 -1
  3. package/dist/core/agent-session.js +1 -1
  4. package/dist/core/sdk.d.ts +2 -2
  5. package/dist/core/sdk.js +1 -1
  6. package/dist/core/system-prompt.d.ts +1 -1
  7. package/dist/core/system-prompt.js +1 -1
  8. package/dist/extensions/ascii.txt +9 -0
  9. package/dist/extensions/package.json +1 -2
  10. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +371 -0
  11. package/dist/extensions/pi-hermes-memory/LICENSE +21 -0
  12. package/dist/extensions/pi-hermes-memory/README.md +643 -0
  13. package/dist/extensions/pi-hermes-memory/docs/0.1/TASKS.md +197 -0
  14. package/dist/extensions/pi-hermes-memory/docs/0.2/PLAN.md +290 -0
  15. package/dist/extensions/pi-hermes-memory/docs/0.2/TASKS.md +134 -0
  16. package/dist/extensions/pi-hermes-memory/docs/0.2/TEST-PLAN.md +216 -0
  17. package/dist/extensions/pi-hermes-memory/docs/0.3/PLAN.md +330 -0
  18. package/dist/extensions/pi-hermes-memory/docs/0.3/TASKS.md +125 -0
  19. package/dist/extensions/pi-hermes-memory/docs/0.4/PLAN.md +160 -0
  20. package/dist/extensions/pi-hermes-memory/docs/0.4/TASKS.md +113 -0
  21. package/dist/extensions/pi-hermes-memory/docs/0.6/CHANGELOG.md +57 -0
  22. package/dist/extensions/pi-hermes-memory/docs/0.6/PLAN.md +202 -0
  23. package/dist/extensions/pi-hermes-memory/docs/0.6/TASKS.md +144 -0
  24. package/dist/extensions/pi-hermes-memory/docs/0.7/PLAN.md +349 -0
  25. package/dist/extensions/pi-hermes-memory/docs/0.7/TASKS.md +110 -0
  26. package/dist/extensions/pi-hermes-memory/docs/PUBLISHING.md +149 -0
  27. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +468 -0
  28. package/dist/extensions/pi-hermes-memory/docs/images/memory-architecture.svg +1 -0
  29. package/dist/extensions/pi-hermes-memory/docs/images/pi-logo.svg +22 -0
  30. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.png +0 -0
  31. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.svg +19 -0
  32. package/dist/extensions/pi-hermes-memory/docs/images/security-flow.svg +1 -0
  33. package/dist/extensions/pi-hermes-memory/docs/images/session-lifecycle.svg +1 -0
  34. package/dist/extensions/pi-hermes-memory/docs/images/source-architecture.svg +1 -0
  35. package/dist/extensions/pi-hermes-memory/docs/mermaid/memory-architecture.mmd +49 -0
  36. package/dist/extensions/pi-hermes-memory/docs/mermaid/security-flow.mmd +40 -0
  37. package/dist/extensions/pi-hermes-memory/docs/mermaid/session-lifecycle.mmd +56 -0
  38. package/dist/extensions/pi-hermes-memory/docs/mermaid/source-architecture.mmd +54 -0
  39. package/dist/extensions/pi-hermes-memory/package-lock.json +3799 -0
  40. package/dist/extensions/pi-hermes-memory/package.json +66 -0
  41. package/dist/extensions/pi-hermes-memory/scripts/check-min-sdk.mjs +99 -0
  42. package/dist/extensions/pi-hermes-memory/scripts/ensure-dev.mjs +57 -0
  43. package/dist/extensions/pi-hermes-memory/src/auto-consolidation-warning.ts +6 -0
  44. package/dist/extensions/pi-hermes-memory/src/config.ts +178 -0
  45. package/dist/extensions/pi-hermes-memory/src/constants.ts +424 -0
  46. package/dist/extensions/pi-hermes-memory/src/extension-root-migration.ts +639 -0
  47. package/dist/extensions/pi-hermes-memory/src/handlers/auto-consolidate.ts +401 -0
  48. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +297 -0
  49. package/dist/extensions/pi-hermes-memory/src/handlers/child-process-watchdog.mjs +90 -0
  50. package/dist/extensions/pi-hermes-memory/src/handlers/correction-detector.ts +300 -0
  51. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +86 -0
  52. package/dist/extensions/pi-hermes-memory/src/handlers/insights.ts +78 -0
  53. package/dist/extensions/pi-hermes-memory/src/handlers/interview.ts +37 -0
  54. package/dist/extensions/pi-hermes-memory/src/handlers/learn-memory.ts +186 -0
  55. package/dist/extensions/pi-hermes-memory/src/handlers/message-parts.ts +27 -0
  56. package/dist/extensions/pi-hermes-memory/src/handlers/pi-child-process.ts +485 -0
  57. package/dist/extensions/pi-hermes-memory/src/handlers/preview-context.ts +105 -0
  58. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +506 -0
  59. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +145 -0
  60. package/dist/extensions/pi-hermes-memory/src/handlers/session-flush.ts +151 -0
  61. package/dist/extensions/pi-hermes-memory/src/handlers/session-live-index.ts +94 -0
  62. package/dist/extensions/pi-hermes-memory/src/handlers/skills-command.ts +1334 -0
  63. package/dist/extensions/pi-hermes-memory/src/handlers/standing-pin.ts +111 -0
  64. package/dist/extensions/pi-hermes-memory/src/handlers/switch-project.ts +75 -0
  65. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +266 -0
  66. package/dist/extensions/pi-hermes-memory/src/index.ts +378 -0
  67. package/dist/extensions/pi-hermes-memory/src/lifecycle-timing.ts +54 -0
  68. package/dist/extensions/pi-hermes-memory/src/paths.ts +61 -0
  69. package/dist/extensions/pi-hermes-memory/src/project-context.ts +13 -0
  70. package/dist/extensions/pi-hermes-memory/src/project-memory-migration.ts +93 -0
  71. package/dist/extensions/pi-hermes-memory/src/project.ts +157 -0
  72. package/dist/extensions/pi-hermes-memory/src/prompt-context.ts +55 -0
  73. package/dist/extensions/pi-hermes-memory/src/store/atomic-lock-coordinator.ts +375 -0
  74. package/dist/extensions/pi-hermes-memory/src/store/canonical-storage-path.ts +54 -0
  75. package/dist/extensions/pi-hermes-memory/src/store/content-scanner.ts +101 -0
  76. package/dist/extensions/pi-hermes-memory/src/store/db.ts +1180 -0
  77. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +101 -0
  78. package/dist/extensions/pi-hermes-memory/src/store/markdown-mutation-lock.ts +38 -0
  79. package/dist/extensions/pi-hermes-memory/src/store/memory-lookup.ts +24 -0
  80. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +1191 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/recovery-maintenance.ts +66 -0
  82. package/dist/extensions/pi-hermes-memory/src/store/schema.ts +118 -0
  83. package/dist/extensions/pi-hermes-memory/src/store/session-anchor-search.ts +472 -0
  84. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +511 -0
  85. package/dist/extensions/pi-hermes-memory/src/store/session-parser.ts +213 -0
  86. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +234 -0
  87. package/dist/extensions/pi-hermes-memory/src/store/skill-store.ts +950 -0
  88. package/dist/extensions/pi-hermes-memory/src/store/skill-utils.ts +133 -0
  89. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +1002 -0
  90. package/dist/extensions/pi-hermes-memory/src/store/sqlite-native.ts +241 -0
  91. package/dist/extensions/pi-hermes-memory/src/store/standing-instructions.ts +247 -0
  92. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +95 -0
  93. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +469 -0
  94. package/dist/extensions/pi-hermes-memory/src/tools/session-search-tool.ts +246 -0
  95. package/dist/extensions/pi-hermes-memory/src/tools/shared-output-view.ts +170 -0
  96. package/dist/extensions/pi-hermes-memory/src/tools/skill-tool.ts +333 -0
  97. package/dist/extensions/pi-hermes-memory/src/tools/tool-result-views.ts +105 -0
  98. package/dist/extensions/pi-hermes-memory/src/types.ts +214 -0
  99. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +434 -0
  100. package/dist/extensions/pi-hermes-memory/tests/extension-root-migration.test.ts +368 -0
  101. package/dist/extensions/pi-hermes-memory/tests/handlers/auto-consolidate.test.ts +984 -0
  102. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +950 -0
  103. package/dist/extensions/pi-hermes-memory/tests/handlers/correction-detector.test.ts +671 -0
  104. package/dist/extensions/pi-hermes-memory/tests/handlers/insights.test.ts +157 -0
  105. package/dist/extensions/pi-hermes-memory/tests/handlers/interview.test.ts +127 -0
  106. package/dist/extensions/pi-hermes-memory/tests/handlers/pi-child-process.test.ts +905 -0
  107. package/dist/extensions/pi-hermes-memory/tests/handlers/preview-context.test.ts +152 -0
  108. package/dist/extensions/pi-hermes-memory/tests/handlers/prompt-context.test.ts +166 -0
  109. package/dist/extensions/pi-hermes-memory/tests/handlers/resources-discover.test.ts +71 -0
  110. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +614 -0
  111. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +192 -0
  112. package/dist/extensions/pi-hermes-memory/tests/handlers/session-flush.test.ts +677 -0
  113. package/dist/extensions/pi-hermes-memory/tests/handlers/session-live-index.test.ts +225 -0
  114. package/dist/extensions/pi-hermes-memory/tests/handlers/skills-command.test.ts +827 -0
  115. package/dist/extensions/pi-hermes-memory/tests/handlers/standing-pin.test.ts +134 -0
  116. package/dist/extensions/pi-hermes-memory/tests/handlers/sync-markdown-memories.test.ts +491 -0
  117. package/dist/extensions/pi-hermes-memory/tests/handlers/system-prompt.test.ts +199 -0
  118. package/dist/extensions/pi-hermes-memory/tests/handlers/thinking-max.test.ts +64 -0
  119. package/dist/extensions/pi-hermes-memory/tests/index.test.ts +30 -0
  120. package/dist/extensions/pi-hermes-memory/tests/integration/flow.test.ts +126 -0
  121. package/dist/extensions/pi-hermes-memory/tests/lifecycle-timing.test.ts +76 -0
  122. package/dist/extensions/pi-hermes-memory/tests/paths.test.ts +23 -0
  123. package/dist/extensions/pi-hermes-memory/tests/project-memory-migration.test.ts +80 -0
  124. package/dist/extensions/pi-hermes-memory/tests/project-rebinding.test.ts +115 -0
  125. package/dist/extensions/pi-hermes-memory/tests/project.test.ts +120 -0
  126. package/dist/extensions/pi-hermes-memory/tests/run-all-timeout.test.ts +107 -0
  127. package/dist/extensions/pi-hermes-memory/tests/run-all.sh +45 -0
  128. package/dist/extensions/pi-hermes-memory/tests/store/atomic-lock-coordinator.test.ts +381 -0
  129. package/dist/extensions/pi-hermes-memory/tests/store/content-scanner.test.ts +388 -0
  130. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +1046 -0
  131. package/dist/extensions/pi-hermes-memory/tests/store/markdown-mutation-lock.test.ts +34 -0
  132. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +2150 -0
  133. package/dist/extensions/pi-hermes-memory/tests/store/recovery-maintenance.test.ts +170 -0
  134. package/dist/extensions/pi-hermes-memory/tests/store/session-anchor-search.test.ts +210 -0
  135. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +643 -0
  136. package/dist/extensions/pi-hermes-memory/tests/store/session-parser.test.ts +282 -0
  137. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +287 -0
  138. package/dist/extensions/pi-hermes-memory/tests/store/skill-store.test.ts +678 -0
  139. package/dist/extensions/pi-hermes-memory/tests/store/skill-utils.test.ts +21 -0
  140. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-lazy-load.test.ts +78 -0
  141. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +543 -0
  142. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +164 -0
  143. package/dist/extensions/pi-hermes-memory/tests/store/standing-instructions.test.ts +171 -0
  144. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +84 -0
  145. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +709 -0
  146. package/dist/extensions/pi-hermes-memory/tests/tools/session-search-tool.test.ts +345 -0
  147. package/dist/extensions/pi-hermes-memory/tests/tools/shared-output-view.test.ts +362 -0
  148. package/dist/extensions/pi-hermes-memory/tests/tools/skill-tool.test.ts +478 -0
  149. package/dist/extensions/pi-hermes-memory/tests/tools/tool-result-renderer-wiring.test.ts +40 -0
  150. package/dist/extensions/pi-hermes-memory/tsconfig.json +19 -0
  151. package/dist/extensions/pi-subagents/.oxlintrc.json +43 -0
  152. package/dist/extensions/pi-subagents/AGENTS.md +5 -0
  153. package/dist/extensions/pi-subagents/CHANGELOG.md +157 -0
  154. package/dist/extensions/pi-subagents/README.md +12 -6
  155. package/dist/extensions/pi-subagents/VISION.md +81 -0
  156. package/dist/extensions/pi-subagents/agents/claude-code-writer.md +15 -0
  157. package/dist/extensions/pi-subagents/agents/claude-code.md +15 -0
  158. package/dist/extensions/pi-subagents/agents/codex-exec-writer.md +15 -0
  159. package/dist/extensions/pi-subagents/agents/codex-exec.md +15 -0
  160. package/dist/extensions/pi-subagents/agents/cursor-agent-writer.md +14 -0
  161. package/dist/extensions/pi-subagents/agents/cursor-agent.md +14 -0
  162. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -3
  163. package/dist/extensions/pi-subagents/docs/agents.md +488 -0
  164. package/dist/extensions/pi-subagents/docs/configuration.md +46 -9
  165. package/dist/extensions/pi-subagents/docs/extension-api.md +42 -3
  166. package/dist/extensions/pi-subagents/docs/models.md +27 -4
  167. package/dist/extensions/pi-subagents/docs/observability.md +10 -9
  168. package/dist/extensions/pi-subagents/docs/tool-reference.md +102 -6
  169. package/dist/extensions/pi-subagents/docs/workflows.md +135 -0
  170. package/dist/extensions/pi-subagents/package-lock.json +389 -2
  171. package/dist/extensions/pi-subagents/package.json +3 -1
  172. package/dist/extensions/pi-subagents/prompts/review-loop.md +2 -2
  173. package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +1 -1
  174. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +3 -1
  175. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +7 -3
  176. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  177. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +26 -5
  178. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -22
  179. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +4 -4
  180. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +9 -2
  181. package/dist/extensions/pi-subagents/src/agents/agents.ts +322 -75
  182. package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +6 -0
  183. package/dist/extensions/pi-subagents/src/agents/runtime-agent-events.ts +70 -0
  184. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +27 -20
  185. package/dist/extensions/pi-subagents/src/api/agents.ts +10 -5
  186. package/dist/extensions/pi-subagents/src/api/background-work.ts +5 -1
  187. package/dist/extensions/pi-subagents/src/api/delegation.ts +0 -7
  188. package/dist/extensions/pi-subagents/src/api/preflight.ts +34 -12
  189. package/dist/extensions/pi-subagents/src/extension/config.ts +70 -0
  190. package/dist/extensions/pi-subagents/src/extension/doctor.ts +3 -3
  191. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -3
  192. package/dist/extensions/pi-subagents/src/extension/index.ts +83 -30
  193. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +44 -15
  194. package/dist/extensions/pi-subagents/src/extension/rpc.ts +55 -19
  195. package/dist/extensions/pi-subagents/src/extension/schemas.ts +41 -15
  196. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -31
  197. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +2 -1
  198. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +2 -10
  199. package/dist/extensions/pi-subagents/src/inspectors/herdr/session-roots-codec.ts +42 -0
  200. package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +51 -3
  201. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +2 -0
  202. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +5 -6
  203. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +77 -10
  204. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +176 -53
  205. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +19 -12
  206. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +29 -27
  207. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +20 -3
  208. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +23 -261
  209. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +73 -7
  210. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +8 -3
  211. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +74 -8
  212. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +37 -21
  213. package/dist/extensions/pi-subagents/src/runs/background/inspect-rpc.ts +8 -8
  214. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +184 -9
  215. package/dist/extensions/pi-subagents/src/runs/background/result-delivery-ownership.ts +45 -0
  216. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +28 -14
  217. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +38 -15
  218. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +1 -1
  219. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +1 -1
  220. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +50 -13
  221. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +93 -8
  222. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +10 -4
  223. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -14
  224. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +549 -377
  225. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +58 -10
  226. package/dist/extensions/pi-subagents/src/runs/background/terminal-run-index.ts +1 -1
  227. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +26 -1
  228. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +23 -9
  229. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +9 -2
  230. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +2 -2
  231. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +204 -129
  232. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +9 -0
  233. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +23 -1
  234. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +773 -311
  235. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +140 -115
  236. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +119 -0
  237. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +463 -0
  238. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +19 -4
  239. package/dist/extensions/pi-subagents/src/runs/shared/child-launch-plan.ts +151 -0
  240. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +21 -7
  241. package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +129 -0
  242. package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +129 -0
  243. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +89 -0
  244. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +5 -4
  245. package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +114 -0
  246. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +3 -3
  247. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +167 -0
  248. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +122 -0
  249. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +348 -55
  250. package/dist/extensions/pi-subagents/src/runs/shared/fast-mode-extension.ts +5 -5
  251. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +230 -0
  252. package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +105 -0
  253. package/dist/extensions/pi-subagents/src/runs/shared/launch-cwd.ts +16 -0
  254. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +2 -1
  255. package/dist/extensions/pi-subagents/src/runs/shared/mcp-config-sources.ts +422 -0
  256. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
  257. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  258. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +69 -7
  259. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +47 -6
  260. package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +7 -2
  261. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +9 -3
  262. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +9 -5
  263. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +419 -7
  264. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +15 -1
  265. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +81 -8
  266. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +13 -0
  267. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +21 -1
  268. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +44 -4
  269. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +98 -13
  270. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +1 -1
  271. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  272. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +18 -0
  273. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +46 -0
  274. package/dist/extensions/pi-subagents/src/shared/extension-context.ts +24 -0
  275. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +21 -0
  276. package/dist/extensions/pi-subagents/src/shared/formatters.ts +18 -3
  277. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +5 -1
  278. package/dist/extensions/pi-subagents/src/shared/pruned-fork.ts +450 -0
  279. package/dist/extensions/pi-subagents/src/shared/session-file-trust.ts +19 -0
  280. package/dist/extensions/pi-subagents/src/shared/session-tokens.ts +14 -3
  281. package/dist/extensions/pi-subagents/src/shared/settings.ts +19 -105
  282. package/dist/extensions/pi-subagents/src/shared/shortcuts.ts +17 -0
  283. package/dist/extensions/pi-subagents/src/shared/types.ts +359 -40
  284. package/dist/extensions/pi-subagents/src/shared/utils.ts +41 -84
  285. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +116 -0
  286. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -9
  287. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +0 -4
  288. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +1 -2
  289. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +378 -88
  290. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +22 -11
  291. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +3 -0
  292. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +147 -79
  293. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +11 -5
  294. package/dist/extensions/pi-subagents/src/tui/fleet.ts +39 -18
  295. package/dist/extensions/pi-subagents/src/tui/render.ts +369 -46
  296. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +1 -1
  297. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +6 -3
  298. package/dist/extensions/pi-subagents/src/workflows/host-command.ts +230 -0
  299. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +740 -58
  300. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +121 -0
  301. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +270 -0
  302. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +195 -6
  303. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +246 -0
  304. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
  305. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1114 -260
  306. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +34 -0
  307. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +150 -3
  308. package/dist/extensions/pi-subagents/test/integration/claude-code-smoke.test.ts +52 -0
  309. package/dist/extensions/pi-subagents/test/integration/claude-code-writer-smoke.test.ts +55 -0
  310. package/dist/extensions/pi-subagents/test/integration/codex-exec-smoke.test.ts +53 -0
  311. package/dist/extensions/pi-subagents/test/integration/codex-exec-writer-smoke.test.ts +57 -0
  312. package/dist/extensions/pi-subagents/test/integration/cursor-agent-smoke.test.ts +59 -0
  313. package/dist/extensions/pi-subagents/test/integration/cursor-agent-writer-smoke.test.ts +62 -0
  314. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +57 -1
  315. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  316. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +20 -0
  317. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +4 -3
  318. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +2 -2
  319. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +54 -4
  320. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +482 -4
  321. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +157 -9
  322. package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +5 -5
  323. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1508 -291
  324. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +423 -1
  325. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +8 -0
  326. package/dist/extensions/pi-subagents/test/support/cursor-smoke-inputs.ts +51 -0
  327. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  328. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +5 -5
  329. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +6 -6
  330. package/dist/extensions/pi-subagents/test/unit/abort-recovery.test.ts +152 -0
  331. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +48 -0
  332. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +184 -6
  333. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +66 -6
  334. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +77 -0
  335. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +18 -0
  336. package/dist/extensions/pi-subagents/test/unit/anti-slop-oxlint.test.ts +86 -0
  337. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +1 -1
  338. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +101 -1
  339. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +121 -0
  340. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +28 -0
  341. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +14 -7
  342. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +53 -0
  343. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +190 -0
  344. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +42 -0
  345. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +8 -4
  346. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +23 -0
  347. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +78 -1
  348. package/dist/extensions/pi-subagents/test/unit/child-launch-plan.test.ts +94 -0
  349. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +21 -0
  350. package/dist/extensions/pi-subagents/test/unit/child-session-name.test.ts +50 -0
  351. package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +245 -0
  352. package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +192 -0
  353. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +27 -0
  354. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +150 -0
  355. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +21 -0
  356. package/dist/extensions/pi-subagents/test/unit/cursor-agent-adapter.test.ts +259 -0
  357. package/dist/extensions/pi-subagents/test/unit/cursor-smoke-inputs.test.ts +77 -0
  358. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +1 -4
  359. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
  360. package/dist/extensions/pi-subagents/test/unit/extension-context.test.ts +27 -0
  361. package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +223 -0
  362. package/dist/extensions/pi-subagents/test/unit/fast-mode-extension.test.ts +27 -0
  363. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +208 -2
  364. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +29 -1
  365. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +110 -1
  366. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +10 -0
  367. package/dist/extensions/pi-subagents/test/unit/formatters.test.ts +14 -0
  368. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +18 -9
  369. package/dist/extensions/pi-subagents/test/unit/herdr-session-roots-codec.test.ts +63 -0
  370. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +2 -2
  371. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +132 -0
  372. package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +156 -0
  373. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +98 -0
  374. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +178 -26
  375. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +2 -1
  376. package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +21 -0
  377. package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +138 -0
  378. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  379. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +91 -0
  380. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +72 -7
  381. package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +21 -0
  382. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +1 -0
  383. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +20 -2
  384. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +129 -1
  385. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +2 -2
  386. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +4 -0
  387. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +358 -0
  388. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +574 -21
  389. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +109 -1
  390. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +71 -1
  391. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +7 -7
  392. package/dist/extensions/pi-subagents/test/unit/pruned-fork.test.ts +214 -0
  393. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +29 -8
  394. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +53 -0
  395. package/dist/extensions/pi-subagents/test/unit/result-delivery-ownership.test.ts +34 -0
  396. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +101 -3
  397. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +1 -0
  398. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +10 -8
  399. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +71 -1
  400. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +41 -7
  401. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +136 -2
  402. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +137 -0
  403. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +33 -12
  404. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +504 -1
  405. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +8 -0
  406. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  407. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +12 -1
  408. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +3 -5
  409. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +5 -6
  410. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +0 -8
  411. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +5 -0
  412. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +157 -2
  413. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +1 -1
  414. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +63 -4
  415. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +27 -23
  416. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +44 -0
  417. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -0
  418. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +7 -5
  419. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +106 -0
  420. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +484 -9
  421. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +38 -4
  422. package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +155 -0
  423. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +318 -0
  424. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +365 -0
  425. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +349 -0
  426. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +21 -0
  427. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +2 -7
  428. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/index.ts +13 -0
  429. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/rules/no-service-constructor-imports.ts +52 -0
  430. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/index.ts +41 -0
  431. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts +77 -0
  432. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts +49 -0
  433. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-known-value-widening.ts +247 -0
  434. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-module-mocking.ts +91 -0
  435. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-object-parameters.ts +126 -0
  436. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-apply.ts +28 -0
  437. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-get.ts +28 -0
  438. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-runtime-typeof.ts +67 -0
  439. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts +39 -0
  440. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-parameters.ts +83 -0
  441. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-returns.ts +115 -0
  442. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts +72 -0
  443. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts +134 -0
  444. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-widen-then-assert.ts +366 -0
  445. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts +62 -0
  446. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/dictionary-types.ts +502 -0
  447. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/lexical-type-parameters.ts +60 -0
  448. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/reflect-method.ts +35 -0
  449. package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +1 -1
  450. package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +2 -2
  451. package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +1 -1
  452. package/dist/modes/interactive/interactive-mode.js +7 -7
  453. package/dist/skills/workflow/SKILL.md +65 -50
  454. package/package.json +112 -110
  455. package/dist/extensions/enable-readonly-tools.test.ts +0 -63
  456. package/dist/extensions/enable-readonly-tools.ts +0 -24
  457. package/dist/extensions/model-prompt-injector/config.json +0 -16
  458. package/dist/extensions/model-prompt-injector/index.test.ts +0 -102
  459. package/dist/extensions/model-prompt-injector/index.ts +0 -148
  460. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +0 -98
  461. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +0 -250
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Auto-consolidation — when memory hits capacity, trigger automatic
3
+ * consolidation instead of returning an error.
4
+ *
5
+ * Default transport: in-process direct completion (same mechanism as
6
+ * background review — see review-memory-ops.ts), used only when a caller
7
+ * supplies model/modelRegistry access (the manual `/memory-consolidate`
8
+ * command has it; the automatic over-capacity consolidator registered on
9
+ * MemoryStore does not, since MemoryStore itself has no extension-runtime
10
+ * access, so that path stays subprocess-only). Falls back to a `pi -p`
11
+ * subprocess when direct mode is unavailable, declines, or fails.
12
+ *
13
+ * The subprocess child process modifies files on disk, so the parent MUST
14
+ * reload from disk after a subprocess-based consolidation completes.
15
+ */
16
+ import { resolveProjectName, resolveProjectStore, type ProjectNameRef, type ProjectStoreRef } from "../project-context.js";
17
+
18
+ import * as fs from "node:fs/promises";
19
+ import * as path from "node:path";
20
+ import { createHash } from "node:crypto";
21
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
22
+ import { MemoryStore } from "../store/memory-store.js";
23
+ import { DatabaseManager } from "../store/db.js";
24
+ import {
25
+ CONSOLIDATION_PROMPT,
26
+ DEFAULT_CONSOLIDATION_TIMEOUT_MS,
27
+ DIRECT_CONSOLIDATION_SYSTEM_PROMPT,
28
+ ENTRY_DELIMITER,
29
+ } from "../constants.js";
30
+ import type { ConsolidationResult, MemoryConfig } from "../types.js";
31
+ import { AGENT_ROOT } from "../paths.js";
32
+ import { execChildPrompt } from "./pi-child-process.js";
33
+ import { runDirectMemoryCompletion, usesDirectTransport } from "./review-memory-ops.js";
34
+ import { AtomicLockCoordinator } from "../store/atomic-lock-coordinator.js";
35
+
36
+ type MemoryTarget = "memory" | "user" | "failure";
37
+ type ToolMemoryTarget = MemoryTarget | "project";
38
+ type ConsolidationLlmConfig = Pick<MemoryConfig, "llmModelOverride" | "llmThinkingOverride" | "reviewTransport">;
39
+
40
+ // staleMs is deliberately decoupled from the consolidation timeout. The holder
41
+ // beats every CONSOLIDATION_LOCK_HEARTBEAT_MS while its child runs, so a
42
+ // legitimately slow consolidation (up to 2x timeoutMs once retryWithoutOverrides
43
+ // fires) never loses its lease, while a holder that stops making progress is
44
+ // reclaimable after seconds instead of after its worst-case runtime (#144).
45
+ const CONSOLIDATION_LOCK_STALE_MS = 45_000;
46
+ const CONSOLIDATION_LOCK_HEARTBEAT_MS = 10_000;
47
+ // Contention is usually transient. Poll for the lock the way
48
+ // acquireMarkdownMutationLock does instead of hard-failing the memory write
49
+ // that triggered auto-consolidation on the very first collision.
50
+ const CONSOLIDATION_LOCK_WAIT_MS = 5_000;
51
+ const CONSOLIDATION_LOCK_POLL_MS = 50;
52
+ const CONSOLIDATION_LOCK_ENV = "PI_HERMES_CONSOLIDATION_LOCK_DIR";
53
+ const CONSOLIDATION_LOCK_WAIT_ENV = "PI_HERMES_CONSOLIDATION_LOCK_WAIT_MS";
54
+
55
+ interface ConsolidationLock {
56
+ release: () => Promise<void>;
57
+ }
58
+
59
+ interface ConsolidationLockAttempt {
60
+ lock: ConsolidationLock | null;
61
+ /** True when the lock was held by someone else on the first attempt. */
62
+ contended: boolean;
63
+ waitedMs: number;
64
+ }
65
+
66
+ function consolidationLockRoot(): string {
67
+ return process.env[CONSOLIDATION_LOCK_ENV]?.trim()
68
+ || path.join(AGENT_ROOT, "pi-hermes-memory", ".consolidation-locks");
69
+ }
70
+
71
+ function sanitizeLockPart(value: string): string {
72
+ return value.replace(/[^a-z0-9._-]+/gi, "_").slice(0, 80) || "unknown";
73
+ }
74
+
75
+ function consolidationLockKey(target: MemoryTarget, toolTarget: ToolMemoryTarget, storageIdentity: string): string {
76
+ const storageHash = createHash("sha256").update(storageIdentity).digest("hex");
77
+ return `${sanitizeLockPart(toolTarget)}:${sanitizeLockPart(target)}:${storageHash}`;
78
+ }
79
+
80
+ function consolidationLockWaitMs(): number {
81
+ const configured = Number(process.env[CONSOLIDATION_LOCK_WAIT_ENV]);
82
+ return Number.isFinite(configured) && configured >= 0 ? configured : CONSOLIDATION_LOCK_WAIT_MS;
83
+ }
84
+
85
+ async function acquireConsolidationLock(
86
+ store: MemoryStore,
87
+ target: MemoryTarget,
88
+ toolTarget: ToolMemoryTarget,
89
+ ): Promise<ConsolidationLockAttempt> {
90
+ const storageIdentity = await store.getStorageIdentity(target);
91
+ const root = consolidationLockRoot();
92
+ await fs.mkdir(root, { recursive: true });
93
+ const coordinator = AtomicLockCoordinator.shared(path.join(root, "locks.sqlite"));
94
+ const key = consolidationLockKey(target, toolTarget, storageIdentity);
95
+ const lockOptions = { staleMs: CONSOLIDATION_LOCK_STALE_MS };
96
+
97
+ const startedAt = Date.now();
98
+ let lease = coordinator.tryAcquire(key, lockOptions);
99
+ const contended = !lease;
100
+ if (contended) {
101
+ const deadline = startedAt + consolidationLockWaitMs();
102
+ while (!lease && Date.now() < deadline) {
103
+ // Same shape as acquireMarkdownMutationLock; Promise.withResolvers would
104
+ // need an ES2024 lib this project does not target.
105
+ await new Promise((resolve) => setTimeout(resolve, CONSOLIDATION_LOCK_POLL_MS));
106
+ lease = coordinator.tryAcquire(key, lockOptions);
107
+ }
108
+ }
109
+
110
+ const waitedMs = Date.now() - startedAt;
111
+ if (!lease) return { lock: null, contended, waitedMs };
112
+
113
+ const held = lease;
114
+ const heartbeat = setInterval(() => {
115
+ try {
116
+ held.renew();
117
+ } catch {
118
+ // A missed beat only moves the lease closer to staleMs; the next beat
119
+ // recovers, and a permanently broken lock DB should not crash the run.
120
+ }
121
+ }, CONSOLIDATION_LOCK_HEARTBEAT_MS);
122
+ heartbeat.unref?.();
123
+
124
+ return {
125
+ lock: {
126
+ release: async () => {
127
+ clearInterval(heartbeat);
128
+ held.release();
129
+ },
130
+ },
131
+ contended,
132
+ waitedMs,
133
+ };
134
+ }
135
+
136
+ function entriesForTarget(store: MemoryStore, target: MemoryTarget): string[] {
137
+ if (target === "user") return store.getUserEntries();
138
+ if (target === "failure") return store.getAllFailureEntries();
139
+ return store.getMemoryEntries();
140
+ }
141
+
142
+ function labelForTarget(target: MemoryTarget, toolTarget: ToolMemoryTarget): string {
143
+ if (toolTarget === "project") return "Project Memory";
144
+ if (target === "user") return "User Profile";
145
+ if (target === "failure") return "Failure Memory";
146
+ return "Memory";
147
+ }
148
+
149
+ function describeConsolidationFailure(
150
+ result: { code: number; stderr?: string; killed?: boolean },
151
+ timeoutMs: number,
152
+ ): string {
153
+ const stderr = result.stderr?.trim();
154
+ const terminated = result.killed || result.code === 124 || result.code === 143;
155
+
156
+ if (terminated) {
157
+ return `Consolidation subprocess was terminated (likely timeout or cancellation). Timeout: ${timeoutMs}ms. Raise consolidationTimeoutMs if consolidation legitimately needs longer.`;
158
+ }
159
+
160
+ return `Consolidation process exited with code ${result.code}: ${stderr?.slice(0, 200) || "unknown error"}`;
161
+ }
162
+
163
+ function buildConsolidationPrompt(
164
+ target: MemoryTarget,
165
+ toolTarget: ToolMemoryTarget,
166
+ entries: string[],
167
+ ): string {
168
+ return [
169
+ CONSOLIDATION_PROMPT,
170
+ "",
171
+ `--- Current ${labelForTarget(target, toolTarget)} Entries ---`,
172
+ entries.join(ENTRY_DELIMITER) || "(empty)",
173
+ "",
174
+ `Use memory_add, memory_replace, or memory_remove to consolidate. Target: '${toolTarget}'`,
175
+ ].join("\n");
176
+ }
177
+
178
+ export async function triggerConsolidation(
179
+ pi: ExtensionAPI,
180
+ store: MemoryStore,
181
+ target: MemoryTarget,
182
+ signal?: AbortSignal,
183
+ timeoutMs: number = DEFAULT_CONSOLIDATION_TIMEOUT_MS,
184
+ toolTarget: ToolMemoryTarget = target,
185
+ llmConfig: ConsolidationLlmConfig = {},
186
+ directCtx: Pick<ExtensionContext, "model" | "modelRegistry"> | null = null,
187
+ dbManager: DatabaseManager | null = null,
188
+ projectName?: string | null,
189
+ deps: { runDirectMemoryCompletion?: typeof runDirectMemoryCompletion } = {},
190
+ ): Promise<ConsolidationResult> {
191
+ const entries = entriesForTarget(store, target);
192
+ const currentContent = entries.join(ENTRY_DELIMITER);
193
+ const runDirect = deps.runDirectMemoryCompletion ?? runDirectMemoryCompletion;
194
+
195
+ if (directCtx && usesDirectTransport(llmConfig)) {
196
+ try {
197
+ const directResult = await runDirect(
198
+ directCtx,
199
+ store,
200
+ toolTarget === "project" ? store : null,
201
+ {
202
+ systemPrompt: DIRECT_CONSOLIDATION_SYSTEM_PROMPT,
203
+ userPrompt: [
204
+ `--- Current ${labelForTarget(target, toolTarget)} Entries (target: '${toolTarget}') ---`,
205
+ currentContent || "(empty)",
206
+ "",
207
+ `Only emit operations with "target": "${toolTarget}".`,
208
+ ].join("\n"),
209
+ config: llmConfig,
210
+ timeoutMs,
211
+ signal,
212
+ requireAtomicShrink: true,
213
+ expectedTarget: toolTarget,
214
+ },
215
+ dbManager,
216
+ projectName,
217
+ );
218
+ // Consolidation only did its job if it actually freed space — unlike
219
+ // review/flush/correction, an empty or fully-skipped result here is a
220
+ // failure worth falling back to subprocess for, not a normal outcome.
221
+ if (directResult.ok && directResult.appliedCount > 0) {
222
+ return { consolidated: true };
223
+ }
224
+ } catch {
225
+ // Fall through to subprocess below.
226
+ }
227
+ }
228
+
229
+ let lock: ConsolidationLock | null = null;
230
+
231
+ try {
232
+ const attempt = await acquireConsolidationLock(store, target, toolTarget);
233
+ lock = attempt.lock;
234
+ if (!lock) {
235
+ // Not a failure: the work is already running in another session. Say so
236
+ // plainly so the memory-write path can ask for a retry instead of
237
+ // reporting a broken consolidation mid-task (#144).
238
+ return {
239
+ consolidated: false,
240
+ deferred: true,
241
+ error: `Consolidation already in progress for target '${toolTarget}' in another session`
242
+ + ` (waited ${attempt.waitedMs}ms). Nothing was consolidated here — retry shortly.`,
243
+ };
244
+ }
245
+
246
+ let promptEntries = entries;
247
+ if (attempt.contended) {
248
+ // We queued behind another session's consolidation and it has now
249
+ // finished. If it already freed space, running a second LLM pass here
250
+ // costs a child turn and over-compresses memory for nothing — hand the
251
+ // caller a reload-and-retry instead.
252
+ try {
253
+ await store.loadFromDisk();
254
+ const refreshed = entriesForTarget(store, target);
255
+ if (refreshed.join(ENTRY_DELIMITER).length < currentContent.length) {
256
+ return { consolidated: true };
257
+ }
258
+ promptEntries = refreshed;
259
+ } catch {
260
+ // Reload failed — consolidate the entries we already read instead.
261
+ }
262
+ }
263
+
264
+ const result = await execChildPrompt(pi, buildConsolidationPrompt(target, toolTarget, promptEntries), llmConfig, {
265
+ signal,
266
+ timeoutMs,
267
+ retryWithoutOverrides: true,
268
+ }) as { code: number; stdout?: string; stderr?: string; killed?: boolean };
269
+
270
+ if (result.code === 0) {
271
+ return { consolidated: true };
272
+ }
273
+ return {
274
+ consolidated: false,
275
+ error: describeConsolidationFailure(result, timeoutMs),
276
+ };
277
+ } catch (err) {
278
+ const message = String(err);
279
+ if (message.includes("extension ctx is stale")) {
280
+ // Session replaced/reloaded while consolidation was running. The new
281
+ // session re-initializes the store and will consolidate on its own next
282
+ // write, so this is a skip, not a failure — report it as deferred so the
283
+ // caller asks for a retry instead of surfacing a stale-ctx error.
284
+ return {
285
+ consolidated: false,
286
+ deferred: true,
287
+ error: "session replaced or reloaded during consolidation — will consolidate on next write",
288
+ };
289
+ }
290
+ return {
291
+ consolidated: false,
292
+ error: `Consolidation failed: ${message.slice(0, 200)}`,
293
+ };
294
+ } finally {
295
+ if (lock) {
296
+ try { await lock.release(); } catch { /* best-effort cleanup */ }
297
+ }
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Register the /memory-consolidate command for manual consolidation.
303
+ */
304
+ export function registerConsolidateCommand(
305
+ pi: ExtensionAPI,
306
+ store: MemoryStore,
307
+ timeoutMs: number = DEFAULT_CONSOLIDATION_TIMEOUT_MS,
308
+ projectStore: ProjectStoreRef = null,
309
+ projectName: ProjectNameRef = null,
310
+ llmConfig: ConsolidationLlmConfig = {},
311
+ dbManager: DatabaseManager | null = null,
312
+ deps: { runDirectMemoryCompletion?: typeof runDirectMemoryCompletion } = {},
313
+ ): void {
314
+ pi.registerCommand("memory-consolidate", {
315
+ description: "Manually trigger memory consolidation to free up space",
316
+ handler: async (_args, ctx) => {
317
+ const results: string[] = [];
318
+ const activeProjectStore = resolveProjectStore(projectStore);
319
+ const activeProjectName = resolveProjectName(projectName);
320
+ const targets: Array<{
321
+ label: string;
322
+ store: MemoryStore;
323
+ target: MemoryTarget;
324
+ toolTarget: ToolMemoryTarget;
325
+ }> = [
326
+ { label: "memory", store, target: "memory", toolTarget: "memory" },
327
+ { label: "user", store, target: "user", toolTarget: "user" },
328
+ { label: "failure", store, target: "failure", toolTarget: "failure" },
329
+ ];
330
+
331
+ if (activeProjectStore) {
332
+ targets.push({
333
+ label: activeProjectName ? `project:${activeProjectName}` : "project",
334
+ store: activeProjectStore,
335
+ target: "memory",
336
+ toolTarget: "project",
337
+ });
338
+ }
339
+
340
+ try {
341
+ ctx.ui.notify(
342
+ `🔄 Starting memory consolidation for ${targets.length} target${targets.length === 1 ? "" : "s"}...`,
343
+ "info",
344
+ );
345
+ } catch {
346
+ // Best-effort only. If the command context is already stale, continue
347
+ // with the consolidation work rather than failing before it starts.
348
+ }
349
+
350
+ for (const item of targets) {
351
+ const entries = entriesForTarget(item.store, item.target);
352
+
353
+ if (entries.length === 0) {
354
+ results.push(`${item.label}: (empty, nothing to consolidate)`);
355
+ continue;
356
+ }
357
+
358
+ try {
359
+ ctx.ui.notify(
360
+ `⏳ Consolidating ${item.label}...`,
361
+ "info",
362
+ );
363
+ } catch {
364
+ // Best-effort progress feedback only.
365
+ }
366
+
367
+ const result = await triggerConsolidation(
368
+ pi,
369
+ item.store,
370
+ item.target,
371
+ ctx.signal,
372
+ timeoutMs,
373
+ item.toolTarget,
374
+ llmConfig,
375
+ ctx,
376
+ dbManager,
377
+ activeProjectName,
378
+ deps,
379
+ );
380
+
381
+ if (result.consolidated) {
382
+ await item.store.loadFromDisk();
383
+ results.push(`${item.label}: ✅ consolidated`);
384
+ } else {
385
+ results.push(`${item.label}: ❌ ${result.error}`);
386
+ }
387
+ }
388
+
389
+ const summary = `\n 🔄 Memory Consolidation\n ${"─".repeat(30)}\n${results.map((r) => ` ${r}`).join("\n")}`;
390
+
391
+ try {
392
+ ctx.ui.notify(summary, "info");
393
+ } catch {
394
+ // Child consolidation can indirectly trigger a runtime reload/session
395
+ // replacement. If that happens, the original command ctx is stale by
396
+ // the time we reach the final summary, so the command should exit
397
+ // quietly instead of surfacing a stale-ctx error.
398
+ }
399
+ },
400
+ });
401
+ }
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Background review — learning loop that auto-saves memory every N turns.
3
+ * Ported from hermes-agent/run_agent.py (_spawn_background_review, _memory_nudge_interval).
4
+ * See PLAN.md → "Hermes Source File Reference Map" for source lines.
5
+ *
6
+ * Default transport: in-process complete() side-channel (preserves parent LLM cache).
7
+ * Fallback: pi.exec("pi", ["-p", ...]) subprocess when direct path is unavailable.
8
+ */
9
+
10
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
11
+ import {
12
+ buildMemoryTargetRoutingGuidance,
13
+ COMBINED_REVIEW_PROMPT,
14
+ DIRECT_REVIEW_SYSTEM_PROMPT,
15
+ } from "../constants.js";
16
+ import { MemoryStore } from "../store/memory-store.js";
17
+ import { DatabaseManager } from "../store/db.js";
18
+ import type { MemoryConfig } from "../types.js";
19
+ import { applyRecentMessageLimit, collectMessageParts } from "./message-parts.js";
20
+ import { execChildPrompt, resolveChildPiModel } from "./pi-child-process.js";
21
+ import { runDirectMemoryCompletion, usesDirectTransport, type DirectReviewResult } from "./review-memory-ops.js";
22
+
23
+ import { resolveProjectName, resolveProjectStore, type ProjectNameRef, type ProjectStoreRef } from "../project-context.js";
24
+ export interface BackgroundReviewOptions {
25
+ dbManager?: DatabaseManager | null;
26
+ projectName?: ProjectNameRef;
27
+ deps?: BackgroundReviewDeps;
28
+ }
29
+
30
+ export interface BackgroundReviewDeps {
31
+ runDirectReview?: typeof runDirectMemoryCompletion;
32
+ execChildPrompt?: typeof execChildPrompt;
33
+ /** Test-only hook: called once runReview() has fully settled (after the
34
+ * fire-and-forget review work completes and reviewInProgress resets),
35
+ * since production callers never await runReview() directly. */
36
+ onReviewSettled?: () => void;
37
+ }
38
+
39
+ export interface ReviewPromptInput {
40
+ parts: string[];
41
+ currentMemory: string;
42
+ currentUser: string;
43
+ currentProject: string | null;
44
+ }
45
+
46
+ export function buildSubprocessReviewPrompt(input: ReviewPromptInput): string {
47
+ const reviewPrompt = [
48
+ COMBINED_REVIEW_PROMPT,
49
+ "",
50
+ buildMemoryTargetRoutingGuidance(input.currentProject !== null),
51
+ "",
52
+ "--- Current Memory ---",
53
+ input.currentMemory || "(empty)",
54
+ "",
55
+ "--- Current User Profile ---",
56
+ input.currentUser || "(empty)",
57
+ ];
58
+
59
+ if (input.currentProject !== null) {
60
+ reviewPrompt.push(
61
+ "",
62
+ "--- Current Project Memory ---",
63
+ input.currentProject || "(empty)",
64
+ );
65
+ }
66
+
67
+ reviewPrompt.push(
68
+ "",
69
+ "--- Conversation to Review ---",
70
+ input.parts.join("\n\n"),
71
+ );
72
+
73
+ return reviewPrompt.join("\n");
74
+ }
75
+
76
+ export function buildDirectReviewUserPrompt(input: ReviewPromptInput): string {
77
+ const sections = [
78
+ "--- Current Memory ---",
79
+ input.currentMemory || "(empty)",
80
+ "",
81
+ "--- Current User Profile ---",
82
+ input.currentUser || "(empty)",
83
+ ];
84
+
85
+ if (input.currentProject !== null) {
86
+ sections.push(
87
+ "",
88
+ "--- Current Project Memory ---",
89
+ input.currentProject || "(empty)",
90
+ );
91
+ }
92
+
93
+ sections.push(
94
+ "",
95
+ "--- Conversation to Review ---",
96
+ input.parts.join("\n\n"),
97
+ );
98
+
99
+ return sections.join("\n");
100
+ }
101
+
102
+ function shouldNotifyDirect(result: DirectReviewResult): boolean {
103
+ return result.ok && result.appliedCount > 0;
104
+ }
105
+
106
+ function shouldNotifySubprocess(stdout: string | undefined): boolean {
107
+ const output = stdout?.trim();
108
+ return !!output && !output.toLowerCase().includes("nothing to save");
109
+ }
110
+
111
+ function diagnosticDetail(value: unknown): string {
112
+ const detail = value instanceof Error ? value.message : String(value ?? "").trim();
113
+ return detail.replace(/\s+/g, " ").slice(0, 300);
114
+ }
115
+
116
+ async function runSubprocessReview(
117
+ pi: ExtensionAPI,
118
+ prompt: string,
119
+ config: MemoryConfig,
120
+ execChild: typeof execChildPrompt,
121
+ ctx: Pick<ExtensionContext, "cwd" | "model" | "signal">,
122
+ ): Promise<{ code: number; stdout?: string; stderr?: string }> {
123
+ return execChild(pi, prompt, config, {
124
+ cwd: ctx.cwd,
125
+ model: resolveChildPiModel(ctx.model),
126
+ signal: ctx.signal,
127
+ timeoutMs: 120000,
128
+ });
129
+ }
130
+
131
+ export function setupBackgroundReview(
132
+ pi: ExtensionAPI,
133
+ store: MemoryStore,
134
+ projectStore: ProjectStoreRef,
135
+ config: MemoryConfig,
136
+ options: BackgroundReviewOptions = {},
137
+ ): void {
138
+ const dbManager = options.dbManager ?? null;
139
+ const projectName = options.projectName ?? null;
140
+ const runDirectReview = options.deps?.runDirectReview ?? runDirectMemoryCompletion;
141
+ const execChild = options.deps?.execChildPrompt ?? execChildPrompt;
142
+ const onReviewSettled = options.deps?.onReviewSettled;
143
+
144
+ let turnsSinceReview = 0;
145
+ let toolCallsSinceReview = 0;
146
+ let userTurnCount = 0;
147
+ let reviewInProgress = false;
148
+
149
+ pi.on("message_end", async (event, _ctx) => {
150
+ if (event.message.role === "user") {
151
+ userTurnCount++;
152
+ }
153
+ });
154
+
155
+ pi.on("turn_end", async (event, ctx) => {
156
+ turnsSinceReview++;
157
+
158
+ if (!config.reviewEnabled) return;
159
+ if (reviewInProgress) return;
160
+
161
+ try {
162
+ const msg = event.message;
163
+ if (msg?.role === "assistant") {
164
+ const content = msg?.content;
165
+ if (Array.isArray(content)) {
166
+ for (const block of content) {
167
+ if (block && typeof block === "object" && block.type === "toolCall") {
168
+ toolCallsSinceReview++;
169
+ }
170
+ }
171
+ }
172
+ }
173
+ } catch {
174
+ // If we can't count tool calls, fall back to turn-based only
175
+ }
176
+
177
+ const turnThresholdMet = turnsSinceReview >= config.nudgeInterval;
178
+ const toolCallThresholdMet = toolCallsSinceReview >= config.nudgeToolCalls;
179
+
180
+ if (!turnThresholdMet && !toolCallThresholdMet) return;
181
+ if (userTurnCount < 3) return;
182
+
183
+ turnsSinceReview = 0;
184
+ toolCallsSinceReview = 0;
185
+ reviewInProgress = true;
186
+
187
+ let allParts: string[] = [];
188
+ try {
189
+ const entries = ctx.sessionManager.getBranch();
190
+ allParts = collectMessageParts(entries);
191
+ } catch {
192
+ reviewInProgress = false;
193
+ return;
194
+ }
195
+ if (allParts.length < 4) {
196
+ reviewInProgress = false;
197
+ return;
198
+ }
199
+
200
+ const parts = applyRecentMessageLimit(allParts, config.reviewRecentMessages);
201
+ const activeProjectStore = resolveProjectStore(projectStore);
202
+ const activeProjectName = resolveProjectName(projectName);
203
+ const promptInput: ReviewPromptInput = {
204
+ parts,
205
+ currentMemory: store.getMemoryEntries().join("\n§\n"),
206
+ currentUser: store.getUserEntries().join("\n§\n"),
207
+ currentProject: activeProjectStore ? activeProjectStore.getMemoryEntries().join("\n§\n") : null,
208
+ };
209
+
210
+ const subprocessPrompt = buildSubprocessReviewPrompt(promptInput);
211
+ const directPrompt = buildDirectReviewUserPrompt(promptInput);
212
+
213
+ const finishReview = () => {
214
+ reviewInProgress = false;
215
+ onReviewSettled?.();
216
+ };
217
+
218
+ const notifyIfSaved = (saved: boolean) => {
219
+ if (saved) {
220
+ ctx.ui.notify("💾 Memory auto-reviewed and updated", "info");
221
+ }
222
+ };
223
+
224
+ const runReview = async (): Promise<void> => {
225
+ let directFailure: string | undefined;
226
+
227
+ if (usesDirectTransport(config)) {
228
+ try {
229
+ const directResult = await runDirectReview(
230
+ ctx as Pick<ExtensionContext, "model" | "modelRegistry">,
231
+ store,
232
+ activeProjectStore,
233
+ {
234
+ userPrompt: directPrompt,
235
+ systemPrompt: [
236
+ DIRECT_REVIEW_SYSTEM_PROMPT,
237
+ "",
238
+ buildMemoryTargetRoutingGuidance(activeProjectStore !== null),
239
+ ].join("\n"),
240
+ config,
241
+ timeoutMs: 120000,
242
+ },
243
+ dbManager,
244
+ activeProjectName,
245
+ );
246
+
247
+ if (directResult.ok) {
248
+ notifyIfSaved(shouldNotifyDirect(directResult));
249
+ return;
250
+ }
251
+
252
+ if (directResult.fallbackReason === "empty") {
253
+ return;
254
+ }
255
+ directFailure = [
256
+ directResult.fallbackReason ?? "failed",
257
+ directResult.error,
258
+ ].filter(Boolean).join(": ");
259
+ } catch (error) {
260
+ directFailure = diagnosticDetail(error);
261
+ }
262
+ }
263
+
264
+ let subprocessResult: { code: number; stdout?: string; stderr?: string };
265
+ try {
266
+ subprocessResult = await runSubprocessReview(pi, subprocessPrompt, config, execChild, ctx);
267
+ } catch (error) {
268
+ if (directFailure) {
269
+ ctx.ui.notify(
270
+ `Memory auto-review failed in both transports. Direct: ${diagnosticDetail(directFailure)}. `
271
+ + `Subprocess: ${diagnosticDetail(error)}. Check the active model/provider or set llmModelOverride.`,
272
+ "warning",
273
+ );
274
+ }
275
+ return;
276
+ }
277
+
278
+ if (subprocessResult.code === 0) {
279
+ notifyIfSaved(shouldNotifySubprocess(subprocessResult.stdout));
280
+ } else if (directFailure) {
281
+ const subprocessDetail = subprocessResult.stderr?.trim() || subprocessResult.stdout?.trim()
282
+ || `exit code ${subprocessResult.code}`;
283
+ ctx.ui.notify(
284
+ `Memory auto-review failed in both transports. Direct: ${diagnosticDetail(directFailure)}. `
285
+ + `Subprocess: ${diagnosticDetail(subprocessDetail)}. Check the active model/provider or set llmModelOverride.`,
286
+ "warning",
287
+ );
288
+ }
289
+ };
290
+
291
+ runReview()
292
+ .catch(() => {
293
+ // Best-effort only; transport failures are diagnosed after both paths settle.
294
+ })
295
+ .finally(finishReview);
296
+ });
297
+ }