@hassangameryt/freeflow 0.7.3 → 0.9.0

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 (426) hide show
  1. package/.agents/plugins/marketplace.json +2 -2
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +2 -2
  4. package/.codex-plugin/plugin.json +3 -3
  5. package/.cursor-plugin/plugin.json +1 -1
  6. package/NOTICE +14 -0
  7. package/README.md +101 -159
  8. package/capabilities/cognitive-routing/SKILL.md +47 -210
  9. package/capabilities/cognitive-routing/references/coordinator.md +163 -0
  10. package/capabilities/cognitive-routing/references/worker.md +38 -0
  11. package/capabilities/compaction/SKILL.md +49 -0
  12. package/capabilities/compaction/references/summary-format.md +250 -0
  13. package/capabilities/messaging/SKILL.md +49 -0
  14. package/capabilities/task-memory/SKILL.md +87 -0
  15. package/command-surface.json +8 -52
  16. package/gemini-extension.json +1 -1
  17. package/hooks/claude/hooks.json +13 -3
  18. package/hooks/codex/hooks.json +2 -1
  19. package/hooks/shared/runtime-context.mjs +73 -83
  20. package/licenses/Apache-2.0.txt +55 -0
  21. package/package.json +42 -12
  22. package/pi-extension/README.md +41 -0
  23. package/pi-extension/dist/ask-user/ask.js +108 -0
  24. package/pi-extension/dist/ask-user/form.js +250 -0
  25. package/pi-extension/dist/ask-user/questions.js +66 -0
  26. package/pi-extension/dist/ask-user/tool.js +124 -0
  27. package/pi-extension/dist/cognitive-routing/assembler.js +417 -0
  28. package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
  29. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +8 -6
  30. package/pi-extension/dist/cognitive-routing/economics.js +45 -0
  31. package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
  32. package/pi-extension/dist/{session-sources/events.js → cognitive-routing/event-store.js} +107 -41
  33. package/pi-extension/dist/cognitive-routing/gate.js +256 -0
  34. package/pi-extension/dist/cognitive-routing/handlers.js +901 -0
  35. package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
  36. package/pi-extension/dist/cognitive-routing/model-control.js +476 -0
  37. package/pi-extension/dist/cognitive-routing/projection.js +520 -0
  38. package/pi-extension/dist/cognitive-routing/provenance.js +83 -0
  39. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +15 -15
  40. package/pi-extension/dist/cognitive-routing/runtime.js +398 -0
  41. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +47 -11
  42. package/pi-extension/dist/cognitive-routing/session.js +223 -0
  43. package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +126 -87
  44. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +55 -386
  45. package/pi-extension/dist/cognitive-routing/status.js +81 -0
  46. package/pi-extension/dist/cognitive-routing/tools.js +74 -0
  47. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +14 -11
  48. package/pi-extension/dist/compaction/carry.js +205 -0
  49. package/pi-extension/dist/compaction/config.js +24 -0
  50. package/pi-extension/dist/compaction/controller.js +494 -0
  51. package/pi-extension/dist/compaction/harness.js +93 -0
  52. package/pi-extension/dist/compaction/results.js +159 -0
  53. package/pi-extension/dist/compaction/thresholds.js +35 -0
  54. package/pi-extension/dist/compaction/tool.js +57 -0
  55. package/pi-extension/dist/host/branch.js +13 -0
  56. package/pi-extension/dist/host/canonical.js +12 -0
  57. package/pi-extension/dist/host/catalog.js +62 -0
  58. package/pi-extension/dist/host/config-validation.js +233 -0
  59. package/pi-extension/dist/host/config.js +361 -0
  60. package/pi-extension/dist/host/inbound.js +142 -0
  61. package/pi-extension/dist/host/projection-tags.js +60 -0
  62. package/pi-extension/dist/host/prompts.js +93 -0
  63. package/pi-extension/dist/{session-sources → host}/read-only-session.js +117 -1
  64. package/pi-extension/dist/{runtime → host}/request-history.js +125 -39
  65. package/pi-extension/dist/host/rows.js +25 -0
  66. package/pi-extension/dist/host/runtime-state.js +204 -0
  67. package/pi-extension/dist/host/section-keeper.js +85 -0
  68. package/pi-extension/dist/host/settings/freeflow-command.js +2323 -0
  69. package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +205 -73
  70. package/pi-extension/dist/host/staging.js +28 -0
  71. package/pi-extension/dist/host/status.js +115 -0
  72. package/pi-extension/dist/index.js +1004 -188
  73. package/pi-extension/dist/messaging/config.js +27 -0
  74. package/pi-extension/dist/messaging/messaging.js +398 -0
  75. package/pi-extension/dist/messaging/registry.js +57 -0
  76. package/pi-extension/dist/messaging/render.js +90 -0
  77. package/pi-extension/dist/messaging/tools.js +109 -0
  78. package/pi-extension/dist/messaging/transport.js +101 -0
  79. package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
  80. package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
  81. package/pi-extension/dist/provider-support/cache/health.js +73 -0
  82. package/pi-extension/dist/provider-support/cache/index.js +58 -0
  83. package/pi-extension/dist/provider-support/cache/keepalive.js +222 -0
  84. package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
  85. package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
  86. package/pi-extension/dist/provider-support/effort.js +13 -0
  87. package/pi-extension/dist/provider-support/index.js +44 -3
  88. package/pi-extension/dist/provider-support/{astra → openai}/adapter.js +46 -29
  89. package/pi-extension/dist/provider-support/{astra → openai}/history.js +10 -8
  90. package/pi-extension/dist/provider-support/{astra → openai}/index.js +8 -4
  91. package/pi-extension/dist/provider-support/{astra → openai}/session-state.js +29 -38
  92. package/pi-extension/dist/provider-support/routes.js +17 -0
  93. package/pi-extension/dist/task-memory/config.js +49 -0
  94. package/pi-extension/dist/task-memory/registry.js +119 -0
  95. package/pi-extension/dist/task-memory/render.js +107 -0
  96. package/pi-extension/dist/task-memory/runtime.js +994 -0
  97. package/pi-extension/dist/task-memory/tool.js +233 -0
  98. package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
  99. package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
  100. package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
  101. package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
  102. package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
  103. package/pi-extension/dist/tool-execution/background.js +430 -0
  104. package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
  105. package/pi-extension/dist/tool-execution/config.js +33 -0
  106. package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
  107. package/pi-extension/dist/tool-execution/file-state.js +138 -0
  108. package/pi-extension/dist/tool-execution/file-tracking.js +163 -0
  109. package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
  110. package/pi-extension/dist/tool-execution/prompt.js +54 -0
  111. package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
  112. package/pi-extension/dist/tool-execution/tools.js +16 -0
  113. package/pi-extension/freeflow/README.md +9 -0
  114. package/runtime/prompts/cognitive-routing.md +4 -4
  115. package/runtime/prompts/core.md +29 -72
  116. package/runtime/prompts/interaction-contract.md +18 -8
  117. package/runtime/prompts/tool-execution.md +25 -0
  118. package/runtime/prompts/working-method.md +25 -0
  119. package/skills/deliver-work/SKILL.md +76 -0
  120. package/skills/deliver-work/references/branch-closeout.md +134 -0
  121. package/skills/deliver-work/references/commit.md +83 -0
  122. package/skills/deliver-work/references/launch.md +94 -0
  123. package/skills/deliver-work/references/release.md +85 -0
  124. package/skills/deliver-work/scripts/commit-state.mjs +139 -0
  125. package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
  126. package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
  127. package/skills/deliver-work/scripts/push-route.mjs +122 -0
  128. package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
  129. package/skills/discuss-work/SKILL.md +148 -0
  130. package/skills/{discuss → discuss-work}/references/discussion-continuity.md +13 -50
  131. package/skills/discuss-work/references/learning-action.md +36 -0
  132. package/skills/discuss-work/scripts/choices.mjs +101 -0
  133. package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
  134. package/skills/evaluate-behavior/SKILL.md +107 -0
  135. package/skills/evaluate-behavior/references/authoring.md +381 -0
  136. package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
  137. package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
  138. package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
  139. package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
  140. package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
  141. package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
  142. package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
  143. package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
  144. package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
  145. package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
  146. package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
  147. package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
  148. package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
  149. package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
  150. package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
  151. package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
  152. package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
  153. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
  154. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
  155. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
  156. package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
  157. package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
  158. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
  159. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
  160. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
  161. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
  162. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
  163. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
  164. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
  165. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
  166. package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
  167. package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
  168. package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
  169. package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
  170. package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
  171. package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
  172. package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
  173. package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
  174. package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
  175. package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
  176. package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
  177. package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
  178. package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
  179. package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
  180. package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
  181. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
  182. package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
  183. package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
  184. package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
  185. package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
  186. package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
  187. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +121 -14
  188. package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
  189. package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
  190. package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
  191. package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
  192. package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
  193. package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
  194. package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +239 -262
  195. package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
  196. package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
  197. package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
  198. package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
  199. package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +218 -11
  200. package/skills/evaluate-behavior/scripts/pi-command-tool.mjs +159 -0
  201. package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
  202. package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
  203. package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
  204. package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
  205. package/skills/execute-work/SKILL.md +96 -72
  206. package/skills/execute-work/references/code-practices.md +27 -49
  207. package/skills/execute-work/references/design.md +271 -0
  208. package/skills/execute-work/references/diagnose.md +117 -0
  209. package/skills/execute-work/references/migration.md +108 -0
  210. package/skills/execute-work/references/simplify.md +65 -0
  211. package/skills/execute-work/scripts/self-review.mjs +422 -0
  212. package/skills/execute-work/scripts/touch-map.mjs +290 -0
  213. package/skills/review-work/SKILL.md +94 -64
  214. package/skills/review-work/references/adjudication.md +94 -0
  215. package/skills/review-work/references/artifact.md +198 -0
  216. package/skills/review-work/references/evals-and-judges.md +66 -0
  217. package/skills/review-work/references/work.md +177 -0
  218. package/skills/review-work/scripts/check-brief.mjs +128 -0
  219. package/skills/review-work/scripts/review-packet.mjs +233 -0
  220. package/skills/setup-freeflow/SKILL.md +69 -16
  221. package/skills/track-work/SKILL.md +135 -151
  222. package/skills/track-work/references/handoff.md +190 -0
  223. package/skills/track-work/references/projects.md +51 -0
  224. package/skills/track-work/references/working-record-format.md +201 -344
  225. package/skills/track-work/scripts/lib/document.mjs +79 -19
  226. package/skills/track-work/scripts/lib/format.mjs +18 -1
  227. package/skills/track-work/scripts/lib/recover.mjs +254 -0
  228. package/skills/track-work/scripts/lib/registry.mjs +656 -0
  229. package/skills/track-work/scripts/lib/search.mjs +40 -0
  230. package/skills/track-work/scripts/lib/store.mjs +53 -21
  231. package/skills/track-work/scripts/lib/transitions.mjs +79 -4
  232. package/skills/track-work/scripts/lib/views.mjs +50 -1
  233. package/skills/track-work/scripts/working-record.mjs +263 -22
  234. package/skills/verify-work/SKILL.md +64 -47
  235. package/skills/verify-work/references/feature-map.md +120 -0
  236. package/skills/verify-work/references/runtime-evidence.md +203 -0
  237. package/skills/verify-work/references/test-design.md +27 -6
  238. package/skills/verify-work/scripts/test-changes.mjs +246 -0
  239. package/skills/write-docs/SKILL.md +171 -0
  240. package/skills/write-docs/references/documentation-shapes.md +184 -0
  241. package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
  242. package/skills/write-docs/scripts/artifact-check.mjs +148 -0
  243. package/skills/write-docs/scripts/docs-check.mjs +229 -0
  244. package/skills/write-docs/scripts/plan-lint.mjs +123 -0
  245. package/skills/write-skill/SKILL.md +165 -165
  246. package/skills/write-skill/references/activation-and-wording.md +143 -0
  247. package/skills/write-skill/references/placement-and-dependencies.md +154 -0
  248. package/skills/write-skill/scripts/lib/compare.mjs +701 -0
  249. package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
  250. package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
  251. package/skills/write-skill/scripts/skill-author.mjs +67 -8
  252. package/capabilities/context-virtualization/SKILL.md +0 -103
  253. package/capabilities/conversation-history/SKILL.md +0 -53
  254. package/hooks/tests/check-host-adapters.test.mjs +0 -82
  255. package/hooks/tests/check-runtime-context-hook.sh +0 -212
  256. package/pi-extension/dist/cognitive-routing-v2/projection.js +0 -330
  257. package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
  258. package/pi-extension/dist/cognitive-routing-v2/tools.js +0 -60
  259. package/pi-extension/dist/context-virtualization/commands.js +0 -89
  260. package/pi-extension/dist/context-virtualization/projector.js +0 -42
  261. package/pi-extension/dist/context-virtualization/runtime.js +0 -378
  262. package/pi-extension/dist/context-virtualization/state.js +0 -57
  263. package/pi-extension/dist/context-virtualization/types.js +0 -10
  264. package/pi-extension/dist/conversation-history/passages.js +0 -66
  265. package/pi-extension/dist/conversation-history/ranking.js +0 -105
  266. package/pi-extension/dist/conversation-history/runtime.js +0 -226
  267. package/pi-extension/dist/conversation-history/search.js +0 -56
  268. package/pi-extension/dist/conversation-history/types.js +0 -1
  269. package/pi-extension/dist/freeflow-context/resolver.js +0 -120
  270. package/pi-extension/dist/freeflow-context/runtime.js +0 -80
  271. package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
  272. package/pi-extension/dist/freeflow-context/tool.js +0 -314
  273. package/pi-extension/dist/freeflow-context/types.js +0 -12
  274. package/pi-extension/dist/freeflow-context/visibility.js +0 -28
  275. package/pi-extension/dist/integrations/mcp-config.js +0 -29
  276. package/pi-extension/dist/runtime/runtime-context.js +0 -754
  277. package/pi-extension/dist/runtime/runtime-identity.js +0 -5
  278. package/pi-extension/dist/session-sources/provenance.js +0 -32
  279. package/pi-extension/dist/settings/settings-ui.js +0 -1360
  280. package/pi-extension/dist/ui/text-component.js +0 -57
  281. package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
  282. package/pi-extension/src/cognitive-routing-v2/config.ts +0 -173
  283. package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -375
  284. package/pi-extension/src/cognitive-routing-v2/render.ts +0 -277
  285. package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -2241
  286. package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -180
  287. package/pi-extension/src/cognitive-routing-v2/state.ts +0 -839
  288. package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -62
  289. package/pi-extension/src/cognitive-routing-v2/types.ts +0 -254
  290. package/pi-extension/src/context-virtualization/commands.ts +0 -111
  291. package/pi-extension/src/context-virtualization/projector.ts +0 -62
  292. package/pi-extension/src/context-virtualization/runtime.ts +0 -428
  293. package/pi-extension/src/context-virtualization/state.ts +0 -79
  294. package/pi-extension/src/context-virtualization/types.ts +0 -92
  295. package/pi-extension/src/conversation-history/passages.ts +0 -81
  296. package/pi-extension/src/conversation-history/ranking.ts +0 -155
  297. package/pi-extension/src/conversation-history/runtime.ts +0 -250
  298. package/pi-extension/src/conversation-history/search.ts +0 -79
  299. package/pi-extension/src/conversation-history/types.ts +0 -31
  300. package/pi-extension/src/freeflow-context/resolver.ts +0 -139
  301. package/pi-extension/src/freeflow-context/runtime.ts +0 -100
  302. package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
  303. package/pi-extension/src/freeflow-context/tool.ts +0 -366
  304. package/pi-extension/src/freeflow-context/types.ts +0 -70
  305. package/pi-extension/src/freeflow-context/visibility.ts +0 -53
  306. package/pi-extension/src/index.ts +0 -390
  307. package/pi-extension/src/integrations/mcp-config.ts +0 -32
  308. package/pi-extension/src/provider-support/astra/adapter.ts +0 -108
  309. package/pi-extension/src/provider-support/astra/history.ts +0 -145
  310. package/pi-extension/src/provider-support/astra/index.ts +0 -12
  311. package/pi-extension/src/provider-support/astra/session-state.ts +0 -113
  312. package/pi-extension/src/provider-support/index.ts +0 -5
  313. package/pi-extension/src/runtime/request-history.ts +0 -243
  314. package/pi-extension/src/runtime/runtime-context.ts +0 -860
  315. package/pi-extension/src/runtime/runtime-identity.ts +0 -8
  316. package/pi-extension/src/session-sources/events.ts +0 -230
  317. package/pi-extension/src/session-sources/provenance.ts +0 -39
  318. package/pi-extension/src/session-sources/read-only-session.ts +0 -170
  319. package/pi-extension/src/session-sources/sources.ts +0 -334
  320. package/pi-extension/src/settings/settings-tui.ts +0 -501
  321. package/pi-extension/src/settings/settings-ui.ts +0 -1652
  322. package/pi-extension/src/ui/text-component.ts +0 -66
  323. package/pi-extension/tests/cache-reuse/history.test.js +0 -117
  324. package/pi-extension/tests/cache-reuse/native.test.js +0 -91
  325. package/pi-extension/tests/cache-reuse/settings.test.js +0 -211
  326. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -577
  327. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
  328. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +0 -557
  329. package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -474
  330. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
  331. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -300
  332. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
  333. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -1034
  334. package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -244
  335. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -423
  336. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
  337. package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -586
  338. package/pi-extension/tests/context-virtualization/extension.test.js +0 -775
  339. package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
  340. package/pi-extension/tests/context-virtualization/runtime.test.js +0 -230
  341. package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
  342. package/pi-extension/tests/conversation-history/search.test.js +0 -97
  343. package/pi-extension/tests/fixtures/pi-host.js +0 -4
  344. package/pi-extension/tests/fixtures/routing-native.js +0 -191
  345. package/pi-extension/tests/integration/mode-free-core.test.js +0 -220
  346. package/pi-extension/tests/integration/pi-extension.test.js +0 -1447
  347. package/pi-extension/tests/integration/prompt-architecture.test.js +0 -447
  348. package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
  349. package/pi-extension/tests/provider-support/astra-boundaries.test.js +0 -75
  350. package/pi-extension/tests/provider-support/astra.test.js +0 -306
  351. package/pi-extension/tests/provider-support/cli.test.js +0 -92
  352. package/pi-extension/tests/provider-support/native.test.js +0 -183
  353. package/pi-extension/tests/settings/settings-tui.test.js +0 -243
  354. package/pi-extension/tsconfig.json +0 -16
  355. package/plugin.json +0 -33
  356. package/runtime/prompts/context-virtualization.md +0 -3
  357. package/runtime/prompts/conversation-history.md +0 -3
  358. package/skills/action-selection/SKILL.md +0 -100
  359. package/skills/action-selection/references/trajectory-stalls.md +0 -71
  360. package/skills/bypass/SKILL.md +0 -153
  361. package/skills/commit-work/SKILL.md +0 -145
  362. package/skills/commit-work/references/git-state-edges.md +0 -94
  363. package/skills/commit-work/references/simple-push.md +0 -92
  364. package/skills/commit-work/references/staging-decisions.md +0 -106
  365. package/skills/decision-gate/SKILL.md +0 -106
  366. package/skills/design-for-depth/SKILL.md +0 -117
  367. package/skills/design-for-depth/references/design-pressure-signals.md +0 -182
  368. package/skills/design-for-depth/references/implementation-decisions.md +0 -76
  369. package/skills/design-for-depth/references/interface-design-loop.md +0 -136
  370. package/skills/design-for-depth/references/module-and-dependency-design.md +0 -147
  371. package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
  372. package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -136
  373. package/skills/diagnose-failure/SKILL.md +0 -125
  374. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -48
  375. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
  376. package/skills/discuss/SKILL.md +0 -116
  377. package/skills/evaluate-skill/SKILL.md +0 -219
  378. package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -385
  379. package/skills/evaluate-skill/references/evaluation-design.md +0 -112
  380. package/skills/evaluate-skill/references/execution-and-evidence.md +0 -90
  381. package/skills/evaluate-skill/references/review-and-revision.md +0 -92
  382. package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -407
  383. package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
  384. package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
  385. package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -368
  386. package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -164
  387. package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -102
  388. package/skills/execute-work/references/execute-work-edges.md +0 -68
  389. package/skills/finish-branch/SKILL.md +0 -117
  390. package/skills/finish-branch/references/integration-options.md +0 -80
  391. package/skills/handoff/SKILL.md +0 -82
  392. package/skills/handoff/references/handoff-templates.md +0 -150
  393. package/skills/launch-work/SKILL.md +0 -116
  394. package/skills/launch-work/references/launch-readiness.md +0 -85
  395. package/skills/migration-work/SKILL.md +0 -115
  396. package/skills/migration-work/references/migration-lifecycle.md +0 -82
  397. package/skills/release-work/SKILL.md +0 -118
  398. package/skills/release-work/references/release-evidence.md +0 -78
  399. package/skills/review-artifact/SKILL.md +0 -142
  400. package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
  401. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
  402. package/skills/review-work/references/adjudicate-work-review.md +0 -97
  403. package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
  404. package/skills/review-work/references/security-risk-lens.md +0 -69
  405. package/skills/setup-freeflow/references/activation-contract.md +0 -109
  406. package/skills/setup-freeflow/references/host-setup.md +0 -67
  407. package/skills/simplify-code/SKILL.md +0 -85
  408. package/skills/simplify-code/references/simplification-patterns.md +0 -70
  409. package/skills/verify-work/references/browser-runtime-evidence.md +0 -55
  410. package/skills/verify-work/references/integration-evidence.md +0 -47
  411. package/skills/verify-work/references/performance-evidence.md +0 -57
  412. package/skills/workflow/SKILL.md +0 -127
  413. package/skills/workflow/references/domain-skill-composition.md +0 -43
  414. package/skills/write-plan/SKILL.md +0 -92
  415. package/skills/write-plan/references/plan-shapes.md +0 -156
  416. package/skills/write-skill/references/activation-boundaries.md +0 -104
  417. package/skills/write-skill/references/agent-first-instructions.md +0 -121
  418. package/skills/write-skill/references/progressive-disclosure.md +0 -127
  419. package/skills/write-skill/references/skill-dependency-graphs.md +0 -190
  420. package/skills/write-spec/SKILL.md +0 -92
  421. package/skills/write-spec/references/artifact-standards.md +0 -86
  422. package/skills/write-spec/references/decision-records.md +0 -78
  423. package/skills/write-spec/references/spec-shapes.md +0 -143
  424. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
  425. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
  426. /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
@@ -1,2181 +0,0 @@
1
- import { estimateRequest } from "./budget.js";
2
- import { annotateSources } from "../session-sources/provenance.js";
3
- import { ROUTING_SCHEMAS } from "./schemas.js";
4
- import { randomUUID } from "node:crypto";
5
- import { existsSync, realpathSync } from "node:fs";
6
- import { relative, resolve as resolvePath } from "node:path";
7
- import { EventStore } from "../session-sources/events.js";
8
- import { Sources, bodyHash, isTaskEvidence } from "../session-sources/sources.js";
9
- import { pairFromProfile, resolveCognitiveRoutingState } from "./config.js";
10
- import { prepareView, changeSelection, representationProblems } from "./projection.js";
11
- import { initialState } from "./state.js";
12
- import {
13
- ROUTING_MESSAGE,
14
- PROFILES,
15
- RoutingError,
16
- canonical,
17
- emptySelection,
18
- isWorkerProfile,
19
- requireCondition as check,
20
- samePair,
21
- workersForDelegation,
22
- } from "./types.js";
23
- export const ROUTING_TOOLS = ["freeflow_delegate", "freeflow_return", "freeflow_unit", "freeflow_project"];
24
- const HANDOFF_TOOLS = new Set(["freeflow_delegate", "freeflow_return"]);
25
- export class RoutingRuntime {
26
- pi;
27
- packageRoots;
28
- store;
29
- ctx;
30
- capability;
31
- token = randomUUID();
32
- turn;
33
- messages = [];
34
- error;
35
- projectionError;
36
- suppressed = false;
37
- applying;
38
- externalChange = false;
39
- targetSignature;
40
- operations = Promise.resolve();
41
- revision = 0;
42
- receipts = new Map();
43
- pages = new Map();
44
- manualHold;
45
- automaticControl = false;
46
- sourceCache;
47
- constructor(pi, packageRoots = []) {
48
- this.pi = pi;
49
- this.packageRoots = packageRoots;
50
- }
51
- subject() {
52
- const branch = this.store?.reader.getBranch() ?? [];
53
- return {
54
- token: this.token,
55
- revision: this.revision,
56
- store: this.store,
57
- leafId: branch.at(-1)?.id ?? null,
58
- userId: branch.filter((e) => e.message?.role === "user").at(-1)?.id ?? null,
59
- };
60
- }
61
- current(s) {
62
- if (s.token !== this.token || s.revision !== this.revision || s.store !== this.store) return false;
63
- const branch = this.store?.reader.getBranch() ?? [];
64
- return (
65
- (!s.leafId || branch.some((e) => e.id === s.leafId)) &&
66
- (branch.filter((e) => e.message?.role === "user").at(-1)?.id ?? null) === s.userId
67
- );
68
- }
69
- guard(s) {
70
- check(
71
- this.current(s),
72
- "stale_operation",
73
- "Routing state changed while the operation was waiting; no further effects applied.",
74
- );
75
- }
76
- taskBasis(state, assignmentId) {
77
- return state.resumeBasis.has(assignmentId)
78
- ? state.resumeBasis.get(assignmentId)
79
- : state.assignments.get(assignmentId)?.basisUserEntryId;
80
- }
81
- enabledWorkers() {
82
- return workersForDelegation(this.capability?.delegation ?? "executor");
83
- }
84
- assignedWorker(state, assignmentId = state.assignmentId) {
85
- const assignment = assignmentId ? state.assignments.get(assignmentId) : undefined;
86
- const handoff = assignment ? state.handoffs.get(assignment.delegateHandoffId) : undefined;
87
- check(
88
- handoff?.kind === "delegate" && handoff.assignmentId === assignment?.id && isWorkerProfile(handoff.to),
89
- "assignment_worker_missing",
90
- "The current assignment has no valid recorded worker.",
91
- );
92
- return handoff.to;
93
- }
94
- requiredProfiles(state = this.stateData()) {
95
- const profiles = new Set(["coordinator", ...this.enabledWorkers()]);
96
- if (state.profile) profiles.add(state.profile);
97
- if (this.manualHold) profiles.add(this.manualHold);
98
- const assignment = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
99
- if (assignment?.state === "outstanding" || state.recoveryId) profiles.add(this.assignedWorker(state));
100
- const pending = state.pendingId ? state.handoffs.get(state.pendingId) : undefined;
101
- if (pending && isWorkerProfile(pending.to)) profiles.add(pending.to);
102
- return [...profiles];
103
- }
104
- retireUnbound(reason) {
105
- for (const execution of this.stateData().executions.values()) {
106
- if (!execution.assistantEntryId && !execution.interrupted)
107
- this.append({ type: "execution-interrupted", executionId: execution.id, reason });
108
- }
109
- this.turn = undefined;
110
- }
111
- openTurn() {
112
- if (!this.turn || this.turn.opened) return;
113
- const t = this.turn;
114
- this.append({
115
- type: "execution-opened",
116
- execution: {
117
- id: t.id,
118
- profile: t.profile,
119
- assignmentId: t.assignmentId,
120
- recoveryId: t.recoveryId,
121
- basisUserEntryId: t.basisUserEntryId,
122
- pair: t.pair,
123
- resultEntryIds: [],
124
- },
125
- });
126
- t.opened = true;
127
- }
128
- async settled(ctx) {
129
- if (!this.store || !this.supported() || this.store.blocked || !ctx.isIdle?.()) return;
130
- this.ctx = ctx;
131
- this.retireUnbound("Native run settled without a complete source binding; prior effects remain unresolved.");
132
- }
133
- async beforeRun(ctx) {
134
- this.ctx = ctx;
135
- if (!this.store || !this.supported() || this.store.blocked || this.error) return;
136
- if (this.stateData().control === "automatic" && isWorkerProfile(this.stateData().profile)) {
137
- const result = await this.control("coordinator", false);
138
- check(result.status !== "blocked", "reconciliation_required", result.reason);
139
- }
140
- }
141
- stateData() {
142
- return this.store?.state() ?? initialState();
143
- }
144
- sources(state = this.stateData()) {
145
- const entries = this.ctx.sessionManager
146
- .getBranch()
147
- .filter((e) => ["message", "custom_message", "compaction", "branch_summary"].includes(e.type));
148
- const cache = this.sourceCache;
149
- if (
150
- cache &&
151
- cache.authors === state.authors.size &&
152
- entries.length === cache.entries.length &&
153
- entries.every((e, i) => e === cache.entries[i])
154
- )
155
- return cache.source;
156
- const active = this.ctx.sessionManager.buildContextEntries?.();
157
- const activeIds = active ? new Set(active.map((e) => e.id)) : undefined;
158
- const source = cache?.source ?? new Sources([], state);
159
- source.refresh(entries, state, activeIds);
160
- this.sourceCache = { entries, authors: state.authors.size, source };
161
- return source;
162
- }
163
- observed(ctx = this.ctx) {
164
- return ctx?.model?.provider && ctx?.model?.id && ctx?.thinkingLevel
165
- ? { provider: ctx.model.provider, modelId: ctx.model.id, thinking: ctx.thinkingLevel }
166
- : undefined;
167
- }
168
- supported() {
169
- return !!this.capability?.effective && !this.suppressed;
170
- }
171
- state() {
172
- let state;
173
- try {
174
- state = this.stateData();
175
- } catch {
176
- state = initialState();
177
- }
178
- const blocked = this.error ?? this.store?.blocked;
179
- return {
180
- effective: this.supported() && state.control !== "inactive" && !blocked,
181
- activeProfile: state.profile,
182
- delegation: this.capability?.delegation ?? "executor",
183
- controlMode: state.control === "manual" ? `manual-${state.profile}` : state.control,
184
- runtimeStatus: blocked ? "blocked" : this.supported() && state.control !== "inactive" ? "active" : "inactive",
185
- runtimeReason: blocked ?? (this.suppressed ? "startup_selection" : this.capability?.blockingReason.message),
186
- projectionFailure: this.projectionError,
187
- };
188
- }
189
- get projectionEnabled() {
190
- return this.supported() && this.capability?.projection === true && this.stateData().control === "automatic";
191
- }
192
- append(data, op, step) {
193
- check(this.store, "routing_unavailable");
194
- return this.store.append(this.store.make(data, op, step));
195
- }
196
- enqueue(action) {
197
- const token = this.token;
198
- const result = this.operations.then(() => {
199
- check(token === this.token, "stale_instance");
200
- return action();
201
- });
202
- this.operations = result.catch(() => {});
203
- return result;
204
- }
205
- mark(error) {
206
- if (this.error && error instanceof RoutingError && error.code === "routing_blocked") return;
207
- this.error =
208
- error instanceof RoutingError
209
- ? `${error.code}: ${error.message}`
210
- : error instanceof Error
211
- ? error.message
212
- : String(error);
213
- }
214
- assertAvailable(profile) {
215
- check(
216
- this.supported() && this.store && !this.error && !this.store.blocked,
217
- "routing_unavailable",
218
- this.error ?? this.store?.blocked ?? "Routing is inactive.",
219
- );
220
- const state = this.stateData();
221
- check(state.control === "automatic", "manual_control");
222
- if (profile) check(state.profile === profile && this.turn?.profile === profile, "wrong_profile");
223
- if (state.profile) check(samePair(this.observed(), this.profilePair(state.profile)), "configuration_mismatch");
224
- }
225
- configuredProfilePair(profile) {
226
- const configured = this.capability?.profiles[profile];
227
- check(configured, "profile_missing");
228
- return pairFromProfile(configured);
229
- }
230
- profilePair(profile) {
231
- return this.stateData().profileOverrides.get(profile) ?? this.configuredProfilePair(profile);
232
- }
233
- profilePairs(profiles = this.requiredProfiles()) {
234
- return Object.fromEntries(profiles.map((profile) => [profile, this.profilePair(profile)]));
235
- }
236
- async validateProfilePairs(pairs) {
237
- check(pairs.coordinator, "profile_missing", "Configure the coordinator profile.");
238
- const helper = pairs.helper !== undefined;
239
- const executor = pairs.executor !== undefined;
240
- check(helper || executor, "profile_missing", "Configure an enabled worker profile.");
241
- const delegation = helper && executor ? "both" : helper ? "helper" : "executor";
242
- const profiles = Object.fromEntries(
243
- Object.entries(pairs).map(([profile, pair]) => [
244
- profile,
245
- { provider: pair.provider, model: pair.modelId, thinking: pair.thinking },
246
- ]),
247
- );
248
- const result = await resolveCognitiveRoutingState(
249
- { cognitiveRouting: { enabled: true, delegation, profiles } },
250
- {},
251
- this.ctx,
252
- );
253
- check(result.effective, result.blockingReason.code, result.blockingReason.message);
254
- }
255
- model(profile) {
256
- const pair = this.profilePair(profile);
257
- const model = this.ctx?.modelRegistry?.find(pair.provider, pair.modelId);
258
- check(model, "profile_unavailable");
259
- return model;
260
- }
261
- async bind(ctx, capability, startup = false) {
262
- this.token = randomUUID();
263
- this.ctx = ctx;
264
- this.capability = capability;
265
- this.turn = undefined;
266
- this.manualHold = undefined;
267
- this.automaticControl = false;
268
- this.messages = [];
269
- this.sourceCache = undefined;
270
- this.receipts.clear();
271
- this.pages.clear();
272
- this.applying = undefined;
273
- this.externalChange = false;
274
- this.error = undefined;
275
- this.projectionError = undefined;
276
- this.operations = Promise.resolve();
277
- this.targetSignature = canonical({ delegation: capability.delegation, profiles: capability.profiles });
278
- this.suppressed = startup && process.argv.some((a) => /^(--model|--thinking)(=|$)/.test(a));
279
- this.store = new EventStore(this.pi, ctx.sessionManager);
280
- const subject = this.subject();
281
- if (!capability.effective || this.suppressed) return;
282
- try {
283
- await this.store.reconcile();
284
- this.guard(subject);
285
- const state = this.stateData();
286
- this.manualHold = state.control === "manual" ? state.profile : undefined;
287
- this.automaticControl = !state.events.size || state.control === "automatic";
288
- await this.validateProfilePairs(this.profilePairs(this.requiredProfiles(state)));
289
- this.retireUnbound("Session rebind found an unfinished execution; no task effects replayed.");
290
- if (!state.events.size) {
291
- this.append({
292
- type: "control",
293
- control: "automatic",
294
- profile: "coordinator",
295
- reason: "Initial routing activation",
296
- });
297
- await this.applyPair(this.profilePair("coordinator"));
298
- } else if (
299
- state.control === "automatic" &&
300
- state.profile &&
301
- !samePair(this.observed(), this.profilePair(state.profile))
302
- ) {
303
- // Reload reconstructs responsibility; it does not silently reapply the last setter.
304
- this.append({
305
- type: "control",
306
- control: "automatic",
307
- profile: "coordinator",
308
- reason: "Rebind reconciles historical responsibility with current host control",
309
- });
310
- await this.applyPair(this.profilePair("coordinator"));
311
- }
312
- } catch (error) {
313
- if (this.current(subject)) this.mark(error);
314
- }
315
- }
316
- unbind() {
317
- this.token = randomUUID();
318
- this.store = undefined;
319
- this.turn = undefined;
320
- this.messages = [];
321
- this.ctx = undefined;
322
- this.sourceCache = undefined;
323
- this.receipts.clear();
324
- this.pages.clear();
325
- }
326
- async refresh(ctx, capability) {
327
- this.ctx = ctx;
328
- const was = this.capability?.effective === true;
329
- const signature = canonical({ delegation: capability.delegation, profiles: capability.profiles });
330
- if (this.capability && (was !== capability.effective || signature !== this.targetSignature)) this.revision++;
331
- this.capability = capability;
332
- if (!capability.effective) {
333
- this.turn = undefined;
334
- return;
335
- }
336
- if (!this.store) {
337
- await this.bind(ctx, capability);
338
- return;
339
- }
340
- if (!was) {
341
- const subject = this.subject();
342
- this.suppressed = false;
343
- try {
344
- await this.store.reconcile();
345
- this.guard(subject);
346
- this.error = undefined;
347
- this.append({
348
- type: "control",
349
- control: "automatic",
350
- profile: "coordinator",
351
- reason: "Capability re-enabled; Coordinator reconciliation",
352
- });
353
- this.automaticControl = true;
354
- await this.applyPair(this.profilePair("coordinator"));
355
- } catch (error) {
356
- if (this.current(subject)) this.mark(error);
357
- }
358
- } else if (this.targetSignature && signature !== this.targetSignature) {
359
- this.error = "configuration_changed: reconcile profile configuration before automatic execution";
360
- }
361
- if (canonical({ delegation: this.capability?.delegation, profiles: this.capability?.profiles }) === signature)
362
- this.targetSignature = signature;
363
- }
364
- async ancestryChanged(ctx, navigation = true) {
365
- this.revision++;
366
- this.ctx = ctx;
367
- this.turn = undefined;
368
- this.messages = [];
369
- this.sourceCache = undefined;
370
- this.receipts.clear();
371
- this.pages.clear();
372
- if (!this.store || !this.supported()) return;
373
- const subject = this.subject();
374
- try {
375
- await this.store.reconcile();
376
- this.guard(subject);
377
- this.error = undefined;
378
- const state = this.stateData();
379
- await this.validateProfilePairs(this.profilePairs(this.requiredProfiles(state)));
380
- this.retireUnbound("Selected historical ancestry ends before execution binding; effects are not replayed.");
381
- if (this.manualHold) {
382
- this.append({
383
- type: "control",
384
- control: "manual",
385
- profile: this.manualHold,
386
- reason: "Current explicit manual hold survives navigation",
387
- });
388
- await this.applyPair(this.profilePair(this.manualHold));
389
- } else if (
390
- (this.automaticControl || !state.events.size) &&
391
- (navigation || !samePair(this.observed(), this.profilePair(state.profile ?? "coordinator")))
392
- ) {
393
- this.append({
394
- type: "control",
395
- control: "automatic",
396
- profile: "coordinator",
397
- reason: "Coordinator reconciles selected native ancestry",
398
- });
399
- await this.applyPair(this.profilePair("coordinator"));
400
- }
401
- } catch (error) {
402
- if (this.current(subject)) this.mark(error);
403
- }
404
- }
405
- async nativeChange(ctx) {
406
- this.ctx = ctx;
407
- if (!this.supported() || !this.store) return;
408
- if (this.applying) {
409
- if (ctx.model?.provider !== this.applying.provider || ctx.model?.id !== this.applying.modelId) {
410
- this.externalChange = true;
411
- this.revision++;
412
- }
413
- return; // Never await a queue already held by our setter.
414
- }
415
- const state = this.stateData();
416
- if (state.control !== "automatic" || !state.profile || samePair(this.observed(), this.profilePair(state.profile)))
417
- return;
418
- try {
419
- this.revision++;
420
- this.manualHold = undefined;
421
- this.automaticControl = false;
422
- this.append({ type: "control", control: "inactive", reason: "External native model/effort change" });
423
- } catch (error) {
424
- this.mark(error);
425
- }
426
- }
427
- async applyPair(target) {
428
- const subject = this.subject(),
429
- token = this.token,
430
- prior = this.observed();
431
- if (samePair(prior, target)) return;
432
- const model = this.ctx.modelRegistry.find(target.provider, target.modelId);
433
- check(model, "profile_unavailable");
434
- const auth = await this.ctx.modelRegistry.getApiKeyAndHeaders(model);
435
- this.guard(subject);
436
- check(auth?.ok, "profile_unauthenticated");
437
- this.applying = target;
438
- let ownedPair = target;
439
- this.externalChange = false;
440
- try {
441
- check(await this.pi.setModel(model), "model_rejected");
442
- this.guard(subject);
443
- this.pi.setThinkingLevel(target.thinking);
444
- check(!this.externalChange && samePair(this.observed(), target), "configuration_mismatch");
445
- } catch (error) {
446
- if (this.current(subject) && prior && !this.externalChange) {
447
- const old = this.ctx.modelRegistry.find(prior.provider, prior.modelId);
448
- try {
449
- this.applying = prior;
450
- ownedPair = prior;
451
- check(old && (await this.pi.setModel(old)), "rollback_failed");
452
- this.guard(subject);
453
- this.pi.setThinkingLevel(prior.thinking);
454
- check(samePair(this.observed(), prior), "rollback_failed");
455
- } catch {
456
- if (this.current(subject)) this.error = "configuration_unknown: rollback could not be observed";
457
- }
458
- }
459
- throw error;
460
- } finally {
461
- if (token === this.token && this.applying === ownedPair) this.applying = undefined;
462
- }
463
- }
464
- async control(profile, manual) {
465
- this.revision++;
466
- const subject = this.subject();
467
- return this.enqueue(async () => {
468
- try {
469
- check(this.capability?.effective && this.store, "routing_unavailable");
470
- if (manual && isWorkerProfile(profile))
471
- check(
472
- this.enabledWorkers().includes(profile),
473
- "worker_disabled",
474
- `${profile} is not enabled by delegation mode.`,
475
- );
476
- await this.store.reconcile();
477
- this.guard(subject);
478
- this.error = undefined;
479
- this.suppressed = false;
480
- const previous = this.stateData();
481
- if (
482
- previous.control === (manual ? "manual" : "automatic") &&
483
- previous.profile === profile &&
484
- samePair(this.observed(), this.profilePair(profile))
485
- ) {
486
- this.manualHold = manual ? profile : undefined;
487
- this.automaticControl = !manual;
488
- return { status: manual ? "active" : "automatic" };
489
- }
490
- this.append({
491
- type: "control",
492
- control: manual ? "manual" : "automatic",
493
- profile,
494
- reason: manual ? "Explicit user manual hold" : "Explicit user automatic release/reconciliation",
495
- });
496
- try {
497
- await this.applyPair(this.profilePair(profile));
498
- this.guard(subject);
499
- } catch (error) {
500
- if (this.current(subject) && !this.error && !this.store.blocked)
501
- this.append({
502
- type: "control",
503
- control: previous.control,
504
- profile: previous.profile,
505
- reason: "Control request failed; prior control retained",
506
- });
507
- throw error;
508
- }
509
- this.manualHold = manual ? profile : undefined;
510
- this.automaticControl = !manual;
511
- return { status: manual ? "active" : "automatic" };
512
- } catch (error) {
513
- if (this.current(subject) && !(error instanceof RoutingError && error.code === "worker_disabled"))
514
- this.mark(error);
515
- return { status: "blocked", reason: error instanceof Error ? error.message : String(error) };
516
- }
517
- });
518
- }
519
- setManualProfile(profile, _mechanism) {
520
- return this.control(profile, true);
521
- }
522
- sessionProfileOverrides() {
523
- return Object.fromEntries(this.stateData().profileOverrides);
524
- }
525
- async setSessionProfileOverride(profile, override, mechanism = "Session profile override") {
526
- this.revision++;
527
- const subject = this.subject();
528
- return this.enqueue(async () => {
529
- try {
530
- check(this.capability?.effective && this.store, "routing_unavailable");
531
- check(
532
- this.ctx?.isIdle?.() !== false,
533
- "host_busy",
534
- "Wait for Pi to become idle before changing session presets.",
535
- );
536
- await this.store.reconcile();
537
- this.guard(subject);
538
- const state = this.stateData();
539
- const previous = state.profileOverrides.get(profile);
540
- if ((override === null && !previous) || (override !== null && samePair(previous, override)))
541
- return { status: "unchanged" };
542
- const target = override ?? this.configuredProfilePair(profile);
543
- const profiles = new Set(this.requiredProfiles(state));
544
- profiles.add(profile);
545
- const pairs = this.profilePairs([...profiles]);
546
- pairs[profile] = target;
547
- await this.validateProfilePairs(pairs);
548
- this.append({
549
- type: "profile-overrides",
550
- overrides: { [profile]: override },
551
- reason: mechanism,
552
- });
553
- if (state.control !== "inactive" && state.profile === profile) {
554
- try {
555
- await this.applyPair(target);
556
- this.guard(subject);
557
- } catch (error) {
558
- if (this.current(subject) && !this.externalChange && !this.error && !this.store.blocked) {
559
- this.append({
560
- type: "profile-overrides",
561
- overrides: { [profile]: previous ?? null },
562
- reason: "Session profile application failed; prior override retained",
563
- });
564
- }
565
- throw error;
566
- }
567
- this.error = undefined;
568
- return { status: "active" };
569
- }
570
- this.error = undefined;
571
- return { status: "stored" };
572
- } catch (error) {
573
- if (this.current(subject)) this.mark(error);
574
- return { status: "blocked", reason: error instanceof Error ? error.message : String(error) };
575
- }
576
- });
577
- }
578
- async resetSessionProfileOverrides(mechanism = "Reset session profile overrides") {
579
- this.revision++;
580
- const subject = this.subject();
581
- return this.enqueue(async () => {
582
- try {
583
- check(this.capability?.effective && this.store, "routing_unavailable");
584
- check(
585
- this.ctx?.isIdle?.() !== false,
586
- "host_busy",
587
- "Wait for Pi to become idle before resetting session presets.",
588
- );
589
- await this.store.reconcile();
590
- this.guard(subject);
591
- const state = this.stateData();
592
- if (state.profileOverrides.size === 0) return { status: "unchanged" };
593
- const previous = Object.fromEntries(state.profileOverrides);
594
- const required = this.requiredProfiles(state);
595
- const targetPairs = Object.fromEntries(
596
- required.map((profile) => [profile, this.configuredProfilePair(profile)]),
597
- );
598
- await this.validateProfilePairs(targetPairs);
599
- this.append({
600
- type: "profile-overrides",
601
- overrides: { coordinator: null, helper: null, executor: null },
602
- reason: mechanism,
603
- });
604
- const active = state.control !== "inactive" ? state.profile : undefined;
605
- if (active && previous[active]) {
606
- try {
607
- await this.applyPair(targetPairs[active]);
608
- this.guard(subject);
609
- } catch (error) {
610
- if (this.current(subject) && !this.externalChange && !this.error && !this.store.blocked) {
611
- this.append({
612
- type: "profile-overrides",
613
- overrides: {
614
- coordinator: previous.coordinator ?? null,
615
- helper: previous.helper ?? null,
616
- executor: previous.executor ?? null,
617
- },
618
- reason: "Session profile reset failed; prior overrides retained",
619
- });
620
- }
621
- throw error;
622
- }
623
- this.error = undefined;
624
- return { status: "active" };
625
- }
626
- this.error = undefined;
627
- return { status: "stored" };
628
- } catch (error) {
629
- if (this.current(subject)) this.mark(error);
630
- return { status: "blocked", reason: error instanceof Error ? error.message : String(error) };
631
- }
632
- });
633
- }
634
- setAutomaticControl(_mechanism) {
635
- return this.control("coordinator", false);
636
- }
637
- lastDeliveredUser(sources, messages) {
638
- const delivered = new Set(
639
- sources.associate(messages).flatMap((x) => (x.source?.message.role === "user" ? [x.source.entry.id] : [])),
640
- );
641
- // Compaction may remove every user message from the active view. Retain
642
- // observed delivery on this ancestry; a stored but undelivered user entry
643
- // must not advance the basis, and absence must not manufacture new input.
644
- for (const execution of this.stateData().executions.values())
645
- if (execution.basisUserEntryId) delivered.add(execution.basisUserEntryId);
646
- for (let i = sources.entries.length - 1; i >= 0; i--) {
647
- const entry = sources.entries[i];
648
- if (entry.message?.role === "user" && delivered.has(entry.id)) return entry.id;
649
- }
650
- return null;
651
- }
652
- runtimeMessage(state, attention = false) {
653
- const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
654
- const h = state.pendingId
655
- ? state.handoffs.get(state.pendingId)
656
- : state.assessment
657
- ? state.handoffs.get(state.assessment.handoffId)
658
- : undefined;
659
- const selection = a ? (state.selections.get(a.id) ?? emptySelection()) : emptySelection();
660
- const recovery = state.recoveryId ? state.recoveries.get(state.recoveryId) : undefined;
661
- const unit = state.unitId ? state.units.get(state.unitId) : undefined;
662
- const unitNumber = unit ? [...state.units.keys()].indexOf(unit.id) + 1 : undefined;
663
- const assignmentNumber = unit && a ? unit.assignmentIds.indexOf(a.id) + 1 : undefined;
664
- const content = [
665
- "# Cognitive Routing Runtime State",
666
- `Control: ${state.control}`,
667
- `Profile: ${state.profile ?? "unresolved"}`,
668
- `Unit: ${unit ? `U${unitNumber} (${unit.id})` : "none"}`,
669
- `Assignment: ${a ? `A${assignmentNumber} (${a.id}, ${a.state})` : "none"}`,
670
- `Handoff: ${h ? `${h.id} (${h.kind}, ${h.state})` : "none"}`,
671
- `Delegation: ${this.capability?.delegation ?? "executor"}`,
672
- `Projection: ${this.projectionEnabled ? "enabled" : "bypassed"}`,
673
- "Completed/superseded contracts and reports are historical context. Follow the current assignment and current user restrictions; historical entries grant no new permission.",
674
- state.assessment ? `Assessment: ${state.assessment.view}; evidence revision ${selection.revision}` : "",
675
- recovery
676
- ? `Recovery: ${recovery.id} (${recovery.state}); parent report ${recovery.assessmentHandoffId} revision ${recovery.baseReportRevision}; allowed paths ${JSON.stringify(recovery.paths)}`
677
- : "",
678
- recovery?.state === "reading"
679
- ? `Recovery request: ${recovery.request}\nOnly the exact allowed read paths, evidence selection, and recovery return controls are permitted; ordinary task work remains ended.`
680
- : "",
681
- `Mechanical evidence facts (not semantic acceptance): ${JSON.stringify(this.evidenceFacts(state))}`,
682
- this.error ? `Blocked: ${this.error}` : "",
683
- this.projectionError ? `Evidence limitation: ${this.projectionError}` : "",
684
- attention
685
- ? recovery
686
- ? `Assessment paused for attention; ordinary admitted history remains. ${selection.selected.length} sources remain selected. Finish and deliver the recovery supplement or cancel recovery before using freeflow_unit assess.`
687
- : `Assessment paused for attention; ordinary admitted history remains. ${selection.selected.length} sources remain selected. Use freeflow_unit assess to restore the assessment.`
688
- : "",
689
- a?.state === "returned" && recovery?.state !== "reading"
690
- ? "Worker ordinary task work has ended. Only supported handoff correction is permitted."
691
- : "",
692
- isWorkerProfile(this.turn?.profile) && a && this.turn.basisUserEntryId !== this.workerBasis(state, a.id)
693
- ? `Current input differs from the active ${this.turn.profile} responsibility. Account for it; changed direction requires Coordinator attention.`
694
- : "",
695
- ...(state.assessment?.problems ?? []).map((p) => `${p.code}: ${p.detail}`),
696
- ]
697
- .filter(Boolean)
698
- .join("\n");
699
- return {
700
- role: "custom",
701
- customType: ROUTING_MESSAGE,
702
- content,
703
- display: false,
704
- timestamp: 0,
705
- details: { routingInstance: this.token },
706
- };
707
- }
708
- evidenceFacts(state) {
709
- const selection = state.assignmentId
710
- ? (state.selections.get(state.assignmentId) ?? emptySelection())
711
- : emptySelection();
712
- const sources = this.sources(state);
713
- return {
714
- revision: selection.revision,
715
- selected: selection.selected.map((ref) => {
716
- const entry = this.ctx?.sessionManager.getEntry?.(ref.slice(4).replace(/#text$/, ""));
717
- const locator = sources.locator(ref);
718
- return {
719
- ref,
720
- kind: ref.endsWith("#text") ? "assistant-text" : (entry?.message?.role ?? "unknown"),
721
- toolName: entry?.message?.toolName,
722
- producer: state.authors.get(entry?.id)?.profile ?? "common",
723
- assignment: state.authors.get(entry?.id)?.assignmentId,
724
- ...(locator ? { locator } : {}),
725
- };
726
- }),
727
- unresolved: selection.unresolved,
728
- withdrawals: selection.withdrawals,
729
- preparedRevision: state.assessment?.reservation?.selectionRevision,
730
- limitations: state.assessment?.problems ?? [],
731
- };
732
- }
733
- prepared(profile, input, handoffId, restoring = false) {
734
- const state = this.stateData(),
735
- model = this.model(profile);
736
- return prepareView({
737
- messages: input,
738
- sources: this.sources(state),
739
- state,
740
- view: profile,
741
- projection: this.projectionEnabled,
742
- model,
743
- pair: this.profilePair(profile),
744
- systemPrompt: this.ctx.getSystemPrompt?.() ?? "",
745
- tools: (this.pi.getAllTools?.() ?? [])
746
- .filter((tool) => this.pi.getActiveTools?.().includes(tool.name))
747
- .map((tool) =>
748
- ROUTING_TOOLS.includes(tool.name) ? { ...tool, parameters: ROUTING_SCHEMAS[tool.name] } : tool,
749
- ),
750
- runtimeMessage: this.runtimeMessage(
751
- state,
752
- profile === "coordinator" && state.assessment?.view === "suspended" && !restoring && !handoffId,
753
- ),
754
- instance: this.token,
755
- preparingReturn: handoffId,
756
- restoring,
757
- });
758
- }
759
- budgetNotice(messages, ctx) {
760
- if (!this.supported()) return;
761
- const tools = (this.pi.getAllTools?.() ?? []).filter((tool) => this.pi.getActiveTools?.().includes(tool.name));
762
- const estimate = estimateRequest(ctx.getSystemPrompt?.() ?? "", tools, messages, ctx.model);
763
- if (!estimate.warnings.length) return;
764
- return {
765
- role: "custom",
766
- customType: "freeflow-routing-budget",
767
- display: false,
768
- content: estimate.warnings.map((warning) => warning.detail).join("\n"),
769
- timestamp: 0,
770
- };
771
- }
772
- ordinaryContext(input) {
773
- try {
774
- const sources = this.sources(),
775
- associated = sources.associate(input);
776
- const mapped = new Map(associated.filter((item) => item.source).map((item) => [item.message, item.source]));
777
- return annotateSources(
778
- input,
779
- mapped,
780
- new Set([...mapped.values()].map((source) => source.ref)),
781
- sources,
782
- this.token,
783
- );
784
- } catch {
785
- return input;
786
- }
787
- }
788
- async context(ctx, incoming) {
789
- this.ctx = ctx;
790
- const input = incoming.filter(
791
- (m) =>
792
- !(
793
- m?.details?.routingInstance === this.token &&
794
- [ROUTING_MESSAGE, "freeflow-routing-v2-refs"].includes(m.customType)
795
- ),
796
- );
797
- this.messages = input;
798
- if (!this.supported() || !this.store) return this.ordinaryContext(input);
799
- try {
800
- const state = this.stateData();
801
- if (state.control !== "automatic") return this.ordinaryContext(input);
802
- check(!this.error && !this.store.blocked && state.profile, "routing_blocked", this.error ?? this.store.blocked);
803
- check(samePair(this.observed(), this.profilePair(state.profile)), "prepared_pair_mismatch");
804
- const sources = this.sources(state),
805
- user = this.lastDeliveredUser(sources, input);
806
- if (state.assessment && user && user !== state.assessment.basisUserEntryId)
807
- this.append(
808
- {
809
- type: "assessment-suspended",
810
- handoffId: state.assessment.handoffId,
811
- reason: "user-attention",
812
- basisUserEntryId: user,
813
- problems: [],
814
- },
815
- JSON.stringify(["attention", state.assessment.handoffId, user]),
816
- );
817
- // New input reaching an already prepared worker request cannot be rerouted by changing only the live model.
818
- const outstanding = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
819
- const worker = outstanding ? this.assignedWorker(state, outstanding.id) : undefined;
820
- const interrupted =
821
- isWorkerProfile(state.profile) &&
822
- (state.profile !== worker || user !== (outstanding ? this.workerBasis(state, outstanding.id) : undefined));
823
- if (!this.turn || this.turn.bound) {
824
- this.receipts.clear();
825
- const execution = {
826
- id: randomUUID(),
827
- profile: state.profile,
828
- assignmentId: state.assignmentId,
829
- recoveryId: state.recoveryId,
830
- basisUserEntryId: user,
831
- pair: this.observed(),
832
- resultEntryIds: [],
833
- };
834
- const before = new Set(ctx.sessionManager.getBranch().map((e) => e.id));
835
- this.turn = {
836
- id: execution.id,
837
- profile: execution.profile,
838
- assignmentId: execution.assignmentId,
839
- recoveryId: execution.recoveryId,
840
- basisUserEntryId: user,
841
- before,
842
- pair: execution.pair,
843
- };
844
- this.openTurn();
845
- }
846
- let prepared = this.prepared(state.profile, input);
847
- if (!prepared.ready && state.profile === "coordinator" && this.stateData().assessment) {
848
- const assessment = this.stateData().assessment;
849
- this.append({
850
- type: "assessment-suspended",
851
- handoffId: assessment.handoffId,
852
- reason: "delivery-gap",
853
- basisUserEntryId: user,
854
- problems: prepared.problems,
855
- });
856
- this.projectionError = prepared.problems.map((p) => p.code).join(", ");
857
- prepared = this.prepared("coordinator", input);
858
- }
859
- check(prepared.ready, "context_unavailable", prepared.problems.map((p) => p.detail).join("; "));
860
- const known = this.stateData().exposure;
861
- const added = prepared.fullSources.filter((s) => known.get(s.ref) !== s.hash);
862
- if (isWorkerProfile(this.turn.profile))
863
- for (let i = 0; i < added.length; i += 128)
864
- this.append({
865
- type: "sources-exposed",
866
- executionId: this.turn.id,
867
- view: this.turn.profile,
868
- sources: added.slice(i, i + 128).map((s) => ({ ref: s.ref, bodyHash: s.hash })),
869
- });
870
- if (interrupted)
871
- prepared.messages.push({
872
- role: "custom",
873
- customType: ROUTING_MESSAGE,
874
- display: false,
875
- timestamp: 0,
876
- details: { routingInstance: this.token },
877
- content:
878
- "New delivered user input requires Coordinator attention. Do not run ordinary task tools; return the current partial result.",
879
- });
880
- return prepared.messages;
881
- } catch (error) {
882
- this.mark(error);
883
- ctx.abort?.();
884
- // Do not send an accidental full worker history on a projection failure.
885
- return [
886
- {
887
- role: "custom",
888
- customType: ROUTING_MESSAGE,
889
- content: `Automatic request blocked: ${this.error}`,
890
- display: false,
891
- timestamp: 0,
892
- details: { routingInstance: this.token, routingRequestBlocked: true },
893
- },
894
- ];
895
- }
896
- }
897
- messageEnd(message) {
898
- if (message?.role === "assistant" && this.turn && !this.turn.bound) this.turn.message = structuredClone(message);
899
- }
900
- contextOperation(name, input) {
901
- return name === "freeflow_context" && ["archive", "restore", "search", "retrieve"].includes(input?.operation);
902
- }
903
- handoffOperation(name, input) {
904
- if (name === "freeflow_delegate") return true;
905
- if (name === "freeflow_return") return ["submit", "supplement", "retry"].includes(input?.operation);
906
- return name === "freeflow_unit" && input?.operation === "recover";
907
- }
908
- stableRecoveryPath(path) {
909
- return (
910
- !path.startsWith("@") &&
911
- path !== "~" &&
912
- !path.startsWith("~/") &&
913
- !path.startsWith("file://") &&
914
- !/[\u00a0\u2000-\u200a\u202f\u205f\u3000]/u.test(path) &&
915
- !(process.platform === "win32" && /^\/(?:mnt\/|cygdrive\/)?[a-z](?:\/|$)/i.test(path))
916
- );
917
- }
918
- canonicalRecoveryPath(path) {
919
- check(this.stableRecoveryPath(path), "recovery_path_unsupported", `Recovery path spelling is unsupported: ${path}`);
920
- const requested = resolvePath(this.ctx.cwd, path);
921
- check(existsSync(requested), "recovery_path_unavailable", `Recovery path is unavailable: ${path}`);
922
- try {
923
- return realpathSync(requested);
924
- } catch {
925
- throw new RoutingError("recovery_path_unavailable", `Recovery path is unavailable: ${path}`);
926
- }
927
- }
928
- packagedInstruction(path) {
929
- return this.packageRoots.some((root) => {
930
- const inside = relative(root, path);
931
- if (!inside || inside.startsWith("..") || resolvePath(root, inside) !== path) return false;
932
- return (
933
- /^(?:skills|capabilities)\/[^/]+\/SKILL\.md$/.test(inside) ||
934
- /^(?:skills|capabilities)\/[^/]+\/references\/[^/]+\.md$/.test(inside)
935
- );
936
- });
937
- }
938
- recoveryReadAllowed(state, name, input) {
939
- if (name !== "read" || typeof input?.path !== "string" || !state.recoveryId) return false;
940
- const recovery = state.recoveries.get(state.recoveryId);
941
- if (!recovery || recovery.state !== "reading" || !this.stableRecoveryPath(input.path)) return false;
942
- try {
943
- const path = this.canonicalRecoveryPath(input.path);
944
- return recovery.paths.includes(path) || this.packagedInstruction(path);
945
- } catch {
946
- return false;
947
- }
948
- }
949
- workerBasis(state, assignmentId) {
950
- const recovery = state.recoveryId ? state.recoveries.get(state.recoveryId) : undefined;
951
- if (recovery?.state === "reading") return state.handoffs.get(recovery.requestHandoffId)?.basisUserEntryId ?? null;
952
- return this.taskBasis(state, assignmentId);
953
- }
954
- batch(callId, name) {
955
- check(this.turn?.message, "batch_unavailable");
956
- const matches = this.ctx.sessionManager
957
- .getBranch()
958
- .filter(
959
- (e) =>
960
- !this.turn.before.has(e.id) &&
961
- e.type === "message" &&
962
- e.message?.role === "assistant" &&
963
- e.message.content?.some((b) => b.type === "toolCall" && b.id === callId),
964
- );
965
- check(matches.length === 1, "batch_source_changed");
966
- const fingerprint = bodyHash(matches[0].message);
967
- check(!this.turn.sourceFingerprint || this.turn.sourceFingerprint === fingerprint, "batch_source_changed");
968
- // Later message_end handlers may legitimately replace the message. The first
969
- // preflight freezes the actually persisted batch before any of its tools act.
970
- this.turn.sourceFingerprint = fingerprint;
971
- this.turn.message = structuredClone(matches[0].message);
972
- const calls = (this.turn.message.content ?? []).filter((b) => b.type === "toolCall");
973
- const handoffs = calls.filter((b) => this.handoffOperation(b.name, b.arguments));
974
- check(
975
- !handoffs.length ||
976
- (handoffs.length === 1 &&
977
- this.handoffOperation(calls.at(-1)?.name, calls.at(-1)?.arguments) &&
978
- calls.every(
979
- (b) =>
980
- this.handoffOperation(b.name, b.arguments) ||
981
- b.name === "freeflow_project" ||
982
- this.contextOperation(b.name, b.arguments),
983
- )),
984
- "invalid_handoff_batch",
985
- "A handoff must be last and cannot accompany ordinary task tools.",
986
- );
987
- check(
988
- calls.some((b) => b.id === callId && b.name === name),
989
- "call_not_in_batch",
990
- );
991
- }
992
- preflight(event, ctx) {
993
- this.ctx = ctx;
994
- const name = event.toolName,
995
- isRouting = ROUTING_TOOLS.includes(name);
996
- if (!this.supported()) return isRouting ? { block: true, reason: "Routing is unavailable." } : undefined;
997
- try {
998
- const state = this.stateData();
999
- if (state.control === "manual")
1000
- return isRouting && name !== "freeflow_unit" ? { block: true, reason: "Manual control is active." } : undefined;
1001
- if (state.control !== "automatic")
1002
- return isRouting ? { block: true, reason: "Automatic routing is inactive." } : undefined;
1003
- this.batch(event.toolCallId, name);
1004
- check(!this.error && !this.store?.blocked, "routing_blocked");
1005
- this.openTurn();
1006
- if (isWorkerProfile(this.turn?.profile) && !isRouting && !this.contextOperation(name, event.input)) {
1007
- const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
1008
- const worker = a ? this.assignedWorker(state, a.id) : undefined;
1009
- const ordinary =
1010
- a?.state === "outstanding" &&
1011
- this.turn.basisUserEntryId === this.taskBasis(state, a.id) &&
1012
- state.profile === worker &&
1013
- this.turn.profile === worker;
1014
- const recovery =
1015
- a?.state === "returned" &&
1016
- this.turn.basisUserEntryId === this.workerBasis(state, a.id) &&
1017
- state.profile === worker &&
1018
- this.turn.profile === worker &&
1019
- this.recoveryReadAllowed(state, name, event.input);
1020
- check(ordinary || recovery, "worker_task_phase_ended");
1021
- }
1022
- return undefined;
1023
- } catch (error) {
1024
- return { block: true, reason: error instanceof Error ? error.message : String(error) };
1025
- }
1026
- }
1027
- callOperation(callId, name) {
1028
- check(this.turn && this.store, "execution_missing");
1029
- this.batch(callId, name);
1030
- return JSON.stringify([this.store.reader.getSessionId(), this.turn.id, callId, name]);
1031
- }
1032
- result(value) {
1033
- return { content: [{ type: "text", text: JSON.stringify(value) }], details: value };
1034
- }
1035
- async invoke(name, callId, input, signal, ctx) {
1036
- return this.enqueue(async () => {
1037
- this.ctx = ctx;
1038
- try {
1039
- check(!signal?.aborted, "cancelled");
1040
- if (name === "freeflow_unit" && input.operation === "inspect") {
1041
- check(this.supported(), "routing_unavailable");
1042
- return this.result(this.inspectUnit(input));
1043
- }
1044
- const state = this.stateData();
1045
- const expected =
1046
- name === "freeflow_delegate" || name === "freeflow_unit" ? "coordinator" : this.assignedWorker(state);
1047
- this.assertAvailable(expected);
1048
- const op = this.callOperation(callId, name);
1049
- const cached = this.receipts.get(op);
1050
- if (cached) {
1051
- check(cached.input === canonical(input), "operation_conflict");
1052
- return this.result(structuredClone(cached.value));
1053
- }
1054
- const value =
1055
- name === "freeflow_delegate"
1056
- ? this.delegate(input, callId, op)
1057
- : name === "freeflow_return"
1058
- ? this.returnReport(input, callId, op)
1059
- : name === "freeflow_project"
1060
- ? this.project(input, op)
1061
- : name === "freeflow_unit"
1062
- ? this.unit(input, callId, op)
1063
- : undefined;
1064
- if (value !== undefined) {
1065
- this.receipts.set(op, { input: canonical(input), value: structuredClone(value) });
1066
- return this.result(value);
1067
- }
1068
- throw new RoutingError("unknown_tool", name);
1069
- } catch (error) {
1070
- return this.result({
1071
- status: "blocked",
1072
- code: error instanceof RoutingError ? error.code : "operation_failed",
1073
- message: error instanceof Error ? error.message : String(error),
1074
- problems: error instanceof RoutingError ? error.problems : [],
1075
- observed: this.observed(),
1076
- expectedProfile:
1077
- name === "freeflow_unit" && input.operation === "inspect"
1078
- ? undefined
1079
- : name === "freeflow_delegate" || name === "freeflow_unit"
1080
- ? "coordinator"
1081
- : (() => {
1082
- try {
1083
- return this.assignedWorker(this.stateData());
1084
- } catch {
1085
- return undefined;
1086
- }
1087
- })(),
1088
- recoveryAction:
1089
- error instanceof RoutingError &&
1090
- [
1091
- "cursor_expired",
1092
- "invalid_cursor",
1093
- "work_ref_required",
1094
- "invalid_work_ref",
1095
- "work_unavailable",
1096
- "invalid_inspection",
1097
- ].includes(error.code)
1098
- ? "Inspect again without a cursor and use a returned work ref. Lookup recovery does not require a new assignment."
1099
- : "Preserve saved work. Use freeflow_unit inspect; reconcile current input/control before retrying the named operation.",
1100
- });
1101
- }
1102
- });
1103
- }
1104
- duplicate(op) {
1105
- return [...this.stateData().events.values()].find(
1106
- (e) =>
1107
- e.operationId === op &&
1108
- [
1109
- "delegate-accepted",
1110
- "return-accepted",
1111
- "recovery-request-accepted",
1112
- "recovery-supplement-accepted",
1113
- "recovery-cancelled",
1114
- "handoff-retry-requested",
1115
- ].includes(e.data.type),
1116
- );
1117
- }
1118
- delegate(input, callId, op) {
1119
- const prior = this.duplicate(op);
1120
- if (prior) {
1121
- check(
1122
- prior.data.type === "delegate-accepted" &&
1123
- prior.data.assignment.contract === input.contract &&
1124
- (input.worker === undefined || prior.data.handoff.to === input.worker),
1125
- "operation_conflict",
1126
- );
1127
- return { status: "accepted", handoff: prior.data.handoff.id, unchanged: true };
1128
- }
1129
- const state = this.stateData();
1130
- const enabled = this.enabledWorkers();
1131
- let worker;
1132
- if (input.worker === undefined) {
1133
- check(enabled.length === 1, "worker_required", "Delegation mode 'both' requires choosing helper or executor.");
1134
- worker = enabled[0];
1135
- } else {
1136
- check(isWorkerProfile(input.worker), "invalid_worker");
1137
- worker = input.worker;
1138
- }
1139
- check(enabled.includes(worker), "worker_disabled", `${worker} is not enabled by delegation mode.`);
1140
- const unit = state.unitId
1141
- ? state.units.get(state.unitId)
1142
- : { id: randomUUID(), objective: input.contract, state: "open", assignmentIds: [] };
1143
- const old = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
1144
- if (input.operation === "replace") check(old?.state === "outstanding" && !this.applying, "no_quiescent_assignment");
1145
- else
1146
- check(
1147
- input.operation === "assign" && old?.state !== "outstanding" && !state.pendingId && !state.recoveryId,
1148
- "assignment_outstanding",
1149
- );
1150
- const assignmentId = randomUUID(),
1151
- id = randomUUID();
1152
- const assignment = {
1153
- id: assignmentId,
1154
- unitId: unit.id,
1155
- contract: input.contract,
1156
- state: "outstanding",
1157
- delegateHandoffId: id,
1158
- basisUserEntryId: this.turn.basisUserEntryId,
1159
- };
1160
- const h = {
1161
- id,
1162
- kind: "delegate",
1163
- assignmentId,
1164
- text: input.contract,
1165
- from: "coordinator",
1166
- to: worker,
1167
- executionId: this.turn.id,
1168
- toolCallId: callId,
1169
- basisUserEntryId: this.turn.basisUserEntryId,
1170
- state: "pending",
1171
- reportRevision: 0,
1172
- limitations: [],
1173
- };
1174
- this.append(
1175
- {
1176
- type: "delegate-accepted",
1177
- unit: { ...unit, assignmentIds: [...unit.assignmentIds, assignmentId] },
1178
- assignment,
1179
- handoff: h,
1180
- ...(input.operation === "replace"
1181
- ? {
1182
- replacement: {
1183
- assignmentId: old.id,
1184
- reason: input.reason,
1185
- ...(state.pendingId ? { supersededHandoffId: state.pendingId } : {}),
1186
- },
1187
- }
1188
- : {}),
1189
- },
1190
- op,
1191
- );
1192
- return {
1193
- status: "accepted",
1194
- unit: unit.id,
1195
- assignment: assignmentId,
1196
- worker,
1197
- handoff: id,
1198
- transition: "pending",
1199
- contract: input.contract,
1200
- reason: input.reason,
1201
- createdUnit: !state.unitId,
1202
- ...(old && input.operation === "replace"
1203
- ? {
1204
- replacedAssignment: old.id,
1205
- continuity:
1206
- "Prior effects and evidence remain on native history. Reconcile uncertain effects before dependent work.",
1207
- }
1208
- : {}),
1209
- };
1210
- }
1211
- returnReport(input, callId, op) {
1212
- const state = this.stateData(),
1213
- a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
1214
- check(a, "assignment_missing");
1215
- const worker = this.assignedWorker(state, a.id);
1216
- const recovery = state.recoveryId ? state.recoveries.get(state.recoveryId) : undefined;
1217
- const original = a.returnHandoffId ? state.handoffs.get(a.returnHandoffId) : undefined;
1218
- const supplement = recovery?.supplementHandoffId ? state.handoffs.get(recovery.supplementHandoffId) : undefined;
1219
- const retryingSupplement = input.operation === "retry" && recovery?.state === "returning";
1220
- const saved = retryingSupplement || input.operation === "supplement" ? supplement : original;
1221
- const selection = state.selections.get(a.id) ?? emptySelection();
1222
- const duplicate = this.duplicate(op);
1223
- if (duplicate) {
1224
- if (input.operation === "retry") check(duplicate.data.type === "handoff-retry-requested", "operation_conflict");
1225
- else {
1226
- const expected = input.operation === "supplement" ? "recovery-supplement-accepted" : "return-accepted";
1227
- check(
1228
- duplicate.data.type === expected &&
1229
- duplicate.data.handoff.text === input.report &&
1230
- duplicate.data.handoff.outcome === input.outcome &&
1231
- canonical(duplicate.data.handoff.limitations) === canonical(input.limitations ?? []),
1232
- "operation_conflict",
1233
- );
1234
- }
1235
- const handoff = duplicate.data.handoffId ?? duplicate.data.handoff.id;
1236
- return { ...this.returnReadiness(handoff), unchanged: true };
1237
- }
1238
- if (input.operation === "retry") {
1239
- check(saved && ["blocked", "pending"].includes(saved.state), "saved_return_missing");
1240
- this.append(
1241
- {
1242
- type: "handoff-retry-requested",
1243
- handoffId: saved.id,
1244
- attemptId: randomUUID(),
1245
- executionId: this.turn.id,
1246
- toolCallId: callId,
1247
- reportRevision: saved.reportRevision,
1248
- selectionRevision: selection.revision,
1249
- },
1250
- op,
1251
- );
1252
- return {
1253
- ...this.returnReadiness(saved.id),
1254
- reportUnchanged: true,
1255
- supplementUnchanged: saved.kind === "recovery-return",
1256
- };
1257
- }
1258
- if (input.operation === "supplement") {
1259
- check(
1260
- recovery &&
1261
- ["reading", "returning"].includes(recovery.state) &&
1262
- (!supplement || ["pending", "blocked"].includes(supplement.state)),
1263
- "recovery_not_returnable",
1264
- );
1265
- const h = {
1266
- id: supplement?.id ?? randomUUID(),
1267
- kind: "recovery-return",
1268
- assignmentId: a.id,
1269
- text: input.report,
1270
- from: worker,
1271
- to: "coordinator",
1272
- executionId: this.turn.id,
1273
- toolCallId: callId,
1274
- basisUserEntryId: this.turn.basisUserEntryId,
1275
- state: "pending",
1276
- reportRevision: (supplement?.reportRevision ?? 0) + 1,
1277
- outcome: input.outcome,
1278
- limitations: input.limitations ?? [],
1279
- };
1280
- this.append({ type: "recovery-supplement-accepted", recoveryId: recovery.id, handoff: h }, op);
1281
- return this.returnReadiness(h.id);
1282
- }
1283
- check(input.operation === "submit", "invalid_return_operation");
1284
- check(
1285
- a.state === "outstanding" ||
1286
- (a.state === "returned" && original && ["pending", "blocked"].includes(original.state)),
1287
- "assignment_task_ended",
1288
- );
1289
- const h = {
1290
- id: original?.id ?? randomUUID(),
1291
- kind: "return",
1292
- assignmentId: a.id,
1293
- text: input.report,
1294
- from: worker,
1295
- to: "coordinator",
1296
- executionId: this.turn.id,
1297
- toolCallId: callId,
1298
- basisUserEntryId: this.turn.basisUserEntryId,
1299
- state: "pending",
1300
- reportRevision: (original?.reportRevision ?? 0) + 1,
1301
- outcome: input.outcome,
1302
- limitations: input.limitations ?? [],
1303
- };
1304
- this.append({ type: "return-accepted", handoff: h }, op);
1305
- return this.returnReadiness(h.id);
1306
- }
1307
- returnReadiness(id) {
1308
- const state = this.stateData();
1309
- const h = state.handoffs.get(id);
1310
- const prepared = this.prepared("coordinator", this.messages, id);
1311
- const isSupplement = h.kind === "recovery-return";
1312
- const recovery = isSupplement
1313
- ? [...state.recoveries.values()].find((candidate) => candidate.supplementHandoffId === h.id)
1314
- : undefined;
1315
- return {
1316
- status: "accepted",
1317
- reportSaved: !isSupplement,
1318
- supplementSaved: isSupplement,
1319
- handoff: id,
1320
- ...(recovery ? { recovery: recovery.id } : {}),
1321
- ...(isSupplement ? { supplementRevision: h.reportRevision } : { reportRevision: h.reportRevision }),
1322
- assignmentRef: `assignment:${h.assignmentId}`,
1323
- ...(isSupplement
1324
- ? { supplementRef: `supplement:${recovery?.id ?? "unknown"}:${h.reportRevision}` }
1325
- : { reportRef: `report:${h.id}:${h.reportRevision}` }),
1326
- producer: h.from,
1327
- outcome: h.outcome,
1328
- limitations: h.limitations,
1329
- ready: prepared.ready,
1330
- transition: prepared.ready ? "pending" : "blocked",
1331
- problems: prepared.problems,
1332
- warnings: prepared.warnings,
1333
- stage: "completed-input preparation",
1334
- finalExchangePending: true,
1335
- provisional:
1336
- "The finalized handoff exchange, target configuration and budget are revalidated at turn_end; this is not delivery evidence.",
1337
- ...(isSupplement ? { supplement: h.text } : { report: h.text }),
1338
- evidence: this.evidenceFacts(state),
1339
- };
1340
- }
1341
- project(input, op) {
1342
- check(this.projectionEnabled, "projection_disabled");
1343
- const state = this.stateData(),
1344
- a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
1345
- check(a, "assignment_missing");
1346
- const sources = this.sources(state);
1347
- sources.associate(this.messages);
1348
- const selection = state.selections.get(a.id) ?? emptySelection();
1349
- let next = selection;
1350
- if (input.operation !== "inspect") {
1351
- check(["add", "remove"].includes(input.operation), "invalid_projection_operation");
1352
- next = changeSelection(selection, input, sources, state);
1353
- if (next !== selection) this.append({ type: "selection-changed", assignmentId: a.id, selection: next }, op);
1354
- }
1355
- const prepared = this.prepared("coordinator", this.messages, a.returnHandoffId);
1356
- const scope = input.scope ?? "assignment";
1357
- let page;
1358
- if (input.operation === "inspect") {
1359
- const model = this.model("coordinator");
1360
- const checksFor = (s) => {
1361
- const eligibility = sources.selectionProblem(s.ref, state);
1362
- const limitations = [
1363
- ...(eligibility ? [eligibility] : []),
1364
- ...representationProblems(s, model),
1365
- ...sources.exchange(s).problems,
1366
- ];
1367
- return { eligible: !eligibility, targetReady: !limitations.length, limitations };
1368
- };
1369
- const rows = () =>
1370
- [...sources.byRef.values()]
1371
- .filter(
1372
- (s) =>
1373
- isWorkerProfile(s.producer) &&
1374
- (isTaskEvidence(s) || (scope === "selected" && next.selected.includes(s.ref))) &&
1375
- (scope === "selected"
1376
- ? next.selected.includes(s.ref)
1377
- : scope === "assignment"
1378
- ? s.assignmentId === a.id
1379
- : scope === "active"
1380
- ? s.active
1381
- : true) &&
1382
- (s.original ||
1383
- s.message.role === "toolResult" ||
1384
- s.message.content?.some((b) => b.type === "text" && b.text?.trim())),
1385
- )
1386
- .map((s) => {
1387
- const locator = sources.locator(s.ref);
1388
- return {
1389
- ref: s.ref,
1390
- kind: s.original ? "assistant-text" : s.message.role,
1391
- producer: s.producer,
1392
- assignment: s.assignmentId,
1393
- toolName: s.message.toolName,
1394
- ...(locator ? { locator } : {}),
1395
- active: s.active,
1396
- selected: next.selected.includes(s.ref),
1397
- retainedSelection: next.selected.includes(s.ref) && !isTaskEvidence(s),
1398
- ...checksFor(s),
1399
- };
1400
- })
1401
- // Discovery offers usable candidates. Saved selections must remain
1402
- // inspectable even when a later source or target check fails.
1403
- .filter((row) => scope === "selected" || row.targetReady);
1404
- page = this.page(
1405
- `evidence:${scope}`,
1406
- rows,
1407
- input.cursor,
1408
- 30,
1409
- scope === "assignment" || scope === "selected" ? a.id : undefined,
1410
- scope === "active",
1411
- (rows) => ({
1412
- scopeCounts: {
1413
- candidates: rows.length,
1414
- eligible: rows.filter((r) => r.eligible).length,
1415
- targetReady: rows.filter((r) => r.targetReady).length,
1416
- },
1417
- otherAssignments: [...sources.byRef.values()].filter(
1418
- (s) =>
1419
- isWorkerProfile(s.producer) && s.assignmentId !== a.id && isTaskEvidence(s) && checksFor(s).targetReady,
1420
- ).length,
1421
- }),
1422
- );
1423
- }
1424
- return {
1425
- status: input.operation === "inspect" || next === selection ? "unchanged" : "saved",
1426
- ...(page
1427
- ? {
1428
- scope,
1429
- count: page.count,
1430
- returned: page.items.length,
1431
- candidates: page.items,
1432
- nextCursor: page.nextCursor,
1433
- ...page.summary,
1434
- pageCounts: {
1435
- candidates: page.items.length,
1436
- eligible: page.items.filter((r) => r.eligible).length,
1437
- targetReady: page.items.filter((r) => r.targetReady).length,
1438
- },
1439
- countsBasis:
1440
- "Inspection snapshot; page counts describe returned candidates, scope counts describe all candidates in this scope.",
1441
- historyHint: page.summary.otherAssignments
1442
- ? "Previously exposed task evidence exists in other assignments; inspect scope history when needed."
1443
- : undefined,
1444
- }
1445
- : {}),
1446
- revision: next.revision,
1447
- selected: next.selected,
1448
- selectedCount: next.selected.length,
1449
- unresolved: next.unresolved,
1450
- ready: !next.unresolved.length && prepared.ready,
1451
- problems: prepared.problems,
1452
- warnings: prepared.warnings,
1453
- evidence: this.evidenceFacts(this.stateData()),
1454
- items: (input.refs ? [...new Set(input.refs)] : []).map((ref) => ({
1455
- ref,
1456
- status: next.unresolved.some((p) => p.ref === ref)
1457
- ? "rejected"
1458
- : input.operation === "remove"
1459
- ? selection.selected.includes(ref)
1460
- ? "removed"
1461
- : selection.unresolved.some((p) => p.ref === ref)
1462
- ? "withdrawn"
1463
- : "unchanged"
1464
- : selection.selected.includes(ref)
1465
- ? "already_selected"
1466
- : "added",
1467
- })),
1468
- estimate: { tokens: prepared.estimatedTokens, method: prepared.estimateMethod },
1469
- };
1470
- }
1471
- unit(input, callId, op) {
1472
- const state = this.stateData();
1473
- const prior = [...state.events.values()].find((e) => e.operationId === op);
1474
- if (prior) {
1475
- if (input.operation === "recover") {
1476
- check(
1477
- prior.data.type === "recovery-request-accepted" &&
1478
- prior.data.recovery.request === input.request &&
1479
- canonical(prior.data.recovery.requestedPaths) === canonical([...new Set(input.paths ?? [])]),
1480
- "operation_conflict",
1481
- );
1482
- const { recovery, handoff } = prior.data;
1483
- return {
1484
- status: "accepted",
1485
- unchanged: true,
1486
- recovery: recovery.id,
1487
- handoff: handoff.id,
1488
- assignmentRef: `assignment:${recovery.assignmentId}`,
1489
- reportRef: `report:${recovery.assessmentHandoffId}:${recovery.baseReportRevision}`,
1490
- request: recovery.request,
1491
- paths: recovery.paths,
1492
- transition: handoff.state,
1493
- };
1494
- }
1495
- if (input.operation === "cancel-recovery") {
1496
- check(prior.data.type === "recovery-cancelled" && prior.data.reason === input.reason, "operation_conflict");
1497
- return {
1498
- status: "cancelled",
1499
- unchanged: true,
1500
- recovery: prior.data.recoveryId,
1501
- reason: prior.data.reason,
1502
- assessment: state.assessment?.handoffId,
1503
- };
1504
- }
1505
- check(
1506
- (input.operation === "assess" && prior.data.type === "assessment-resumed") ||
1507
- (input.operation === "close" &&
1508
- prior.data.type === "unit-closed" &&
1509
- prior.data.outcome === input.outcome &&
1510
- prior.data.assessment === input.assessment),
1511
- "operation_conflict",
1512
- );
1513
- return { status: "unchanged", operation: input.operation };
1514
- }
1515
- if (input.operation === "recover") {
1516
- const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
1517
- const base = a?.returnHandoffId ? state.handoffs.get(a.returnHandoffId) : undefined;
1518
- check(
1519
- state.unitId &&
1520
- a?.state === "returned" &&
1521
- base?.kind === "return" &&
1522
- base.state === "configured" &&
1523
- state.assessment?.handoffId === base.id &&
1524
- !state.pendingId &&
1525
- !state.recoveryId &&
1526
- !this.applying,
1527
- "recovery_not_available",
1528
- );
1529
- const worker = this.assignedWorker(state, a.id);
1530
- const id = randomUUID(),
1531
- handoffId = randomUUID();
1532
- const paths = [...new Set((input.paths ?? []).map((path) => this.canonicalRecoveryPath(path)))];
1533
- const recovery = {
1534
- id,
1535
- assignmentId: a.id,
1536
- assessmentHandoffId: base.id,
1537
- baseReportRevision: base.reportRevision,
1538
- request: input.request,
1539
- requestedPaths: [...new Set(input.paths ?? [])],
1540
- paths,
1541
- state: "requested",
1542
- requestHandoffId: handoffId,
1543
- supplementRevision: 0,
1544
- };
1545
- const handoff = {
1546
- id: handoffId,
1547
- kind: "recovery-request",
1548
- assignmentId: a.id,
1549
- text: input.request,
1550
- from: "coordinator",
1551
- to: worker,
1552
- executionId: this.turn.id,
1553
- toolCallId: callId,
1554
- basisUserEntryId: this.turn.basisUserEntryId,
1555
- state: "pending",
1556
- reportRevision: 0,
1557
- limitations: [],
1558
- };
1559
- this.append({ type: "recovery-request-accepted", recovery, handoff }, op);
1560
- return {
1561
- status: "accepted",
1562
- recovery: id,
1563
- handoff: handoffId,
1564
- assignmentRef: `assignment:${a.id}`,
1565
- reportRef: `report:${base.id}:${base.reportRevision}`,
1566
- request: input.request,
1567
- paths,
1568
- transition: "pending",
1569
- };
1570
- }
1571
- if (input.operation === "cancel-recovery") {
1572
- check(state.recoveryId && !this.applying, "recovery_not_cancellable");
1573
- check(
1574
- ![...state.executions.values()].some(
1575
- (execution) =>
1576
- execution.recoveryId === state.recoveryId &&
1577
- isWorkerProfile(execution.profile) &&
1578
- !execution.assistantEntryId &&
1579
- !execution.interrupted,
1580
- ),
1581
- "recovery_not_cancellable",
1582
- );
1583
- this.append({ type: "recovery-cancelled", recoveryId: state.recoveryId, reason: input.reason }, op);
1584
- return {
1585
- status: "cancelled",
1586
- recovery: state.recoveryId,
1587
- reason: input.reason,
1588
- assessment: state.assessment?.handoffId,
1589
- };
1590
- }
1591
- if (input.operation === "assess") {
1592
- check(state.assessment, "assessment_missing");
1593
- check(!state.recoveryId, "recovery_outstanding");
1594
- if (state.assessment.view === "active") return { status: "unchanged", ready: true };
1595
- const prepared = this.prepared("coordinator", this.messages, state.assessment.handoffId, true);
1596
- if (!prepared.ready) return { status: "suspended", ready: false, problems: prepared.problems };
1597
- if (this.projectionEnabled) check(prepared.reservation, "reservation_missing");
1598
- this.append(
1599
- {
1600
- type: "assessment-resumed",
1601
- handoffId: state.assessment.handoffId,
1602
- basisUserEntryId: this.turn.basisUserEntryId,
1603
- ...(prepared.reservation ? { reservation: prepared.reservation } : {}),
1604
- },
1605
- op,
1606
- );
1607
- this.projectionError = undefined;
1608
- return {
1609
- status: "resumed",
1610
- ready: true,
1611
- stage: "prepared assessment; next request revalidated",
1612
- evidence: this.evidenceFacts(this.stateData()),
1613
- };
1614
- }
1615
- check(input.operation === "close" && state.unitId, "unit_missing");
1616
- const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
1617
- check(!this.applying, "not_quiescent");
1618
- if (state.recoveryId) {
1619
- check(input.outcome !== "accepted", "unfinished_work");
1620
- check(
1621
- ![...state.executions.values()].some(
1622
- (execution) =>
1623
- execution.recoveryId === state.recoveryId &&
1624
- isWorkerProfile(execution.profile) &&
1625
- !execution.assistantEntryId &&
1626
- !execution.interrupted,
1627
- ),
1628
- "not_quiescent",
1629
- );
1630
- }
1631
- this.append(
1632
- {
1633
- type: "unit-closed",
1634
- unitId: state.unitId,
1635
- outcome: input.outcome,
1636
- assessment: input.assessment,
1637
- ...(a?.state === "outstanding" && input.outcome !== "accepted" ? { supersededAssignmentId: a.id } : {}),
1638
- },
1639
- op,
1640
- );
1641
- return { status: "closed", unit: state.unitId, outcome: input.outcome, assessment: input.assessment };
1642
- }
1643
- page(scope, rows, cursor, size = 30, assignment, active = false, summarize) {
1644
- const branch = this.ctx.sessionManager.getBranch();
1645
- const compaction = branch.filter((e) => e.type === "compaction").at(-1)?.id;
1646
- let key,
1647
- offset = 0;
1648
- if (cursor) {
1649
- const match = /^([a-f0-9-]+):([0-9]+)$/.exec(cursor);
1650
- check(match, "invalid_cursor", "Use the returned cursor unchanged.");
1651
- key = match[1];
1652
- offset = Number(match[2]);
1653
- const page = this.pages.get(key);
1654
- check(
1655
- page &&
1656
- page.scope === scope &&
1657
- page.assignment === assignment &&
1658
- (!page.basis || branch.some((e) => e.id === page.basis)) &&
1659
- (!active || page.compaction === compaction),
1660
- "cursor_expired",
1661
- "History or scope changed. Inspect again without a cursor.",
1662
- );
1663
- check(
1664
- Number.isSafeInteger(offset) && offset >= 0 && offset < page.rows.length && offset % page.size === 0,
1665
- "invalid_cursor",
1666
- );
1667
- } else {
1668
- key = randomUUID();
1669
- if (this.pages.size >= 8) this.pages.delete(this.pages.keys().next().value);
1670
- const items = typeof rows === "function" ? rows() : rows;
1671
- this.pages.set(key, {
1672
- scope,
1673
- basis: branch.at(-1)?.id ?? null,
1674
- assignment,
1675
- compaction,
1676
- rows: items,
1677
- size,
1678
- summary: summarize?.(items),
1679
- });
1680
- }
1681
- const page = this.pages.get(key);
1682
- return {
1683
- scope,
1684
- count: page.rows.length,
1685
- offset,
1686
- summary: page.summary,
1687
- items: page.rows.slice(offset, offset + page.size),
1688
- nextCursor: offset + page.size < page.rows.length ? `${key}:${offset + page.size}` : undefined,
1689
- };
1690
- }
1691
- inspectUnit(input) {
1692
- const state = this.stateData(),
1693
- view = input.view ?? "current";
1694
- check(view === "detail" || !input.ref, "invalid_inspection", "A work ref belongs to view detail.");
1695
- check(
1696
- view === "history" || (!input.cursor && !input.limit),
1697
- "invalid_inspection",
1698
- "Pagination belongs to view history.",
1699
- );
1700
- if (view === "current")
1701
- return {
1702
- ...this.status(),
1703
- status: "inspected",
1704
- view,
1705
- currentRef: state.assignmentId ? `assignment:${state.assignmentId}` : undefined,
1706
- };
1707
- if (view === "history") {
1708
- const numbers = new Map([...state.units.keys()].map((id, i) => [id, i + 1]));
1709
- const rows = () =>
1710
- [...state.assignments.values()].reverse().map((a) => {
1711
- const u = state.units.get(a.unitId),
1712
- h = a.returnHandoffId ? state.handoffs.get(a.returnHandoffId) : undefined;
1713
- const delegate = state.handoffs.get(a.delegateHandoffId);
1714
- return {
1715
- ref: `assignment:${a.id}`,
1716
- unitRef: `unit:${a.unitId}`,
1717
- unitNumber: numbers.get(a.unitId),
1718
- assignmentNumber: (u?.assignmentIds.indexOf(a.id) ?? 0) + 1,
1719
- state: a.state,
1720
- unitState: u?.state,
1721
- disposition: u?.disposition,
1722
- summary: a.contract.slice(0, 160),
1723
- reportAvailable: !!h,
1724
- outcome: h?.outcome,
1725
- from: delegate?.from,
1726
- to: delegate?.to,
1727
- };
1728
- });
1729
- const page = this.page("work-history", rows, input.cursor, input.limit ?? 20);
1730
- return {
1731
- status: "inspected",
1732
- view,
1733
- history: page.items,
1734
- count: page.count,
1735
- returned: page.items.length,
1736
- nextCursor: page.nextCursor,
1737
- };
1738
- }
1739
- check(
1740
- view === "detail" && typeof input.ref === "string",
1741
- "work_ref_required",
1742
- "Use a ref returned by history inspection.",
1743
- );
1744
- check(
1745
- /^(?:(?:unit|assignment|recovery):[^:\s]+|(?:report|supplement):[^:\s]+:[1-9][0-9]*)$/.test(input.ref),
1746
- "invalid_work_ref",
1747
- "Use assignment, report, recovery, or supplement refs returned by a receipt or inspection, unchanged. A bare handoff ID is not a work ref.",
1748
- );
1749
- if (input.ref.startsWith("unit:")) {
1750
- const u = state.units.get(input.ref.slice(5));
1751
- check(u, "work_unavailable", "Unit is not on current ancestry.");
1752
- return {
1753
- status: "inspected",
1754
- view,
1755
- ref: input.ref,
1756
- unit: {
1757
- id: u.id,
1758
- state: u.state,
1759
- objective: u.objective,
1760
- disposition: u.disposition,
1761
- assessment: u.assessment,
1762
- },
1763
- assignmentCount: u.assignmentIds.length,
1764
- assignmentRefs: u.assignmentIds.slice(0, 30).map((id) => `assignment:${id}`),
1765
- remaining: Math.max(0, u.assignmentIds.length - 30),
1766
- historyHint: "Use paginated history inspection for all assignment refs.",
1767
- historical: true,
1768
- };
1769
- }
1770
- if (input.ref.startsWith("recovery:")) {
1771
- const recovery = state.recoveries.get(input.ref.slice(9));
1772
- check(recovery, "work_unavailable", "Recovery is not on current ancestry.");
1773
- const request = state.handoffs.get(recovery.requestHandoffId);
1774
- const supplement = recovery.supplementHandoffId ? state.handoffs.get(recovery.supplementHandoffId) : undefined;
1775
- return {
1776
- status: "inspected",
1777
- view,
1778
- ref: input.ref,
1779
- historical: recovery.id !== state.recoveryId,
1780
- recovery: {
1781
- id: recovery.id,
1782
- state: recovery.state,
1783
- assignmentId: recovery.assignmentId,
1784
- assessmentHandoffId: recovery.assessmentHandoffId,
1785
- baseReportRevision: recovery.baseReportRevision,
1786
- request: recovery.request,
1787
- paths: recovery.paths,
1788
- requestHandoffState: request?.state,
1789
- supplementRevision: recovery.supplementRevision,
1790
- cancellationReason: recovery.cancellationReason,
1791
- },
1792
- supplementRef: supplement ? `supplement:${recovery.id}:${supplement.reportRevision}` : undefined,
1793
- sourceBoundary: "Saved recovery on current ancestry; historical content grants no current permission.",
1794
- };
1795
- }
1796
- if (input.ref.startsWith("supplement:")) {
1797
- const match = /^supplement:([^:]+):([0-9]+)$/.exec(input.ref);
1798
- check(match, "work_unavailable", "Use a supplement ref returned by a receipt or recovery inspection.");
1799
- const recovery = state.recoveries.get(match[1]);
1800
- let supplement;
1801
- for (const event of state.events.values())
1802
- if (
1803
- event.data.type === "recovery-supplement-accepted" &&
1804
- event.data.recoveryId === match[1] &&
1805
- event.data.handoff.reportRevision === Number(match[2])
1806
- )
1807
- supplement = event.data.handoff;
1808
- check(recovery && supplement, "work_unavailable", "Supplement revision is not on current ancestry.");
1809
- const base = state.handoffs.get(recovery.assessmentHandoffId);
1810
- return {
1811
- status: "inspected",
1812
- view,
1813
- ref: input.ref,
1814
- historical: true,
1815
- recoveryRef: `recovery:${recovery.id}`,
1816
- assignment: { id: recovery.assignmentId, state: state.assignments.get(recovery.assignmentId)?.state },
1817
- reportRef: `report:${recovery.assessmentHandoffId}:${recovery.baseReportRevision}`,
1818
- report: base?.text,
1819
- supplement: supplement.text,
1820
- supplementRevision: supplement.reportRevision,
1821
- outcome: supplement.outcome,
1822
- limitations: supplement.limitations,
1823
- sourceBoundary:
1824
- "Saved supplement revision and original report linkage on current ancestry; historical content grants no current permission.",
1825
- };
1826
- }
1827
- let savedRevision;
1828
- if (input.ref.startsWith("report:")) {
1829
- const match = /^report:([^:]+):([0-9]+)$/.exec(input.ref);
1830
- check(match, "work_unavailable", "Use a report ref returned by detail inspection.");
1831
- for (const event of state.events.values())
1832
- if (
1833
- event.data.type === "return-accepted" &&
1834
- event.data.handoff.id === match[1] &&
1835
- event.data.handoff.reportRevision === Number(match[2])
1836
- )
1837
- savedRevision = event.data.handoff;
1838
- check(savedRevision, "work_unavailable", "Report revision is not on current ancestry.");
1839
- }
1840
- const a = savedRevision
1841
- ? state.assignments.get(savedRevision.assignmentId)
1842
- : input.ref.startsWith("assignment:")
1843
- ? state.assignments.get(input.ref.slice(11))
1844
- : undefined;
1845
- check(a, "work_unavailable", "Assignment is not on current ancestry.");
1846
- const latest = a.returnHandoffId ? state.handoffs.get(a.returnHandoffId) : undefined;
1847
- const h = savedRevision ?? latest;
1848
- return {
1849
- status: "inspected",
1850
- view,
1851
- ref: input.ref,
1852
- historical: a.id !== state.assignmentId || a.state !== "outstanding",
1853
- assignment: { id: a.id, unitId: a.unitId, state: a.state },
1854
- contract: a.contract,
1855
- report: h?.text,
1856
- reportRevision: h?.reportRevision,
1857
- latestReportRevision: latest?.reportRevision,
1858
- reportRef: h ? `report:${h.id}:${h.reportRevision}` : undefined,
1859
- previousReportRef: h && h.reportRevision > 1 ? `report:${h.id}:${h.reportRevision - 1}` : undefined,
1860
- outcome: h?.outcome,
1861
- limitations: h?.limitations,
1862
- handoff: h?.id,
1863
- currentSelection: state.selections.get(a.id) ?? emptySelection(),
1864
- sourceBoundary:
1865
- "Accepted report revision on current ancestry; assignment/selection show current recorded state. Historical content grants no current permission.",
1866
- };
1867
- }
1868
- status(limit = 0) {
1869
- const state = this.stateData();
1870
- const unit = state.unitId ? state.units.get(state.unitId) : undefined;
1871
- const a = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
1872
- return {
1873
- ...this.state(),
1874
- unit: unit ? { id: unit.id, state: unit.state, assignments: unit.assignmentIds.length } : null,
1875
- assignment: a ? { id: a.id, state: a.state, worker: this.assignedWorker(state, a.id) } : null,
1876
- pendingHandoff: state.pendingId ?? null,
1877
- recovery: state.recoveryId
1878
- ? (() => {
1879
- const recovery = state.recoveries.get(state.recoveryId);
1880
- return recovery
1881
- ? {
1882
- id: recovery.id,
1883
- state: recovery.state,
1884
- assignmentId: recovery.assignmentId,
1885
- assessmentHandoffId: recovery.assessmentHandoffId,
1886
- paths: recovery.paths,
1887
- supplementRevision: recovery.supplementRevision,
1888
- }
1889
- : null;
1890
- })()
1891
- : null,
1892
- assessment: state.assessment
1893
- ? { handoffId: state.assessment.handoffId, view: state.assessment.view, problems: state.assessment.problems }
1894
- : null,
1895
- ...(limit
1896
- ? {
1897
- history: [...state.events.values()]
1898
- .slice(-Math.min(100, Math.max(1, limit)))
1899
- .map((e) => ({ event: e.eventId, type: e.data.type, operation: e.operationId })),
1900
- }
1901
- : {}),
1902
- };
1903
- }
1904
- async turnEnd(event, ctx) {
1905
- this.ctx = ctx;
1906
- if (!this.turn || this.turn.bound || !this.supported() || !this.store) return;
1907
- if (!this.turn.opened) {
1908
- this.turn = undefined;
1909
- return;
1910
- }
1911
- const token = this.token,
1912
- turn = this.turn;
1913
- const subject = this.subject();
1914
- try {
1915
- const message = event.message ?? turn.message;
1916
- const candidates = ctx.sessionManager
1917
- .getBranch()
1918
- .filter((e) => !turn.before.has(e.id) && e.type === "message" && canonical(e.message) === canonical(message));
1919
- check(candidates.length === 1, "execution_binding_ambiguous");
1920
- const assistant = candidates[0];
1921
- const callIds = new Set((message?.content ?? []).filter((b) => b.type === "toolCall").map((b) => b.id));
1922
- const results = ctx.sessionManager
1923
- .getBranch()
1924
- .filter((e) => !turn.before.has(e.id) && e.message?.role === "toolResult" && callIds.has(e.message.toolCallId));
1925
- const outcome =
1926
- message?.stopReason === "error" ? "failed" : message?.stopReason === "aborted" ? "aborted" : "completed";
1927
- this.append(
1928
- {
1929
- type: "execution-bound",
1930
- executionId: turn.id,
1931
- assistantEntryId: assistant.id,
1932
- resultEntryIds: results.map((e) => e.id),
1933
- outcome,
1934
- },
1935
- JSON.stringify(["binding", turn.id]),
1936
- );
1937
- turn.bound = assistant.id;
1938
- const state = this.stateData(),
1939
- pending = state.pendingId ? state.handoffs.get(state.pendingId) : undefined;
1940
- if (!pending || pending.state === "superseded") return;
1941
- const attempt = state.attempts.get(pending.id);
1942
- if (pending.executionId !== turn.id && attempt?.executionId !== turn.id) return;
1943
- if (ctx.signal?.aborted || outcome === "aborted" || state.control !== "automatic") return;
1944
- const inputs = [...this.messages, message, ...results.map((e) => e.message)].filter(Boolean);
1945
- const latest = this.stateData();
1946
- const retry = latest.attempts.get(pending.id);
1947
- if (retry?.executionId === turn.id)
1948
- check(
1949
- retry.reportRevision === pending.reportRevision &&
1950
- retry.selectionRevision === (latest.selections.get(pending.assignmentId)?.revision ?? 0),
1951
- "stale_retry_revision",
1952
- );
1953
- await this.finishHandoff(pending, inputs, token);
1954
- } catch (error) {
1955
- if (this.current(subject)) {
1956
- this.mark(error);
1957
- ctx.abort?.();
1958
- }
1959
- }
1960
- }
1961
- async finishHandoff(h, inputs, token) {
1962
- const subject = this.subject();
1963
- const state = this.stateData();
1964
- check(state.control === "automatic" && !this.store?.blocked && token === this.token, "transition_ineligible");
1965
- const execution = state.executions.get(h.executionId);
1966
- check(
1967
- execution?.assistantEntryId,
1968
- "source_turn_incomplete",
1969
- "Saved communication belongs to an incomplete historical turn. Reconcile its effects and replace or cancel the outstanding work; no tool result is fabricated.",
1970
- );
1971
- const source = new Sources(this.ctx.sessionManager.getBranch(), state);
1972
- const carrier = source.byRef.get(`ctx:${execution.assistantEntryId}`);
1973
- check(
1974
- carrier && !source.exchange(carrier).problems.length,
1975
- "source_exchange_incomplete",
1976
- "The saved handoff has an incomplete native exchange; reconcile or explicitly dispose of it.",
1977
- );
1978
- let attentionProblems = [];
1979
- if (["return", "recovery-return"].includes(h.kind) && this.projectionEnabled) {
1980
- const prepared = this.prepared("coordinator", inputs, h.id);
1981
- if (!prepared.ready) {
1982
- const attentionFallback = h.kind === "recovery-return" && h.outcome !== "completed";
1983
- this.projectionError = prepared.problems.map((p) => p.code).join(", ");
1984
- if (attentionFallback) attentionProblems = prepared.problems;
1985
- else {
1986
- this.append({
1987
- type: "handoff-state",
1988
- handoffId: h.id,
1989
- state: "blocked",
1990
- reason: prepared.problems
1991
- .map((p) => p.detail)
1992
- .join("; ")
1993
- .slice(0, 4096),
1994
- });
1995
- return;
1996
- }
1997
- } else {
1998
- check(prepared.reservation, "reservation_missing");
1999
- this.append({ type: "handoff-prepared", handoffId: h.id, reservation: prepared.reservation });
2000
- }
2001
- }
2002
- try {
2003
- const current = this.stateData();
2004
- check(current.pendingId === h.id && current.control === "automatic" && token === this.token, "stale_handoff");
2005
- await this.applyPair(this.profilePair(h.to));
2006
- this.guard(subject);
2007
- this.append({ type: "handoff-state", handoffId: h.id, state: "configured", observedPair: this.observed() });
2008
- const completed = this.stateData();
2009
- const reservation = completed.reservations.get(h.id);
2010
- const recovery =
2011
- h.kind === "recovery-return"
2012
- ? [...completed.recoveries.values()].find((candidate) => candidate.supplementHandoffId === h.id)
2013
- : undefined;
2014
- if (h.kind === "recovery-return" && recovery && completed.assessment?.view === "suspended") {
2015
- if (attentionProblems.length) {
2016
- this.append({
2017
- type: "assessment-suspended",
2018
- handoffId: recovery.assessmentHandoffId,
2019
- reason: "delivery-gap",
2020
- basisUserEntryId: this.lastDeliveredUser(this.sources(completed), inputs),
2021
- problems: attentionProblems,
2022
- });
2023
- } else if (completed.assessment.suspensionReason === "recovery") {
2024
- this.append({
2025
- type: "assessment-resumed",
2026
- handoffId: recovery.assessmentHandoffId,
2027
- basisUserEntryId: this.lastDeliveredUser(this.sources(completed), inputs),
2028
- ...(reservation ? { reservation } : {}),
2029
- });
2030
- }
2031
- }
2032
- if (h.kind === "return" && completed.assessment?.view === "suspended" && reservation) {
2033
- this.append({
2034
- type: "assessment-resumed",
2035
- handoffId: h.id,
2036
- basisUserEntryId: this.lastDeliveredUser(this.sources(completed), inputs),
2037
- reservation,
2038
- });
2039
- }
2040
- if (!attentionProblems.length) this.projectionError = undefined;
2041
- } catch (error) {
2042
- if (this.current(subject) && !this.store?.blocked && !this.error && this.stateData().pendingId === h.id) {
2043
- this.append({
2044
- type: "handoff-state",
2045
- handoffId: h.id,
2046
- state: "blocked",
2047
- reason: error instanceof Error ? error.message.slice(0, 4096) : "configuration_failed",
2048
- });
2049
- this.ctx.ui?.notify?.(
2050
- `Couldn’t switch to ${h.to.charAt(0).toUpperCase()}${h.to.slice(1)} — ${["return", "recovery-return"].includes(h.kind) ? "report" : "assignment"} saved.`,
2051
- "warning",
2052
- );
2053
- } else throw error;
2054
- }
2055
- }
2056
- async resume(ctx) {
2057
- this.ctx = ctx;
2058
- check(ctx.isIdle?.(), "not_idle");
2059
- check(this.store && this.capability?.effective, "routing_unavailable");
2060
- this.revision++;
2061
- const subject = this.subject();
2062
- return this.enqueue(() => this.resumeCurrent(ctx, subject));
2063
- }
2064
- async resumeCurrent(ctx, subject) {
2065
- this.guard(subject);
2066
- check(this.store, "routing_unavailable");
2067
- await this.store.reconcile();
2068
- this.guard(subject);
2069
- this.error = undefined;
2070
- this.retireUnbound("Explicit resume retires an interrupted attempt without claiming its effects completed.");
2071
- const state = this.stateData();
2072
- check(state.control === "automatic", "manual_control");
2073
- check(
2074
- !ctx.hasPendingMessages?.(),
2075
- "pending_input",
2076
- "Queued input must be delivered and reconciled before resuming a worker.",
2077
- );
2078
- const pending = state.pendingId ? state.handoffs.get(state.pendingId) : undefined;
2079
- if (pending) {
2080
- const source = ctx.sessionManager.buildSessionContext?.().messages;
2081
- check(Array.isArray(source) || this.messages.length, "resume_context_unavailable");
2082
- await this.finishHandoff(pending, source ?? this.messages, this.token);
2083
- this.guard(subject);
2084
- if (this.stateData().pendingId) {
2085
- check(
2086
- ["return", "recovery-return"].includes(pending.kind) && this.projectionError,
2087
- "handoff_still_blocked",
2088
- "Saved transfer is still blocked; reconcile its configuration before retrying.",
2089
- );
2090
- const worker = this.assignedWorker(this.stateData(), pending.assignmentId);
2091
- this.append({
2092
- type: "control",
2093
- control: "automatic",
2094
- profile: worker,
2095
- reason: "Explicit resume of saved-return evidence correction only; ordinary assignment work remains ended",
2096
- });
2097
- await this.applyPair(this.profilePair(worker));
2098
- }
2099
- } else {
2100
- const assignment = state.assignmentId ? state.assignments.get(state.assignmentId) : undefined;
2101
- const input = ctx.sessionManager.buildSessionContext?.().messages ?? this.messages;
2102
- const user = this.lastDeliveredUser(new Sources(ctx.sessionManager.getBranch(), state), input);
2103
- const coordinatorSawInput = [...state.executions.values()].some(
2104
- (x) =>
2105
- x.profile === "coordinator" && x.basisUserEntryId === user && x.assistantEntryId && x.outcome === "completed",
2106
- );
2107
- const recovery = state.recoveryId ? state.recoveries.get(state.recoveryId) : undefined;
2108
- const worker = assignment ? this.assignedWorker(state, assignment.id) : undefined;
2109
- if (assignment?.state === "returned" && recovery?.state === "reading") {
2110
- check(
2111
- user === this.workerBasis(state, assignment.id) || coordinatorSawInput,
2112
- "input_unreconciled",
2113
- "New input needs Coordinator attention before evidence recovery can resume.",
2114
- );
2115
- this.append({
2116
- type: "control",
2117
- control: "automatic",
2118
- profile: worker,
2119
- reason: "Explicit user resume of unchanged evidence recovery; ordinary assignment work remains ended",
2120
- });
2121
- await this.applyPair(this.profilePair(worker));
2122
- } else if (assignment?.state === "outstanding") {
2123
- check(
2124
- user === this.taskBasis(state, assignment.id) || coordinatorSawInput,
2125
- "input_unreconciled",
2126
- "New input needs Coordinator attention before the unchanged assignment can resume.",
2127
- );
2128
- this.append({ type: "assignment-resumed", assignmentId: assignment.id, basisUserEntryId: user });
2129
- this.append({
2130
- type: "control",
2131
- control: "automatic",
2132
- profile: worker,
2133
- reason: "Explicit user resume of the unchanged assignment; current restrictions remain applicable",
2134
- });
2135
- await this.applyPair(this.profilePair(worker));
2136
- } else await this.applyPair(this.profilePair("coordinator"));
2137
- }
2138
- this.guard(subject);
2139
- this.pi.sendMessage(
2140
- {
2141
- customType: "freeflow-routing-v2-resume",
2142
- content:
2143
- "Explicit user resume of the saved routing responsibility. Recover the current contract and evidence before continuing.",
2144
- display: true,
2145
- },
2146
- { triggerTurn: true },
2147
- );
2148
- }
2149
- async command(args, ctx) {
2150
- const words = args.trim().split(/\s+/);
2151
- if (!["profile", "resume"].includes(words[0])) return false;
2152
- try {
2153
- if (words[0] === "profile" && words[1] === "history") {
2154
- check(words.length === 2 || (words.length === 3 && words[2] === "diagnostics"), "invalid_history_command");
2155
- ctx.ui.notify(
2156
- JSON.stringify(
2157
- words[2] === "diagnostics" ? this.status(30) : this.inspectUnit({ view: "history", limit: 30 }),
2158
- ),
2159
- "info",
2160
- );
2161
- return true;
2162
- }
2163
- check(ctx.isIdle?.(), "not_idle");
2164
- this.ctx = ctx;
2165
- if (words[0] === "resume" && words.length === 1) {
2166
- await this.resume(ctx);
2167
- return true;
2168
- }
2169
- check(
2170
- words.length === 2 && [...PROFILES, "auto"].includes(words[1]),
2171
- "invalid_profile_command",
2172
- "Use /freeflow profile coordinator|helper|executor|auto|history, or /freeflow resume.",
2173
- );
2174
- const result = words[1] === "auto" ? await this.setAutomaticControl() : await this.setManualProfile(words[1]);
2175
- ctx.ui.notify(JSON.stringify(result), result.status === "blocked" ? "warning" : "info");
2176
- } catch (error) {
2177
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
2178
- }
2179
- return true;
2180
- }
2181
- }