@hassangameryt/freeflow 0.7.3 → 0.8.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 (276) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/NOTICE +14 -0
  6. package/README.md +26 -31
  7. package/capabilities/cognitive-routing/SKILL.md +106 -161
  8. package/capabilities/cognitive-routing/references/both-mode.md +36 -0
  9. package/capabilities/cognitive-routing/references/executor-mode.md +48 -0
  10. package/capabilities/cognitive-routing/references/helper-mode.md +47 -0
  11. package/capabilities/compaction/SKILL.md +49 -0
  12. package/capabilities/compaction/references/summary-format.md +239 -0
  13. package/command-surface.json +4 -0
  14. package/gemini-extension.json +1 -1
  15. package/hooks/shared/runtime-context.mjs +28 -30
  16. package/licenses/Apache-2.0.txt +55 -0
  17. package/package.json +22 -9
  18. package/pi-extension/README.md +41 -0
  19. package/pi-extension/dist/cognitive-routing/assembler.js +393 -0
  20. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +5 -2
  21. package/pi-extension/dist/cognitive-routing/economics.js +45 -0
  22. package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
  23. package/pi-extension/dist/{session-sources/events.js → cognitive-routing/event-store.js} +103 -40
  24. package/pi-extension/dist/cognitive-routing/gate.js +241 -0
  25. package/pi-extension/dist/cognitive-routing/handlers.js +899 -0
  26. package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
  27. package/pi-extension/dist/cognitive-routing/model-control.js +467 -0
  28. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/projection.js +229 -56
  29. package/pi-extension/dist/cognitive-routing/provenance.js +81 -0
  30. package/pi-extension/dist/cognitive-routing/runtime.js +396 -0
  31. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +43 -7
  32. package/pi-extension/dist/cognitive-routing/session.js +221 -0
  33. package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +99 -85
  34. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +37 -378
  35. package/pi-extension/dist/cognitive-routing/status.js +80 -0
  36. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/tools.js +12 -12
  37. package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +5 -11
  38. package/pi-extension/dist/compaction/carry.js +191 -0
  39. package/pi-extension/dist/compaction/config.js +22 -0
  40. package/pi-extension/dist/compaction/controller.js +471 -0
  41. package/pi-extension/dist/compaction/harness.js +77 -0
  42. package/pi-extension/dist/compaction/results.js +158 -0
  43. package/pi-extension/dist/compaction/thresholds.js +35 -0
  44. package/pi-extension/dist/compaction/tool.js +57 -0
  45. package/pi-extension/dist/host/branch.js +13 -0
  46. package/pi-extension/dist/host/canonical.js +12 -0
  47. package/pi-extension/dist/host/catalog.js +77 -0
  48. package/pi-extension/dist/host/config.js +374 -0
  49. package/pi-extension/dist/host/projection-tags.js +60 -0
  50. package/pi-extension/dist/host/prompts.js +82 -0
  51. package/pi-extension/dist/{session-sources → host}/read-only-session.js +112 -1
  52. package/pi-extension/dist/{runtime → host}/request-history.js +121 -39
  53. package/pi-extension/dist/host/runtime-state.js +189 -0
  54. package/pi-extension/dist/{settings/settings-ui.js → host/settings/freeflow-command.js} +620 -410
  55. package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +186 -73
  56. package/pi-extension/dist/host/staging.js +28 -0
  57. package/pi-extension/dist/host/status.js +101 -0
  58. package/pi-extension/dist/index.js +423 -173
  59. package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
  60. package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
  61. package/pi-extension/dist/provider-support/cache/health.js +73 -0
  62. package/pi-extension/dist/provider-support/cache/index.js +58 -0
  63. package/pi-extension/dist/provider-support/cache/keepalive.js +210 -0
  64. package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
  65. package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
  66. package/pi-extension/dist/provider-support/effort.js +13 -0
  67. package/pi-extension/dist/provider-support/index.js +44 -3
  68. package/pi-extension/dist/provider-support/{astra → openai}/adapter.js +46 -29
  69. package/pi-extension/dist/provider-support/{astra → openai}/history.js +10 -8
  70. package/pi-extension/dist/provider-support/{astra → openai}/index.js +8 -4
  71. package/pi-extension/dist/provider-support/{astra → openai}/session-state.js +29 -38
  72. package/pi-extension/dist/provider-support/routes.js +17 -0
  73. package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
  74. package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
  75. package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
  76. package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
  77. package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
  78. package/pi-extension/dist/tool-execution/background.js +238 -0
  79. package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
  80. package/pi-extension/dist/tool-execution/config.js +22 -0
  81. package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
  82. package/pi-extension/dist/tool-execution/file-state.js +138 -0
  83. package/pi-extension/dist/tool-execution/file-tracking.js +143 -0
  84. package/pi-extension/dist/tool-execution/prompt.js +54 -0
  85. package/pi-extension/dist/tool-execution/tools.js +16 -0
  86. package/pi-extension/freeflow/README.md +9 -0
  87. package/plugin.json +1 -1
  88. package/runtime/prompts/cognitive-routing.md +4 -4
  89. package/runtime/prompts/core.md +22 -75
  90. package/runtime/prompts/interaction-contract.md +15 -8
  91. package/runtime/prompts/tool-execution.md +38 -0
  92. package/runtime/prompts/working-method.md +108 -0
  93. package/skills/action-selection/SKILL.md +39 -47
  94. package/skills/action-selection/references/trajectory-stalls.md +2 -0
  95. package/skills/bypass/SKILL.md +2 -2
  96. package/skills/commit-work/SKILL.md +31 -83
  97. package/skills/commit-work/references/git-state-edges.md +1 -1
  98. package/skills/commit-work/references/simple-push.md +2 -0
  99. package/skills/commit-work/references/staging-decisions.md +1 -1
  100. package/skills/decision-gate/SKILL.md +31 -50
  101. package/skills/design-for-depth/SKILL.md +44 -53
  102. package/skills/design-for-depth/references/design-pressure-signals.md +46 -144
  103. package/skills/design-for-depth/references/implementation-decisions.md +1 -1
  104. package/skills/design-for-depth/references/interface-design-loop.md +5 -36
  105. package/skills/design-for-depth/references/module-and-dependency-design.md +2 -32
  106. package/skills/design-for-depth/references/state-and-failure-boundaries.md +2 -13
  107. package/skills/diagnose-failure/SKILL.md +39 -24
  108. package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +2 -0
  109. package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +2 -2
  110. package/skills/discuss/SKILL.md +35 -11
  111. package/skills/discuss/references/discussion-continuity.md +7 -27
  112. package/skills/evaluate-skill/SKILL.md +49 -80
  113. package/skills/evaluate-skill/references/evaluation-definition-schema.md +35 -5
  114. package/skills/evaluate-skill/references/evaluation-design.md +14 -14
  115. package/skills/evaluate-skill/references/execution-and-evidence.md +26 -5
  116. package/skills/evaluate-skill/references/review-and-revision.md +3 -1
  117. package/skills/evaluate-skill/scripts/lib/definitions.mjs +49 -8
  118. package/skills/evaluate-skill/scripts/lib/pi.mjs +14 -7
  119. package/skills/evaluate-skill/scripts/lib/review.mjs +196 -0
  120. package/skills/evaluate-skill/scripts/lib/runner.mjs +45 -8
  121. package/skills/evaluate-skill/scripts/lib/sandbox.mjs +2 -0
  122. package/skills/evaluate-skill/scripts/lib/trials.mjs +103 -0
  123. package/skills/evaluate-skill/scripts/lib/view.mjs +67 -1
  124. package/skills/evaluate-skill/scripts/pi-command-tool.mjs +144 -0
  125. package/skills/evaluate-skill/scripts/pi-guard.mjs +30 -13
  126. package/skills/evaluate-skill/scripts/skill-eval.mjs +114 -34
  127. package/skills/execute-work/SKILL.md +41 -9
  128. package/skills/execute-work/references/code-practices.md +2 -0
  129. package/skills/execute-work/references/execute-work-edges.md +11 -1
  130. package/skills/handoff/SKILL.md +1 -1
  131. package/skills/handoff/references/handoff-templates.md +1 -1
  132. package/skills/review-artifact/SKILL.md +42 -41
  133. package/skills/review-artifact/references/adjudicate-artifact-review.md +1 -1
  134. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  135. package/skills/review-work/SKILL.md +38 -32
  136. package/skills/review-work/references/adjudicate-work-review.md +1 -1
  137. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  138. package/skills/review-work/references/security-risk-lens.md +2 -4
  139. package/skills/setup-freeflow/SKILL.md +3 -1
  140. package/skills/setup-freeflow/references/activation-contract.md +7 -13
  141. package/skills/setup-freeflow/references/host-setup.md +3 -3
  142. package/skills/track-work/SKILL.md +113 -161
  143. package/skills/track-work/references/working-record-format.md +189 -350
  144. package/skills/track-work/scripts/lib/document.mjs +62 -6
  145. package/skills/track-work/scripts/lib/format.mjs +16 -1
  146. package/skills/track-work/scripts/lib/store.mjs +9 -8
  147. package/skills/track-work/scripts/lib/transitions.mjs +79 -4
  148. package/skills/track-work/scripts/working-record.mjs +34 -12
  149. package/skills/verify-work/SKILL.md +29 -20
  150. package/skills/verify-work/references/browser-runtime-evidence.md +1 -5
  151. package/skills/verify-work/references/integration-evidence.md +2 -0
  152. package/skills/verify-work/references/performance-evidence.md +3 -1
  153. package/skills/verify-work/references/test-design.md +3 -1
  154. package/skills/workflow/SKILL.md +65 -21
  155. package/skills/workflow/references/domain-skill-composition.md +5 -3
  156. package/skills/write-docs/SKILL.md +83 -0
  157. package/skills/write-docs/references/documentation-shapes.md +119 -0
  158. package/skills/write-docs/scripts/docs-check.mjs +139 -0
  159. package/skills/write-plan/SKILL.md +40 -40
  160. package/skills/write-plan/references/plan-shapes.md +1 -1
  161. package/skills/write-skill/SKILL.md +107 -147
  162. package/skills/write-skill/references/activation-boundaries.md +2 -0
  163. package/skills/write-skill/references/agent-first-instructions.md +22 -10
  164. package/skills/write-skill/references/progressive-disclosure.md +4 -2
  165. package/skills/write-skill/references/skill-dependency-graphs.md +2 -0
  166. package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
  167. package/skills/write-skill/scripts/skill-author.mjs +18 -2
  168. package/skills/write-spec/SKILL.md +34 -41
  169. package/skills/write-spec/references/artifact-standards.md +1 -1
  170. package/skills/write-spec/references/decision-records.md +2 -0
  171. package/skills/write-spec/references/spec-shapes.md +1 -1
  172. package/capabilities/context-virtualization/SKILL.md +0 -103
  173. package/capabilities/conversation-history/SKILL.md +0 -53
  174. package/hooks/tests/check-host-adapters.test.mjs +0 -82
  175. package/hooks/tests/check-runtime-context-hook.sh +0 -212
  176. package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
  177. package/pi-extension/dist/context-virtualization/commands.js +0 -89
  178. package/pi-extension/dist/context-virtualization/projector.js +0 -42
  179. package/pi-extension/dist/context-virtualization/runtime.js +0 -378
  180. package/pi-extension/dist/context-virtualization/state.js +0 -57
  181. package/pi-extension/dist/context-virtualization/types.js +0 -10
  182. package/pi-extension/dist/conversation-history/passages.js +0 -66
  183. package/pi-extension/dist/conversation-history/ranking.js +0 -105
  184. package/pi-extension/dist/conversation-history/runtime.js +0 -226
  185. package/pi-extension/dist/conversation-history/search.js +0 -56
  186. package/pi-extension/dist/conversation-history/types.js +0 -1
  187. package/pi-extension/dist/freeflow-context/resolver.js +0 -120
  188. package/pi-extension/dist/freeflow-context/runtime.js +0 -80
  189. package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
  190. package/pi-extension/dist/freeflow-context/tool.js +0 -314
  191. package/pi-extension/dist/freeflow-context/types.js +0 -12
  192. package/pi-extension/dist/freeflow-context/visibility.js +0 -28
  193. package/pi-extension/dist/integrations/mcp-config.js +0 -29
  194. package/pi-extension/dist/runtime/runtime-context.js +0 -754
  195. package/pi-extension/dist/runtime/runtime-identity.js +0 -5
  196. package/pi-extension/dist/session-sources/provenance.js +0 -32
  197. package/pi-extension/dist/ui/text-component.js +0 -57
  198. package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
  199. package/pi-extension/src/cognitive-routing-v2/config.ts +0 -173
  200. package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -375
  201. package/pi-extension/src/cognitive-routing-v2/render.ts +0 -277
  202. package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -2241
  203. package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -180
  204. package/pi-extension/src/cognitive-routing-v2/state.ts +0 -839
  205. package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -62
  206. package/pi-extension/src/cognitive-routing-v2/types.ts +0 -254
  207. package/pi-extension/src/context-virtualization/commands.ts +0 -111
  208. package/pi-extension/src/context-virtualization/projector.ts +0 -62
  209. package/pi-extension/src/context-virtualization/runtime.ts +0 -428
  210. package/pi-extension/src/context-virtualization/state.ts +0 -79
  211. package/pi-extension/src/context-virtualization/types.ts +0 -92
  212. package/pi-extension/src/conversation-history/passages.ts +0 -81
  213. package/pi-extension/src/conversation-history/ranking.ts +0 -155
  214. package/pi-extension/src/conversation-history/runtime.ts +0 -250
  215. package/pi-extension/src/conversation-history/search.ts +0 -79
  216. package/pi-extension/src/conversation-history/types.ts +0 -31
  217. package/pi-extension/src/freeflow-context/resolver.ts +0 -139
  218. package/pi-extension/src/freeflow-context/runtime.ts +0 -100
  219. package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
  220. package/pi-extension/src/freeflow-context/tool.ts +0 -366
  221. package/pi-extension/src/freeflow-context/types.ts +0 -70
  222. package/pi-extension/src/freeflow-context/visibility.ts +0 -53
  223. package/pi-extension/src/index.ts +0 -390
  224. package/pi-extension/src/integrations/mcp-config.ts +0 -32
  225. package/pi-extension/src/provider-support/astra/adapter.ts +0 -108
  226. package/pi-extension/src/provider-support/astra/history.ts +0 -145
  227. package/pi-extension/src/provider-support/astra/index.ts +0 -12
  228. package/pi-extension/src/provider-support/astra/session-state.ts +0 -113
  229. package/pi-extension/src/provider-support/index.ts +0 -5
  230. package/pi-extension/src/runtime/request-history.ts +0 -243
  231. package/pi-extension/src/runtime/runtime-context.ts +0 -860
  232. package/pi-extension/src/runtime/runtime-identity.ts +0 -8
  233. package/pi-extension/src/session-sources/events.ts +0 -230
  234. package/pi-extension/src/session-sources/provenance.ts +0 -39
  235. package/pi-extension/src/session-sources/read-only-session.ts +0 -170
  236. package/pi-extension/src/session-sources/sources.ts +0 -334
  237. package/pi-extension/src/settings/settings-tui.ts +0 -501
  238. package/pi-extension/src/settings/settings-ui.ts +0 -1652
  239. package/pi-extension/src/ui/text-component.ts +0 -66
  240. package/pi-extension/tests/cache-reuse/history.test.js +0 -117
  241. package/pi-extension/tests/cache-reuse/native.test.js +0 -91
  242. package/pi-extension/tests/cache-reuse/settings.test.js +0 -211
  243. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -577
  244. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
  245. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +0 -557
  246. package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -474
  247. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
  248. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -300
  249. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
  250. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -1034
  251. package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -244
  252. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -423
  253. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
  254. package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -586
  255. package/pi-extension/tests/context-virtualization/extension.test.js +0 -775
  256. package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
  257. package/pi-extension/tests/context-virtualization/runtime.test.js +0 -230
  258. package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
  259. package/pi-extension/tests/conversation-history/search.test.js +0 -97
  260. package/pi-extension/tests/fixtures/pi-host.js +0 -4
  261. package/pi-extension/tests/fixtures/routing-native.js +0 -191
  262. package/pi-extension/tests/integration/mode-free-core.test.js +0 -220
  263. package/pi-extension/tests/integration/pi-extension.test.js +0 -1447
  264. package/pi-extension/tests/integration/prompt-architecture.test.js +0 -447
  265. package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
  266. package/pi-extension/tests/provider-support/astra-boundaries.test.js +0 -75
  267. package/pi-extension/tests/provider-support/astra.test.js +0 -306
  268. package/pi-extension/tests/provider-support/cli.test.js +0 -92
  269. package/pi-extension/tests/provider-support/native.test.js +0 -183
  270. package/pi-extension/tests/settings/settings-tui.test.js +0 -243
  271. package/pi-extension/tsconfig.json +0 -16
  272. package/runtime/prompts/context-virtualization.md +0 -3
  273. package/runtime/prompts/conversation-history.md +0 -3
  274. package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
  275. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
  276. /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +0 -0
@@ -1,839 +0,0 @@
1
- import {
2
- EFFORTS,
3
- PROFILES,
4
- ROUTING_ENTRY,
5
- canonical,
6
- emptySelection,
7
- eventKey,
8
- eventValue,
9
- isObject,
10
- isWorkerProfile,
11
- requireCondition as check,
12
- refFor,
13
- type RoutingEvent,
14
- type State,
15
- type NativeEntry,
16
- type Pair,
17
- type Handoff,
18
- type Recovery,
19
- type Reservation,
20
- } from "./types.js";
21
-
22
- const text = (x: unknown, max = 32768): x is string => typeof x === "string" && x.trim().length > 0 && x.length <= max;
23
- const identity = (x: unknown): x is string => text(x, 512);
24
- const nullableId = (x: unknown) => x === null || identity(x);
25
- const shape = (x: unknown, keys: string[]) => isObject(x) && Object.keys(x).every((k) => keys.includes(k));
26
- const uniqueStrings = (x: unknown): x is string[] =>
27
- Array.isArray(x) && x.every(identity) && new Set(x).size === x.length;
28
- const pair = (x: any): x is Pair =>
29
- shape(x, ["provider", "modelId", "thinking"]) &&
30
- identity(x.provider) &&
31
- identity(x.modelId) &&
32
- EFFORTS.includes(x.thinking);
33
- const profileOverrides = (x: any): boolean =>
34
- isObject(x) &&
35
- Object.keys(x).length > 0 &&
36
- Object.keys(x).every((key) => PROFILES.includes(key as any)) &&
37
- Object.values(x).every((value) => value === null || pair(value));
38
- const problemList = (xs: any): boolean =>
39
- Array.isArray(xs) &&
40
- xs.every(
41
- (x) =>
42
- shape(x, ["ref", "code", "detail"]) && typeof x.ref === "string" && text(x.code, 256) && text(x.detail, 4096),
43
- );
44
- const reservation = (r: any): r is Reservation =>
45
- shape(r, [
46
- "id",
47
- "selectionRevision",
48
- "receiver",
49
- "target",
50
- "qualification",
51
- "sources",
52
- "maximumInputTokens",
53
- "outputReserve",
54
- "estimateMethod",
55
- ]) &&
56
- identity(r.id) &&
57
- Number.isSafeInteger(r.selectionRevision) &&
58
- r.selectionRevision >= 0 &&
59
- PROFILES.includes(r.receiver) &&
60
- pair(r.target) &&
61
- text(r.qualification) &&
62
- Array.isArray(r.sources) &&
63
- r.sources.every((s) => shape(s, ["ref", "bodyHash"]) && identity(s.ref) && /^[a-f0-9]{64}$/.test(s.bodyHash)) &&
64
- new Set(r.sources.map((s) => s.ref)).size === r.sources.length &&
65
- Number.isFinite(r.maximumInputTokens) &&
66
- r.maximumInputTokens > 0 &&
67
- Number.isFinite(r.outputReserve) &&
68
- r.outputReserve >= 0 &&
69
- text(r.estimateMethod);
70
- const recovery = (r: any): r is Recovery =>
71
- shape(r, [
72
- "id",
73
- "assignmentId",
74
- "assessmentHandoffId",
75
- "baseReportRevision",
76
- "request",
77
- "requestedPaths",
78
- "paths",
79
- "state",
80
- "requestHandoffId",
81
- "supplementHandoffId",
82
- "supplementRevision",
83
- "cancellationReason",
84
- ]) &&
85
- identity(r.id) &&
86
- identity(r.assignmentId) &&
87
- identity(r.assessmentHandoffId) &&
88
- Number.isSafeInteger(r.baseReportRevision) &&
89
- r.baseReportRevision > 0 &&
90
- text(r.request) &&
91
- Array.isArray(r.requestedPaths) &&
92
- r.requestedPaths.length <= 32 &&
93
- r.requestedPaths.every((path: unknown) => text(path, 4096)) &&
94
- new Set(r.requestedPaths).size === r.requestedPaths.length &&
95
- Array.isArray(r.paths) &&
96
- r.paths.length <= 32 &&
97
- r.paths.every((path: unknown) => text(path, 4096)) &&
98
- new Set(r.paths).size === r.paths.length &&
99
- ["requested", "reading", "returning", "completed", "cancelled"].includes(r.state) &&
100
- identity(r.requestHandoffId) &&
101
- (r.supplementHandoffId === undefined || identity(r.supplementHandoffId)) &&
102
- Number.isSafeInteger(r.supplementRevision) &&
103
- r.supplementRevision >= 0 &&
104
- (r.cancellationReason === undefined || text(r.cancellationReason, 2048));
105
- const handoff = (h: any): h is Handoff =>
106
- shape(h, [
107
- "id",
108
- "kind",
109
- "assignmentId",
110
- "text",
111
- "from",
112
- "to",
113
- "executionId",
114
- "toolCallId",
115
- "basisUserEntryId",
116
- "state",
117
- "reportRevision",
118
- "outcome",
119
- "limitations",
120
- "reason",
121
- ]) &&
122
- identity(h.id) &&
123
- identity(h.assignmentId) &&
124
- text(h.text) &&
125
- identity(h.executionId) &&
126
- identity(h.toolCallId) &&
127
- nullableId(h.basisUserEntryId) &&
128
- ["delegate", "return", "recovery-request", "recovery-return"].includes(h.kind) &&
129
- PROFILES.includes(h.from) &&
130
- PROFILES.includes(h.to) &&
131
- h.from !== h.to &&
132
- ["pending", "configured", "blocked", "superseded"].includes(h.state) &&
133
- Number.isSafeInteger(h.reportRevision) &&
134
- h.reportRevision >= 0 &&
135
- Array.isArray(h.limitations) &&
136
- h.limitations.length <= 32 &&
137
- h.limitations.every((x: unknown) => text(x, 2048)) &&
138
- (h.reason === undefined || text(h.reason, 4096)) &&
139
- (h.outcome === undefined || ["completed", "partial", "blocked"].includes(h.outcome)) &&
140
- (["delegate", "recovery-request"].includes(h.kind)
141
- ? h.outcome === undefined && h.reportRevision === 0
142
- : ["completed", "partial", "blocked"].includes(h.outcome) && h.reportRevision > 0);
143
- const fields: Record<string, string[]> = {
144
- "execution-interrupted": ["executionId", "reason"],
145
- "assignment-resumed": ["assignmentId", "basisUserEntryId"],
146
- control: ["control", "profile", "reason"],
147
- "profile-overrides": ["overrides", "reason"],
148
- "execution-opened": ["execution"],
149
- "execution-bound": ["executionId", "assistantEntryId", "resultEntryIds", "outcome"],
150
- "delegate-accepted": ["unit", "assignment", "handoff", "replacement"],
151
- "return-accepted": ["handoff"],
152
- "recovery-request-accepted": ["recovery", "handoff"],
153
- "recovery-supplement-accepted": ["recoveryId", "handoff"],
154
- "recovery-cancelled": ["recoveryId", "reason"],
155
- "handoff-retry-requested": [
156
- "handoffId",
157
- "attemptId",
158
- "executionId",
159
- "toolCallId",
160
- "reportRevision",
161
- "selectionRevision",
162
- ],
163
- "handoff-prepared": ["handoffId", "reservation"],
164
- "handoff-state": ["handoffId", "state", "reason", "observedPair"],
165
- "selection-changed": ["assignmentId", "selection"],
166
- "assessment-suspended": ["handoffId", "reason", "basisUserEntryId", "problems"],
167
- "assessment-resumed": ["handoffId", "basisUserEntryId", "reservation"],
168
- "unit-closed": ["unitId", "outcome", "assessment", "supersededAssignmentId"],
169
- "sources-exposed": ["executionId", "view", "sources"],
170
- "request-observed": ["executionId", "profile", "boundary", "manifestHash", "handoffId", "selectionRevision"],
171
- };
172
- export function parseRoutingEvent(raw: unknown): RoutingEvent {
173
- check(
174
- isObject(raw) &&
175
- raw.version === 2 &&
176
- identity(raw.eventId) &&
177
- identity(raw.operationId) &&
178
- identity(raw.stepId) &&
179
- identity(raw.recordedSessionId) &&
180
- isObject(raw.data),
181
- "invalid_event",
182
- );
183
- check(
184
- Object.keys(raw).every((k) =>
185
- ["version", "eventId", "operationId", "stepId", "recordedSessionId", "data"].includes(k),
186
- ),
187
- "invalid_event_field",
188
- );
189
- const d: any = raw.data;
190
- check(
191
- typeof d.type === "string" &&
192
- Object.hasOwn(fields, d.type) &&
193
- Object.keys(d).every((k) => k === "type" || fields[d.type].includes(k)),
194
- "invalid_event_type",
195
- );
196
- check(Buffer.byteLength(JSON.stringify(raw)) <= 512 * 1024, "event_too_large");
197
- if (d.handoffId !== undefined) check(identity(d.handoffId), "invalid_handoff_id");
198
- switch (d.type) {
199
- case "execution-interrupted":
200
- check(identity(d.executionId) && text(d.reason, 4096), "invalid_interruption");
201
- break;
202
- case "assignment-resumed":
203
- check(identity(d.assignmentId) && nullableId(d.basisUserEntryId), "invalid_resume");
204
- break;
205
- case "control":
206
- check(
207
- ["automatic", "manual", "inactive"].includes(d.control) &&
208
- text(d.reason, 4096) &&
209
- (d.profile === undefined || PROFILES.includes(d.profile)) &&
210
- (d.control === "inactive" || PROFILES.includes(d.profile)),
211
- "invalid_control",
212
- );
213
- break;
214
- case "profile-overrides":
215
- check(profileOverrides(d.overrides) && text(d.reason, 4096), "invalid_profile_overrides");
216
- break;
217
- case "execution-opened": {
218
- const e = d.execution;
219
- check(
220
- shape(e, ["id", "profile", "assignmentId", "recoveryId", "basisUserEntryId", "pair", "resultEntryIds"]) &&
221
- identity(e.id) &&
222
- ["solo", ...PROFILES].includes(e.profile) &&
223
- nullableId(e.basisUserEntryId) &&
224
- pair(e.pair) &&
225
- uniqueStrings(e.resultEntryIds) &&
226
- e.resultEntryIds.length === 0 &&
227
- (e.assignmentId === undefined || identity(e.assignmentId)) &&
228
- (e.recoveryId === undefined || identity(e.recoveryId)),
229
- "invalid_execution",
230
- );
231
- break;
232
- }
233
- case "execution-bound":
234
- check(
235
- identity(d.executionId) &&
236
- identity(d.assistantEntryId) &&
237
- uniqueStrings(d.resultEntryIds) &&
238
- ["completed", "failed", "aborted"].includes(d.outcome),
239
- "invalid_binding",
240
- );
241
- break;
242
- case "delegate-accepted": {
243
- const u = d.unit,
244
- a = d.assignment,
245
- r = d.replacement;
246
- check(
247
- shape(u, ["id", "objective", "state", "assignmentIds"]) &&
248
- identity(u.id) &&
249
- text(u.objective) &&
250
- u.state === "open" &&
251
- uniqueStrings(u.assignmentIds),
252
- "invalid_unit",
253
- );
254
- check(
255
- shape(a, ["id", "unitId", "contract", "basisUserEntryId", "state", "delegateHandoffId"]) &&
256
- identity(a.id) &&
257
- identity(a.unitId) &&
258
- text(a.contract) &&
259
- nullableId(a.basisUserEntryId) &&
260
- a.state === "outstanding" &&
261
- identity(a.delegateHandoffId) &&
262
- handoff(d.handoff),
263
- "invalid_assignment",
264
- );
265
- check(
266
- r === undefined ||
267
- (shape(r, ["assignmentId", "supersededHandoffId", "reason"]) &&
268
- identity(r.assignmentId) &&
269
- text(r.reason, 2048) &&
270
- (r.supersededHandoffId === undefined || identity(r.supersededHandoffId))),
271
- "invalid_replacement",
272
- );
273
- break;
274
- }
275
- case "return-accepted":
276
- check(handoff(d.handoff) && d.handoff.kind === "return", "invalid_return");
277
- break;
278
- case "recovery-request-accepted":
279
- check(
280
- recovery(d.recovery) &&
281
- handoff(d.handoff) &&
282
- d.handoff.kind === "recovery-request" &&
283
- d.recovery.requestHandoffId === d.handoff.id &&
284
- d.recovery.assignmentId === d.handoff.assignmentId &&
285
- d.recovery.request === d.handoff.text,
286
- "invalid_recovery_request",
287
- );
288
- break;
289
- case "recovery-supplement-accepted":
290
- check(
291
- identity(d.recoveryId) && handoff(d.handoff) && d.handoff.kind === "recovery-return",
292
- "invalid_recovery_supplement",
293
- );
294
- break;
295
- case "recovery-cancelled":
296
- check(identity(d.recoveryId) && text(d.reason, 2048), "invalid_recovery_cancellation");
297
- break;
298
- case "handoff-retry-requested":
299
- check(
300
- identity(d.attemptId) &&
301
- identity(d.executionId) &&
302
- identity(d.toolCallId) &&
303
- Number.isSafeInteger(d.reportRevision) &&
304
- d.reportRevision > 0 &&
305
- Number.isSafeInteger(d.selectionRevision) &&
306
- d.selectionRevision >= 0,
307
- "invalid_retry",
308
- );
309
- break;
310
- case "handoff-prepared":
311
- check(reservation(d.reservation), "invalid_reservation");
312
- break;
313
- case "handoff-state":
314
- check(
315
- ["configured", "blocked", "superseded"].includes(d.state) &&
316
- (d.reason === undefined || text(d.reason, 4096)) &&
317
- (d.observedPair === undefined || pair(d.observedPair)),
318
- "invalid_delivery",
319
- );
320
- break;
321
- case "selection-changed": {
322
- const s = d.selection;
323
- check(
324
- identity(d.assignmentId) &&
325
- shape(s, ["revision", "selected", "unresolved", "withdrawals"]) &&
326
- Number.isSafeInteger(s.revision) &&
327
- s.revision > 0 &&
328
- uniqueStrings(s.selected) &&
329
- problemList(s.unresolved) &&
330
- Array.isArray(s.withdrawals) &&
331
- s.withdrawals.every((w) => shape(w, ["ref", "reason"]) && identity(w.ref) && text(w.reason, 2048)),
332
- "invalid_selection",
333
- );
334
- break;
335
- }
336
- case "assessment-suspended":
337
- check(
338
- ["user-attention", "delivery-gap"].includes(d.reason) &&
339
- nullableId(d.basisUserEntryId) &&
340
- problemList(d.problems),
341
- "invalid_suspension",
342
- );
343
- break;
344
- case "assessment-resumed":
345
- check(
346
- nullableId(d.basisUserEntryId) && (d.reservation === undefined || reservation(d.reservation)),
347
- "invalid_resumption",
348
- );
349
- break;
350
- case "unit-closed":
351
- check(
352
- identity(d.unitId) &&
353
- ["accepted", "cancelled", "deferred"].includes(d.outcome) &&
354
- text(d.assessment) &&
355
- (d.supersededAssignmentId === undefined || identity(d.supersededAssignmentId)),
356
- "invalid_closure",
357
- );
358
- break;
359
- case "request-observed":
360
- check(
361
- identity(d.executionId) &&
362
- ["solo", ...PROFILES].includes(d.profile) &&
363
- ["assembled", "payload-hook"].includes(d.boundary) &&
364
- /^[a-f0-9]{64}$/.test(d.manifestHash) &&
365
- (d.selectionRevision === undefined ||
366
- (Number.isSafeInteger(d.selectionRevision) && d.selectionRevision >= 0)),
367
- "invalid_request_observation",
368
- );
369
- break;
370
- case "sources-exposed":
371
- check(
372
- identity(d.executionId) &&
373
- ["solo", ...PROFILES].includes(d.view) &&
374
- Array.isArray(d.sources) &&
375
- d.sources.every((s) => shape(s, ["ref", "bodyHash"]) && identity(s.ref) && /^[a-f0-9]{64}$/.test(s.bodyHash)),
376
- "invalid_exposure",
377
- );
378
- break;
379
- }
380
- return structuredClone(raw) as RoutingEvent;
381
- }
382
- export function initialState(): State {
383
- return {
384
- control: "inactive",
385
- units: new Map(),
386
- assignments: new Map(),
387
- handoffs: new Map(),
388
- recoveries: new Map(),
389
- executions: new Map(),
390
- selections: new Map(),
391
- reservations: new Map(),
392
- attempts: new Map(),
393
- exposure: new Map(),
394
- authors: new Map(),
395
- resumeBasis: new Map(),
396
- profileOverrides: new Map(),
397
- events: new Map(),
398
- eventIds: new Map(),
399
- };
400
- }
401
- function applyEvent(state: State, event: RoutingEvent, owned = false): State {
402
- const e = parseRoutingEvent(event),
403
- key = eventKey(e),
404
- value = eventValue(e);
405
- const previous = state.events.get(key),
406
- priorId = state.eventIds.get(e.eventId);
407
- check(!priorId || priorId === value, "event_identity_conflict");
408
- if (previous) {
409
- check(eventValue(previous) === value, "operation_conflict");
410
- return state;
411
- }
412
- const s = owned
413
- ? state
414
- : {
415
- ...state,
416
- units: new Map([...state.units].map(([k, v]) => [k, { ...v }])),
417
- assignments: new Map([...state.assignments].map(([k, v]) => [k, { ...v }])),
418
- handoffs: new Map([...state.handoffs].map(([k, v]) => [k, { ...v }])),
419
- recoveries: new Map([...state.recoveries].map(([k, v]) => [k, { ...v, paths: [...v.paths] }])),
420
- executions: new Map([...state.executions].map(([k, v]) => [k, { ...v }])),
421
- selections: new Map(state.selections),
422
- reservations: new Map(state.reservations),
423
- attempts: new Map(state.attempts),
424
- exposure: new Map(state.exposure),
425
- authors: new Map(state.authors),
426
- resumeBasis: new Map(state.resumeBasis),
427
- profileOverrides: new Map(state.profileOverrides),
428
- events: new Map(state.events),
429
- eventIds: new Map(state.eventIds),
430
- assessment: state.assessment ? { ...state.assessment } : undefined,
431
- },
432
- d = e.data;
433
- const assignment = () => (s.assignmentId ? s.assignments.get(s.assignmentId) : undefined);
434
- const assignmentWorker = () => {
435
- const a = assignment();
436
- const h = a ? s.handoffs.get(a.delegateHandoffId) : undefined;
437
- check(h?.kind === "delegate" && h.assignmentId === a?.id && isWorkerProfile(h.to), "assignment_worker_missing");
438
- return h.to;
439
- };
440
- const pending = () => (s.pendingId ? s.handoffs.get(s.pendingId) : undefined);
441
- const isPending = () => pending() && ["pending", "blocked"].includes(pending()!.state);
442
- const currentReturn = (id: string) => {
443
- const h = s.handoffs.get(id);
444
- check(
445
- h && ["return", "recovery-return"].includes(h.kind) && h.assignmentId === s.assignmentId,
446
- "not_current_return",
447
- );
448
- return h;
449
- };
450
- switch (d.type) {
451
- case "execution-interrupted": {
452
- const x = s.executions.get(d.executionId);
453
- check(x && !x.assistantEntryId, "execution_not_unbound");
454
- x.interrupted = d.reason;
455
- break;
456
- }
457
- case "assignment-resumed": {
458
- const a = assignment();
459
- check(
460
- s.control === "automatic" && a?.id === d.assignmentId && a.state === "outstanding" && !isPending(),
461
- "assignment_not_resumable",
462
- );
463
- s.resumeBasis.set(a.id, d.basisUserEntryId);
464
- break;
465
- }
466
- case "control":
467
- s.control = d.control;
468
- s.profile = d.profile;
469
- break;
470
- case "profile-overrides":
471
- for (const profile of PROFILES) {
472
- if (!Object.hasOwn(d.overrides, profile)) continue;
473
- const override = d.overrides[profile];
474
- if (override === null) s.profileOverrides.delete(profile);
475
- else s.profileOverrides.set(profile, override);
476
- }
477
- break;
478
- case "execution-opened":
479
- check(!s.executions.has(d.execution.id), "duplicate_execution");
480
- s.executions.set(d.execution.id, d.execution);
481
- break;
482
- case "execution-bound": {
483
- const x = s.executions.get(d.executionId);
484
- check(x, "execution_missing");
485
- if (x.assistantEntryId)
486
- check(
487
- x.assistantEntryId === d.assistantEntryId &&
488
- canonical(x.resultEntryIds) === canonical(d.resultEntryIds) &&
489
- x.outcome === d.outcome,
490
- "binding_conflict",
491
- );
492
- x.assistantEntryId = d.assistantEntryId;
493
- x.resultEntryIds = d.resultEntryIds;
494
- x.outcome = d.outcome;
495
- for (const id of [d.assistantEntryId, ...d.resultEntryIds]) {
496
- const prev = s.authors.get(id);
497
- check(!prev || prev.executionId === x.id, "source_authorship_conflict");
498
- s.authors.set(id, { profile: x.profile, executionId: x.id, assignmentId: x.assignmentId });
499
- }
500
- break;
501
- }
502
- case "delegate-accepted": {
503
- check(s.control === "automatic" && s.profile === "coordinator", "wrong_control");
504
- const a = assignment(),
505
- h = d.handoff,
506
- u = s.unitId ? s.units.get(s.unitId) : undefined;
507
- check(!s.assignments.has(d.assignment.id) && !s.handoffs.has(h.id), "reused_work_identity");
508
- check(
509
- h.kind === "delegate" &&
510
- h.from === "coordinator" &&
511
- isWorkerProfile(h.to) &&
512
- h.state === "pending" &&
513
- h.text === d.assignment.contract &&
514
- h.assignmentId === d.assignment.id &&
515
- d.assignment.delegateHandoffId === h.id &&
516
- d.assignment.unitId === d.unit.id,
517
- "delegate_identity_mismatch",
518
- );
519
- if (u) check(u.state === "open" && u.id === d.unit.id && u.objective === d.unit.objective, "unit_conflict");
520
- else check(!s.units.has(d.unit.id), "closed_unit_reused");
521
- check(
522
- canonical(d.unit.assignmentIds) === canonical([...(u?.assignmentIds ?? []), d.assignment.id]),
523
- "assignment_order",
524
- );
525
- if (d.replacement) {
526
- check(a?.state === "outstanding" && a.id === d.replacement.assignmentId && u, "no_replaceable_assignment");
527
- check(
528
- ![...s.executions.values()].some(
529
- (x) => x.assignmentId === a.id && isWorkerProfile(x.profile) && !x.assistantEntryId && !x.interrupted,
530
- ),
531
- "unresolved_worker_execution",
532
- );
533
- check(
534
- !isPending() || (pending()!.kind === "delegate" && d.replacement.supersededHandoffId === pending()!.id),
535
- "handoff_pending",
536
- );
537
- a.state = "superseded";
538
- s.handoffs.get(a.delegateHandoffId)!.state = "superseded";
539
- } else check(a?.state !== "outstanding" && !isPending(), "assignment_outstanding");
540
- s.units.set(d.unit.id, d.unit);
541
- s.unitId = d.unit.id;
542
- s.assignments.set(d.assignment.id, d.assignment);
543
- s.assignmentId = d.assignment.id;
544
- s.handoffs.set(h.id, h);
545
- s.pendingId = h.id;
546
- s.assessment = undefined;
547
- break;
548
- }
549
- case "return-accepted": {
550
- check(s.control === "automatic" && s.profile === assignmentWorker(), "wrong_control");
551
- const a = assignment(),
552
- h = d.handoff;
553
- check(
554
- a &&
555
- h.assignmentId === a.id &&
556
- h.kind === "return" &&
557
- h.from === assignmentWorker() &&
558
- h.to === "coordinator" &&
559
- h.state === "pending",
560
- "return_identity_mismatch",
561
- );
562
- const previousReturn = a.returnHandoffId ? s.handoffs.get(a.returnHandoffId) : undefined;
563
- if (a.state === "outstanding")
564
- check(!isPending() && !s.handoffs.has(h.id) && h.reportRevision === 1, "handoff_pending");
565
- else
566
- check(
567
- a.state === "returned" &&
568
- previousReturn &&
569
- previousReturn.id === h.id &&
570
- ["pending", "blocked"].includes(previousReturn.state) &&
571
- h.reportRevision === previousReturn.reportRevision + 1,
572
- "invalid_report_revision",
573
- );
574
- a.state = "returned";
575
- a.returnHandoffId = h.id;
576
- s.handoffs.set(h.id, h);
577
- s.pendingId = h.id;
578
- s.assessment = {
579
- handoffId: h.id,
580
- assignmentId: a.id,
581
- view: "active",
582
- basisUserEntryId: h.basisUserEntryId,
583
- problems: [],
584
- };
585
- break;
586
- }
587
- case "recovery-request-accepted": {
588
- const a = assignment(),
589
- base = s.handoffs.get(d.recovery.assessmentHandoffId),
590
- h = d.handoff;
591
- check(
592
- s.control === "automatic" &&
593
- s.profile === "coordinator" &&
594
- a?.state === "returned" &&
595
- d.recovery.assignmentId === a.id &&
596
- d.recovery.state === "requested" &&
597
- d.recovery.supplementRevision === 0 &&
598
- d.recovery.supplementHandoffId === undefined &&
599
- d.recovery.cancellationReason === undefined &&
600
- s.assessment?.handoffId === base?.id &&
601
- base?.kind === "return" &&
602
- a.returnHandoffId === base.id &&
603
- base.state === "configured" &&
604
- base.reportRevision === d.recovery.baseReportRevision &&
605
- !s.recoveryId &&
606
- !isPending() &&
607
- h.kind === "recovery-request" &&
608
- h.assignmentId === a.id &&
609
- h.from === "coordinator" &&
610
- h.to === assignmentWorker() &&
611
- h.state === "pending" &&
612
- !s.handoffs.has(h.id) &&
613
- !s.recoveries.has(d.recovery.id),
614
- "recovery_not_available",
615
- );
616
- s.recoveries.set(d.recovery.id, d.recovery);
617
- s.recoveryId = d.recovery.id;
618
- s.handoffs.set(h.id, h);
619
- s.pendingId = h.id;
620
- s.assessment.view = "suspended";
621
- s.assessment.suspensionReason = "recovery";
622
- s.assessment.basisUserEntryId = h.basisUserEntryId;
623
- s.assessment.problems = [];
624
- break;
625
- }
626
- case "recovery-supplement-accepted": {
627
- const a = assignment(),
628
- r = s.recoveries.get(d.recoveryId),
629
- h = d.handoff,
630
- previous = r?.supplementHandoffId ? s.handoffs.get(r.supplementHandoffId) : undefined;
631
- check(
632
- s.control === "automatic" &&
633
- s.profile === assignmentWorker() &&
634
- a?.state === "returned" &&
635
- r?.id === s.recoveryId &&
636
- r.assignmentId === a.id &&
637
- s.assessment?.handoffId === r.assessmentHandoffId &&
638
- s.handoffs.get(r.assessmentHandoffId)?.reportRevision === r.baseReportRevision &&
639
- ["reading", "returning"].includes(r.state) &&
640
- h.kind === "recovery-return" &&
641
- h.assignmentId === a.id &&
642
- h.from === assignmentWorker() &&
643
- h.to === "coordinator" &&
644
- h.state === "pending" &&
645
- (!previous ? !s.handoffs.has(h.id) : ["pending", "blocked"].includes(previous.state) && previous.id === h.id),
646
- "recovery_not_returnable",
647
- );
648
- const expectedRevision = (previous?.reportRevision ?? 0) + 1;
649
- check(h.reportRevision === expectedRevision, "invalid_report_revision");
650
- r.state = "returning";
651
- r.supplementHandoffId = h.id;
652
- r.supplementRevision = h.reportRevision;
653
- s.handoffs.set(h.id, h);
654
- s.pendingId = h.id;
655
- break;
656
- }
657
- case "recovery-cancelled": {
658
- const r = s.recoveries.get(d.recoveryId);
659
- check(
660
- s.control === "automatic" &&
661
- s.profile === "coordinator" &&
662
- r?.id === s.recoveryId &&
663
- ["requested", "reading", "returning"].includes(r.state),
664
- "recovery_not_cancellable",
665
- );
666
- const p = pending();
667
- if (p) {
668
- check([r.requestHandoffId, r.supplementHandoffId].includes(p.id), "handoff_pending");
669
- p.state = "superseded";
670
- s.pendingId = undefined;
671
- }
672
- r.state = "cancelled";
673
- r.cancellationReason = d.reason;
674
- s.recoveryId = undefined;
675
- break;
676
- }
677
- case "handoff-retry-requested": {
678
- const h = currentReturn(d.handoffId);
679
- check(
680
- s.control === "automatic" &&
681
- s.profile === assignmentWorker() &&
682
- ["blocked", "pending"].includes(h.state) &&
683
- h.reportRevision === d.reportRevision &&
684
- (s.selections.get(h.assignmentId)?.revision ?? 0) === d.selectionRevision,
685
- "invalid_return_retry",
686
- );
687
- s.attempts.set(h.id, {
688
- executionId: d.executionId,
689
- reportRevision: d.reportRevision,
690
- selectionRevision: d.selectionRevision,
691
- });
692
- h.state = "pending";
693
- break;
694
- }
695
- case "handoff-prepared": {
696
- const h = currentReturn(d.handoffId);
697
- check(["pending", "blocked"].includes(h.state), "handoff_terminal");
698
- check(d.reservation.selectionRevision === (s.selections.get(h.assignmentId)?.revision ?? 0), "stale_selection");
699
- s.reservations.set(h.id, d.reservation);
700
- if (s.assessment?.handoffId === h.id) s.assessment.reservation = d.reservation;
701
- if (h.kind === "recovery-return") {
702
- const recovery = [...s.recoveries.values()].find((candidate) => candidate.supplementHandoffId === h.id);
703
- if (recovery && s.assessment?.handoffId === recovery.assessmentHandoffId)
704
- s.assessment.reservation = d.reservation;
705
- }
706
- break;
707
- }
708
- case "handoff-state": {
709
- const h = s.handoffs.get(d.handoffId);
710
- check(h && s.pendingId === h.id && ["pending", "blocked"].includes(h.state), "handoff_terminal");
711
- h.state = d.state;
712
- h.reason = d.reason;
713
- if (d.state === "configured") {
714
- check(d.observedPair, "configuration_unobserved");
715
- s.profile = h.to;
716
- s.pendingId = undefined;
717
- const r = s.recoveryId ? s.recoveries.get(s.recoveryId) : undefined;
718
- if (r && h.id === r.requestHandoffId) r.state = "reading";
719
- if (r && h.id === r.supplementHandoffId) {
720
- r.state = "completed";
721
- s.recoveryId = undefined;
722
- }
723
- }
724
- if (d.state === "superseded") s.pendingId = undefined;
725
- break;
726
- }
727
- case "selection-changed": {
728
- const a = assignment();
729
- check(
730
- a?.id === d.assignmentId &&
731
- s.control === "automatic" &&
732
- s.profile === assignmentWorker() &&
733
- (a.state === "outstanding" ||
734
- (a.state === "returned" &&
735
- (isPending() || (s.recoveryId !== undefined && s.recoveries.get(s.recoveryId)?.state === "reading")))),
736
- "selection_wrong_phase",
737
- );
738
- check(d.selection.revision === (s.selections.get(a.id)?.revision ?? 0) + 1, "selection_revision");
739
- s.selections.set(a.id, d.selection);
740
- break;
741
- }
742
- case "assessment-suspended": {
743
- check(s.assessment?.handoffId === d.handoffId, "assessment_missing");
744
- s.assessment.view = "suspended";
745
- s.assessment.suspensionReason = d.reason;
746
- s.assessment.basisUserEntryId = d.basisUserEntryId;
747
- s.assessment.problems = d.problems;
748
- break;
749
- }
750
- case "assessment-resumed": {
751
- check(
752
- s.control === "automatic" &&
753
- s.profile === "coordinator" &&
754
- !s.recoveryId &&
755
- s.assessment?.handoffId === d.handoffId &&
756
- s.assessment.view === "suspended",
757
- s.recoveryId ? "recovery_outstanding" : "assessment_not_suspended",
758
- );
759
- if (d.reservation)
760
- check(
761
- d.reservation.selectionRevision === (s.selections.get(s.assessment.assignmentId)?.revision ?? 0),
762
- "stale_selection",
763
- );
764
- s.assessment.view = "active";
765
- s.assessment.suspensionReason = undefined;
766
- s.assessment.basisUserEntryId = d.basisUserEntryId;
767
- if (d.reservation) {
768
- s.assessment.reservation = d.reservation;
769
- s.reservations.set(d.handoffId, d.reservation);
770
- }
771
- s.assessment.problems = [];
772
- break;
773
- }
774
- case "unit-closed": {
775
- const u = s.units.get(d.unitId),
776
- a = assignment();
777
- check(
778
- s.control === "automatic" && s.profile === "coordinator" && u?.id === s.unitId && u.state === "open",
779
- "unit_not_open",
780
- );
781
- if (d.outcome === "accepted")
782
- check(a?.state !== "outstanding" && !isPending() && !s.recoveryId, "unfinished_work");
783
- const recovery = s.recoveryId ? s.recoveries.get(s.recoveryId) : undefined;
784
- if (recovery) {
785
- check(
786
- d.outcome !== "accepted" && ["requested", "reading", "returning"].includes(recovery.state),
787
- "unfinished_work",
788
- );
789
- const transfer = pending();
790
- if (transfer) {
791
- check([recovery.requestHandoffId, recovery.supplementHandoffId].includes(transfer.id), "handoff_pending");
792
- transfer.state = "superseded";
793
- s.pendingId = undefined;
794
- }
795
- recovery.state = "cancelled";
796
- recovery.cancellationReason = d.assessment;
797
- s.recoveryId = undefined;
798
- }
799
- if (a?.state === "outstanding") {
800
- check(d.supersededAssignmentId === a.id && d.outcome !== "accepted", "supersession_required");
801
- a.state = "superseded";
802
- }
803
- if (pending()) pending()!.state = "superseded";
804
- u.state = "closed";
805
- u.disposition = d.outcome;
806
- u.assessment = d.assessment;
807
- s.unitId = undefined;
808
- s.assignmentId = undefined;
809
- s.pendingId = undefined;
810
- s.assessment = undefined;
811
- break;
812
- }
813
- case "request-observed": {
814
- const x = s.executions.get(d.executionId);
815
- check(x && x.profile === d.profile, "request_execution_missing");
816
- if (d.handoffId) check(s.handoffs.has(d.handoffId), "request_handoff_missing");
817
- break;
818
- }
819
- case "sources-exposed": {
820
- const x = s.executions.get(d.executionId);
821
- check(x && x.profile === d.view, "exposure_execution_missing");
822
- if (isWorkerProfile(d.view)) for (const source of d.sources) s.exposure.set(source.ref, source.bodyHash);
823
- break;
824
- }
825
- }
826
- s.events.set(key, e);
827
- s.eventIds.set(e.eventId, value);
828
- return s;
829
- }
830
- export function reduce(state: State, event: RoutingEvent): State {
831
- return applyEvent(state, event);
832
- }
833
- export function replay(entries: readonly NativeEntry[]): State {
834
- let state = initialState();
835
- for (const entry of entries)
836
- if (entry.type === "custom" && entry.customType === ROUTING_ENTRY)
837
- state = applyEvent(state, parseRoutingEvent(entry.data), true);
838
- return state;
839
- }