@hassangameryt/freeflow 0.7.1 → 0.7.3

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 (147) 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/README.md +153 -128
  6. package/capabilities/cognitive-routing/SKILL.md +126 -274
  7. package/command-surface.json +7 -1
  8. package/gemini-extension.json +1 -1
  9. package/package.json +4 -4
  10. package/pi-extension/dist/cognitive-routing-v2/budget.js +42 -0
  11. package/pi-extension/dist/cognitive-routing-v2/config.js +136 -0
  12. package/pi-extension/dist/cognitive-routing-v2/projection.js +330 -0
  13. package/pi-extension/dist/cognitive-routing-v2/render.js +252 -0
  14. package/pi-extension/dist/cognitive-routing-v2/runtime.js +2181 -0
  15. package/pi-extension/dist/cognitive-routing-v2/schemas.js +173 -0
  16. package/pi-extension/dist/cognitive-routing-v2/state.js +828 -0
  17. package/pi-extension/dist/cognitive-routing-v2/tools.js +60 -0
  18. package/pi-extension/dist/cognitive-routing-v2/types.js +48 -0
  19. package/pi-extension/dist/context-virtualization/runtime.js +29 -7
  20. package/pi-extension/dist/freeflow-context/tool.js +12 -6
  21. package/pi-extension/dist/index.js +302 -626
  22. package/pi-extension/dist/provider-support/astra/adapter.js +110 -0
  23. package/pi-extension/dist/provider-support/astra/history.js +125 -0
  24. package/pi-extension/dist/provider-support/astra/index.js +11 -0
  25. package/pi-extension/dist/provider-support/astra/session-state.js +114 -0
  26. package/pi-extension/dist/provider-support/index.js +4 -0
  27. package/pi-extension/dist/runtime/request-history.js +216 -0
  28. package/pi-extension/dist/runtime/runtime-context.js +85 -16
  29. package/pi-extension/dist/session-sources/events.js +217 -0
  30. package/pi-extension/dist/session-sources/provenance.js +32 -0
  31. package/pi-extension/dist/session-sources/read-only-session.js +148 -0
  32. package/pi-extension/dist/session-sources/sources.js +277 -0
  33. package/pi-extension/dist/settings/settings-ui.js +274 -147
  34. package/pi-extension/src/cognitive-routing-v2/budget.ts +42 -0
  35. package/pi-extension/src/cognitive-routing-v2/config.ts +173 -0
  36. package/pi-extension/src/cognitive-routing-v2/projection.ts +375 -0
  37. package/pi-extension/src/cognitive-routing-v2/render.ts +277 -0
  38. package/pi-extension/src/cognitive-routing-v2/runtime.ts +2241 -0
  39. package/pi-extension/src/cognitive-routing-v2/schemas.ts +180 -0
  40. package/pi-extension/src/cognitive-routing-v2/state.ts +839 -0
  41. package/pi-extension/src/cognitive-routing-v2/tools.ts +62 -0
  42. package/pi-extension/src/cognitive-routing-v2/types.ts +254 -0
  43. package/pi-extension/src/context-virtualization/runtime.ts +30 -8
  44. package/pi-extension/src/freeflow-context/tool.ts +12 -6
  45. package/pi-extension/src/index.ts +308 -675
  46. package/pi-extension/src/provider-support/astra/adapter.ts +108 -0
  47. package/pi-extension/src/provider-support/astra/history.ts +145 -0
  48. package/pi-extension/src/provider-support/astra/index.ts +12 -0
  49. package/pi-extension/src/provider-support/astra/session-state.ts +113 -0
  50. package/pi-extension/src/provider-support/index.ts +5 -0
  51. package/pi-extension/src/runtime/request-history.ts +243 -0
  52. package/pi-extension/src/runtime/runtime-context.ts +100 -17
  53. package/pi-extension/src/session-sources/events.ts +230 -0
  54. package/pi-extension/src/session-sources/provenance.ts +39 -0
  55. package/pi-extension/src/session-sources/read-only-session.ts +170 -0
  56. package/pi-extension/src/session-sources/sources.ts +334 -0
  57. package/pi-extension/src/settings/settings-ui.ts +311 -158
  58. package/pi-extension/tests/cache-reuse/history.test.js +117 -0
  59. package/pi-extension/tests/cache-reuse/native.test.js +91 -0
  60. package/pi-extension/tests/cache-reuse/settings.test.js +211 -0
  61. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +577 -0
  62. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +183 -0
  63. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
  64. package/pi-extension/tests/cognitive-routing-v2/native.test.js +474 -0
  65. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +87 -0
  66. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +300 -0
  67. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +241 -0
  68. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +1034 -0
  69. package/pi-extension/tests/cognitive-routing-v2/render.test.js +244 -0
  70. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +423 -0
  71. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +521 -0
  72. package/pi-extension/tests/cognitive-routing-v2/state.test.js +586 -0
  73. package/pi-extension/tests/context-virtualization/extension.test.js +33 -7
  74. package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
  75. package/pi-extension/tests/fixtures/routing-native.js +191 -0
  76. package/pi-extension/tests/integration/mode-free-core.test.js +6 -4
  77. package/pi-extension/tests/integration/pi-extension.test.js +655 -59
  78. package/pi-extension/tests/integration/prompt-architecture.test.js +127 -30
  79. package/pi-extension/tests/integration/unaffected-regressions.test.js +78 -11
  80. package/pi-extension/tests/provider-support/astra-boundaries.test.js +75 -0
  81. package/pi-extension/tests/provider-support/astra.test.js +306 -0
  82. package/pi-extension/tests/provider-support/cli.test.js +92 -0
  83. package/pi-extension/tests/provider-support/native.test.js +183 -0
  84. package/plugin.json +1 -1
  85. package/runtime/prompts/cognitive-routing.md +4 -5
  86. package/runtime/prompts/core.md +51 -45
  87. package/runtime/prompts/interaction-contract.md +5 -5
  88. package/skills/action-selection/SKILL.md +57 -97
  89. package/skills/design-for-depth/SKILL.md +58 -193
  90. package/skills/design-for-depth/references/implementation-decisions.md +76 -0
  91. package/skills/diagnose-failure/SKILL.md +64 -196
  92. package/skills/discuss/SKILL.md +64 -123
  93. package/skills/discuss/references/discussion-continuity.md +7 -3
  94. package/skills/execute-work/SKILL.md +69 -88
  95. package/skills/execute-work/references/execute-work-edges.md +9 -3
  96. package/skills/handoff/SKILL.md +44 -54
  97. package/skills/handoff/references/handoff-templates.md +15 -6
  98. package/skills/review-artifact/SKILL.md +80 -127
  99. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  100. package/skills/review-work/SKILL.md +65 -115
  101. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  102. package/skills/simplify-code/SKILL.md +47 -100
  103. package/skills/track-work/SKILL.md +128 -119
  104. package/skills/track-work/references/working-record-format.md +14 -2
  105. package/skills/verify-work/SKILL.md +58 -62
  106. package/skills/verify-work/references/integration-evidence.md +4 -2
  107. package/skills/verify-work/references/test-design.md +89 -0
  108. package/skills/workflow/SKILL.md +73 -119
  109. package/skills/write-plan/SKILL.md +46 -77
  110. package/skills/write-plan/references/plan-shapes.md +5 -5
  111. package/skills/write-spec/SKILL.md +51 -63
  112. package/skills/write-spec/references/artifact-standards.md +2 -2
  113. package/skills/write-spec/references/decision-records.md +3 -2
  114. package/skills/write-spec/references/spec-shapes.md +15 -0
  115. package/pi-extension/dist/cognitive-routing/commands.js +0 -96
  116. package/pi-extension/dist/cognitive-routing/config.js +0 -232
  117. package/pi-extension/dist/cognitive-routing/controller.js +0 -1292
  118. package/pi-extension/dist/cognitive-routing/history.js +0 -451
  119. package/pi-extension/dist/cognitive-routing/host.js +0 -188
  120. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +0 -414
  121. package/pi-extension/dist/cognitive-routing/pi-session-control.js +0 -320
  122. package/pi-extension/dist/cognitive-routing/runtime.js +0 -43
  123. package/pi-extension/dist/cognitive-routing/tool.js +0 -212
  124. package/pi-extension/dist/cognitive-routing/types.js +0 -6
  125. package/pi-extension/src/cognitive-routing/commands.ts +0 -129
  126. package/pi-extension/src/cognitive-routing/config.ts +0 -303
  127. package/pi-extension/src/cognitive-routing/controller.ts +0 -1628
  128. package/pi-extension/src/cognitive-routing/history.ts +0 -650
  129. package/pi-extension/src/cognitive-routing/host.ts +0 -266
  130. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +0 -538
  131. package/pi-extension/src/cognitive-routing/pi-session-control.ts +0 -444
  132. package/pi-extension/src/cognitive-routing/runtime.ts +0 -57
  133. package/pi-extension/src/cognitive-routing/tool.ts +0 -273
  134. package/pi-extension/src/cognitive-routing/types.ts +0 -89
  135. package/pi-extension/tests/cognitive-routing/commands.test.js +0 -134
  136. package/pi-extension/tests/cognitive-routing/controller.test.js +0 -1128
  137. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +0 -1142
  138. package/pi-extension/tests/cognitive-routing/history.test.js +0 -274
  139. package/pi-extension/tests/cognitive-routing/host-identity.test.js +0 -63
  140. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +0 -434
  141. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +0 -174
  142. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +0 -91
  143. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +0 -279
  144. package/pi-extension/tests/cognitive-routing/tool.test.js +0 -230
  145. package/skills/tdd/SKILL.md +0 -143
  146. package/skills/tdd/references/test-design.md +0 -96
  147. /package/pi-extension/tests/{cognitive-routing/host-fixture.js → fixtures/pi-host.js} +0 -0
@@ -0,0 +1,839 @@
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
+ }