@selesai/code 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (466) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/core/agent-session.d.ts +1 -1
  3. package/dist/core/agent-session.js +1 -1
  4. package/dist/core/sdk.d.ts +2 -2
  5. package/dist/core/sdk.js +1 -1
  6. package/dist/core/system-prompt.d.ts +1 -1
  7. package/dist/core/system-prompt.js +1 -1
  8. package/dist/core/usage-totals.d.ts +2 -0
  9. package/dist/core/usage-totals.js +10 -0
  10. package/dist/extensions/ascii.txt +9 -0
  11. package/dist/extensions/package.json +1 -2
  12. package/dist/extensions/pi-hermes-memory/CHANGELOG.md +371 -0
  13. package/dist/extensions/pi-hermes-memory/LICENSE +21 -0
  14. package/dist/extensions/pi-hermes-memory/README.md +643 -0
  15. package/dist/extensions/pi-hermes-memory/docs/0.1/TASKS.md +197 -0
  16. package/dist/extensions/pi-hermes-memory/docs/0.2/PLAN.md +290 -0
  17. package/dist/extensions/pi-hermes-memory/docs/0.2/TASKS.md +134 -0
  18. package/dist/extensions/pi-hermes-memory/docs/0.2/TEST-PLAN.md +216 -0
  19. package/dist/extensions/pi-hermes-memory/docs/0.3/PLAN.md +330 -0
  20. package/dist/extensions/pi-hermes-memory/docs/0.3/TASKS.md +125 -0
  21. package/dist/extensions/pi-hermes-memory/docs/0.4/PLAN.md +160 -0
  22. package/dist/extensions/pi-hermes-memory/docs/0.4/TASKS.md +113 -0
  23. package/dist/extensions/pi-hermes-memory/docs/0.6/CHANGELOG.md +57 -0
  24. package/dist/extensions/pi-hermes-memory/docs/0.6/PLAN.md +202 -0
  25. package/dist/extensions/pi-hermes-memory/docs/0.6/TASKS.md +144 -0
  26. package/dist/extensions/pi-hermes-memory/docs/0.7/PLAN.md +349 -0
  27. package/dist/extensions/pi-hermes-memory/docs/0.7/TASKS.md +110 -0
  28. package/dist/extensions/pi-hermes-memory/docs/PUBLISHING.md +149 -0
  29. package/dist/extensions/pi-hermes-memory/docs/ROADMAP.md +468 -0
  30. package/dist/extensions/pi-hermes-memory/docs/images/memory-architecture.svg +1 -0
  31. package/dist/extensions/pi-hermes-memory/docs/images/pi-logo.svg +22 -0
  32. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.png +0 -0
  33. package/dist/extensions/pi-hermes-memory/docs/images/pi_memory.svg +19 -0
  34. package/dist/extensions/pi-hermes-memory/docs/images/security-flow.svg +1 -0
  35. package/dist/extensions/pi-hermes-memory/docs/images/session-lifecycle.svg +1 -0
  36. package/dist/extensions/pi-hermes-memory/docs/images/source-architecture.svg +1 -0
  37. package/dist/extensions/pi-hermes-memory/docs/mermaid/memory-architecture.mmd +49 -0
  38. package/dist/extensions/pi-hermes-memory/docs/mermaid/security-flow.mmd +40 -0
  39. package/dist/extensions/pi-hermes-memory/docs/mermaid/session-lifecycle.mmd +56 -0
  40. package/dist/extensions/pi-hermes-memory/docs/mermaid/source-architecture.mmd +54 -0
  41. package/dist/extensions/pi-hermes-memory/package-lock.json +3799 -0
  42. package/dist/extensions/pi-hermes-memory/package.json +66 -0
  43. package/dist/extensions/pi-hermes-memory/scripts/check-min-sdk.mjs +99 -0
  44. package/dist/extensions/pi-hermes-memory/scripts/ensure-dev.mjs +57 -0
  45. package/dist/extensions/pi-hermes-memory/src/auto-consolidation-warning.ts +6 -0
  46. package/dist/extensions/pi-hermes-memory/src/config.ts +178 -0
  47. package/dist/extensions/pi-hermes-memory/src/constants.ts +424 -0
  48. package/dist/extensions/pi-hermes-memory/src/extension-root-migration.ts +639 -0
  49. package/dist/extensions/pi-hermes-memory/src/handlers/auto-consolidate.ts +401 -0
  50. package/dist/extensions/pi-hermes-memory/src/handlers/background-review.ts +297 -0
  51. package/dist/extensions/pi-hermes-memory/src/handlers/child-process-watchdog.mjs +90 -0
  52. package/dist/extensions/pi-hermes-memory/src/handlers/correction-detector.ts +300 -0
  53. package/dist/extensions/pi-hermes-memory/src/handlers/index-sessions.ts +86 -0
  54. package/dist/extensions/pi-hermes-memory/src/handlers/insights.ts +78 -0
  55. package/dist/extensions/pi-hermes-memory/src/handlers/interview.ts +37 -0
  56. package/dist/extensions/pi-hermes-memory/src/handlers/learn-memory.ts +186 -0
  57. package/dist/extensions/pi-hermes-memory/src/handlers/message-parts.ts +27 -0
  58. package/dist/extensions/pi-hermes-memory/src/handlers/pi-child-process.ts +485 -0
  59. package/dist/extensions/pi-hermes-memory/src/handlers/preview-context.ts +105 -0
  60. package/dist/extensions/pi-hermes-memory/src/handlers/review-memory-ops.ts +506 -0
  61. package/dist/extensions/pi-hermes-memory/src/handlers/session-backfill.ts +145 -0
  62. package/dist/extensions/pi-hermes-memory/src/handlers/session-flush.ts +151 -0
  63. package/dist/extensions/pi-hermes-memory/src/handlers/session-live-index.ts +94 -0
  64. package/dist/extensions/pi-hermes-memory/src/handlers/skills-command.ts +1334 -0
  65. package/dist/extensions/pi-hermes-memory/src/handlers/standing-pin.ts +111 -0
  66. package/dist/extensions/pi-hermes-memory/src/handlers/switch-project.ts +75 -0
  67. package/dist/extensions/pi-hermes-memory/src/handlers/sync-markdown-memories.ts +266 -0
  68. package/dist/extensions/pi-hermes-memory/src/index.ts +378 -0
  69. package/dist/extensions/pi-hermes-memory/src/lifecycle-timing.ts +54 -0
  70. package/dist/extensions/pi-hermes-memory/src/paths.ts +61 -0
  71. package/dist/extensions/pi-hermes-memory/src/project-context.ts +13 -0
  72. package/dist/extensions/pi-hermes-memory/src/project-memory-migration.ts +93 -0
  73. package/dist/extensions/pi-hermes-memory/src/project.ts +157 -0
  74. package/dist/extensions/pi-hermes-memory/src/prompt-context.ts +55 -0
  75. package/dist/extensions/pi-hermes-memory/src/store/atomic-lock-coordinator.ts +375 -0
  76. package/dist/extensions/pi-hermes-memory/src/store/canonical-storage-path.ts +54 -0
  77. package/dist/extensions/pi-hermes-memory/src/store/content-scanner.ts +101 -0
  78. package/dist/extensions/pi-hermes-memory/src/store/db.ts +1180 -0
  79. package/dist/extensions/pi-hermes-memory/src/store/fts-query.ts +101 -0
  80. package/dist/extensions/pi-hermes-memory/src/store/markdown-mutation-lock.ts +38 -0
  81. package/dist/extensions/pi-hermes-memory/src/store/memory-lookup.ts +24 -0
  82. package/dist/extensions/pi-hermes-memory/src/store/memory-store.ts +1191 -0
  83. package/dist/extensions/pi-hermes-memory/src/store/recovery-maintenance.ts +66 -0
  84. package/dist/extensions/pi-hermes-memory/src/store/schema.ts +118 -0
  85. package/dist/extensions/pi-hermes-memory/src/store/session-anchor-search.ts +472 -0
  86. package/dist/extensions/pi-hermes-memory/src/store/session-indexer.ts +511 -0
  87. package/dist/extensions/pi-hermes-memory/src/store/session-parser.ts +213 -0
  88. package/dist/extensions/pi-hermes-memory/src/store/session-search.ts +234 -0
  89. package/dist/extensions/pi-hermes-memory/src/store/skill-store.ts +950 -0
  90. package/dist/extensions/pi-hermes-memory/src/store/skill-utils.ts +133 -0
  91. package/dist/extensions/pi-hermes-memory/src/store/sqlite-memory-store.ts +1002 -0
  92. package/dist/extensions/pi-hermes-memory/src/store/sqlite-native.ts +241 -0
  93. package/dist/extensions/pi-hermes-memory/src/store/standing-instructions.ts +247 -0
  94. package/dist/extensions/pi-hermes-memory/src/tools/memory-search-tool.ts +95 -0
  95. package/dist/extensions/pi-hermes-memory/src/tools/memory-tool.ts +469 -0
  96. package/dist/extensions/pi-hermes-memory/src/tools/session-search-tool.ts +246 -0
  97. package/dist/extensions/pi-hermes-memory/src/tools/shared-output-view.ts +170 -0
  98. package/dist/extensions/pi-hermes-memory/src/tools/skill-tool.ts +333 -0
  99. package/dist/extensions/pi-hermes-memory/src/tools/tool-result-views.ts +105 -0
  100. package/dist/extensions/pi-hermes-memory/src/types.ts +214 -0
  101. package/dist/extensions/pi-hermes-memory/tests/config.test.ts +434 -0
  102. package/dist/extensions/pi-hermes-memory/tests/extension-root-migration.test.ts +368 -0
  103. package/dist/extensions/pi-hermes-memory/tests/handlers/auto-consolidate.test.ts +984 -0
  104. package/dist/extensions/pi-hermes-memory/tests/handlers/background-review.test.ts +950 -0
  105. package/dist/extensions/pi-hermes-memory/tests/handlers/correction-detector.test.ts +671 -0
  106. package/dist/extensions/pi-hermes-memory/tests/handlers/insights.test.ts +157 -0
  107. package/dist/extensions/pi-hermes-memory/tests/handlers/interview.test.ts +127 -0
  108. package/dist/extensions/pi-hermes-memory/tests/handlers/pi-child-process.test.ts +905 -0
  109. package/dist/extensions/pi-hermes-memory/tests/handlers/preview-context.test.ts +152 -0
  110. package/dist/extensions/pi-hermes-memory/tests/handlers/prompt-context.test.ts +166 -0
  111. package/dist/extensions/pi-hermes-memory/tests/handlers/resources-discover.test.ts +71 -0
  112. package/dist/extensions/pi-hermes-memory/tests/handlers/review-memory-ops.test.ts +614 -0
  113. package/dist/extensions/pi-hermes-memory/tests/handlers/session-backfill.test.ts +192 -0
  114. package/dist/extensions/pi-hermes-memory/tests/handlers/session-flush.test.ts +677 -0
  115. package/dist/extensions/pi-hermes-memory/tests/handlers/session-live-index.test.ts +225 -0
  116. package/dist/extensions/pi-hermes-memory/tests/handlers/skills-command.test.ts +827 -0
  117. package/dist/extensions/pi-hermes-memory/tests/handlers/standing-pin.test.ts +134 -0
  118. package/dist/extensions/pi-hermes-memory/tests/handlers/sync-markdown-memories.test.ts +491 -0
  119. package/dist/extensions/pi-hermes-memory/tests/handlers/system-prompt.test.ts +199 -0
  120. package/dist/extensions/pi-hermes-memory/tests/handlers/thinking-max.test.ts +64 -0
  121. package/dist/extensions/pi-hermes-memory/tests/index.test.ts +30 -0
  122. package/dist/extensions/pi-hermes-memory/tests/integration/flow.test.ts +126 -0
  123. package/dist/extensions/pi-hermes-memory/tests/lifecycle-timing.test.ts +76 -0
  124. package/dist/extensions/pi-hermes-memory/tests/paths.test.ts +23 -0
  125. package/dist/extensions/pi-hermes-memory/tests/project-memory-migration.test.ts +80 -0
  126. package/dist/extensions/pi-hermes-memory/tests/project-rebinding.test.ts +115 -0
  127. package/dist/extensions/pi-hermes-memory/tests/project.test.ts +120 -0
  128. package/dist/extensions/pi-hermes-memory/tests/run-all-timeout.test.ts +107 -0
  129. package/dist/extensions/pi-hermes-memory/tests/run-all.sh +45 -0
  130. package/dist/extensions/pi-hermes-memory/tests/store/atomic-lock-coordinator.test.ts +381 -0
  131. package/dist/extensions/pi-hermes-memory/tests/store/content-scanner.test.ts +388 -0
  132. package/dist/extensions/pi-hermes-memory/tests/store/db.test.ts +1046 -0
  133. package/dist/extensions/pi-hermes-memory/tests/store/markdown-mutation-lock.test.ts +34 -0
  134. package/dist/extensions/pi-hermes-memory/tests/store/memory-store.test.ts +2150 -0
  135. package/dist/extensions/pi-hermes-memory/tests/store/recovery-maintenance.test.ts +170 -0
  136. package/dist/extensions/pi-hermes-memory/tests/store/session-anchor-search.test.ts +210 -0
  137. package/dist/extensions/pi-hermes-memory/tests/store/session-indexer.test.ts +643 -0
  138. package/dist/extensions/pi-hermes-memory/tests/store/session-parser.test.ts +282 -0
  139. package/dist/extensions/pi-hermes-memory/tests/store/session-search.test.ts +287 -0
  140. package/dist/extensions/pi-hermes-memory/tests/store/skill-store.test.ts +678 -0
  141. package/dist/extensions/pi-hermes-memory/tests/store/skill-utils.test.ts +21 -0
  142. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-lazy-load.test.ts +78 -0
  143. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-memory-store.test.ts +543 -0
  144. package/dist/extensions/pi-hermes-memory/tests/store/sqlite-native.test.ts +164 -0
  145. package/dist/extensions/pi-hermes-memory/tests/store/standing-instructions.test.ts +171 -0
  146. package/dist/extensions/pi-hermes-memory/tests/tools/memory-search-tool.test.ts +84 -0
  147. package/dist/extensions/pi-hermes-memory/tests/tools/memory-tool.test.ts +709 -0
  148. package/dist/extensions/pi-hermes-memory/tests/tools/session-search-tool.test.ts +345 -0
  149. package/dist/extensions/pi-hermes-memory/tests/tools/shared-output-view.test.ts +362 -0
  150. package/dist/extensions/pi-hermes-memory/tests/tools/skill-tool.test.ts +478 -0
  151. package/dist/extensions/pi-hermes-memory/tests/tools/tool-result-renderer-wiring.test.ts +40 -0
  152. package/dist/extensions/pi-hermes-memory/tsconfig.json +19 -0
  153. package/dist/extensions/pi-subagents/.oxlintrc.json +43 -0
  154. package/dist/extensions/pi-subagents/AGENTS.md +5 -0
  155. package/dist/extensions/pi-subagents/CHANGELOG.md +157 -0
  156. package/dist/extensions/pi-subagents/README.md +12 -6
  157. package/dist/extensions/pi-subagents/VISION.md +81 -0
  158. package/dist/extensions/pi-subagents/agents/claude-code-writer.md +15 -0
  159. package/dist/extensions/pi-subagents/agents/claude-code.md +15 -0
  160. package/dist/extensions/pi-subagents/agents/codex-exec-writer.md +15 -0
  161. package/dist/extensions/pi-subagents/agents/codex-exec.md +15 -0
  162. package/dist/extensions/pi-subagents/agents/cursor-agent-writer.md +14 -0
  163. package/dist/extensions/pi-subagents/agents/cursor-agent.md +14 -0
  164. package/dist/extensions/pi-subagents/agents/reviewer.md +1 -3
  165. package/dist/extensions/pi-subagents/docs/agents.md +488 -0
  166. package/dist/extensions/pi-subagents/docs/configuration.md +46 -9
  167. package/dist/extensions/pi-subagents/docs/extension-api.md +42 -3
  168. package/dist/extensions/pi-subagents/docs/models.md +27 -4
  169. package/dist/extensions/pi-subagents/docs/observability.md +10 -9
  170. package/dist/extensions/pi-subagents/docs/tool-reference.md +102 -6
  171. package/dist/extensions/pi-subagents/docs/workflows.md +135 -0
  172. package/dist/extensions/pi-subagents/package-lock.json +389 -2
  173. package/dist/extensions/pi-subagents/package.json +3 -1
  174. package/dist/extensions/pi-subagents/prompts/review-loop.md +2 -2
  175. package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +1 -1
  176. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +3 -1
  177. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +7 -3
  178. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  179. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +26 -5
  180. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -22
  181. package/dist/extensions/pi-subagents/src/agents/agent-refinements.ts +4 -4
  182. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +9 -2
  183. package/dist/extensions/pi-subagents/src/agents/agents.ts +322 -75
  184. package/dist/extensions/pi-subagents/src/agents/builtin-names.ts +6 -0
  185. package/dist/extensions/pi-subagents/src/agents/runtime-agent-events.ts +70 -0
  186. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +27 -20
  187. package/dist/extensions/pi-subagents/src/api/agents.ts +10 -5
  188. package/dist/extensions/pi-subagents/src/api/background-work.ts +5 -1
  189. package/dist/extensions/pi-subagents/src/api/delegation.ts +0 -7
  190. package/dist/extensions/pi-subagents/src/api/preflight.ts +34 -12
  191. package/dist/extensions/pi-subagents/src/extension/config.ts +70 -0
  192. package/dist/extensions/pi-subagents/src/extension/doctor.ts +3 -3
  193. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +5 -3
  194. package/dist/extensions/pi-subagents/src/extension/index.ts +83 -30
  195. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +44 -15
  196. package/dist/extensions/pi-subagents/src/extension/rpc.ts +55 -19
  197. package/dist/extensions/pi-subagents/src/extension/schemas.ts +41 -15
  198. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -31
  199. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +2 -1
  200. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +2 -10
  201. package/dist/extensions/pi-subagents/src/inspectors/herdr/session-roots-codec.ts +42 -0
  202. package/dist/extensions/pi-subagents/src/integrations/herdr-status.ts +51 -3
  203. package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +2 -0
  204. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +5 -6
  205. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +77 -10
  206. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +176 -53
  207. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +19 -12
  208. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +29 -27
  209. package/dist/extensions/pi-subagents/src/runs/background/async-retention.ts +20 -3
  210. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +23 -261
  211. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +73 -7
  212. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +8 -3
  213. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +74 -8
  214. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +37 -21
  215. package/dist/extensions/pi-subagents/src/runs/background/inspect-rpc.ts +8 -8
  216. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +184 -9
  217. package/dist/extensions/pi-subagents/src/runs/background/result-delivery-ownership.ts +45 -0
  218. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +28 -14
  219. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +38 -15
  220. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +1 -1
  221. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +1 -1
  222. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +50 -13
  223. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +93 -8
  224. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +10 -4
  225. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -14
  226. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +549 -377
  227. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +58 -10
  228. package/dist/extensions/pi-subagents/src/runs/background/terminal-run-index.ts +1 -1
  229. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +26 -1
  230. package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +23 -9
  231. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +9 -2
  232. package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +2 -2
  233. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +204 -129
  234. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +9 -0
  235. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +23 -1
  236. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +773 -311
  237. package/dist/extensions/pi-subagents/src/runs/foreground/workflow-detach-reconcile.ts +140 -115
  238. package/dist/extensions/pi-subagents/src/runs/shared/abort-recovery.ts +119 -0
  239. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +463 -0
  240. package/dist/extensions/pi-subagents/src/runs/shared/child-identity.ts +19 -4
  241. package/dist/extensions/pi-subagents/src/runs/shared/child-launch-plan.ts +151 -0
  242. package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +21 -7
  243. package/dist/extensions/pi-subagents/src/runs/shared/claude-code-adapter.ts +129 -0
  244. package/dist/extensions/pi-subagents/src/runs/shared/codex-exec-adapter.ts +129 -0
  245. package/dist/extensions/pi-subagents/src/runs/shared/completion-evidence.ts +89 -0
  246. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +5 -4
  247. package/dist/extensions/pi-subagents/src/runs/shared/cursor-agent-adapter.ts +114 -0
  248. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +3 -3
  249. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-contract.ts +167 -0
  250. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-preflight.ts +122 -0
  251. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +348 -55
  252. package/dist/extensions/pi-subagents/src/runs/shared/fast-mode-extension.ts +5 -5
  253. package/dist/extensions/pi-subagents/src/runs/shared/host-step-status.ts +230 -0
  254. package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +105 -0
  255. package/dist/extensions/pi-subagents/src/runs/shared/launch-cwd.ts +16 -0
  256. package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +2 -1
  257. package/dist/extensions/pi-subagents/src/runs/shared/mcp-config-sources.ts +422 -0
  258. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +221 -158
  259. package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +197 -0
  260. package/dist/extensions/pi-subagents/src/runs/shared/model-exclusions.ts +69 -7
  261. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +47 -6
  262. package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +7 -2
  263. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +9 -3
  264. package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +9 -5
  265. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +419 -7
  266. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +15 -1
  267. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +81 -8
  268. package/dist/extensions/pi-subagents/src/runs/shared/process-signal.ts +13 -0
  269. package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +21 -1
  270. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +44 -4
  271. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +98 -13
  272. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +1 -1
  273. package/dist/extensions/pi-subagents/src/runs/shared/worktree-cleanup-plan.ts +847 -0
  274. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +18 -0
  275. package/dist/extensions/pi-subagents/src/shared/child-session-name.ts +46 -0
  276. package/dist/extensions/pi-subagents/src/shared/extension-context.ts +24 -0
  277. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +21 -0
  278. package/dist/extensions/pi-subagents/src/shared/formatters.ts +18 -3
  279. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +5 -1
  280. package/dist/extensions/pi-subagents/src/shared/pruned-fork.ts +450 -0
  281. package/dist/extensions/pi-subagents/src/shared/session-file-trust.ts +19 -0
  282. package/dist/extensions/pi-subagents/src/shared/session-tokens.ts +14 -3
  283. package/dist/extensions/pi-subagents/src/shared/settings.ts +19 -105
  284. package/dist/extensions/pi-subagents/src/shared/shortcuts.ts +17 -0
  285. package/dist/extensions/pi-subagents/src/shared/types.ts +359 -40
  286. package/dist/extensions/pi-subagents/src/shared/utils.ts +41 -84
  287. package/dist/extensions/pi-subagents/src/shared/workflow-child-permit.ts +116 -0
  288. package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +1 -9
  289. package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +0 -4
  290. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +1 -2
  291. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +378 -88
  292. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +22 -11
  293. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +3 -0
  294. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +147 -79
  295. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +11 -5
  296. package/dist/extensions/pi-subagents/src/tui/fleet.ts +39 -18
  297. package/dist/extensions/pi-subagents/src/tui/render.ts +369 -46
  298. package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +1 -1
  299. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +6 -3
  300. package/dist/extensions/pi-subagents/src/workflows/host-command.ts +230 -0
  301. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +740 -58
  302. package/dist/extensions/pi-subagents/src/workflows/workflow-child-summary.ts +121 -0
  303. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +270 -0
  304. package/dist/extensions/pi-subagents/src/workflows/workflow-receipt.ts +195 -6
  305. package/dist/extensions/pi-subagents/src/workflows/workflow-settlement.ts +246 -0
  306. package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +2 -2
  307. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1114 -260
  308. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +34 -0
  309. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +150 -3
  310. package/dist/extensions/pi-subagents/test/integration/claude-code-smoke.test.ts +52 -0
  311. package/dist/extensions/pi-subagents/test/integration/claude-code-writer-smoke.test.ts +55 -0
  312. package/dist/extensions/pi-subagents/test/integration/codex-exec-smoke.test.ts +53 -0
  313. package/dist/extensions/pi-subagents/test/integration/codex-exec-writer-smoke.test.ts +57 -0
  314. package/dist/extensions/pi-subagents/test/integration/cursor-agent-smoke.test.ts +59 -0
  315. package/dist/extensions/pi-subagents/test/integration/cursor-agent-writer-smoke.test.ts +62 -0
  316. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +57 -1
  317. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  318. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +20 -0
  319. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +4 -3
  320. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +2 -2
  321. package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +54 -4
  322. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +482 -4
  323. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +157 -9
  324. package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +5 -5
  325. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1508 -291
  326. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +423 -1
  327. package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +8 -0
  328. package/dist/extensions/pi-subagents/test/support/cursor-smoke-inputs.ts +51 -0
  329. package/dist/extensions/pi-subagents/test/support/helpers.ts +2 -0
  330. package/dist/extensions/pi-subagents/test/support/mock-pi.ts +5 -5
  331. package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +6 -6
  332. package/dist/extensions/pi-subagents/test/unit/abort-recovery.test.ts +152 -0
  333. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +48 -0
  334. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +184 -6
  335. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +66 -6
  336. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +77 -0
  337. package/dist/extensions/pi-subagents/test/unit/agent-refinements.test.ts +18 -0
  338. package/dist/extensions/pi-subagents/test/unit/anti-slop-oxlint.test.ts +86 -0
  339. package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +1 -1
  340. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +101 -1
  341. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +121 -0
  342. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +28 -0
  343. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +14 -7
  344. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +53 -0
  345. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +190 -0
  346. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +42 -0
  347. package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +8 -4
  348. package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +23 -0
  349. package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +78 -1
  350. package/dist/extensions/pi-subagents/test/unit/child-launch-plan.test.ts +94 -0
  351. package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +21 -0
  352. package/dist/extensions/pi-subagents/test/unit/child-session-name.test.ts +50 -0
  353. package/dist/extensions/pi-subagents/test/unit/claude-code-adapter.test.ts +245 -0
  354. package/dist/extensions/pi-subagents/test/unit/codex-exec-adapter.test.ts +192 -0
  355. package/dist/extensions/pi-subagents/test/unit/compaction-resume.test.ts +27 -0
  356. package/dist/extensions/pi-subagents/test/unit/completion-evidence.test.ts +150 -0
  357. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +21 -0
  358. package/dist/extensions/pi-subagents/test/unit/cursor-agent-adapter.test.ts +259 -0
  359. package/dist/extensions/pi-subagents/test/unit/cursor-smoke-inputs.test.ts +77 -0
  360. package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +1 -4
  361. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +1 -1
  362. package/dist/extensions/pi-subagents/test/unit/extension-context.test.ts +27 -0
  363. package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +223 -0
  364. package/dist/extensions/pi-subagents/test/unit/fast-mode-extension.test.ts +27 -0
  365. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +208 -2
  366. package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +29 -1
  367. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +110 -1
  368. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +10 -0
  369. package/dist/extensions/pi-subagents/test/unit/formatters.test.ts +14 -0
  370. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +18 -9
  371. package/dist/extensions/pi-subagents/test/unit/herdr-session-roots-codec.test.ts +63 -0
  372. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +2 -2
  373. package/dist/extensions/pi-subagents/test/unit/herdr-status-bridge.test.ts +132 -0
  374. package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +156 -0
  375. package/dist/extensions/pi-subagents/test/unit/host-step-status.test.ts +98 -0
  376. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +178 -26
  377. package/dist/extensions/pi-subagents/test/unit/index-segment.test.ts +2 -1
  378. package/dist/extensions/pi-subagents/test/unit/inspect-rpc.test.ts +21 -0
  379. package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +138 -0
  380. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  381. package/dist/extensions/pi-subagents/test/unit/model-exclusions.test.ts +91 -0
  382. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +72 -7
  383. package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +21 -0
  384. package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +1 -0
  385. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +20 -2
  386. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +129 -1
  387. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +2 -2
  388. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +4 -0
  389. package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +358 -0
  390. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +574 -21
  391. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +109 -1
  392. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +71 -1
  393. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +7 -7
  394. package/dist/extensions/pi-subagents/test/unit/pruned-fork.test.ts +214 -0
  395. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +29 -8
  396. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +53 -0
  397. package/dist/extensions/pi-subagents/test/unit/result-delivery-ownership.test.ts +34 -0
  398. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +101 -3
  399. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +1 -0
  400. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +10 -8
  401. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +71 -1
  402. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +41 -7
  403. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +136 -2
  404. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +137 -0
  405. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +33 -12
  406. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +504 -1
  407. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +8 -0
  408. package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +1 -1
  409. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +12 -1
  410. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +3 -5
  411. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +5 -6
  412. package/dist/extensions/pi-subagents/test/unit/subagent-action-recovery.test.ts +0 -8
  413. package/dist/extensions/pi-subagents/test/unit/subagent-guide.test.ts +5 -0
  414. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +157 -2
  415. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +1 -1
  416. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +63 -4
  417. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +27 -23
  418. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +44 -0
  419. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -0
  420. package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +7 -5
  421. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +106 -0
  422. package/dist/extensions/pi-subagents/test/unit/workflow-detach-reconcile.test.ts +484 -9
  423. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +38 -4
  424. package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +155 -0
  425. package/dist/extensions/pi-subagents/test/unit/workflow-receipt.test.ts +318 -0
  426. package/dist/extensions/pi-subagents/test/unit/workflow-resume-hint.test.ts +365 -0
  427. package/dist/extensions/pi-subagents/test/unit/worktree-cleanup-plan.test.ts +349 -0
  428. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +21 -0
  429. package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +2 -7
  430. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/index.ts +13 -0
  431. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/effect/rules/no-service-constructor-imports.ts +52 -0
  432. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/index.ts +41 -0
  433. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts +77 -0
  434. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-conditional-empty-object-spread.ts +49 -0
  435. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-known-value-widening.ts +247 -0
  436. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-module-mocking.ts +91 -0
  437. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-object-parameters.ts +126 -0
  438. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-apply.ts +28 -0
  439. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-reflect-get.ts +28 -0
  440. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-runtime-typeof.ts +67 -0
  441. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-shape-in-symbol-names.ts +39 -0
  442. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-parameters.ts +83 -0
  443. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-returns.ts +115 -0
  444. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unknown-type-aliases.ts +72 -0
  445. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts +134 -0
  446. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/no-widen-then-assert.ts +366 -0
  447. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts +62 -0
  448. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/dictionary-types.ts +502 -0
  449. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/lexical-type-parameters.ts +60 -0
  450. package/dist/extensions/pi-subagents/tools/oxlint/anti-slop/shared/reflect-method.ts +35 -0
  451. package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +1 -1
  452. package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +2 -2
  453. package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +1 -1
  454. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +2 -1
  455. package/dist/extensions/pi-zentui/test/format.test.ts +15 -1
  456. package/dist/modes/interactive/components/footer.js +2 -2
  457. package/dist/modes/interactive/interactive-mode.js +11 -11
  458. package/dist/skills/workflow/SKILL.md +65 -50
  459. package/package.json +112 -110
  460. package/dist/extensions/enable-readonly-tools.test.ts +0 -63
  461. package/dist/extensions/enable-readonly-tools.ts +0 -24
  462. package/dist/extensions/model-prompt-injector/config.json +0 -16
  463. package/dist/extensions/model-prompt-injector/index.test.ts +0 -102
  464. package/dist/extensions/model-prompt-injector/index.ts +0 -148
  465. package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +0 -98
  466. package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +0 -250
@@ -0,0 +1,66 @@
1
+ /**
2
+ * One-shot snapshot retention sweep across every memory store on disk (#202).
3
+ *
4
+ * Snapshot pruning normally runs inside saveToDisk(), so a store that stops
5
+ * being written keeps its .recovery-* and .retired-* artifacts forever. This
6
+ * sweep applies the identical per-file policy to dormant stores once per
7
+ * session start. Enumeration never follows symlinks and never enters hidden
8
+ * or nested directories.
9
+ */
10
+
11
+ import * as fs from "node:fs/promises";
12
+ import * as path from "node:path";
13
+ import { resolveProjectsRoot } from "../paths.js";
14
+ import { MemoryStore } from "./memory-store.js";
15
+ import type { MemoryConfig } from "../types.js";
16
+
17
+ export interface RecoveryMaintenanceOptions {
18
+ config: MemoryConfig;
19
+ globalDir: string;
20
+ }
21
+
22
+ export interface RecoveryMaintenanceResult {
23
+ storesMaintained: number;
24
+ failedStores: string[];
25
+ }
26
+
27
+ // loadConfig only ever stores a single AGENT_ROOT-relative segment here; an
28
+ // absolute value is a direct-injection seam for tests and embedding hosts.
29
+ function resolveProjectsRootFor(projectsMemoryDir: MemoryConfig["projectsMemoryDir"]): string {
30
+ return projectsMemoryDir && path.isAbsolute(projectsMemoryDir)
31
+ ? projectsMemoryDir
32
+ : resolveProjectsRoot(projectsMemoryDir);
33
+ }
34
+
35
+ export async function listMemoryStoreDirs(options: RecoveryMaintenanceOptions): Promise<string[]> {
36
+ const dirs = [options.globalDir];
37
+ const projectsRoot = resolveProjectsRootFor(options.config.projectsMemoryDir);
38
+ let entries;
39
+ try {
40
+ entries = await fs.readdir(projectsRoot, { withFileTypes: true });
41
+ } catch {
42
+ return dirs;
43
+ }
44
+ for (const entry of entries) {
45
+ if (entry.isSymbolicLink() || !entry.isDirectory()) continue;
46
+ if (entry.name.startsWith(".") || entry.name === "skills") continue;
47
+ dirs.push(path.join(projectsRoot, entry.name));
48
+ }
49
+ return dirs;
50
+ }
51
+
52
+ export async function runRecoveryMaintenance(options: RecoveryMaintenanceOptions): Promise<RecoveryMaintenanceResult> {
53
+ const result: RecoveryMaintenanceResult = { storesMaintained: 0, failedStores: [] };
54
+ for (const memoryDir of await listMemoryStoreDirs(options)) {
55
+ // The constructor only assigns config; maintainRecoveryFiles touches
56
+ // sidecar files without loading or writing MEMORY.md/USER.md/failures.md.
57
+ const store = new MemoryStore({ ...options.config, memoryDir });
58
+ try {
59
+ await store.maintainRecoveryFiles();
60
+ result.storesMaintained++;
61
+ } catch {
62
+ result.failedStores.push(memoryDir);
63
+ }
64
+ }
65
+ return result;
66
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * SQLite schema for pi-hermes-memory v0.4
3
+ *
4
+ * Tables:
5
+ * - sessions — Pi session metadata
6
+ * - session_files — indexed JSONL metadata for incremental backfill
7
+ * - messages — all conversation messages
8
+ * - message_fts — FTS5 index for full-text search across messages
9
+ * - memories — extended memory entries (unlimited, searchable)
10
+ * - memory_fts — FTS5 index for memory search
11
+ */
12
+
13
+ export const SCHEMA_SQL = `
14
+ -- Extension key/value metadata
15
+ CREATE TABLE IF NOT EXISTS extension_metadata (
16
+ key TEXT PRIMARY KEY,
17
+ value TEXT NOT NULL
18
+ );
19
+
20
+ -- Session metadata
21
+ CREATE TABLE IF NOT EXISTS sessions (
22
+ id TEXT PRIMARY KEY,
23
+ project TEXT NOT NULL,
24
+ cwd TEXT NOT NULL,
25
+ started_at TEXT NOT NULL,
26
+ ended_at TEXT,
27
+ message_count INTEGER DEFAULT 0
28
+ );
29
+
30
+ -- Indexed session file metadata for cheap incremental backfill
31
+ CREATE TABLE IF NOT EXISTS session_files (
32
+ path TEXT PRIMARY KEY,
33
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
34
+ size INTEGER NOT NULL,
35
+ mtime_ms INTEGER NOT NULL,
36
+ indexed_at TEXT NOT NULL
37
+ );
38
+
39
+ -- All messages from all sessions
40
+ CREATE TABLE IF NOT EXISTS messages (
41
+ id TEXT PRIMARY KEY,
42
+ session_id TEXT NOT NULL REFERENCES sessions(id),
43
+ role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
44
+ content TEXT NOT NULL,
45
+ timestamp TEXT NOT NULL,
46
+ tool_calls TEXT
47
+ );
48
+
49
+ -- FTS5 trigram indexes support substring search for CJK and retain
50
+ -- normal token search for English. Queries shorter than three characters
51
+ -- are not indexed by the trigram tokenizer.
52
+ CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts5(
53
+ content,
54
+ content='messages',
55
+ content_rowid='rowid',
56
+ tokenize='trigram'
57
+ );
58
+
59
+ -- Triggers to keep message_fts in sync with messages table
60
+ CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
61
+ INSERT INTO message_fts(rowid, content) VALUES (new.rowid, new.content);
62
+ END;
63
+
64
+ CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
65
+ INSERT INTO message_fts(message_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
66
+ END;
67
+
68
+ CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
69
+ INSERT INTO message_fts(message_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
70
+ INSERT INTO message_fts(rowid, content) VALUES (new.rowid, new.content);
71
+ END;
72
+
73
+ -- Extended memory entries (beyond MEMORY.md limit)
74
+ CREATE TABLE IF NOT EXISTS memories (
75
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
76
+ project TEXT,
77
+ target TEXT NOT NULL CHECK (target IN ('memory', 'user', 'failure')),
78
+ category TEXT CHECK (category IN ('failure', 'correction', 'insight', 'preference', 'convention', 'tool-quirk')),
79
+ content TEXT NOT NULL,
80
+ failure_reason TEXT,
81
+ tool_state TEXT,
82
+ corrected_to TEXT,
83
+ created DATE NOT NULL,
84
+ last_referenced DATE NOT NULL
85
+ );
86
+
87
+ -- FTS5 trigram index for memory substring search
88
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
89
+ content,
90
+ content='memories',
91
+ content_rowid='id',
92
+ tokenize='trigram'
93
+ );
94
+
95
+ -- Triggers to keep memory_fts in sync with memories table
96
+ CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
97
+ INSERT INTO memory_fts(rowid, content) VALUES (new.id, new.content);
98
+ END;
99
+
100
+ CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
101
+ INSERT INTO memory_fts(memory_fts, rowid, content) VALUES ('delete', old.id, old.content);
102
+ END;
103
+
104
+ CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
105
+ INSERT INTO memory_fts(memory_fts, rowid, content) VALUES ('delete', old.id, old.content);
106
+ INSERT INTO memory_fts(rowid, content) VALUES (new.id, new.content);
107
+ END;
108
+
109
+ -- Indexes for common queries
110
+ CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id);
111
+ CREATE INDEX IF NOT EXISTS idx_messages_timestamp ON messages(timestamp);
112
+ CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project);
113
+ CREATE INDEX IF NOT EXISTS idx_memories_target ON memories(target);
114
+ CREATE INDEX IF NOT EXISTS idx_memories_category ON memories(category);
115
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project);
116
+ CREATE INDEX IF NOT EXISTS idx_sessions_started_at ON sessions(started_at);
117
+ CREATE INDEX IF NOT EXISTS idx_session_files_session_id ON session_files(session_id);
118
+ `;
@@ -0,0 +1,472 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+
4
+ const DEFAULT_LIMIT = 50;
5
+ const MAX_LIMIT = 100;
6
+ const DEFAULT_MAX_FILES = 5000;
7
+ const DEFAULT_MAX_LINES = 500000;
8
+ const LIST_FIELDS = new Set(["all", "any", "exclude"]);
9
+ const VALUE_FIELDS = new Set(["from", "to", "cwd", "limit"]);
10
+
11
+ export interface SessionAnchorRange {
12
+ path: string;
13
+ startLine: number;
14
+ endLine: number;
15
+ sessionId?: string;
16
+ cwd?: string;
17
+ startTime?: string;
18
+ endTime?: string;
19
+ score?: number;
20
+ reason: string;
21
+ }
22
+
23
+ export interface SessionAnchorSearchResult {
24
+ success: boolean;
25
+ ranges: SessionAnchorRange[];
26
+ message?: string;
27
+ }
28
+
29
+ export interface SessionAnchorSearchOptions {
30
+ sessionsDir?: string;
31
+ maxFiles?: number;
32
+ maxLines?: number;
33
+ }
34
+
35
+ interface ParsedAnchorRequest {
36
+ from?: Date;
37
+ to?: Date;
38
+ cwd?: string;
39
+ limit: number;
40
+ all: string[];
41
+ any: string[];
42
+ exclude: string[];
43
+ hasTimeConstraint: boolean;
44
+ hasTextConstraint: boolean;
45
+ }
46
+
47
+ interface LineHit {
48
+ path: string;
49
+ lineNumber: number;
50
+ sessionId?: string;
51
+ cwd?: string;
52
+ timestamp?: string;
53
+ timestampMs?: number;
54
+ text: string;
55
+ score: number;
56
+ reason: string;
57
+ }
58
+
59
+ interface PendingRange {
60
+ path: string;
61
+ startLine: number;
62
+ endLine: number;
63
+ sessionId?: string;
64
+ cwd?: string;
65
+ startTime?: string;
66
+ endTime?: string;
67
+ score: number;
68
+ reason: string;
69
+ text: string;
70
+ }
71
+
72
+ export function searchSessionAnchors(
73
+ markdown: string,
74
+ options: SessionAnchorSearchOptions = {},
75
+ ): SessionAnchorSearchResult {
76
+ const parsed = parseMarkdownRequest(markdown);
77
+ if (!parsed.success) {
78
+ return { success: false, ranges: [], message: parsed.message };
79
+ }
80
+
81
+ if (!options.sessionsDir) {
82
+ return { success: false, ranges: [], message: "sessionsDir is required" };
83
+ }
84
+
85
+ if (!fs.existsSync(options.sessionsDir)) {
86
+ return { success: false, ranges: [], message: `sessionsDir does not exist: ${options.sessionsDir}` };
87
+ }
88
+
89
+ const files = findJsonlFiles(options.sessionsDir).sort();
90
+ const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
91
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
92
+ if (files.length > maxFiles) {
93
+ return {
94
+ success: false,
95
+ ranges: [],
96
+ message: `Request too broad: ${files.length} session files exceed the configured scan cap of ${maxFiles}. Add from/to, cwd, all, or any constraints.`,
97
+ };
98
+ }
99
+
100
+ const ranges: PendingRange[] = [];
101
+ let scannedLines = 0;
102
+
103
+ for (const file of files) {
104
+ const remainingLines = maxLines - scannedLines;
105
+ const fileResult = searchJsonlFile(file, parsed.request, remainingLines, scannedLines, maxLines);
106
+ if (!fileResult.success) {
107
+ return { success: false, ranges: [], message: fileResult.message };
108
+ }
109
+ scannedLines += fileResult.scannedLines;
110
+ ranges.push(...fileResult.ranges);
111
+ }
112
+
113
+ const filtered = ranges.filter((range) => !containsAny(range.text, parsed.request.exclude));
114
+ const sorted = sortRanges(filtered, parsed.request.hasTextConstraint);
115
+ const limited = sorted.slice(0, parsed.request.limit).map(({ text: _text, ...range }) => range);
116
+
117
+ return {
118
+ success: true,
119
+ ranges: limited,
120
+ message: limited.length === 0 ? "No matching session anchors found." : undefined,
121
+ };
122
+ }
123
+
124
+ function parseMarkdownRequest(markdown: string): { success: true; request: ParsedAnchorRequest } | { success: false; message: string } {
125
+ if (!markdown || markdown.trim().length === 0) {
126
+ return { success: false, message: "markdown is required" };
127
+ }
128
+
129
+ const fields = new Map<string, string>();
130
+ const lists: Record<"all" | "any" | "exclude", string[]> = { all: [], any: [], exclude: [] };
131
+ const seen = new Set<string>();
132
+ let currentList: "all" | "any" | "exclude" | null = null;
133
+
134
+ const lines = markdown.split(/\r?\n/);
135
+ for (const line of lines) {
136
+ const trimmed = line.trim();
137
+ if (trimmed.length === 0) continue;
138
+
139
+ const fieldMatch = /^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$/.exec(trimmed);
140
+ if (fieldMatch) {
141
+ const field = fieldMatch[1];
142
+ const value = fieldMatch[2];
143
+
144
+ if (!VALUE_FIELDS.has(field) && !LIST_FIELDS.has(field)) {
145
+ return {
146
+ success: false,
147
+ message: `Invalid field '${field}'. Supported fields: from, to, cwd, limit, all, any, exclude.`,
148
+ };
149
+ }
150
+ if (seen.has(field)) {
151
+ return { success: false, message: `Duplicate field '${field}'. Keep one value.` };
152
+ }
153
+ seen.add(field);
154
+
155
+ if (LIST_FIELDS.has(field)) {
156
+ if (value.trim().length > 0) {
157
+ return { success: false, message: `Invalid list section '${field}'. Use '${field}:' followed by '- item' lines.` };
158
+ }
159
+ currentList = field as "all" | "any" | "exclude";
160
+ } else {
161
+ fields.set(field, value.trim());
162
+ currentList = null;
163
+ }
164
+ continue;
165
+ }
166
+
167
+ const listMatch = /^-\s+(.*)$/.exec(trimmed);
168
+ if (listMatch && currentList) {
169
+ const term = listMatch[1].trim();
170
+ if (term.length === 0) {
171
+ return { success: false, message: `Empty term in '${currentList}'. Remove it or provide text.` };
172
+ }
173
+ lists[currentList].push(term);
174
+ continue;
175
+ }
176
+
177
+ if (listMatch && !currentList) {
178
+ return { success: false, message: "List item found outside all, any, or exclude section." };
179
+ }
180
+
181
+ return { success: false, message: `Invalid markdown line: ${trimmed}` };
182
+ }
183
+
184
+ const limitValue = fields.get("limit");
185
+ let limit = DEFAULT_LIMIT;
186
+ if (limitValue !== undefined) {
187
+ if (!/^\d+$/.test(limitValue)) {
188
+ return { success: false, message: "Invalid limit. Use a positive integer." };
189
+ }
190
+ const parsedLimit = Number(limitValue);
191
+ if (!Number.isSafeInteger(parsedLimit) || parsedLimit <= 0) {
192
+ return { success: false, message: "Invalid limit. Use a positive integer." };
193
+ }
194
+ limit = Math.min(parsedLimit, MAX_LIMIT);
195
+ }
196
+
197
+ const fromValue = fields.get("from");
198
+ const toValue = fields.get("to");
199
+ const from = fromValue === undefined ? undefined : parseDateTime(fromValue, "from");
200
+ if (from === null) return { success: false, message: "Invalid from. Use YYYY-MM-DD or an ISO timestamp." };
201
+ const to = toValue === undefined ? undefined : parseDateTime(toValue, "to");
202
+ if (to === null) return { success: false, message: "Invalid to. Use YYYY-MM-DD or an ISO timestamp." };
203
+ if (from && to && from.getTime() > to.getTime()) {
204
+ return { success: false, message: "Invalid time window. 'from' must be before or equal to 'to'." };
205
+ }
206
+
207
+ const cwd = fields.get("cwd");
208
+ if (fields.has("cwd") && (!cwd || cwd.trim().length === 0)) {
209
+ return { success: false, message: "Invalid cwd. Provide a non-empty path." };
210
+ }
211
+ const all = lists.all;
212
+ const any = lists.any;
213
+ const exclude = lists.exclude;
214
+ const hasTimeConstraint = Boolean(from || to);
215
+ const hasCwdConstraint = Boolean(cwd);
216
+ const hasTextConstraint = all.length > 0 || any.length > 0;
217
+
218
+ if (!hasTimeConstraint && !hasCwdConstraint && !hasTextConstraint) {
219
+ return {
220
+ success: false,
221
+ message: "Request needs at least one constraint: provide from/to, cwd, all, or any.",
222
+ };
223
+ }
224
+ return {
225
+ success: true,
226
+ request: { from, to, cwd, limit, all, any, exclude, hasTimeConstraint, hasTextConstraint },
227
+ };
228
+ }
229
+
230
+ function parseDateTime(value: string, boundary: "from" | "to"): Date | null {
231
+ const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
232
+ if (dateOnly) {
233
+ const year = Number(dateOnly[1]);
234
+ const month = Number(dateOnly[2]);
235
+ const day = Number(dateOnly[3]);
236
+ const date = boundary === "from"
237
+ ? new Date(year, month - 1, day, 0, 0, 0, 0)
238
+ : new Date(year, month - 1, day, 23, 59, 59, 999);
239
+ if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
240
+ return null;
241
+ }
242
+ return date;
243
+ }
244
+
245
+ const date = new Date(value);
246
+ return Number.isNaN(date.getTime()) ? null : date;
247
+ }
248
+
249
+ function findJsonlFiles(dir: string): string[] {
250
+ const files: string[] = [];
251
+
252
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
253
+ const fullPath = path.join(dir, entry.name);
254
+ if (entry.isDirectory()) {
255
+ files.push(...findJsonlFiles(fullPath));
256
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
257
+ files.push(fullPath);
258
+ }
259
+ }
260
+
261
+ return files;
262
+ }
263
+
264
+ function searchJsonlFile(
265
+ filePath: string,
266
+ request: ParsedAnchorRequest,
267
+ maxLines: number,
268
+ scannedBefore: number,
269
+ scanCap: number,
270
+ ): { success: true; ranges: PendingRange[]; scannedLines: number } | { success: false; message: string } {
271
+ const content = fs.readFileSync(filePath, "utf-8");
272
+ const lines = content.split(/\r?\n/);
273
+ const hits: LineHit[] = [];
274
+ let currentSessionId: string | undefined;
275
+ let currentCwd: string | undefined;
276
+
277
+ let scannedLines = 0;
278
+
279
+ for (let index = 0; index < lines.length; index += 1) {
280
+ const line = lines[index];
281
+ if (line.trim().length === 0) continue;
282
+
283
+ scannedLines += 1;
284
+ if (scannedLines > maxLines) {
285
+ return {
286
+ success: false,
287
+ message: `Request too broad: scanned ${scannedBefore + scannedLines} session lines, exceeding the configured scan cap of ${scanCap}. Add from/to, cwd, all, or any constraints.`,
288
+ };
289
+ }
290
+
291
+ let event: unknown;
292
+ try {
293
+ event = JSON.parse(line);
294
+ } catch {
295
+ return { success: false, message: `Invalid JSON in ${filePath}:${index + 1}` };
296
+ }
297
+
298
+ const sessionId = getSessionId(event) ?? currentSessionId;
299
+ if (sessionId) currentSessionId = sessionId;
300
+
301
+ const cwd = getCwd(event) ?? currentCwd;
302
+ if (cwd) currentCwd = cwd;
303
+
304
+ if (request.cwd && cwd !== request.cwd) continue;
305
+
306
+ const timestamp = getTimestamp(event);
307
+ const timestampMs = timestamp ? Date.parse(timestamp) : undefined;
308
+ const hasValidTimestamp = timestampMs !== undefined && !Number.isNaN(timestampMs);
309
+ if (request.hasTimeConstraint) {
310
+ if (!hasValidTimestamp) continue;
311
+ if (request.from && timestampMs < request.from.getTime()) continue;
312
+ if (request.to && timestampMs > request.to.getTime()) continue;
313
+ }
314
+
315
+ const text = textualizeEvent(event);
316
+ const termScore = scoreTerms(text, request);
317
+ const matchesTerms = request.hasTextConstraint ? termScore > 0 : true;
318
+ if (!matchesTerms) continue;
319
+
320
+ if (!request.hasTextConstraint && !hasValidTimestamp) continue;
321
+
322
+ hits.push({
323
+ path: filePath,
324
+ lineNumber: index + 1,
325
+ sessionId,
326
+ cwd,
327
+ timestamp: hasValidTimestamp ? timestamp : undefined,
328
+ timestampMs: hasValidTimestamp ? timestampMs : undefined,
329
+ text,
330
+ score: request.hasTextConstraint ? termScore : 1,
331
+ reason: buildReason(request, text),
332
+ });
333
+ }
334
+
335
+ return { success: true, ranges: mergeAdjacentHits(hits), scannedLines };
336
+ }
337
+
338
+ function mergeAdjacentHits(hits: LineHit[]): PendingRange[] {
339
+ const ranges: PendingRange[] = [];
340
+
341
+ for (const hit of hits) {
342
+ const last = ranges.at(-1);
343
+ if (last && last.path === hit.path && last.endLine + 1 === hit.lineNumber && last.reason === hit.reason) {
344
+ last.endLine = hit.lineNumber;
345
+ last.score += hit.score;
346
+ last.text += "\n" + hit.text;
347
+ last.sessionId ??= hit.sessionId;
348
+ last.cwd ??= hit.cwd;
349
+ if (!last.startTime && hit.timestamp) last.startTime = hit.timestamp;
350
+ if (hit.timestamp) last.endTime = hit.timestamp;
351
+ continue;
352
+ }
353
+
354
+ ranges.push({
355
+ path: hit.path,
356
+ startLine: hit.lineNumber,
357
+ endLine: hit.lineNumber,
358
+ sessionId: hit.sessionId,
359
+ cwd: hit.cwd,
360
+ startTime: hit.timestamp,
361
+ endTime: hit.timestamp,
362
+ score: hit.score,
363
+ reason: hit.reason,
364
+ text: hit.text,
365
+ });
366
+ }
367
+
368
+ return ranges;
369
+ }
370
+
371
+ function sortRanges(ranges: PendingRange[], textConstrained: boolean): PendingRange[] {
372
+ return [...ranges].sort((a, b) => {
373
+ if (textConstrained && b.score !== a.score) return b.score - a.score;
374
+ const timeCompare = Date.parse(a.startTime ?? "") - Date.parse(b.startTime ?? "");
375
+ if (!Number.isNaN(timeCompare) && timeCompare !== 0) return timeCompare;
376
+ const pathCompare = a.path.localeCompare(b.path);
377
+ if (pathCompare !== 0) return pathCompare;
378
+ return a.startLine - b.startLine;
379
+ });
380
+ }
381
+
382
+ function scoreTerms(text: string, request: ParsedAnchorRequest): number {
383
+ const lower = text.toLocaleLowerCase();
384
+ const matchedAll = request.all.filter((term) => lower.includes(term.toLocaleLowerCase()));
385
+ const matchedAny = request.any.filter((term) => lower.includes(term.toLocaleLowerCase()));
386
+
387
+ if (request.all.length > 0 && matchedAll.length !== request.all.length) return 0;
388
+ if (request.any.length > 0 && matchedAny.length === 0) return 0;
389
+
390
+ if (request.all.length === 0 && request.any.length === 0) return 1;
391
+ return matchedAll.length * 2 + matchedAny.length;
392
+ }
393
+
394
+ function buildReason(request: ParsedAnchorRequest, text: string): string {
395
+ if (!request.hasTextConstraint) {
396
+ if (request.hasTimeConstraint && request.cwd) return "cwd+time window";
397
+ if (request.hasTimeConstraint) return "time window";
398
+ return "cwd";
399
+ }
400
+
401
+ const lower = text.toLocaleLowerCase();
402
+ const parts: string[] = [];
403
+ if (request.all.length > 0) parts.push(`matched all: ${request.all.join(", ")}`);
404
+ const matchedAny = request.any.filter((term) => lower.includes(term.toLocaleLowerCase()));
405
+ if (matchedAny.length > 0) parts.push(`matched any: ${matchedAny.join(", ")}`);
406
+ return parts.join("; ");
407
+ }
408
+
409
+ function containsAny(text: string, terms: string[]): boolean {
410
+ const lower = text.toLocaleLowerCase();
411
+ return terms.some((term) => lower.includes(term.toLocaleLowerCase()));
412
+ }
413
+
414
+ function getTimestamp(event: unknown): string | undefined {
415
+ if (!isRecord(event)) return undefined;
416
+ if (typeof event.timestamp === "string") return event.timestamp;
417
+ if (isRecord(event.message) && typeof event.message.timestamp === "string") return event.message.timestamp;
418
+ return undefined;
419
+ }
420
+
421
+ function getSessionId(event: unknown): string | undefined {
422
+ if (!isRecord(event)) return undefined;
423
+ if (typeof event.sessionId === "string") return event.sessionId;
424
+ if (typeof event.session_id === "string") return event.session_id;
425
+ if (event.type === "session" && typeof event.id === "string") return event.id;
426
+ if (isRecord(event.session) && typeof event.session.id === "string") return event.session.id;
427
+ return undefined;
428
+ }
429
+
430
+ function getCwd(event: unknown): string | undefined {
431
+ if (!isRecord(event)) return undefined;
432
+ if (typeof event.cwd === "string") return event.cwd;
433
+ if (isRecord(event.session) && typeof event.session.cwd === "string") return event.session.cwd;
434
+ return undefined;
435
+ }
436
+
437
+ function textualizeEvent(event: unknown): string {
438
+ const parts: string[] = [];
439
+ collectStrings(event, parts);
440
+ return parts.join("\n");
441
+ }
442
+
443
+ const METADATA_TEXT_KEYS = new Set([
444
+ "type",
445
+ "id",
446
+ "parentId",
447
+ "sessionId",
448
+ "session_id",
449
+ "timestamp",
450
+ "cwd",
451
+ "role",
452
+ "customType",
453
+ ]);
454
+
455
+ function collectStrings(value: unknown, parts: string[], key?: string): void {
456
+ if (typeof value === "string") {
457
+ if (!key || !METADATA_TEXT_KEYS.has(key)) parts.push(value);
458
+ return;
459
+ }
460
+
461
+ if (Array.isArray(value)) {
462
+ for (const item of value) collectStrings(item, parts, key);
463
+ return;
464
+ }
465
+
466
+ if (!isRecord(value)) return;
467
+ for (const [childKey, item] of Object.entries(value)) collectStrings(item, parts, childKey);
468
+ }
469
+
470
+ function isRecord(value: unknown): value is Record<string, unknown> {
471
+ return typeof value === "object" && value !== null;
472
+ }