@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
@@ -0,0 +1,116 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ normalizeFts5Query,
5
+ buildFallbackFts5Query,
6
+ hasExplicitFts5Operator,
7
+ normalizeNaturalLanguageFts5Query,
8
+ buildNaturalLanguageFallbackQuery,
9
+ isFts5QueryError,
10
+ } from '../../src/store/fts-query.js';
11
+
12
+ describe('fts-query', () => {
13
+ describe('stop word filtering', () => {
14
+ it('filters filler words but keeps meaningful terms', () => {
15
+ const q = normalizeFts5Query('how to fix the authentication bug');
16
+ assert.ok(q.includes('"authentication"'), 'keeps meaningful term');
17
+ assert.ok(q.includes('"bug"'), 'keeps meaningful term');
18
+ assert.ok(!q.includes('"how"'), 'filters "how"');
19
+ assert.ok(!q.includes('"to"'), 'filters "to"');
20
+ assert.ok(!q.includes('"the"'), 'filters "the"');
21
+ });
22
+
23
+ it('returns empty string when every term is a stop word', () => {
24
+ assert.strictEqual(normalizeFts5Query('the a is was were'), '');
25
+ assert.strictEqual(normalizeFts5Query('for in on'), '');
26
+ });
27
+
28
+ it('keeps quoted phrases intact even when they contain stop words', () => {
29
+ const q = normalizeNaturalLanguageFts5Query('"the quick brown fox" jumps');
30
+ assert.ok(q.includes('"the quick brown fox"'));
31
+ assert.ok(q.includes('"jumps"'));
32
+ });
33
+
34
+ it('normalizes case-insensitively against the stop word list', () => {
35
+ const q = normalizeFts5Query('The QUICK brown Fox');
36
+ assert.ok(q.includes('"QUICK"'));
37
+ assert.ok(q.includes('"Fox"'));
38
+ assert.ok(!q.includes('"The"'));
39
+ });
40
+ });
41
+
42
+ describe('fallback queries', () => {
43
+ it('builds an OR fallback for multi-term natural language queries', () => {
44
+ const fb = buildFallbackFts5Query('authentication bug fix');
45
+ assert.ok(fb);
46
+ assert.ok(fb.includes('OR'));
47
+ assert.ok(fb.includes('"authentication"'));
48
+ assert.ok(fb.includes('"bug"'));
49
+ });
50
+
51
+ it('returns null when there are not enough meaningful terms', () => {
52
+ assert.strictEqual(buildFallbackFts5Query('auth'), null);
53
+ assert.strictEqual(buildFallbackFts5Query('the a'), null);
54
+ });
55
+
56
+ it('returns null for explicit operator queries', () => {
57
+ assert.strictEqual(buildFallbackFts5Query('auth OR database'), null);
58
+ });
59
+ });
60
+
61
+ describe('operator handling', () => {
62
+ it('detects explicit FTS5 operators', () => {
63
+ assert.ok(hasExplicitFts5Operator('auth OR database'));
64
+ assert.ok(hasExplicitFts5Operator('"auth bug" AND database'));
65
+ assert.ok(!hasExplicitFts5Operator('auth database'));
66
+ });
67
+
68
+ it('preserves explicit operator queries verbatim in normalizeFts5Query', () => {
69
+ assert.strictEqual(normalizeFts5Query('auth OR database'), 'auth OR database');
70
+ });
71
+ });
72
+
73
+ describe('natural language recovery', () => {
74
+ it('normalizes uppercase operator words as natural language', () => {
75
+ // 'DO' is a stop word and 'NOT' a natural-language connector, so only
76
+ // the two meaningful terms survive, quoted for implicit AND matching.
77
+ assert.strictEqual(normalizeNaturalLanguageFts5Query('DO NOT USE FIND'), '"USE" "FIND"');
78
+ });
79
+
80
+ it('keeps intent-bearing words out of the stop list (can/need/off/like/get)', () => {
81
+ // These words frequently carry the actual search intent ("can the app
82
+ // start", "need to restart", "like this", "get the build passing"), so
83
+ // they must survive normalization rather than be silently dropped.
84
+ assert.strictEqual(
85
+ normalizeFts5Query('can need off like get'),
86
+ '"can" "need" "off" "like" "get"',
87
+ );
88
+ });
89
+
90
+ it('builds a natural language OR fallback ignoring operator detection', () => {
91
+ const fb = buildNaturalLanguageFallbackQuery('DO NOT USE FIND');
92
+ assert.ok(fb && fb.includes('OR'));
93
+ });
94
+ });
95
+
96
+ describe('isFts5QueryError', () => {
97
+ it('detects genuine FTS5 query/index failure messages', () => {
98
+ assert.ok(isFts5QueryError(new Error('fts5: syntax error near "AND"')), 'fts5');
99
+ assert.ok(isFts5QueryError(new Error('fts5: unterminated string')), 'unterminated string');
100
+ assert.ok(isFts5QueryError(new Error('unterminated string constant in FTS5 query')), 'unterminated string constant');
101
+ });
102
+
103
+ it('rejects corruption signals so DatabaseManager recovery still runs (#186)', () => {
104
+ // These are corruption/recovery signals, NOT recoverable FTS query errors.
105
+ // Swallowing them would suppress quarantine/rebuild of a corrupt database.
106
+ assert.ok(!isFts5QueryError(new Error('database disk image is malformed')), 'malformed is corruption, not FTS');
107
+ assert.ok(!isFts5QueryError(new Error('SQL logic error')), 'sql logic error is too broad to swallow');
108
+ assert.ok(!isFts5QueryError(new Error('file is not a database')), 'notadb is corruption, not FTS');
109
+ });
110
+
111
+ it('rejects unrelated errors', () => {
112
+ assert.ok(!isFts5QueryError(new Error('disk full')));
113
+ assert.ok(!isFts5QueryError('string, not an Error'));
114
+ });
115
+ });
116
+ });
@@ -117,6 +117,9 @@ describe("MemoryStore", { concurrency: 1 }, () => {
117
117
  await removeFile(memoryPath);
118
118
  await removeFile(userPath);
119
119
  await removeFile(failurePath);
120
+ const artifactNames = (await fs.readdir(MEMORY_DIR))
121
+ .filter((name) => name.includes(".recovery-") || name.includes(".retired-") || name.includes(".conflict-"));
122
+ await Promise.all(artifactNames.map((name) => removeFile(path.join(MEMORY_DIR, name))));
120
123
  await new Promise((r) => setTimeout(r, 50));
121
124
  }
122
125
 
@@ -184,6 +187,48 @@ describe("MemoryStore", { concurrency: 1 }, () => {
184
187
  assert.ok(result.error!.includes("chars"));
185
188
  });
186
189
 
190
+ it("bypasses the Markdown cap for memory, user, failure, and project stores in policy-only mode", async () => {
191
+ const config = makeConfig({
192
+ memoryMode: "policy-only",
193
+ memoryCharLimit: 1,
194
+ userCharLimit: 1,
195
+ projectCharLimit: 1,
196
+ memoryOverflowStrategy: "auto-consolidate",
197
+ autoConsolidate: true,
198
+ });
199
+ const store = new MemoryStore(config);
200
+ const projectStore = new MemoryStore({
201
+ ...config,
202
+ memoryDir: path.join(MEMORY_DIR, "project"),
203
+ });
204
+ let consolidatorCalls = 0;
205
+ const consolidator = async () => {
206
+ consolidatorCalls++;
207
+ return { consolidated: true };
208
+ };
209
+ store.setConsolidator(consolidator);
210
+ projectStore.setConsolidator(consolidator);
211
+ await store.loadFromDisk();
212
+ await projectStore.loadFromDisk();
213
+
214
+ const results = await Promise.all([
215
+ store.add("memory", `${TEST_MARKER} policy-only memory ${"x".repeat(100)}`),
216
+ store.add("user", `${TEST_MARKER} policy-only user ${"x".repeat(100)}`),
217
+ store.addFailure(`${TEST_MARKER} policy-only failure ${"x".repeat(100)}`, { category: "failure" }),
218
+ projectStore.add("memory", `${TEST_MARKER} policy-only project ${"x".repeat(100)}`),
219
+ ]);
220
+
221
+ for (const result of results) {
222
+ assert.equal(result.success, true, result.error);
223
+ }
224
+ assert.equal(consolidatorCalls, 0);
225
+ assert.equal(store.getRawEntriesForSync("memory").length, 1);
226
+ assert.equal(store.getRawEntriesForSync("user").length, 1);
227
+ assert.equal(store.getRawEntriesForSync("failure").length, 1);
228
+ assert.equal(projectStore.getRawEntriesForSync("memory").length, 1);
229
+ });
230
+
231
+
187
232
  it("rejects without consolidation when memoryOverflowStrategy is reject", async () => {
188
233
  let consolidatorCalled = false;
189
234
  const store = new MemoryStore(makeConfig({
@@ -603,6 +648,45 @@ describe("MemoryStore", { concurrency: 1 }, () => {
603
648
  // ─── replace() tests ───
604
649
 
605
650
  describe("replace()", () => {
651
+ it("bypasses the Markdown cap for replacements in policy-only mode", async () => {
652
+ const config = makeConfig({
653
+ memoryMode: "policy-only",
654
+ memoryCharLimit: 1,
655
+ userCharLimit: 1,
656
+ projectCharLimit: 1,
657
+ });
658
+ const store = new MemoryStore(config);
659
+ const projectStore = new MemoryStore({
660
+ ...config,
661
+ memoryDir: path.join(MEMORY_DIR, "project-replace"),
662
+ });
663
+ let consolidatorCalls = 0;
664
+ const consolidator = async () => {
665
+ consolidatorCalls++;
666
+ return { consolidated: true };
667
+ };
668
+ store.setConsolidator(consolidator);
669
+ projectStore.setConsolidator(consolidator);
670
+ await store.loadFromDisk();
671
+ await projectStore.loadFromDisk();
672
+ await store.add("memory", `${TEST_MARKER} replace memory seed`);
673
+ await store.add("user", `${TEST_MARKER} replace user seed`);
674
+ await store.addFailure(`${TEST_MARKER} replace failure seed`, { category: "failure" });
675
+ await projectStore.add("memory", `${TEST_MARKER} replace project seed`);
676
+
677
+ const replacement = `${TEST_MARKER} ${"x".repeat(100)}`;
678
+ const results = await Promise.all([
679
+ store.replace("memory", "replace memory seed", replacement),
680
+ store.replace("user", "replace user seed", replacement),
681
+ store.replace("failure", "replace failure seed", replacement),
682
+ projectStore.replace("memory", "replace project seed", replacement),
683
+ ]);
684
+
685
+ for (const result of results) {
686
+ assert.equal(result.success, true, result.error);
687
+ }
688
+ assert.equal(consolidatorCalls, 0);
689
+ });
606
690
  it("updates entry in file", async () => {
607
691
  const store = new MemoryStore(makeConfig());
608
692
  await store.loadFromDisk();
@@ -1318,6 +1402,52 @@ describe("MemoryStore", { concurrency: 1 }, () => {
1318
1402
  assert.equal(await readRaw(memoryPath), beforeDisk);
1319
1403
  }
1320
1404
  });
1405
+ it("bypasses the Markdown cap for batch mutations in policy-only mode", async () => {
1406
+ const config = makeConfig({
1407
+ memoryMode: "policy-only",
1408
+ memoryCharLimit: 1,
1409
+ userCharLimit: 1,
1410
+ projectCharLimit: 1,
1411
+ });
1412
+ const store = new MemoryStore(config);
1413
+ const projectStore = new MemoryStore({
1414
+ ...config,
1415
+ memoryDir: path.join(MEMORY_DIR, "project-batch"),
1416
+ });
1417
+ let consolidatorCalls = 0;
1418
+ const consolidator = async () => {
1419
+ consolidatorCalls++;
1420
+ return { consolidated: true };
1421
+ };
1422
+ store.setConsolidator(consolidator);
1423
+ projectStore.setConsolidator(consolidator);
1424
+ await store.loadFromDisk();
1425
+ await projectStore.loadFromDisk();
1426
+
1427
+ const results = await Promise.all([
1428
+ store.applyMutationPlan("memory", [
1429
+ { action: "add", content: `${TEST_MARKER} batch memory ${"x".repeat(100)}` },
1430
+ { action: "add", content: `${TEST_MARKER} batch memory second ${"x".repeat(100)}` },
1431
+ ]),
1432
+ store.applyMutationPlan("user", [
1433
+ { action: "add", content: `${TEST_MARKER} batch user ${"x".repeat(100)}` },
1434
+ { action: "add", content: `${TEST_MARKER} batch user second ${"x".repeat(100)}` },
1435
+ ]),
1436
+ store.applyMutationPlan("failure", [
1437
+ { action: "add", content: `${TEST_MARKER} batch failure ${"x".repeat(100)}`, category: "failure" },
1438
+ { action: "add", content: `${TEST_MARKER} batch failure second ${"x".repeat(100)}`, category: "failure" },
1439
+ ]),
1440
+ projectStore.applyMutationPlan("memory", [
1441
+ { action: "add", content: `${TEST_MARKER} batch project ${"x".repeat(100)}` },
1442
+ { action: "add", content: `${TEST_MARKER} batch project second ${"x".repeat(100)}` },
1443
+ ]),
1444
+ ]);
1445
+
1446
+ for (const result of results) {
1447
+ assert.equal(result.success, true, result.error);
1448
+ }
1449
+ assert.equal(consolidatorCalls, 0);
1450
+ });
1321
1451
  });
1322
1452
 
1323
1453
  describe("external file changes", () => {
@@ -1710,6 +1840,39 @@ describe("MemoryStore", { concurrency: 1 }, () => {
1710
1840
  assert.match(raw, /later add/);
1711
1841
  });
1712
1842
 
1843
+ it("reuses one recent recovery snapshot during rapid successive writes", async () => {
1844
+ const store = new MemoryStore(makeConfig());
1845
+ await store.loadFromDisk();
1846
+ await store.add("memory", `${TEST_MARKER} recovery seed`);
1847
+
1848
+ for (let index = 0; index < 10; index++) {
1849
+ await store.add("memory", `${TEST_MARKER} rapid overwrite ${index}`);
1850
+ }
1851
+
1852
+ const recoveryFiles = (await fs.readdir(MEMORY_DIR))
1853
+ .filter((name) => name.startsWith(`.${MEMORY_FILE}.recovery-`));
1854
+ assert.ok(recoveryFiles.length <= 2);
1855
+ });
1856
+
1857
+ it("takes a new recovery snapshot after the reuse interval", async () => {
1858
+ const store = new MemoryStore(makeConfig());
1859
+ await store.loadFromDisk();
1860
+ await store.add("memory", `${TEST_MARKER} recovery seed`);
1861
+ await store.add("memory", `${TEST_MARKER} first overwrite`);
1862
+
1863
+ const initialRecoveryFiles = (await fs.readdir(MEMORY_DIR))
1864
+ .filter((name) => name.startsWith(`.${MEMORY_FILE}.recovery-`));
1865
+ assert.equal(initialRecoveryFiles.length, 1);
1866
+ const old = new Date(Date.now() - 60 * 60 * 1000 - 1_000);
1867
+ await fs.utimes(path.join(MEMORY_DIR, initialRecoveryFiles[0]), old, old);
1868
+
1869
+ await store.add("memory", `${TEST_MARKER} overwrite after interval`);
1870
+
1871
+ const recoveryFiles = (await fs.readdir(MEMORY_DIR))
1872
+ .filter((name) => name.startsWith(`.${MEMORY_FILE}.recovery-`));
1873
+ assert.equal(recoveryFiles.length, 2);
1874
+ });
1875
+
1713
1876
  it("prunes expired recovery files but retains recently active ones", async () => {
1714
1877
  const pathStore = new MemoryStore(makeConfig());
1715
1878
  const expiredPath = (pathStore as any).recoveryPathFor(memoryPath) as string;
@@ -19,6 +19,7 @@ import {
19
19
  truncateMessageContent,
20
20
  upsertSessionFileMetadata,
21
21
  pruneEphemeralReviewSessions,
22
+ pruneOldSessions,
22
23
  } from '../../src/store/session-indexer.js';
23
24
  import { DEFAULT_MAX_MESSAGE_CONTENT_LENGTH } from '../../src/constants.js';
24
25
  import { parseSessionFile, type ParsedSession } from '../../src/store/session-parser.js';
@@ -221,6 +222,44 @@ describe('session-indexer', () => {
221
222
  const result = indexAllSessions(dbManager, '/nonexistent/path');
222
223
  assert.strictEqual(result.sessionsProcessed, 0);
223
224
  });
225
+
226
+ it('skips files outside the retention window and reports expiredSkipped', () => {
227
+ const sessionsDir = path.join(tmpDir, 'sessions');
228
+ const projDir = path.join(sessionsDir, 'test-project');
229
+ fs.mkdirSync(projDir, { recursive: true });
230
+
231
+ const write = (fileName: string, sessionId: string) => {
232
+ const lines = [
233
+ JSON.stringify({ type: 'session', id: sessionId, timestamp: '2026-05-03T00:00:00Z', cwd: '/test' }),
234
+ JSON.stringify({
235
+ type: 'message',
236
+ id: `${sessionId}-m1`,
237
+ parentId: null,
238
+ timestamp: '2026-05-03T00:01:00Z',
239
+ message: { role: 'user', content: [{ type: 'text', text: 'Hello' }], timestamp: Date.now() },
240
+ }),
241
+ ];
242
+ fs.writeFileSync(path.join(projDir, fileName), lines.join('\n'));
243
+ };
244
+ write('recent.jsonl', 'recent');
245
+ write('expired.jsonl', 'expired');
246
+
247
+ // Age the second file beyond the retention window.
248
+ const old = new Date('2026-01-01T00:00:00Z');
249
+ fs.utimesSync(path.join(projDir, 'expired.jsonl'), old, old);
250
+
251
+ const cutoff = Date.parse('2026-03-01T00:00:00Z');
252
+ const result = indexAllSessions(dbManager, sessionsDir, undefined, cutoff);
253
+ assert.strictEqual(result.sessionsIndexed, 1);
254
+ assert.strictEqual(result.expiredSkipped, 1);
255
+ assert.strictEqual(dbManager.getStats().sessions, 1);
256
+
257
+ // Without a cutoff the same directory indexes both files.
258
+ const result2 = indexAllSessions(dbManager, sessionsDir);
259
+ assert.strictEqual(result2.sessionsIndexed, 1);
260
+ assert.strictEqual(result2.expiredSkipped, undefined);
261
+ assert.strictEqual(dbManager.getStats().sessions, 2);
262
+ });
224
263
  });
225
264
 
226
265
  describe('indexChangedSessions', () => {
@@ -565,6 +604,75 @@ describe('session-indexer', () => {
565
604
  assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date('2026-05-03T01:00:00Z')), true);
566
605
  });
567
606
 
607
+ it('needsBackfill returns no work when retention is enabled and every file is expired, even without a backfill timestamp', () => {
608
+ const sessionsDir = path.join(tmpDir, 'sessions');
609
+ writeJsonlSession(sessionsDir, 'project-a', 's1');
610
+ const filePath = path.join(sessionsDir, 'project-a', 's1.jsonl');
611
+
612
+ // Age the file beyond the retention window.
613
+ const old = new Date('2026-01-01T00:00:00Z');
614
+ fs.utimesSync(filePath, old, old);
615
+
616
+ const now = new Date('2026-05-03T01:00:00Z');
617
+ const cutoff = Date.parse('2026-03-01T00:00:00Z');
618
+ assert.ok(old.getTime() < cutoff, 'sanity: the file must be outside the window');
619
+
620
+ // Fresh DB: no indexed rows and no backfill timestamp, so the periodic
621
+ // timestamp check alone would demand a backfill. With retention enabled
622
+ // and no retained files, there is no work to do — this must return
623
+ // false instead of scheduling an empty backfill on every startup.
624
+ assert.strictEqual(needsBackfill(dbManager, sessionsDir, now, cutoff), false);
625
+ });
626
+
627
+ it('needsBackfill ignores expired files instead of using a blind file-count shortcut', () => {
628
+ const sessionsDir = path.join(tmpDir, 'sessions');
629
+ // Two files on disk, zero indexed rows: a file-count-based shortcut
630
+ // would return true here. With retention enabled and every file
631
+ // expired, there is nothing to backfill.
632
+ writeJsonlSession(sessionsDir, 'project-a', 's1');
633
+ writeJsonlSession(sessionsDir, 'project-b', 's2');
634
+ const old = new Date('2026-01-01T00:00:00Z');
635
+ fs.utimesSync(path.join(sessionsDir, 'project-a', 's1.jsonl'), old, old);
636
+ fs.utimesSync(path.join(sessionsDir, 'project-b', 's2.jsonl'), old, old);
637
+
638
+ const cutoff = Date.parse('2026-03-01T00:00:00Z');
639
+ assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date('2026-05-03T01:00:00Z'), cutoff), false);
640
+
641
+ // A retained file with no stored metadata still demands a backfill,
642
+ // even though the file count alone (2 files, 0 rows) is unchanged.
643
+ const recent = new Date('2026-04-15T00:00:00Z');
644
+ fs.utimesSync(path.join(sessionsDir, 'project-b', 's2.jsonl'), recent, recent);
645
+ assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date('2026-05-03T01:00:00Z'), cutoff), true);
646
+ });
647
+
648
+ it('needsBackfill still schedules when a retained file exists without a backfill timestamp', () => {
649
+ const sessionsDir = path.join(tmpDir, 'sessions');
650
+ writeJsonlSession(sessionsDir, 'project-a', 's1');
651
+ const filePath = path.join(sessionsDir, 'project-a', 's1.jsonl');
652
+
653
+ // File stays inside the retention window.
654
+ const recent = new Date('2026-04-15T00:00:00Z');
655
+ fs.utimesSync(filePath, recent, recent);
656
+
657
+ const now = new Date('2026-05-03T01:00:00Z');
658
+ const cutoff = Date.parse('2026-03-01T00:00:00Z');
659
+ assert.ok(recent.getTime() >= cutoff, 'sanity: the file must be inside the window');
660
+
661
+ assert.strictEqual(needsBackfill(dbManager, sessionsDir, now, cutoff), true);
662
+ });
663
+
664
+ it('needsBackfill keeps the periodic check active when retention is disabled', () => {
665
+ const sessionsDir = path.join(tmpDir, 'sessions');
666
+ writeJsonlSession(sessionsDir, 'project-a', 's1');
667
+ const filePath = path.join(sessionsDir, 'project-a', 's1.jsonl');
668
+
669
+ // An old file is still eligible when retention is disabled (cutoff 0).
670
+ const old = new Date('2026-01-01T00:00:00Z');
671
+ fs.utimesSync(filePath, old, old);
672
+
673
+ assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date('2026-05-03T01:00:00Z')), true);
674
+ });
675
+
568
676
  it('touchBackfillTimestamp upserts the metadata row', () => {
569
677
  touchBackfillTimestamp(dbManager, new Date('2026-05-03T00:00:00Z'));
570
678
  touchBackfillTimestamp(dbManager, new Date('2026-05-03T01:00:00Z'));
@@ -602,6 +710,131 @@ describe('session-indexer', () => {
602
710
  });
603
711
  });
604
712
 
713
+ describe('retention and backfill alignment', () => {
714
+ /** Write a session JSONL file and force its mtime to `ms` (ms epoch). */
715
+ function writeJsonlSessionAt(sessionsDir: string, sessionId: string, ms: number): string {
716
+ const filePath = path.join(sessionsDir, 'project-a', `${sessionId}.jsonl`);
717
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
718
+ const lines = [
719
+ JSON.stringify({ type: 'session', id: sessionId, timestamp: new Date(ms).toISOString(), cwd: `/test/${sessionId}` }),
720
+ JSON.stringify({ type: 'message', id: `${sessionId}-m1`, parentId: null, timestamp: new Date(ms + 1000).toISOString(), message: { role: 'user', content: [{ type: 'text', text: `Hello ${sessionId}` }], timestamp: Date.now() } }),
721
+ ];
722
+ fs.writeFileSync(filePath, lines.join('\n'));
723
+ fs.utimesSync(filePath, new Date(ms), new Date(ms));
724
+ return filePath;
725
+ }
726
+
727
+ const DAY = 24 * 60 * 60 * 1000;
728
+ const now = () => Date.now();
729
+ const cutoffFor = (days: number) => now() - days * DAY;
730
+
731
+ it('does not re-index a session file outside the retention cutoff after pruning', () => {
732
+ const sessionsDir = path.join(tmpDir, 'sessions');
733
+ // A file last modified 60 days ago; retention window of 30 days => expired.
734
+ writeJsonlSessionAt(sessionsDir, 's1', cutoffFor(60));
735
+ const cutoff = cutoffFor(30);
736
+
737
+ // Fresh index (no cutoff) mirrors the pre-retention state, then retention
738
+ // pruning removes the session row; the on-disk file stays.
739
+ indexAllSessions(dbManager, sessionsDir);
740
+ const pruned = pruneOldSessions(dbManager, 30);
741
+ assert.strictEqual(pruned.sessionsRemoved, 1);
742
+ assert.strictEqual(dbManager.getStats().sessions, 0);
743
+
744
+ // A later startup backfill, given the same retention cutoff, must NOT
745
+ // re-queue the expired file -- otherwise the pruned session returns.
746
+ const result = indexChangedSessions(dbManager, sessionsDir, { retentionCutoffMs: cutoff });
747
+ assert.strictEqual(result.sessionsProcessed, 0);
748
+ assert.strictEqual(result.sessionsIndexed, 0);
749
+ assert.strictEqual(result.sessionsSkipped, 0);
750
+ assert.strictEqual(dbManager.getStats().sessions, 0);
751
+ });
752
+
753
+ it('does not schedule a startup backfill when only expired files remain', () => {
754
+ const sessionsDir = path.join(tmpDir, 'sessions');
755
+ writeJsonlSessionAt(sessionsDir, 's1', cutoffFor(60));
756
+ const cutoff = cutoffFor(30);
757
+ indexAllSessions(dbManager, sessionsDir);
758
+ pruneOldSessions(dbManager, 30);
759
+ touchBackfillTimestamp(dbManager);
760
+
761
+ // With every file outside the window, needsBackfill must be false so no
762
+ // backfill is scheduled on the next startup (no repeated re-indexing).
763
+ assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date(), cutoff), false);
764
+ });
765
+
766
+ it('still indexes files within the retention cutoff even when others are expired', () => {
767
+ const sessionsDir = path.join(tmpDir, 'sessions');
768
+ writeJsonlSessionAt(sessionsDir, 'old', cutoffFor(60));
769
+ writeJsonlSessionAt(sessionsDir, 'fresh', now());
770
+ const cutoff = cutoffFor(30);
771
+
772
+ const result = indexChangedSessions(dbManager, sessionsDir, { retentionCutoffMs: cutoff });
773
+ // Only the fresh file is eligible; the expired one is skipped.
774
+ assert.strictEqual(result.sessionsProcessed, 1);
775
+ assert.strictEqual(result.sessionsIndexed, 1);
776
+ const stats = getSessionStats(dbManager);
777
+ assert.deepStrictEqual(stats.projects[0].sessions, 1); // only the retained session
778
+ assert.strictEqual(needsBackfill(dbManager, sessionsDir, new Date(), cutoff), true);
779
+ });
780
+
781
+ it('prunes only sessions whose file mtime is outside the retention window', () => {
782
+ const sessionsDir = path.join(tmpDir, 'sessions');
783
+ writeJsonlSessionAt(sessionsDir, 'old', cutoffFor(60));
784
+ writeJsonlSessionAt(sessionsDir, 'recent', now());
785
+ indexAllSessions(dbManager, sessionsDir);
786
+
787
+ const pruned = pruneOldSessions(dbManager, 30);
788
+ // 'old' has an expired file mtime and is pruned; 'recent' has a fresh
789
+ // file mtime and is kept, even though both were indexed.
790
+ assert.strictEqual(pruned.sessionsRemoved, 1);
791
+ const stats = getSessionStats(dbManager);
792
+ assert.strictEqual(stats.totalSessions, 1);
793
+ const remaining = dbManager.getDb().prepare('SELECT id FROM sessions').all() as { id: string }[];
794
+ assert.deepStrictEqual(remaining.map((r) => r.id), ['recent']);
795
+ });
796
+ });
797
+
798
+ describe('pruneOldSessions', () => {
799
+ it('removes nothing when the database is empty', () => {
800
+ const result = pruneOldSessions(dbManager, 30);
801
+ assert.deepStrictEqual(result, { sessionsRemoved: 0, messagesRemoved: 0 });
802
+ });
803
+
804
+ it('removes sessions older than the retention window and their messages', () => {
805
+ indexSession(dbManager, createTestSession({ id: 'old', startedAt: '2020-01-01T00:00:00Z' }));
806
+ indexSession(dbManager, createTestSession({ id: 'recent', startedAt: new Date().toISOString() }));
807
+
808
+ const result = pruneOldSessions(dbManager, 30);
809
+ assert.strictEqual(result.sessionsRemoved, 1);
810
+ assert.strictEqual(result.messagesRemoved, 2); // old session has 2 messages
811
+
812
+ const stats = getSessionStats(dbManager);
813
+ assert.strictEqual(stats.totalSessions, 1);
814
+ assert.strictEqual(stats.totalMessages, 2);
815
+ const remaining = dbManager.getDb().prepare('SELECT id FROM sessions').all() as { id: string }[];
816
+ assert.deepStrictEqual(remaining.map((r) => r.id), ['recent']);
817
+ });
818
+
819
+ it('leaves sessions exactly at the retention boundary (not strictly older) untouched', () => {
820
+ const now = Date.now();
821
+ const justWithin = new Date(now - 10 * 24 * 60 * 60 * 1000).toISOString(); // 10 days ago
822
+ indexSession(dbManager, createTestSession({ id: 'within', startedAt: justWithin }));
823
+
824
+ const result = pruneOldSessions(dbManager, 30);
825
+ assert.strictEqual(result.sessionsRemoved, 0);
826
+ assert.strictEqual(dbManager.getStats().sessions, 1);
827
+ });
828
+
829
+ it('is idempotent across repeated calls', () => {
830
+ indexSession(dbManager, createTestSession({ id: 'old', startedAt: '2020-01-01T00:00:00Z' }));
831
+
832
+ pruneOldSessions(dbManager, 30);
833
+ const again = pruneOldSessions(dbManager, 30);
834
+ assert.deepStrictEqual(again, { sessionsRemoved: 0, messagesRemoved: 0 });
835
+ });
836
+ });
837
+
605
838
  describe('getSessionStats', () => {
606
839
  it('should return zero counts for empty database', () => {
607
840
  const stats = getSessionStats(dbManager);
@@ -50,6 +50,18 @@ describe('session-search', () => {
50
50
  assert.ok(results.length > 0);
51
51
  assert.ok(results.some(r => r.content.includes('Prisma')));
52
52
  });
53
+ it('degrades an all-stop-word query to the LIKE fallback instead of []', () => {
54
+ // "the" is a stop word and "and"/"or" connectors, so FTS5 normalization
55
+ // leaves nothing to match. The query must fall through to the scoped
56
+ // literal fallback (which still searches the raw terms) rather than
57
+ // hard-return an empty result set.
58
+ indexSession(dbManager, createTestSession());
59
+
60
+ const results = searchSessions(dbManager, 'the and or');
61
+ assert.ok(results.length > 0, 'all-stop-word query must reach the LIKE fallback, not []');
62
+ assert.ok(results.some(r => r.content.includes('the')));
63
+ });
64
+
53
65
  it('finds pure CJK substrings with the trigram tokenizer', () => {
54
66
  indexSession(dbManager, createTestSession({ id: 'cjk-trigram-session', messages: [
55
67
  { id: 'cjk-trigram-session-msg-1', role: 'assistant', content: '设备清单包含 NAS 和备份策略', timestamp: '2026-05-03T00:01:00Z' },