@selesai/code 0.13.15 → 0.13.18

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 (473) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +2 -14
  3. package/dist/bun/cli.d.ts +3 -1
  4. package/dist/bun/cli.js +3 -9
  5. package/dist/bun/{register-bedrock.js → runtime-setup.js} +5 -0
  6. package/dist/bun/sandbox-env-setup.d.ts +1 -0
  7. package/dist/bun/sandbox-env-setup.js +3 -0
  8. package/dist/cli/auth-command.d.ts +23 -0
  9. package/dist/cli/auth-command.js +102 -0
  10. package/dist/cli/config-selector.js +2 -1
  11. package/dist/cli/setup.d.ts +1 -0
  12. package/dist/cli/setup.js +11 -0
  13. package/dist/cli.js +2 -14
  14. package/dist/config.d.ts +3 -2
  15. package/dist/config.js +9 -4
  16. package/dist/core/agent-session-runtime.js +15 -7
  17. package/dist/core/agent-session.d.ts +2 -6
  18. package/dist/core/agent-session.js +15 -4
  19. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  20. package/dist/core/compaction/branch-summarization.js +2 -1
  21. package/dist/core/extensions/loader.js +3 -0
  22. package/dist/core/extensions/types.d.ts +16 -2
  23. package/dist/core/http-dispatcher.js +2 -0
  24. package/dist/core/keybindings.d.ts +5 -0
  25. package/dist/core/keybindings.js +4 -0
  26. package/dist/core/messages.d.ts +1 -1
  27. package/dist/core/model-config.d.ts +15 -0
  28. package/dist/core/model-config.js +3 -0
  29. package/dist/core/model-runtime.d.ts +1 -0
  30. package/dist/core/model-runtime.js +5 -2
  31. package/dist/core/provider-composer.js +8 -2
  32. package/dist/core/session-export.d.ts +3 -0
  33. package/dist/core/session-export.js +31 -0
  34. package/dist/core/session-manager.d.ts +3 -2
  35. package/dist/core/session-manager.js +40 -13
  36. package/dist/core/skills.d.ts +1 -1
  37. package/dist/core/skills.js +4 -2
  38. package/dist/core/system-prompt.js +10 -10
  39. package/dist/core/tools/bash.js +3 -117
  40. package/dist/core/tools/edit.d.ts +1 -12
  41. package/dist/core/tools/edit.js +5 -163
  42. package/dist/core/tools/find.js +4 -55
  43. package/dist/core/tools/grep.js +4 -60
  44. package/dist/core/tools/ls.js +4 -49
  45. package/dist/core/tools/read.d.ts +4 -0
  46. package/dist/core/tools/read.js +10 -122
  47. package/dist/core/tools/renderers/bash.d.ts +11 -0
  48. package/dist/core/tools/renderers/bash.js +129 -0
  49. package/dist/core/tools/renderers/edit.d.ts +22 -0
  50. package/dist/core/tools/renderers/edit.js +166 -0
  51. package/dist/core/tools/renderers/find.d.ts +9 -0
  52. package/dist/core/tools/renderers/find.js +63 -0
  53. package/dist/core/tools/renderers/grep.d.ts +9 -0
  54. package/dist/core/tools/renderers/grep.js +68 -0
  55. package/dist/core/tools/renderers/index.d.ts +27 -0
  56. package/dist/core/tools/renderers/index.js +46 -0
  57. package/dist/core/tools/renderers/ls.d.ts +9 -0
  58. package/dist/core/tools/renderers/ls.js +57 -0
  59. package/dist/core/tools/renderers/read.d.ts +10 -0
  60. package/dist/core/tools/renderers/read.js +128 -0
  61. package/dist/core/tools/renderers/write.d.ts +9 -0
  62. package/dist/core/tools/renderers/write.js +152 -0
  63. package/dist/core/tools/write.js +5 -146
  64. package/dist/extensions/capability-gateway/index.ts +12 -1
  65. package/dist/extensions/capability-gateway/integration.test.ts +23 -0
  66. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +18 -0
  67. package/dist/extensions/pi-hermes-memory/README.md +16 -12
  68. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +1 -1
  69. package/dist/extensions/pi-hermes-memory/package-lock.json +587 -62
  70. package/dist/extensions/pi-hermes-memory/package.json +4 -4
  71. package/dist/extensions/pi-hermes-memory/src/config.ts +32 -0
  72. package/dist/extensions/pi-hermes-memory/src/constants.ts +12 -2
  73. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +109 -51
  74. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +9 -3
  75. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +257 -98
  76. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +9 -2
  77. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +5 -0
  78. package/dist/extensions/pi-hermes-memory/src/index.ts +22 -2
  79. package/dist/extensions/pi-hermes-memory/src/paths.ts +1 -1
  80. package/dist/extensions/pi-hermes-memory/src/store/db.ts +6 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +58 -1
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +60 -8
  83. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +169 -7
  84. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +7 -21
  85. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +134 -15
  86. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +4 -2
  87. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +24 -9
  88. package/dist/extensions/pi-hermes-memory/src/types.ts +11 -0
  89. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +42 -0
  90. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +159 -3
  91. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +452 -0
  92. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +39 -0
  93. package/dist/extensions/pi-hermes-memory/tests/integration/corruption-recovery-integration.test.ts +91 -0
  94. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +29 -0
  95. package/dist/extensions/pi-hermes-memory/tests/store/fts-query.test.ts +116 -0
  96. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +163 -0
  97. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +233 -0
  98. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +12 -0
  99. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +175 -0
  100. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +2 -2
  101. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +22 -0
  102. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +37 -1
  103. package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
  104. package/dist/extensions/pi-intercom/README.md +16 -2
  105. package/dist/extensions/pi-intercom/broker/spawn.test.ts +19 -2
  106. package/dist/extensions/pi-intercom/broker/spawn.ts +4 -4
  107. package/dist/extensions/pi-intercom/index.ts +1 -1
  108. package/dist/extensions/pi-intercom/intercom.integration.test.ts +121 -2
  109. package/dist/extensions/pi-intercom/package.json +1 -1
  110. package/dist/extensions/pi-intercom/project-agent.test.ts +1 -1
  111. package/dist/extensions/pi-intercom/project-agent.ts +1 -6
  112. package/dist/extensions/pi-intercom/skills/pi-intercom/SKILL.md +7 -6
  113. package/dist/extensions/pi-rewind-hook/index.test.ts +7 -0
  114. package/dist/extensions/pi-subagents/.github/workflows/test.yml +36 -4
  115. package/dist/extensions/pi-subagents/CHANGELOG.md +120 -0
  116. package/dist/extensions/pi-subagents/README.md +3 -3
  117. package/dist/extensions/pi-subagents/agents/researcher.md +23 -13
  118. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -1
  119. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  120. package/dist/extensions/pi-subagents/docs/agents.md +32 -16
  121. package/dist/extensions/pi-subagents/docs/configuration.md +41 -15
  122. package/dist/extensions/pi-subagents/docs/extension-api.md +109 -6
  123. package/dist/extensions/pi-subagents/docs/missions.md +2 -0
  124. package/dist/extensions/pi-subagents/docs/models.md +58 -1
  125. package/dist/extensions/pi-subagents/docs/observability.md +50 -12
  126. package/dist/extensions/pi-subagents/docs/tool-reference.md +17 -10
  127. package/dist/extensions/pi-subagents/docs/watchdog.md +1 -1
  128. package/dist/extensions/pi-subagents/docs/workflows.md +26 -9
  129. package/dist/extensions/pi-subagents/package-lock.json +747 -177
  130. package/dist/extensions/pi-subagents/package.json +6 -4
  131. package/dist/extensions/pi-subagents/runner-server-preload.mjs +13 -0
  132. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +2 -1
  133. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  134. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +21 -4
  135. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -1
  136. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +2 -0
  137. package/dist/extensions/pi-subagents/src/agents/advertised-agent-prompt.ts +63 -0
  138. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +61 -16
  139. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  140. package/dist/extensions/pi-subagents/src/agents/agents.ts +8 -0
  141. package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +0 -1
  142. package/dist/extensions/pi-subagents/src/api/{pi-args.ts → child-tool-plan.ts} +1 -1
  143. package/dist/extensions/pi-subagents/src/api/preflight.ts +7 -4
  144. package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -2
  145. package/dist/extensions/pi-subagents/src/api/workflow-resources.ts +6 -0
  146. package/dist/extensions/pi-subagents/src/extension/config.ts +4 -2
  147. package/dist/extensions/pi-subagents/src/extension/doctor.ts +2 -10
  148. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +9 -11
  149. package/dist/extensions/pi-subagents/src/extension/index.ts +99 -10
  150. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +13 -0
  151. package/dist/extensions/pi-subagents/src/extension/rpc.ts +7 -23
  152. package/dist/extensions/pi-subagents/src/extension/schemas.ts +11 -8
  153. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +25 -7
  154. package/dist/extensions/pi-subagents/src/integrations/pi-web-session-liveness.ts +73 -0
  155. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +228 -136
  156. package/dist/extensions/pi-subagents/src/intercom/supervisor-ui.ts +244 -0
  157. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +37 -16
  158. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +18 -18
  159. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +51 -25
  160. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +46 -3
  161. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -3
  162. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +9 -0
  163. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +10 -12
  164. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -15
  165. package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +40 -29
  166. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +8 -0
  167. package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +79 -247
  168. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +88 -12
  169. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +6 -6
  170. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +24 -24
  171. package/dist/extensions/pi-subagents/src/runs/background/retained-nested-route-tracker.ts +96 -0
  172. package/dist/extensions/pi-subagents/src/runs/background/run-child-session.ts +642 -0
  173. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +75 -6
  174. package/dist/extensions/pi-subagents/src/runs/background/runner-aliases.ts +163 -0
  175. package/dist/extensions/pi-subagents/src/runs/background/runner-child-launch.ts +86 -0
  176. package/dist/extensions/pi-subagents/src/runs/background/runner-child-sessions.ts +31 -0
  177. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +18 -4
  178. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -1
  179. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +597 -1092
  180. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +3 -0
  181. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +4 -0
  182. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +20 -17
  183. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +296 -394
  184. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +4 -0
  185. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +3 -1
  186. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +9 -5
  187. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +630 -283
  188. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +8 -5
  189. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-foreground-steering.ts +78 -98
  190. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +3 -3
  191. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +16 -3
  192. package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +1 -1
  193. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +47 -47
  194. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -2
  195. package/dist/extensions/pi-subagents/src/runs/shared/child-hooks.ts +174 -0
  196. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +14 -3
  197. package/dist/extensions/pi-subagents/src/runs/shared/child-launch.ts +319 -0
  198. package/dist/extensions/pi-subagents/src/runs/shared/child-lifecycle.ts +25 -0
  199. package/dist/extensions/pi-subagents/src/runs/shared/child-runtime-config.ts +126 -0
  200. package/dist/extensions/pi-subagents/src/runs/shared/child-session.ts +373 -0
  201. package/dist/extensions/pi-subagents/src/runs/shared/child-tool-plan.ts +530 -0
  202. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +2 -2
  203. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +2 -1
  204. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +16 -0
  205. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +11 -11
  206. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +30 -20
  207. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +5 -4
  208. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +84 -15
  209. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +88 -12
  210. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +37 -53
  211. package/dist/extensions/pi-subagents/src/runs/shared/nested-path.ts +1 -1
  212. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +18 -7
  213. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +57 -12
  214. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +3 -4
  215. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +0 -11
  216. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +4 -1
  217. package/dist/extensions/pi-subagents/src/runs/shared/readonly-drain-observation.ts +42 -0
  218. package/dist/extensions/pi-subagents/src/runs/shared/readonly-model-continuation.ts +69 -0
  219. package/dist/extensions/pi-subagents/src/runs/shared/readonly-session-evidence.ts +307 -0
  220. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +8 -21
  221. package/dist/extensions/pi-subagents/src/runs/shared/runtime-acknowledged-extensions.ts +3 -30
  222. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +17 -4
  223. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +2 -0
  224. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +99 -386
  225. package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +18 -62
  226. package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +0 -14
  227. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +31 -9
  228. package/dist/extensions/pi-subagents/src/runs/shared/worktree-setup-command.ts +190 -0
  229. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +506 -226
  230. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +1 -1
  231. package/dist/extensions/pi-subagents/src/shared/jsonl-writer.ts +11 -0
  232. package/dist/extensions/pi-subagents/src/shared/model-response-aliases.ts +13 -0
  233. package/dist/extensions/pi-subagents/src/shared/thinking-ceiling.ts +0 -6
  234. package/dist/extensions/pi-subagents/src/shared/types.ts +145 -89
  235. package/dist/extensions/pi-subagents/src/shared/utils.ts +13 -6
  236. package/dist/extensions/pi-subagents/src/shared/watch-strategy.ts +2 -0
  237. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +18 -13
  238. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +0 -6
  239. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +1 -1
  240. package/dist/extensions/pi-subagents/src/tui/fleet.ts +11 -6
  241. package/dist/extensions/pi-subagents/src/tui/render.ts +277 -46
  242. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +0 -1
  243. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +12 -12
  244. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -3
  245. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +116 -15
  246. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +15 -18
  247. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +57 -8
  248. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +19 -19
  249. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +3 -3
  250. package/dist/extensions/pi-subagents/src/workflows/workflow-resources.ts +97 -22
  251. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +3 -0
  252. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +336 -21
  253. package/dist/extensions/pi-subagents/test/integration/async-execution.part-1.test.ts +1181 -0
  254. package/dist/extensions/pi-subagents/test/integration/async-execution.part-2.test.ts +2240 -0
  255. package/dist/extensions/pi-subagents/test/integration/async-execution.part-3.test.ts +2268 -0
  256. package/dist/extensions/pi-subagents/test/integration/async-execution.part-4.test.ts +1512 -0
  257. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +231 -16
  258. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +4 -18
  259. package/dist/extensions/pi-subagents/test/integration/foreign-workflow-steering.test.ts +95 -0
  260. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +9 -15
  261. package/dist/extensions/pi-subagents/test/integration/in-process-child.test.ts +374 -0
  262. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +20 -10
  263. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +18 -6
  264. package/dist/extensions/pi-subagents/test/integration/recovered-assistant-error.test.ts +151 -0
  265. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +42 -1
  266. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +150 -25
  267. package/dist/extensions/pi-subagents/test/integration/result-publication.test.ts +354 -0
  268. package/dist/extensions/pi-subagents/test/integration/shared-cwd-terminal-evidence.test.ts +117 -0
  269. package/dist/extensions/pi-subagents/test/integration/single-execution.part-1.test.ts +3678 -0
  270. package/dist/extensions/pi-subagents/test/integration/{single-execution.test.ts → single-execution.part-2.test.ts} +500 -4207
  271. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -2
  272. package/dist/extensions/pi-subagents/test/integration/workflow-result-publication.test.ts +127 -0
  273. package/dist/extensions/pi-subagents/test/integration/workflow-steer-inbox.test.ts +365 -0
  274. package/dist/extensions/pi-subagents/test/smoke/pi085-child.ts +31 -0
  275. package/dist/extensions/pi-subagents/test/smoke/pi085-clean-install.mjs +74 -0
  276. package/dist/extensions/pi-subagents/test/smoke/pi085-extension.ts +16 -0
  277. package/dist/extensions/pi-subagents/test/support/async-execution-fixture.ts +699 -0
  278. package/dist/extensions/pi-subagents/test/support/fake-child-session.ts +418 -0
  279. package/dist/extensions/pi-subagents/test/support/foreign-workflow-steer-host.mjs +13 -0
  280. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +30 -76
  281. package/dist/extensions/pi-subagents/test/support/notify-diagnostics-fixture.ts +84 -0
  282. package/dist/extensions/pi-subagents/test/support/result-publication-capacity-preload.mjs +70 -0
  283. package/dist/extensions/pi-subagents/test/support/runner-child-session-factory.ts +17 -0
  284. package/dist/extensions/pi-subagents/test/support/single-execution-fixture.ts +390 -0
  285. package/dist/extensions/pi-subagents/test/unit/acceptance-compaction.test.ts +120 -0
  286. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +24 -3
  287. package/dist/extensions/pi-subagents/test/unit/advertised-agent-prompt.test.ts +87 -0
  288. package/dist/extensions/pi-subagents/test/unit/advertised-agent-refresh.test.ts +163 -0
  289. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +70 -31
  290. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +78 -3
  291. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +2 -8
  292. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +1 -1
  293. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +73 -66
  294. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +51 -1
  295. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +49 -1
  296. package/dist/extensions/pi-subagents/test/unit/async-spawn-preload.test.ts +103 -0
  297. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +3 -3
  298. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-agent-allowlist.test.ts +9 -8
  299. package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +10 -7
  300. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +48 -6
  301. package/dist/extensions/pi-subagents/test/unit/child-runtime-config.test.ts +90 -0
  302. package/dist/extensions/pi-subagents/test/unit/{pi-args-permission-system.test.ts → child-tool-plan-permission-system.test.ts} +48 -86
  303. package/dist/extensions/pi-subagents/test/unit/child-tool-plan.test.ts +33 -0
  304. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +18 -18
  305. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +9 -9
  306. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +8 -7
  307. package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +225 -65
  308. package/dist/extensions/pi-subagents/test/unit/extension-bindings.test.ts +17 -22
  309. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +11 -1
  310. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +47 -3
  311. package/dist/extensions/pi-subagents/test/unit/foreign-workflow-steering.test.ts +74 -0
  312. package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +23 -29
  313. package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +96 -4
  314. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +4 -4
  315. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +116 -55
  316. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +58 -1
  317. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +104 -3
  318. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +10 -1
  319. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +31 -1
  320. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +102 -51
  321. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +196 -6
  322. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +181 -38
  323. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +62 -70
  324. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +58 -53
  325. package/dist/extensions/pi-subagents/test/unit/notify-diagnostics.test.ts +42 -0
  326. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +84 -0
  327. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +13 -5
  328. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +21 -5
  329. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +5 -4
  330. package/dist/extensions/pi-subagents/test/unit/permissions.test.ts +2 -6
  331. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +28 -2
  332. package/dist/extensions/pi-subagents/test/unit/pi-web-session-liveness.test.ts +209 -0
  333. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +38 -1
  334. package/dist/extensions/pi-subagents/test/unit/prompt-audit-optional-peers.test.ts +43 -0
  335. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +24 -2
  336. package/dist/extensions/pi-subagents/test/unit/readonly-drain-observation.test.ts +174 -0
  337. package/dist/extensions/pi-subagents/test/unit/readonly-model-continuation.test.ts +94 -0
  338. package/dist/extensions/pi-subagents/test/unit/readonly-session-evidence.test.ts +1329 -0
  339. package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +29 -80
  340. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +11 -8
  341. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +2 -2
  342. package/dist/extensions/pi-subagents/test/unit/retained-nested-route-tracker.test.ts +175 -0
  343. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +38 -12
  344. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +2 -5
  345. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +89 -1
  346. package/dist/extensions/pi-subagents/test/unit/runtime-acknowledged-extensions.test.ts +2 -16
  347. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +27 -3
  348. package/dist/extensions/pi-subagents/test/unit/scheduled-supervisor-demand.test.ts +215 -0
  349. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +15 -11
  350. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +237 -1
  351. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -5
  352. package/dist/extensions/pi-subagents/test/unit/streamed-progress-bounds.test.ts +2 -2
  353. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +14 -1
  354. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +130 -739
  355. package/dist/extensions/pi-subagents/test/unit/supervisor-ask-registration.test.ts +682 -0
  356. package/dist/extensions/pi-subagents/test/unit/supervisor-ui.test.ts +137 -0
  357. package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +1 -11
  358. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +23 -3
  359. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +8 -1
  360. package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +19 -1
  361. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +72 -4
  362. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +17 -3
  363. package/dist/extensions/pi-subagents/test/unit/workflow-child-summary.test.ts +34 -0
  364. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +12 -2
  365. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +46 -6
  366. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +3 -3
  367. package/dist/extensions/pi-subagents/test/unit/workflow-resources.test.ts +133 -1
  368. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +53 -2
  369. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +222 -14
  370. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +526 -85
  371. package/dist/extensions/pi-web-agent/src/backends/config.ts +1 -0
  372. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +5 -3
  373. package/dist/extensions/pi-web-agent/src/search/tokenin.ts +7 -3
  374. package/dist/extensions/pi-web-agent/src/types.ts +1 -1
  375. package/dist/extensions/pi-zentui/README.md +17 -0
  376. package/dist/extensions/pi-zentui/assets/screenshots/footer-status-hyperlink.png +0 -0
  377. package/dist/extensions/pi-zentui/docs/configuration.md +71 -2
  378. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +11 -0
  379. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +14 -0
  380. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +29 -13
  381. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +5 -4
  382. package/dist/extensions/pi-zentui/extensions/zentui/footer-text.ts +11 -0
  383. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +11 -10
  384. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +77 -3
  385. package/dist/extensions/pi-zentui/extensions/zentui/presets.ts +77 -0
  386. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +103 -2
  387. package/dist/extensions/pi-zentui/extensions/zentui/working-line-extension-segments.ts +115 -0
  388. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +87 -5
  389. package/dist/extensions/pi-zentui/package-lock.json +277 -517
  390. package/dist/extensions/pi-zentui/package.json +7 -4
  391. package/dist/extensions/pi-zentui/test/config-presets.test.ts +238 -0
  392. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +24 -1
  393. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +570 -2
  394. package/dist/extensions/pi-zentui/test/extension-status.test.ts +90 -0
  395. package/dist/extensions/pi-zentui/test/footer-hyperlink.test.ts +243 -0
  396. package/dist/extensions/pi-zentui/test/package-contents.mjs +1 -0
  397. package/dist/extensions/pi-zentui/test/presets.test.ts +121 -0
  398. package/dist/extensions/pi-zentui/test/settings-command.test.ts +205 -1
  399. package/dist/extensions/pi-zentui/test/working-line-extension-segments.test.ts +139 -0
  400. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +237 -3
  401. package/dist/extensions/pi-zentui/test/working-line.test.ts +94 -2
  402. package/dist/index.d.ts +1 -1
  403. package/dist/main.js +4 -1
  404. package/dist/modes/interactive/chat-viewport.d.ts +19 -0
  405. package/dist/modes/interactive/chat-viewport.js +27 -0
  406. package/dist/modes/interactive/components/assistant-message.d.ts +6 -2
  407. package/dist/modes/interactive/components/assistant-message.js +30 -13
  408. package/dist/modes/interactive/components/custom-editor.d.ts +10 -1
  409. package/dist/modes/interactive/components/custom-editor.js +39 -1
  410. package/dist/modes/interactive/components/first-time-setup.js +1 -1
  411. package/dist/modes/interactive/components/index.d.ts +1 -1
  412. package/dist/modes/interactive/components/markdown-transform.d.ts +2 -0
  413. package/dist/modes/interactive/components/markdown-transform.js +18 -0
  414. package/dist/modes/interactive/components/model-selector.js +19 -17
  415. package/dist/modes/interactive/components/scoped-models-selector.js +21 -23
  416. package/dist/modes/interactive/components/settings-selector.d.ts +7 -1
  417. package/dist/modes/interactive/components/settings-selector.js +128 -20
  418. package/dist/modes/interactive/components/settings-submenu.d.ts +71 -0
  419. package/dist/modes/interactive/components/settings-submenu.js +164 -0
  420. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  421. package/dist/modes/interactive/components/status-indicator.js +10 -3
  422. package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
  423. package/dist/modes/interactive/components/thinking-selector.js +75 -18
  424. package/dist/modes/interactive/components/tool-execution.d.ts +21 -4
  425. package/dist/modes/interactive/components/tool-execution.js +35 -33
  426. package/dist/modes/interactive/components/trust-selector.js +2 -2
  427. package/dist/modes/interactive/interactive-mode.d.ts +13 -15
  428. package/dist/modes/interactive/interactive-mode.js +144 -80
  429. package/dist/modes/interactive/session-share.d.ts +15 -0
  430. package/dist/modes/interactive/session-share.js +183 -0
  431. package/dist/modes/interactive/theme/dark.json +2 -1
  432. package/dist/modes/interactive/theme/light.json +2 -1
  433. package/dist/modes/interactive/theme/theme-controller.d.ts +1 -0
  434. package/dist/modes/interactive/theme/theme-controller.js +5 -0
  435. package/dist/modes/interactive/theme/theme-json.d.ts +83 -0
  436. package/dist/modes/interactive/theme/theme-json.js +129 -0
  437. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  438. package/dist/modes/interactive/theme/theme.d.ts +14 -5
  439. package/dist/modes/interactive/theme/theme.js +18 -119
  440. package/dist/modes/interactive/tui-renderer.d.ts +20 -0
  441. package/dist/modes/interactive/tui-renderer.js +65 -0
  442. package/dist/utils/exif-orientation.js +2 -3
  443. package/dist/utils/management-http.d.ts +24 -0
  444. package/dist/utils/management-http.js +53 -0
  445. package/dist/utils/text.d.ts +7 -0
  446. package/dist/utils/text.js +8 -0
  447. package/dist/utils/tools-manager.d.ts +1 -0
  448. package/dist/utils/tools-manager.js +43 -21
  449. package/docs/docs.json +0 -4
  450. package/package.json +4 -4
  451. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +0 -415
  452. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +0 -1059
  453. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +0 -116
  454. package/dist/extensions/pi-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  455. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +0 -224
  456. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +0 -6986
  457. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +0 -447
  458. package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +0 -185
  459. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +0 -311
  460. package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +0 -84
  461. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +0 -202
  462. package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +0 -101
  463. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +0 -2347
  464. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +0 -104
  465. package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +0 -49
  466. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -1465
  467. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -987
  468. package/dist/skills/implanger/SKILL.md +0 -70
  469. package/dist/skills/output/SKILL.md +0 -49
  470. package/dist/skills/planger/SKILL.md +0 -167
  471. package/dist/skills/workflow/SKILL.md +0 -95
  472. package/docs/workflows.md +0 -18
  473. /package/dist/bun/{register-bedrock.d.ts → runtime-setup.d.ts} +0 -0
@@ -2,6 +2,30 @@ const FTS5_OPERATOR_PATTERN = /\b(OR|AND|NOT|NEAR)\b/;
2
2
  const FTS5_TOKEN_PATTERN = /"([^"]*)"|(\S+)/g;
3
3
  const NATURAL_LANGUAGE_CONNECTORS = new Set(['and', 'or', 'not', 'near']);
4
4
 
5
+ // Common English stop words that add noise to FTS5 searches. They are silently
6
+ // dropped from natural-language queries so common filler words cannot dominate
7
+ // the FTS5 ranking or produce misleading "AND"-style misses. The list is
8
+ // deliberately conservative: only high-frequency function words are dropped.
9
+ // Words that frequently carry the actual search intent (can, need, off, like,
10
+ // get) stay, so "can the app start" still searches for "can".
11
+ const STOP_WORDS = new Set([
12
+ 'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
13
+ 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should',
14
+ 'may', 'might', 'shall', 'dare', 'ought', 'used',
15
+ 'i', 'me', 'my', 'mine', 'we', 'our', 'ours', 'you', 'your', 'yours',
16
+ 'he', 'him', 'his', 'she', 'her', 'hers', 'it', 'its', 'they', 'them',
17
+ 'their', 'theirs', 'that', 'this', 'these', 'those', 'what', 'which',
18
+ 'who', 'whom', 'whose', 'when', 'where', 'why', 'how', 'all', 'each',
19
+ 'every', 'both', 'few', 'more', 'most', 'other', 'some', 'such', 'no',
20
+ 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's',
21
+ 't', 'just', 'don', 'now', 'about', 'above', 'after', 'again', 'against',
22
+ 'am', 'at', 'before', 'behind', 'below', 'between', 'by', 'during',
23
+ 'from', 'further', 'into', 'over', 'per', 'through',
24
+ 'throughout', 'till', 'to', 'under', 'until', 'up', 'upon', 'via', 'with',
25
+ 'within', 'without', 'as', 'if', 'or', 'but', 'and', 'out', 'of',
26
+ 'for', 'in', 'on', 'any', 'got', 'here', 'there',
27
+ ]);
28
+
5
29
  export function hasExplicitFts5Operator(query: string): boolean {
6
30
  return FTS5_OPERATOR_PATTERN.test(query.trim());
7
31
  }
@@ -15,6 +39,10 @@ function collectNaturalLanguageTerms(query: string): string[] {
15
39
  if (phrase === undefined && term && NATURAL_LANGUAGE_CONNECTORS.has(term.toLowerCase())) {
16
40
  continue;
17
41
  }
42
+ // Skip high-frequency stop words so they cannot dominate FTS5 ranking.
43
+ if (phrase === undefined && term && STOP_WORDS.has(term.toLowerCase())) {
44
+ continue;
45
+ }
18
46
 
19
47
  const rawValue = phrase ?? term ?? '';
20
48
  if (rawValue.length > 0) terms.push(rawValue);
@@ -67,6 +95,30 @@ function quoteTerms(terms: string[], separator: string): string {
67
95
  return terms.map((term) => `"${term.replace(/"/g, '""')}"`).join(separator);
68
96
  }
69
97
 
98
+ /**
99
+ * Collect the raw terms of a query for literal (LIKE) fallback searches.
100
+ * Unlike normalizeFts5Query this does NOT drop stop words — a literal
101
+ * substring fallback is the last resort, and the original terms are what the
102
+ * user typed. Natural-language connectors are skipped because they never
103
+ * carry search intent.
104
+ */
105
+ export function collectLikeTerms(query: string): string[] {
106
+ const terms: string[] = [];
107
+
108
+ for (const match of query.matchAll(FTS5_TOKEN_PATTERN)) {
109
+ const phrase = match[1];
110
+ const term = match[2];
111
+ if (phrase === undefined && term && NATURAL_LANGUAGE_CONNECTORS.has(term.toLowerCase())) {
112
+ continue;
113
+ }
114
+
115
+ const rawValue = phrase ?? term ?? '';
116
+ if (rawValue.length > 0) terms.push(rawValue);
117
+ }
118
+
119
+ return terms;
120
+ }
121
+
70
122
  /**
71
123
  * Normalize a query as natural language even when it contains uppercase
72
124
  * operator words. Queries like "DO NOT USE FIND /" are passed through as raw
@@ -97,5 +149,10 @@ export function buildNaturalLanguageFallbackQuery(query: string): string | null
97
149
  export function isFts5QueryError(err: unknown): boolean {
98
150
  if (!(err instanceof Error)) return false;
99
151
  const msg = err.message.toLowerCase();
100
- return msg.includes('fts5') || msg.includes('unterminated string');
152
+ // NOTE: "malformed" and "sql logic error" are intentionally NOT matched here.
153
+ // They are corruption/recovery signals used by DatabaseManager (#186); swallowing
154
+ // them would suppress database quarantine/rebuild. Only genuine FTS5 query/index
155
+ // errors are considered recoverable search-path failures.
156
+ return msg.includes('fts5') ||
157
+ msg.includes('unterminated string');
101
158
  }
@@ -40,6 +40,7 @@ import { canonicalMarkdownIdentity, withMarkdownMutationLock } from "./markdown-
40
40
 
41
41
  const MAX_EXTERNAL_WRITE_RETRIES = 2;
42
42
  const RECOVERY_ACTIVE_GRACE_MS = 7 * 24 * 60 * 60 * 1000;
43
+ const RECOVERY_SNAPSHOT_MIN_INTERVAL_MS = 60 * 60 * 1000;
43
44
  const RECOVERY_MAX_COUNT = 32;
44
45
  const RECOVERY_MAX_BYTES = 64 * 1024 * 1024;
45
46
  const RETIRED_RECOVERY_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
@@ -119,6 +120,9 @@ export class MemoryStore {
119
120
  if (target === "failure") return this.config.memoryCharLimit * 2; // Failures get more space
120
121
  return target === "user" ? this.config.userCharLimit : this.config.memoryCharLimit;
121
122
  }
123
+ private get capEnforced(): boolean {
124
+ return this.config.memoryMode !== "policy-only";
125
+ }
122
126
 
123
127
  private charCount(target: "memory" | "user" | "failure"): number {
124
128
  const entries = this.entriesFor(target);
@@ -190,10 +194,11 @@ export class MemoryStore {
190
194
  toolState?: string;
191
195
  correctedTo?: string;
192
196
  project?: string;
197
+ signal?: AbortSignal;
193
198
  }): Promise<MemoryResult> {
194
199
  const failureText = this.buildFailureMemoryText(content, options);
195
200
  return this.addWithConsolidation(
196
- "failure", failureText, undefined, 1, "Failure memory saved: " + options.category, options.project,
201
+ "failure", failureText, options.signal, 1, "Failure memory saved: " + options.category, options.project,
197
202
  );
198
203
  }
199
204
 
@@ -244,7 +249,7 @@ export class MemoryStore {
244
249
  const encoded = this.encodeEntry(content, today, today, project);
245
250
 
246
251
  const newTotal = [...entries, encoded].join(ENTRY_DELIMITER).length;
247
- if (newTotal > limit) {
252
+ if (this.capEnforced && newTotal > limit) {
248
253
  this.overflowSince[target] ??= Date.now();
249
254
  const strategy = this.memoryOverflowStrategy();
250
255
 
@@ -276,6 +281,7 @@ export class MemoryStore {
276
281
  const result = await this.runTargetMutation(
277
282
  target,
278
283
  (markMutation) => this._add(target, content, signal, addedMessage, project, markMutation),
284
+ signal,
279
285
  );
280
286
  if (
281
287
  result.success
@@ -374,7 +380,7 @@ export class MemoryStore {
374
380
  async applyMutationPlan(
375
381
  target: "memory" | "user" | "failure",
376
382
  operations: MemoryMutationOperation[],
377
- options: { requireShrink?: boolean } = {},
383
+ options: { requireShrink?: boolean; signal?: AbortSignal } = {},
378
384
  ): Promise<MemoryResult> {
379
385
  return this.runTargetMutation(target, async (markMutation) => {
380
386
  await this.syncTargetFromDiskIfChanged(target);
@@ -440,7 +446,7 @@ export class MemoryStore {
440
446
 
441
447
  const originalTotal = originalEntries.join(ENTRY_DELIMITER).length;
442
448
  const plannedTotal = plannedEntries.join(ENTRY_DELIMITER).length;
443
- if (plannedTotal > this.charLimit(target)) {
449
+ if (this.capEnforced && plannedTotal > this.charLimit(target)) {
444
450
  return {
445
451
  success: false,
446
452
  error: `Memory mutation plan would put memory at ${plannedTotal}/${this.charLimit(target)} chars.`,
@@ -457,13 +463,14 @@ export class MemoryStore {
457
463
  await this.saveToDisk(target);
458
464
  markMutation();
459
465
  return this.successResponse(target, `Applied ${operations.length} memory operations atomically.`);
460
- });
466
+ }, options.signal);
461
467
  }
462
468
 
463
- async replace(target: "memory" | "user" | "failure", oldText: string, newContent: string): Promise<MemoryResult> {
469
+ async replace(target: "memory" | "user" | "failure", oldText: string, newContent: string, signal?: AbortSignal): Promise<MemoryResult> {
464
470
  return this.runTargetMutation(
465
471
  target,
466
472
  (markMutation) => this.replaceUnlocked(target, oldText, newContent, markMutation),
473
+ signal,
467
474
  );
468
475
  }
469
476
 
@@ -506,7 +513,7 @@ export class MemoryStore {
506
513
 
507
514
  const newTotal = testEntries.join(ENTRY_DELIMITER).length;
508
515
 
509
- if (newTotal > this.charLimit(target)) {
516
+ if (this.capEnforced && newTotal > this.charLimit(target)) {
510
517
  return {
511
518
  success: false,
512
519
  error: `Replacement would put memory at ${newTotal}/${this.charLimit(target)} chars. Shorten or remove other entries first.`,
@@ -520,10 +527,11 @@ export class MemoryStore {
520
527
  return this.successResponse(target, "Entry replaced.");
521
528
  }
522
529
 
523
- async remove(target: "memory" | "user" | "failure", oldText: string): Promise<MemoryResult> {
530
+ async remove(target: "memory" | "user" | "failure", oldText: string, signal?: AbortSignal): Promise<MemoryResult> {
524
531
  return this.runTargetMutation(
525
532
  target,
526
533
  (markMutation) => this.removeUnlocked(target, oldText, markMutation),
534
+ signal,
527
535
  );
528
536
  }
529
537
 
@@ -839,10 +847,14 @@ export class MemoryStore {
839
847
  private async runTargetMutation(
840
848
  target: "memory" | "user" | "failure",
841
849
  mutation: (markMutation: () => void) => Promise<MemoryResult>,
850
+ signal?: AbortSignal,
842
851
  ): Promise<MemoryResult> {
843
852
  const storagePath = await this.resolveStoragePath(target);
844
853
  return withMarkdownMutationLock(storagePath, async () => {
845
854
  for (let attempt = 0; ; attempt++) {
855
+ if (signal?.aborted) {
856
+ return { success: false, error: "Memory mutation cancelled." };
857
+ }
846
858
  let mutated = false;
847
859
  try {
848
860
  const result = await mutation(() => {
@@ -914,6 +926,15 @@ export class MemoryStore {
914
926
  }
915
927
  throw error;
916
928
  }
929
+ } else if (await this.shouldReuseRecoverySnapshot(filePath)) {
930
+ try {
931
+ await fs.rename(tmpPath, filePath);
932
+ } catch (error) {
933
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") {
934
+ throw new ExternalMemoryWriteConflict();
935
+ }
936
+ throw error;
937
+ }
917
938
  } else {
918
939
  const recoveryPath = this.recoveryPathFor(filePath);
919
940
  const publishedIdentity = await this.fileIdentity(tmpPath);
@@ -1048,6 +1069,37 @@ export class MemoryStore {
1048
1069
  );
1049
1070
  }
1050
1071
 
1072
+ private async shouldReuseRecoverySnapshot(filePath: string): Promise<boolean> {
1073
+ const directory = path.dirname(filePath);
1074
+ const escapedName = path.basename(filePath).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1075
+ const uuidPattern = "[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
1076
+ const recoveryPattern = new RegExp(`^\\.${escapedName}\\.recovery-\\d+-${uuidPattern}$`, "i");
1077
+ try {
1078
+ const names = await fs.readdir(directory);
1079
+ const mtimes = await Promise.all(
1080
+ names
1081
+ .filter((name) => recoveryPattern.test(name))
1082
+ .map(async (name) => {
1083
+ try {
1084
+ const state = await fs.lstat(path.join(directory, name));
1085
+ return state.isFile() ? state.mtimeMs : null;
1086
+ } catch {
1087
+ return null;
1088
+ }
1089
+ }),
1090
+ );
1091
+ let newestMtimeMs: number | null = null;
1092
+ for (const mtimeMs of mtimes) {
1093
+ if (mtimeMs !== null && (newestMtimeMs === null || mtimeMs > newestMtimeMs)) {
1094
+ newestMtimeMs = mtimeMs;
1095
+ }
1096
+ }
1097
+ return newestMtimeMs !== null && Date.now() - newestMtimeMs < RECOVERY_SNAPSHOT_MIN_INTERVAL_MS;
1098
+ } catch {
1099
+ return false;
1100
+ }
1101
+ }
1102
+
1051
1103
  private retiredRecoveryPathFor(filePath: string): string {
1052
1104
  return path.join(
1053
1105
  path.dirname(filePath),
@@ -25,6 +25,8 @@ export interface BulkIndexResult {
25
25
  messagesIndexed: number;
26
26
  errors: string[];
27
27
  reachedLimit?: boolean;
28
+ /** Session files skipped because their mtime is outside the retention window. */
29
+ expiredSkipped?: number;
28
30
  }
29
31
 
30
32
  interface SessionFileMetadata {
@@ -36,6 +38,25 @@ interface SessionFileMetadata {
36
38
  export interface IncrementalIndexOptions {
37
39
  projectDir?: string;
38
40
  maxFilesToIndex?: number;
41
+ /**
42
+ * Optional retention cutoff (ms epoch). JSONL session files whose mtime is
43
+ * strictly older than this cutoff are considered outside the retained window
44
+ * and are skipped entirely (not queued for indexing or counted as changed).
45
+ * This keeps incremental backfill aligned with session retention pruning:
46
+ * sessions pruned by pruneOldSessions() are never re-indexed on a later
47
+ * startup. Omit/0 to index every file (backwards-compatible default).
48
+ */
49
+ retentionCutoffMs?: number;
50
+ }
51
+
52
+ /**
53
+ * True when a session JSONL file's last-modified time falls within the
54
+ * retention window (mtime >= cutoff). Files older than the cutoff are treated
55
+ * as expired and are ineligible for backfill/indexing so that pruned sessions
56
+ * are not re-surfaced.
57
+ */
58
+ function isWithinRetention(mtimeMs: number, retentionCutoffMs: number | undefined): boolean {
59
+ return !retentionCutoffMs || mtimeMs >= retentionCutoffMs;
39
60
  }
40
61
 
41
62
  export function truncateMessageContent(
@@ -339,21 +360,39 @@ function indexSessionFile(dbManager: DatabaseManager, file: string, result: Bulk
339
360
 
340
361
  /**
341
362
  * Index all sessions from disk.
363
+ * With retentionCutoffMs > 0, files whose mtime falls outside the window are
364
+ * skipped (counted in expiredSkipped) so a manual reindex honors the same
365
+ * retention policy the auto pruning enforces, instead of re-adding expired
366
+ * sessions that pruning just deleted.
342
367
  *
343
368
  * @param dbManager — Database manager instance
344
369
  * @param sessionsDir — Path to ~/.selesai/agent/sessions/
345
370
  * @param projectDir — Optional: specific project directory to index
371
+ * @param retentionCutoffMs — Optional: epoch ms; files modified before it are skipped
346
372
  * @returns Bulk index result
347
373
  */
348
374
  export function indexAllSessions(
349
375
  dbManager: DatabaseManager,
350
376
  sessionsDir: string,
351
- projectDir?: string
377
+ projectDir?: string,
378
+ retentionCutoffMs = 0,
352
379
  ): BulkIndexResult {
353
380
  const files = getSessionFiles(sessionsDir, projectDir);
354
381
  const result = emptyBulkIndexResult();
382
+ let expiredSkipped = 0;
355
383
 
356
384
  for (const file of files) {
385
+ if (retentionCutoffMs > 0) {
386
+ try {
387
+ if (!isWithinRetention(getSessionFileMetadata(file).mtimeMs, retentionCutoffMs)) {
388
+ expiredSkipped++;
389
+ continue;
390
+ }
391
+ } catch {
392
+ // Unreadable metadata: let indexSessionFile report the real error.
393
+ }
394
+ }
395
+
357
396
  try {
358
397
  indexSessionFile(dbManager, file, result);
359
398
  } catch (err) {
@@ -361,6 +400,10 @@ export function indexAllSessions(
361
400
  }
362
401
  }
363
402
 
403
+ if (expiredSkipped > 0) {
404
+ result.expiredSkipped = expiredSkipped;
405
+ }
406
+
364
407
  return result;
365
408
  }
366
409
 
@@ -390,6 +433,12 @@ export function indexChangedSessions(
390
433
  for (const file of files) {
391
434
  try {
392
435
  const metadata = getSessionFileMetadata(file);
436
+ if (!isWithinRetention(metadata.mtimeMs, options.retentionCutoffMs)) {
437
+ // Outside the retained window (e.g. pruned by pruneOldSessions):
438
+ // never re-queue it for indexing, so a pruned session does not come
439
+ // back on the next startup backfill.
440
+ continue;
441
+ }
393
442
  if (storedSessionFileMatches(dbManager, metadata)) {
394
443
  result.sessionsSkipped++;
395
444
  continue;
@@ -443,25 +492,58 @@ function isRecentBackfillTimestamp(value: string | null, nowMs: number): boolean
443
492
  * The check stays cheap: it compares file counts and stored file size/mtime
444
493
  * metadata. Full JSONL parsing is left to the scheduled incremental backfill.
445
494
  */
446
- export function needsBackfill(dbManager: DatabaseManager, sessionsDir: string, now = new Date()): boolean {
495
+ export function needsBackfill(
496
+ dbManager: DatabaseManager,
497
+ sessionsDir: string,
498
+ now = new Date(),
499
+ retentionCutoffMs = 0,
500
+ ): boolean {
447
501
  const db = dbManager.getDb();
448
502
  const files = getSessionFiles(sessionsDir);
449
503
  const indexed = db.prepare('SELECT COUNT(*) as count FROM sessions').get() as { count: number };
450
504
 
451
- if (files.length > indexed.count) {
452
- return true;
505
+ if (retentionCutoffMs <= 0) {
506
+ // Retention disabled: keep the historical cheap path — a plain file-count
507
+ // vs row-count comparison decides before any per-file stat work, so
508
+ // large session directories stay fast on startup.
509
+ if (files.length > indexed.count) {
510
+ return true;
511
+ }
512
+
513
+ for (const file of files) {
514
+ try {
515
+ const metadata = getSessionFileMetadata(file);
516
+ if (storedSessionFileMatches(dbManager, metadata)) continue;
517
+ return true;
518
+ } catch {
519
+ // An unreadable or malformed session file still needs indexing.
520
+ return true;
521
+ }
522
+ }
523
+
524
+ return !isRecentBackfillTimestamp(getLastBackfillTimestamp(dbManager), now.getTime());
453
525
  }
454
526
 
527
+ // Retention enabled: one metadata pass decides everything — a retained file
528
+ // with stale stored metadata demands a backfill, and when no file is inside
529
+ // the window there is no work at all. Returning here (instead of falling
530
+ // through to the periodic timestamp check) keeps an all-expired store from
531
+ // scheduling an empty backfill on every startup before a timestamp is ever
532
+ // written.
533
+ let hasRetainedFile = false;
455
534
  for (const file of files) {
456
535
  try {
457
536
  const metadata = getSessionFileMetadata(file);
458
- if (storedSessionFileMatches(dbManager, metadata)) continue;
459
- return true;
537
+ if (!isWithinRetention(metadata.mtimeMs, retentionCutoffMs)) continue;
538
+ hasRetainedFile = true;
539
+ if (!storedSessionFileMatches(dbManager, metadata)) return true;
460
540
  } catch {
461
541
  return true;
462
542
  }
463
543
  }
464
-
544
+ if (!hasRetainedFile) {
545
+ return false;
546
+ }
465
547
  return !isRecentBackfillTimestamp(getLastBackfillTimestamp(dbManager), now.getTime());
466
548
  }
467
549
 
@@ -509,3 +591,83 @@ export function getSessionStats(dbManager: DatabaseManager): {
509
591
  projects,
510
592
  };
511
593
  }
594
+
595
+ /**
596
+ * Compute the retention cutoff (ms epoch) from a retention window in days.
597
+ * Returns 0 (no cutoff) when retention is undefined/zero/disabled.
598
+ */
599
+ export function retentionCutoffMs(retentionDays: number | undefined): number {
600
+ if (!retentionDays || retentionDays <= 0) return 0;
601
+ return Date.now() - retentionDays * 24 * 60 * 60 * 1000;
602
+ }
603
+
604
+ /**
605
+ * Delete sessions outside the retention window, along with their messages,
606
+ * to bound the growth of the session index database (see #183).
607
+ *
608
+ * A session is eligible for pruning when its session file's last-mod time is
609
+ * older than the window (falling back to started_at when no file metadata
610
+ * exists). This deliberately mirrors the backfill eligibility check in
611
+ * `needsBackfill`/`indexChangedSessions` (also keyed to file mtime), so a
612
+ * pruned session's on-disk JSONL file is never re-indexed by a later startup
613
+ * and never re-triggers a backfill. Retention and backfill therefore agree on
614
+ * the same eligible file set.
615
+ *
616
+ * `messages` and `session_files` reference `sessions`, but only `session_files`
617
+ * is declared `ON DELETE CASCADE`. Orphaned `messages` rows are deleted
618
+ * explicitly first so a `PRAGMA foreign_keys`-enabled delete never trips a
619
+ * FK constraint, and so an accurate `messagesRemoved` count is reported.
620
+ *
621
+ * Returns the number of sessions and messages removed.
622
+ */
623
+ export function pruneOldSessions(
624
+ dbManager: DatabaseManager,
625
+ retentionDays: number,
626
+ ): { sessionsRemoved: number; messagesRemoved: number } {
627
+ const db = dbManager.getDb();
628
+ const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
629
+ const cutoffIso = new Date(cutoffMs).toISOString();
630
+
631
+ // A session is pruned when it falls outside the retention window. We use the
632
+ // same signal the backfill eligibility uses -- the session file's last-mod
633
+ // time -- so that a pruned session's on-disk JSONL file is not re-indexed on
634
+ // a later startup. Sessions without file metadata (e.g. indexed before files
635
+ // were tracked) fall back to started_at. Both must be strictly older than
636
+ // the cutoff.
637
+ const eligibleSessionIds = db.prepare(`
638
+ SELECT s.id
639
+ FROM sessions s
640
+ LEFT JOIN session_files sf ON sf.session_id = s.id
641
+ WHERE
642
+ (sf.path IS NOT NULL AND sf.mtime_ms < ?)
643
+ OR (sf.path IS NULL AND s.started_at < ?)
644
+ `).all(cutoffMs, cutoffIso) as Array<{ id: string }>;
645
+
646
+ if (eligibleSessionIds.length === 0) {
647
+ return { sessionsRemoved: 0, messagesRemoved: 0 };
648
+ }
649
+
650
+ // Count messages upfront because SQLite does not surface cascade/orphan
651
+ // counts from DELETE ... RETURNING across tables.
652
+ const messageCount = db.prepare(
653
+ `SELECT COUNT(*) as cnt FROM messages WHERE session_id IN (${eligibleSessionIds.map(() => '?').join(',')})`,
654
+ ).get(...eligibleSessionIds.map((r) => r.id)) as { cnt: number };
655
+
656
+ const prune = () => {
657
+ const delMessages = db.prepare(
658
+ `DELETE FROM messages WHERE session_id IN (${eligibleSessionIds.map(() => '?').join(',')})`,
659
+ ).run(...eligibleSessionIds.map((r) => r.id));
660
+ const delSessions = db.prepare(
661
+ `DELETE FROM sessions WHERE id IN (${eligibleSessionIds.map(() => '?').join(',')})`,
662
+ ).run(...eligibleSessionIds.map((r) => r.id));
663
+ return { messagesRemoved: delMessages.changes, sessionsRemoved: delSessions.changes };
664
+ };
665
+
666
+ const result = db.transaction ? db.transaction(prune)() : prune();
667
+ // Fall back to the pre-counted values if the transaction changed nothing
668
+ // unexpectedly (defensive; DELETE should always affect the counted rows).
669
+ return {
670
+ sessionsRemoved: result.sessionsRemoved || eligibleSessionIds.length,
671
+ messagesRemoved: result.messagesRemoved || messageCount.cnt,
672
+ };
673
+ }
@@ -2,6 +2,7 @@ import { DatabaseManager } from './db.js';
2
2
  import {
3
3
  buildFallbackFts5Query,
4
4
  buildNaturalLanguageFallbackQuery,
5
+ collectLikeTerms,
5
6
  hasExplicitFts5Operator,
6
7
  isFts5QueryError,
7
8
  normalizeFts5Query,
@@ -38,30 +39,10 @@ type SearchMatch =
38
39
  | { type: 'fts'; query: string }
39
40
  | { type: 'like'; terms: string[] };
40
41
 
41
- const QUERY_TOKEN_PATTERN = /"([^"]*)"|(\S+)/g;
42
- const NATURAL_LANGUAGE_CONNECTORS = new Set(['and', 'or', 'not', 'near']);
43
-
44
42
  function escapeLikePattern(text: string): string {
45
43
  return text.replace(/[\\%_]/g, '\\$&');
46
44
  }
47
45
 
48
- function collectLikeTerms(query: string): string[] {
49
- const terms: string[] = [];
50
-
51
- for (const match of query.matchAll(QUERY_TOKEN_PATTERN)) {
52
- const phrase = match[1];
53
- const term = match[2];
54
- if (phrase === undefined && term && NATURAL_LANGUAGE_CONNECTORS.has(term.toLowerCase())) {
55
- continue;
56
- }
57
-
58
- const rawValue = phrase ?? term ?? '';
59
- if (rawValue.length > 0) terms.push(rawValue);
60
- }
61
-
62
- return terms;
63
- }
64
-
65
46
  function mapRows(rows: Array<{
66
47
  session_id: string;
67
48
  project: string;
@@ -178,7 +159,12 @@ export function searchSessions(
178
159
 
179
160
  const normalizedQuery = normalizeFts5Query(query);
180
161
  if (normalizedQuery.length === 0) {
181
- return [];
162
+ // Every term was a stop word or connector: nothing is left for FTS5 to
163
+ // match. Fall through to the scoped LIKE fallback instead of a hard [] so
164
+ // the query degrades to a literal substring search (the same fallback a
165
+ // normal query that matches nothing ends with).
166
+ const likeTerms = collectLikeTerms(query);
167
+ return executeSearch({ type: 'like', terms: likeTerms });
182
168
  }
183
169
 
184
170
  const exactResults = executeSearch({ type: 'fts', query: normalizedQuery });