@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
@@ -1,102 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import type { ExtensionAPI } from "@selesai/code";
3
- import modelPromptInjector, {
4
- findRule,
5
- patternMatches,
6
- type InjectConfig,
7
- type InjectRule,
8
- type ModelRef,
9
- } from "./index.ts";
10
-
11
- const model = (provider: string, id: string, name = id): ModelRef => ({ provider, id, name });
12
-
13
- function harness(config: InjectConfig, activeModel: ModelRef | undefined) {
14
- const handlers: Record<string, Function> = {};
15
- const pi = {
16
- on: vi.fn((event: string, handler: Function) => {
17
- handlers[event] = handler;
18
- }),
19
- } as unknown as ExtensionAPI;
20
- const ctx = {
21
- model: activeModel,
22
- ui: { setStatus: vi.fn() },
23
- };
24
- modelPromptInjector(pi, config);
25
- return { handlers, ctx };
26
- }
27
-
28
- describe("patternMatches", () => {
29
- it("matches provider/model exactly, case-insensitively", () => {
30
- expect(patternMatches("deepseek/deepseek-v4-pro", model("DeepSeek", "deepseek-v4-pro"))).toBe(true);
31
- expect(patternMatches("deepseek/deepseek-v4-pro", model("openrouter", "deepseek-v4-pro"))).toBe(false);
32
- });
33
-
34
- it("supports wildcards in provider and id", () => {
35
- expect(patternMatches("*/deepseek-v4-pro", model("openrouter", "deepseek-v4-pro"))).toBe(true);
36
- expect(patternMatches("deepseek/*", model("deepseek", "deepseek-v4-pro"))).toBe(true);
37
- expect(patternMatches("deepseek/*", model("deepseek", "deepseek-chat"))).toBe(true);
38
- expect(patternMatches("deepseek/*", model("openai", "gpt-5"))).toBe(false);
39
- });
40
-
41
- it("bare patterns match id or display name", () => {
42
- expect(patternMatches("deepseek-v4-pro", model("deepseek", "deepseek-v4-pro"))).toBe(true);
43
- expect(patternMatches("gpt-*", model("openai", "gpt-5", "GPT-5"))).toBe(true);
44
- expect(patternMatches("deepseek*", model("deepseek", "deepseek-v4-pro"))).toBe(true);
45
- });
46
-
47
- it("matches * against any model", () => {
48
- expect(patternMatches("*", model("openai", "gpt-5"))).toBe(true);
49
- });
50
- });
51
-
52
- describe("findRule", () => {
53
- const rules: InjectRule[] = [
54
- { match: ["deepseek/*"], prompt: "A" },
55
- { match: ["*/deepseek-v4-pro"], prompt: "B" },
56
- { match: ["*"], prompt: "C", enabled: false },
57
- ];
58
-
59
- it("first matching rule wins", () => {
60
- expect(findRule(rules, model("deepseek", "deepseek-v4-pro"))?.prompt).toBe("A");
61
- expect(findRule(rules, model("openrouter", "deepseek-v4-pro"))?.prompt).toBe("B");
62
- });
63
-
64
- it("skips disabled rules", () => {
65
- expect(findRule([{ match: ["*"], prompt: "C", enabled: false }], model("openai", "gpt-5"))).toBeUndefined();
66
- });
67
- });
68
-
69
- describe("before_agent_start", () => {
70
- it("appends the rule prompt to the system prompt", async () => {
71
- const { handlers, ctx } = harness({ rules: [{ match: ["deepseek/*"], prompt: "EXTRA" }] }, model("deepseek", "deepseek-chat"));
72
- const result = await handlers["before_agent_start"]({ systemPrompt: "BASE" }, ctx);
73
- expect(result).toEqual({ systemPrompt: "BASE\n\nEXTRA" });
74
- });
75
-
76
- it("prepends the rule prompt to the top of the system prompt", async () => {
77
- const { handlers, ctx } = harness(
78
- { rules: [{ match: ["deepseek/*"], prompt: "TOP", mode: "prepend" }] },
79
- model("deepseek", "deepseek-chat"),
80
- );
81
- const result = await handlers["before_agent_start"]({ systemPrompt: "BASE" }, ctx);
82
- expect(result).toEqual({ systemPrompt: "TOP\n\nBASE" });
83
- });
84
-
85
- it("replaces the system prompt in replace mode", async () => {
86
- const { handlers, ctx } = harness(
87
- { rules: [{ match: ["deepseek/*"], prompt: "ONLY ME", mode: "replace" }] },
88
- model("deepseek", "deepseek-chat"),
89
- );
90
- const result = await handlers["before_agent_start"]({ systemPrompt: "BASE" }, ctx);
91
- expect(result).toEqual({ systemPrompt: "ONLY ME" });
92
- });
93
-
94
- it("returns undefined when no rule matches", async () => {
95
- const { handlers, ctx } = harness(
96
- { rules: [{ match: ["deepseek/*"], prompt: "EXTRA" }] },
97
- model("openai", "gpt-5"),
98
- );
99
- const result = await handlers["before_agent_start"]({ systemPrompt: "BASE" }, ctx);
100
- expect(result).toBeUndefined();
101
- });
102
- });
@@ -1,148 +0,0 @@
1
- /**
2
- * model-prompt-injector
3
- *
4
- * Injects a model-specific prompt into the system prompt whenever the active
5
- * model matches a rule in config.json (sibling of this file).
6
- *
7
- * Config lives only here in host code — nothing is exposed to user settings.
8
- * `/reload` re-reads it.
9
- *
10
- * config.json format:
11
- * {
12
- * "rules": [
13
- * {
14
- * "match": ["provider/model", "provider/*", "<any>/model", "bare-name", "*"],
15
- * "mode": "prepend" | "append" | "replace", // default: "append"
16
- * "prompt": "...",
17
- * "enabled": true // default: true
18
- * }
19
- * ]
20
- * }
21
- *
22
- * Matching (case-insensitive, "*" is a wildcard):
23
- * - "provider/model" — glob against `<provider>/<id>`.
24
- * - bare pattern (no "/") — glob against model id OR display name.
25
- * - First matching rule wins; later rules are ignored.
26
- * - mode "prepend" puts the prompt at the very top of the system prompt;
27
- * mode "append" adds it to the end; mode "replace" replaces the whole system prompt.
28
- */
29
- import { readFileSync } from "node:fs";
30
- import { dirname, join } from "node:path";
31
- import { fileURLToPath } from "node:url";
32
- import type { ExtensionAPI } from "@selesai/code";
33
-
34
- export type InjectMode = "prepend" | "append" | "replace";
35
-
36
- export interface InjectRule {
37
- match: string[];
38
- prompt: string;
39
- mode?: InjectMode;
40
- enabled?: boolean;
41
- }
42
-
43
- export interface InjectConfig {
44
- rules: InjectRule[];
45
- }
46
-
47
- /** Model shape needed for matching (structurally compatible with pi's Model). */
48
- export interface ModelRef {
49
- provider: string;
50
- id: string;
51
- name: string;
52
- }
53
-
54
- const CONFIG_PATH = join(dirname(fileURLToPath(import.meta.url)), "config.json");
55
-
56
- function globToRegex(glob: string): RegExp {
57
- const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
58
- return new RegExp(`^${escaped}$`, "i");
59
- }
60
-
61
- export function patternMatches(pattern: string, model: ModelRef): boolean {
62
- const slash = pattern.indexOf("/");
63
- if (slash >= 0) {
64
- return (
65
- globToRegex(pattern.slice(0, slash)).test(model.provider) &&
66
- globToRegex(pattern.slice(slash + 1)).test(model.id)
67
- );
68
- }
69
- return globToRegex(pattern).test(model.id) || globToRegex(pattern).test(model.name);
70
- }
71
-
72
- export function findRule(rules: readonly InjectRule[], model: ModelRef): InjectRule | undefined {
73
- for (const rule of rules) {
74
- if (rule.enabled === false) continue;
75
- if (rule.match.some((pattern) => patternMatches(pattern, model))) return rule;
76
- }
77
- return undefined;
78
- }
79
-
80
- export function loadConfig(path: string = CONFIG_PATH): InjectConfig {
81
- const fail = (message: string): InjectConfig => {
82
- console.error(`[model-prompt-injector] ${message}`);
83
- return { rules: [] };
84
- };
85
-
86
- let raw: unknown;
87
- try {
88
- raw = JSON.parse(readFileSync(path, "utf8"));
89
- } catch (error) {
90
- return fail(`cannot load ${path}: ${(error as Error).message}`);
91
- }
92
-
93
- if (typeof raw !== "object" || raw === null || !Array.isArray((raw as { rules?: unknown }).rules)) {
94
- return fail(`invalid config: expected { "rules": [...] }`);
95
- }
96
-
97
- const rules: InjectRule[] = [];
98
- for (const [index, entry] of (raw as { rules: unknown[] }).rules.entries()) {
99
- const label = `rule #${index + 1}`;
100
- if (typeof entry !== "object" || entry === null) {
101
- console.error(`[model-prompt-injector] ${label} is not an object — skipped`);
102
- continue;
103
- }
104
- const rule = entry as Partial<InjectRule>;
105
- if (!Array.isArray(rule.match) || rule.match.length === 0 || !rule.match.every((m) => typeof m === "string")) {
106
- console.error(`[model-prompt-injector] ${label} needs a non-empty "match" string array — skipped`);
107
- continue;
108
- }
109
- if (typeof rule.prompt !== "string" || rule.prompt.trim() === "") {
110
- console.error(`[model-prompt-injector] ${label} needs a non-empty "prompt" string — skipped`);
111
- continue;
112
- }
113
- if (rule.mode !== undefined && rule.mode !== "prepend" && rule.mode !== "append" && rule.mode !== "replace") {
114
- console.error(`[model-prompt-injector] ${label}: "mode" must be "prepend", "append" or "replace" — skipped`);
115
- continue;
116
- }
117
- rules.push({
118
- match: rule.match,
119
- prompt: rule.prompt,
120
- mode: rule.mode ?? "append",
121
- enabled: rule.enabled ?? true,
122
- });
123
- }
124
- return { rules };
125
- }
126
-
127
- export default function modelPromptInjector(pi: ExtensionAPI, config: InjectConfig = loadConfig()) {
128
- const ruleFor = (model: ModelRef | undefined): InjectRule | undefined =>
129
- model ? findRule(config.rules, model) : undefined;
130
-
131
- const active = config.rules.filter((rule) => rule.enabled !== false);
132
- if (active.length > 0) {
133
- console.log(
134
- `[model-prompt-injector] ${active.length} rule(s) active: ${active
135
- .map((rule) => rule.match.join(","))
136
- .join(" | ")}`,
137
- );
138
- }
139
-
140
- pi.on("before_agent_start", async (event, ctx) => {
141
- const rule = ruleFor(ctx.model);
142
- if (!rule) return;
143
- if (rule.mode === "replace") return { systemPrompt: rule.prompt };
144
- if (rule.mode === "prepend") return { systemPrompt: `${rule.prompt}\n\n${event.systemPrompt}` };
145
- return { systemPrompt: `${event.systemPrompt}\n\n${rule.prompt}` };
146
- });
147
-
148
- }
@@ -1,98 +0,0 @@
1
- import type { ResolvedTurnBudget, TurnBudgetState } from "../../shared/types.ts";
2
-
3
- export const DEFAULT_TURN_BUDGET_GRACE_TURNS = 1;
4
-
5
- export function resolveTurnBudgetConfig(
6
- raw: unknown,
7
- label = "turnBudget",
8
- ): { turnBudget?: ResolvedTurnBudget; error?: string } {
9
- if (raw === undefined) return {};
10
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
11
- return { error: `${label} must be an object with maxTurns and optional graceTurns.` };
12
- }
13
- const unknownField = Object.keys(raw).find((key) => key !== "maxTurns" && key !== "graceTurns");
14
- if (unknownField) return { error: `${label}.${unknownField} is not supported.` };
15
- const budget = raw as { maxTurns?: unknown; graceTurns?: unknown };
16
- if (typeof budget.maxTurns !== "number" || !Number.isInteger(budget.maxTurns) || budget.maxTurns < 1) {
17
- return { error: `${label}.maxTurns must be an integer >= 1.` };
18
- }
19
- const graceTurns = budget.graceTurns ?? DEFAULT_TURN_BUDGET_GRACE_TURNS;
20
- if (typeof graceTurns !== "number" || !Number.isInteger(graceTurns) || graceTurns < 0) {
21
- return { error: `${label}.graceTurns must be an integer >= 0.` };
22
- }
23
- return { turnBudget: { maxTurns: budget.maxTurns, graceTurns } };
24
- }
25
-
26
- export function appendTurnBudgetSystemPrompt(systemPrompt: string, budget: { maxTurns: number; graceTurns?: number } | undefined): string {
27
- if (!budget) return systemPrompt;
28
- const graceTurns = budget.graceTurns ?? DEFAULT_TURN_BUDGET_GRACE_TURNS;
29
- const grace = graceTurns === 1 ? "1 additional assistant turn" : `${graceTurns} additional assistant turns`;
30
- const block = [
31
- "## Turn budget",
32
- `This child run has a soft budget of ${budget.maxTurns} assistant turn${budget.maxTurns === 1 ? "" : "s"}.`,
33
- `After that, ${grace} may be allowed only for a final wrap-up.`,
34
- "When you approach or reach the soft budget, stop starting new tool work and return the final answer immediately.",
35
- "This runner uses process-mode execution, so live steering after launch may be unavailable; treat this instruction as the wrap-up request.",
36
- "If you continue past the soft budget plus grace turns, the supervisor may abort the process and return only partial output.",
37
- ].join("\n");
38
- return systemPrompt.trim() ? `${systemPrompt.trim()}\n\n${block}` : block;
39
- }
40
-
41
- export function turnBudgetSoftNote(budget: ResolvedTurnBudget, turnCount: number): string {
42
- return `Turn budget wrap-up was requested after ${turnCount} assistant turn${turnCount === 1 ? "" : "s"} (soft limit ${budget.maxTurns}, grace ${budget.graceTurns}). Process-mode live steering is unavailable, so the child was warned at launch to wrap up by this budget. Output may be partial.`;
43
- }
44
-
45
- export function turnBudgetExceededMessage(budget: ResolvedTurnBudget, turnCount: number): string {
46
- return `Subagent exceeded turn budget after ${turnCount} assistant turn${turnCount === 1 ? "" : "s"} (soft limit ${budget.maxTurns} + grace ${budget.graceTurns}).`;
47
- }
48
-
49
- export function turnBudgetDeferredNote(budget: ResolvedTurnBudget, turnCount: number): string {
50
- return `Turn-budget termination was deferred at ${turnCount} assistant turn${turnCount === 1 ? "" : "s"} (soft limit ${budget.maxTurns} + grace ${budget.graceTurns}) because the assistant started tool work. The run ended before another safe assistant boundary; output may be partial.`;
51
- }
52
-
53
- export function formatTurnBudgetOutput(message: string, output: string): string {
54
- return output.trim()
55
- ? `${message}\n\nPartial output before turn-budget abort:\n${output}`
56
- : message;
57
- }
58
-
59
- export function initialTurnBudgetState(budget: { maxTurns: number; graceTurns?: number }): TurnBudgetState {
60
- return { maxTurns: budget.maxTurns, graceTurns: budget.graceTurns ?? DEFAULT_TURN_BUDGET_GRACE_TURNS, outcome: "within-budget", turnCount: 0 };
61
- }
62
-
63
- export function turnBudgetState(budget: ResolvedTurnBudget, turnCount: number, exceeded: boolean): TurnBudgetState {
64
- return {
65
- ...budget,
66
- turnCount,
67
- outcome: exceeded ? "exceeded" : "wrap-up-requested",
68
- wrapUpRequestedAtTurn: budget.maxTurns,
69
- ...(exceeded ? { exceededAtTurn: turnCount } : {}),
70
- };
71
- }
72
-
73
- export function turnBudgetDeferredState(
74
- budget: ResolvedTurnBudget,
75
- turnCount: number,
76
- terminationDeferredAtTurn = turnCount,
77
- ): TurnBudgetState {
78
- return {
79
- ...budget,
80
- turnCount,
81
- outcome: "termination-deferred",
82
- wrapUpRequestedAtTurn: budget.maxTurns,
83
- terminationDeferredAtTurn,
84
- };
85
- }
86
-
87
- export function turnBudgetDecision(
88
- budget: ResolvedTurnBudget,
89
- turnCount: number,
90
- terminalAssistantStop: boolean,
91
- toolWorkActiveOrStarting: boolean,
92
- enforceHardLimit = false,
93
- ): "continue" | "defer" | "abort" {
94
- const hardLimit = budget.maxTurns + budget.graceTurns;
95
- if (terminalAssistantStop || turnCount < hardLimit) return "continue";
96
- if (toolWorkActiveOrStarting && !enforceHardLimit) return "defer";
97
- return "abort";
98
- }
@@ -1,250 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import type { ResolvedTurnBudget, TurnBudgetState } from "../../src/shared/types.ts";
4
- import {
5
- DEFAULT_TURN_BUDGET_GRACE_TURNS,
6
- appendTurnBudgetSystemPrompt,
7
- formatTurnBudgetOutput,
8
- initialTurnBudgetState,
9
- resolveTurnBudgetConfig,
10
- turnBudgetDecision,
11
- turnBudgetDeferredState,
12
- turnBudgetExceededMessage,
13
- turnBudgetSoftNote,
14
- turnBudgetState,
15
- } from "../../src/runs/shared/turn-budget.ts";
16
-
17
- function budget(overrides: Partial<ResolvedTurnBudget> = {}): ResolvedTurnBudget {
18
- return { maxTurns: 3, graceTurns: 1, ...overrides };
19
- }
20
-
21
- describe("turn-budget module", () => {
22
- describe("DEFAULT_TURN_BUDGET_GRACE_TURNS", () => {
23
- it("defaults to one grace turn", () => {
24
- assert.equal(DEFAULT_TURN_BUDGET_GRACE_TURNS, 1);
25
- });
26
- });
27
-
28
- describe("resolveTurnBudgetConfig", () => {
29
- it("resolves valid budgets and supplies the default grace turn", () => {
30
- assert.deepEqual(resolveTurnBudgetConfig({ maxTurns: 5 }), {
31
- turnBudget: { maxTurns: 5, graceTurns: 1 },
32
- });
33
- assert.deepEqual(resolveTurnBudgetConfig({ maxTurns: 5, graceTurns: 0 }), {
34
- turnBudget: { maxTurns: 5, graceTurns: 0 },
35
- });
36
- });
37
-
38
- it("rejects invalid values with the caller's boundary label", () => {
39
- assert.deepEqual(resolveTurnBudgetConfig({ maxTurns: 0 }, "agent.turnBudget"), {
40
- error: "agent.turnBudget.maxTurns must be an integer >= 1.",
41
- });
42
- assert.deepEqual(resolveTurnBudgetConfig({ maxTurns: 2, graceTurns: -1 }), {
43
- error: "turnBudget.graceTurns must be an integer >= 0.",
44
- });
45
- assert.deepEqual(resolveTurnBudgetConfig({ maxTurns: 2, extra: true }), {
46
- error: "turnBudget.extra is not supported.",
47
- });
48
- });
49
- });
50
-
51
- describe("appendTurnBudgetSystemPrompt", () => {
52
- it("returns the system prompt unchanged when no budget is configured", () => {
53
- assert.equal(appendTurnBudgetSystemPrompt("You are a reviewer.", undefined), "You are a reviewer.");
54
- assert.equal(appendTurnBudgetSystemPrompt("", undefined), "");
55
- });
56
-
57
- it("appends a turn-budget block after an existing system prompt", () => {
58
- const result = appendTurnBudgetSystemPrompt("You are a reviewer.", budget({ maxTurns: 5, graceTurns: 2 }));
59
- assert.ok(result.startsWith("You are a reviewer.\n\n## Turn budget\n"), result);
60
- assert.match(result, /soft budget of 5 assistant turns/);
61
- assert.match(result, /2 additional assistant turns/);
62
- });
63
-
64
- it("emits only the turn-budget block when the system prompt is empty", () => {
65
- const result = appendTurnBudgetSystemPrompt(" ", budget({ maxTurns: 5, graceTurns: 1 }));
66
- assert.ok(result.startsWith("## Turn budget\n"), result);
67
- assert.equal(result.includes("You are"), false);
68
- });
69
-
70
- it("trims surrounding whitespace from the existing system prompt before joining", () => {
71
- const result = appendTurnBudgetSystemPrompt("\nYou are a reviewer.\n\n", budget());
72
- assert.ok(result.startsWith("You are a reviewer.\n\n## Turn budget\n"), result);
73
- assert.equal(result.startsWith("\n"), false);
74
- });
75
-
76
- it("pluralizes maxTurns for single and multiple turns", () => {
77
- assert.match(appendTurnBudgetSystemPrompt("", budget({ maxTurns: 1 })), /soft budget of 1 assistant turn\./);
78
- assert.match(appendTurnBudgetSystemPrompt("", budget({ maxTurns: 4 })), /soft budget of 4 assistant turns\./);
79
- });
80
-
81
- it("pluralizes graceTurns for one versus many or zero", () => {
82
- assert.match(appendTurnBudgetSystemPrompt("", budget({ graceTurns: 1 })), /1 additional assistant turn may be allowed/);
83
- assert.match(appendTurnBudgetSystemPrompt("", budget({ graceTurns: 0 })), /0 additional assistant turns may be allowed/);
84
- assert.match(appendTurnBudgetSystemPrompt("", budget({ graceTurns: 3 })), /3 additional assistant turns may be allowed/);
85
- });
86
- });
87
-
88
- describe("turnBudgetSoftNote", () => {
89
- it("describes the wrap-up request with soft limit and grace values", () => {
90
- assert.equal(
91
- turnBudgetSoftNote(budget({ maxTurns: 3, graceTurns: 1 }), 3),
92
- "Turn budget wrap-up was requested after 3 assistant turns (soft limit 3, grace 1). Process-mode live steering is unavailable, so the child was warned at launch to wrap up by this budget. Output may be partial.",
93
- );
94
- });
95
-
96
- it("pluralizes the turn count", () => {
97
- assert.match(turnBudgetSoftNote(budget(), 1), /requested after 1 assistant turn\b/);
98
- assert.match(turnBudgetSoftNote(budget(), 5), /requested after 5 assistant turns\b/);
99
- });
100
- });
101
-
102
- describe("turnBudgetExceededMessage", () => {
103
- it("describes the hard abort with soft limit plus grace", () => {
104
- assert.equal(
105
- turnBudgetExceededMessage(budget({ maxTurns: 3, graceTurns: 1 }), 4),
106
- "Subagent exceeded turn budget after 4 assistant turns (soft limit 3 + grace 1).",
107
- );
108
- });
109
-
110
- it("pluralizes the turn count", () => {
111
- assert.match(turnBudgetExceededMessage(budget(), 1), /after 1 assistant turn\b/);
112
- assert.match(turnBudgetExceededMessage(budget(), 3), /after 3 assistant turns\b/);
113
- });
114
- });
115
-
116
- describe("formatTurnBudgetOutput", () => {
117
- it("prefixes the abort message onto captured partial output", () => {
118
- assert.equal(
119
- formatTurnBudgetOutput("ABORT", "partial work"),
120
- "ABORT\n\nPartial output before turn-budget abort:\npartial work",
121
- );
122
- });
123
-
124
- it("preserves multi-line partial output verbatim", () => {
125
- assert.equal(
126
- formatTurnBudgetOutput("ABORT", "line1\nline2\nline3"),
127
- "ABORT\n\nPartial output before turn-budget abort:\nline1\nline2\nline3",
128
- );
129
- });
130
-
131
- it("returns only the message when there is no partial output", () => {
132
- assert.equal(formatTurnBudgetOutput("ABORT", ""), "ABORT");
133
- assert.equal(formatTurnBudgetOutput("ABORT", " \n "), "ABORT");
134
- });
135
- });
136
-
137
- describe("initialTurnBudgetState", () => {
138
- it("starts within budget at turn zero and copies the resolved budget", () => {
139
- const state = initialTurnBudgetState(budget({ maxTurns: 5, graceTurns: 2 }));
140
- const expected: TurnBudgetState = { maxTurns: 5, graceTurns: 2, outcome: "within-budget", turnCount: 0 };
141
- assert.deepEqual(state, expected);
142
- assert.equal("wrapUpRequestedAtTurn" in state, false);
143
- assert.equal("exceededAtTurn" in state, false);
144
- });
145
- });
146
-
147
- describe("turnBudgetState", () => {
148
- it("marks a wrap-up request without recording an exceeded turn", () => {
149
- const state = turnBudgetState(budget({ maxTurns: 3, graceTurns: 1 }), 3, false);
150
- assert.deepEqual(state, {
151
- maxTurns: 3,
152
- graceTurns: 1,
153
- turnCount: 3,
154
- outcome: "wrap-up-requested",
155
- wrapUpRequestedAtTurn: 3,
156
- });
157
- assert.equal("exceededAtTurn" in state, false);
158
- });
159
-
160
- it("marks an exceeded budget and records the turn where it happened", () => {
161
- const state = turnBudgetState(budget({ maxTurns: 3, graceTurns: 1 }), 4, true);
162
- assert.deepEqual(state, {
163
- maxTurns: 3,
164
- graceTurns: 1,
165
- turnCount: 4,
166
- outcome: "exceeded",
167
- wrapUpRequestedAtTurn: 3,
168
- exceededAtTurn: 4,
169
- });
170
- });
171
-
172
- it("records the wrap-up request at the soft-limit turn even when exceeded", () => {
173
- const state = turnBudgetState(budget({ maxTurns: 2, graceTurns: 0 }), 2, true);
174
- assert.equal(state.wrapUpRequestedAtTurn, 2);
175
- assert.equal(state.exceededAtTurn, 2);
176
- assert.equal(state.outcome, "exceeded");
177
- });
178
- });
179
-
180
- describe("turnBudgetDecision", () => {
181
- it("continues for a clean terminal response even after the hard limit", () => {
182
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 3, false, false), "continue");
183
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 4, true, false), "continue");
184
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 5, true, false), "continue");
185
- });
186
-
187
- it("defers termination when the hard-limit assistant response starts tool work", () => {
188
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 4, false, true), "defer");
189
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 5, false, true), "defer");
190
- });
191
-
192
- it("enforces the hard limit for strict foreground delegation but allows terminal completion", () => {
193
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 4, false, true, true), "abort");
194
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 4, true, true, true), "continue");
195
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 5, true, true, true), "continue");
196
- });
197
-
198
- it("aborts non-terminal work at the next safe assistant boundary", () => {
199
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 4, false, false), "abort");
200
- assert.equal(turnBudgetDecision(budget({ maxTurns: 3, graceTurns: 1 }), 5, false, false), "abort");
201
- });
202
-
203
- it("allows a terminal response at the soft limit when grace turns are zero", () => {
204
- assert.equal(turnBudgetDecision(budget({ maxTurns: 2, graceTurns: 0 }), 2, true, false), "continue");
205
- assert.equal(turnBudgetDecision(budget({ maxTurns: 2, graceTurns: 0 }), 2, false, false), "abort");
206
- });
207
- });
208
-
209
- describe("turnBudgetDeferredState", () => {
210
- it("records the exact turn where hard termination became pending", () => {
211
- assert.deepEqual(turnBudgetDeferredState(budget({ maxTurns: 2, graceTurns: 0 }), 2), {
212
- maxTurns: 2,
213
- graceTurns: 0,
214
- turnCount: 2,
215
- outcome: "termination-deferred",
216
- wrapUpRequestedAtTurn: 2,
217
- terminationDeferredAtTurn: 2,
218
- });
219
- });
220
- });
221
-
222
- describe("turn budget state transitions", () => {
223
- it("walks within-budget -> wrap-up-requested -> exceeded as turns accrue", () => {
224
- const resolved = budget({ maxTurns: 3, graceTurns: 1 });
225
-
226
- const start = initialTurnBudgetState(resolved);
227
- assert.equal(start.outcome, "within-budget");
228
- assert.equal(start.turnCount, 0);
229
-
230
- const warning = turnBudgetState(resolved, resolved.maxTurns, false);
231
- assert.equal(warning.outcome, "wrap-up-requested");
232
- assert.equal(warning.turnCount, resolved.maxTurns);
233
- assert.equal(warning.wrapUpRequestedAtTurn, resolved.maxTurns);
234
- assert.equal("exceededAtTurn" in warning, false);
235
-
236
- const exceeded = turnBudgetState(resolved, resolved.maxTurns + resolved.graceTurns, true);
237
- assert.equal(exceeded.outcome, "exceeded");
238
- assert.equal(exceeded.turnCount, resolved.maxTurns + resolved.graceTurns);
239
- assert.equal(exceeded.exceededAtTurn, resolved.maxTurns + resolved.graceTurns);
240
- assert.equal(exceeded.wrapUpRequestedAtTurn, resolved.maxTurns);
241
- });
242
-
243
- it("can represent immediate exceedance when grace turns are zero", () => {
244
- const resolved = budget({ maxTurns: 2, graceTurns: 0 });
245
- const exceeded = turnBudgetState(resolved, resolved.maxTurns, true);
246
- assert.equal(exceeded.outcome, "exceeded");
247
- assert.equal(exceeded.exceededAtTurn, resolved.maxTurns);
248
- });
249
- });
250
- });