@hassangameryt/freeflow 0.7.2 → 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 (79) 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 +147 -127
  6. package/capabilities/cognitive-routing/SKILL.md +162 -93
  7. package/gemini-extension.json +1 -1
  8. package/package.json +1 -1
  9. package/pi-extension/dist/cognitive-routing-v2/config.js +35 -19
  10. package/pi-extension/dist/cognitive-routing-v2/projection.js +53 -44
  11. package/pi-extension/dist/cognitive-routing-v2/render.js +31 -10
  12. package/pi-extension/dist/cognitive-routing-v2/runtime.js +701 -105
  13. package/pi-extension/dist/cognitive-routing-v2/schemas.js +90 -24
  14. package/pi-extension/dist/cognitive-routing-v2/state.js +243 -27
  15. package/pi-extension/dist/cognitive-routing-v2/tools.js +19 -50
  16. package/pi-extension/dist/cognitive-routing-v2/types.js +5 -1
  17. package/pi-extension/dist/context-virtualization/runtime.js +29 -7
  18. package/pi-extension/dist/freeflow-context/tool.js +7 -6
  19. package/pi-extension/dist/index.js +36 -15
  20. package/pi-extension/dist/provider-support/astra/adapter.js +110 -0
  21. package/pi-extension/dist/provider-support/astra/history.js +125 -0
  22. package/pi-extension/dist/provider-support/astra/index.js +11 -0
  23. package/pi-extension/dist/provider-support/astra/session-state.js +114 -0
  24. package/pi-extension/dist/provider-support/index.js +4 -0
  25. package/pi-extension/dist/runtime/request-history.js +216 -0
  26. package/pi-extension/dist/runtime/runtime-context.js +29 -10
  27. package/pi-extension/dist/session-sources/events.js +22 -0
  28. package/pi-extension/dist/session-sources/provenance.js +32 -0
  29. package/pi-extension/dist/session-sources/sources.js +51 -7
  30. package/pi-extension/dist/settings/settings-ui.js +247 -47
  31. package/pi-extension/src/cognitive-routing-v2/config.ts +49 -19
  32. package/pi-extension/src/cognitive-routing-v2/projection.ts +53 -43
  33. package/pi-extension/src/cognitive-routing-v2/render.ts +31 -10
  34. package/pi-extension/src/cognitive-routing-v2/runtime.ts +712 -105
  35. package/pi-extension/src/cognitive-routing-v2/schemas.ts +95 -24
  36. package/pi-extension/src/cognitive-routing-v2/state.ts +244 -27
  37. package/pi-extension/src/cognitive-routing-v2/tools.ts +19 -50
  38. package/pi-extension/src/cognitive-routing-v2/types.ts +35 -3
  39. package/pi-extension/src/context-virtualization/runtime.ts +30 -8
  40. package/pi-extension/src/freeflow-context/tool.ts +7 -6
  41. package/pi-extension/src/index.ts +36 -14
  42. package/pi-extension/src/provider-support/astra/adapter.ts +108 -0
  43. package/pi-extension/src/provider-support/astra/history.ts +145 -0
  44. package/pi-extension/src/provider-support/astra/index.ts +12 -0
  45. package/pi-extension/src/provider-support/astra/session-state.ts +113 -0
  46. package/pi-extension/src/provider-support/index.ts +5 -0
  47. package/pi-extension/src/runtime/request-history.ts +243 -0
  48. package/pi-extension/src/runtime/runtime-context.ts +32 -10
  49. package/pi-extension/src/session-sources/events.ts +22 -0
  50. package/pi-extension/src/session-sources/provenance.ts +39 -0
  51. package/pi-extension/src/session-sources/sources.ts +77 -6
  52. package/pi-extension/src/settings/settings-ui.ts +276 -46
  53. package/pi-extension/tests/cache-reuse/history.test.js +117 -0
  54. package/pi-extension/tests/cache-reuse/native.test.js +91 -0
  55. package/pi-extension/tests/cache-reuse/settings.test.js +211 -0
  56. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +94 -1
  57. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
  58. package/pi-extension/tests/cognitive-routing-v2/native.test.js +34 -1
  59. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +55 -0
  60. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +800 -2
  61. package/pi-extension/tests/cognitive-routing-v2/render.test.js +15 -1
  62. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +273 -3
  63. package/pi-extension/tests/cognitive-routing-v2/state.test.js +312 -1
  64. package/pi-extension/tests/context-virtualization/extension.test.js +20 -10
  65. package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
  66. package/pi-extension/tests/fixtures/routing-native.js +4 -1
  67. package/pi-extension/tests/integration/mode-free-core.test.js +4 -3
  68. package/pi-extension/tests/integration/pi-extension.test.js +508 -29
  69. package/pi-extension/tests/integration/prompt-architecture.test.js +56 -40
  70. package/pi-extension/tests/integration/unaffected-regressions.test.js +3 -3
  71. package/pi-extension/tests/provider-support/astra-boundaries.test.js +75 -0
  72. package/pi-extension/tests/provider-support/astra.test.js +306 -0
  73. package/pi-extension/tests/provider-support/cli.test.js +92 -0
  74. package/pi-extension/tests/provider-support/native.test.js +183 -0
  75. package/plugin.json +1 -1
  76. package/runtime/prompts/cognitive-routing.md +4 -4
  77. package/skills/design-for-depth/SKILL.md +1 -1
  78. package/skills/design-for-depth/references/implementation-decisions.md +3 -3
  79. package/skills/discuss/SKILL.md +2 -2
@@ -14,6 +14,12 @@ test("evidence inspection renders metadata and gaps without source previews", ()
14
14
  kind: "toolResult",
15
15
  producer: "executor",
16
16
  toolName: "read",
17
+ locator: {
18
+ callRef: "ctx:call-source",
19
+ toolCallId: "call-1",
20
+ label: "read: evidence.txt (offset 1, limit 20)",
21
+ truncated: false,
22
+ },
17
23
  assignment: "a1",
18
24
  selected: true,
19
25
  active: false,
@@ -30,7 +36,15 @@ test("evidence inspection renders metadata and gaps without source previews", ()
30
36
  .render(100)
31
37
  .join("\n");
32
38
  assert.doesNotMatch(rendered, /SOURCE_BODY_MUST_NOT_APPEAR|undefined/);
33
- for (const value of ["ctx:old", "executor", "read", "assignment a1", "Required image unsupported"])
39
+ for (const value of [
40
+ "ctx:old",
41
+ "executor",
42
+ "read",
43
+ "read: evidence.txt (offset 1, limit 20)",
44
+ "call-1 (ctx:call-source)",
45
+ "assignment a1",
46
+ "Required image unsupported",
47
+ ])
34
48
  assert.ok(rendered.includes(value));
35
49
  });
36
50
 
@@ -8,10 +8,19 @@ import { RoutingRuntime } from "../../dist/cognitive-routing-v2/runtime.js";
8
8
  import { EventStore } from "../../dist/session-sources/events.js";
9
9
  import { replay } from "../../dist/cognitive-routing-v2/state.js";
10
10
 
11
+ const modelIds = ["coordinator", "helper", "executor", "coordinator-fast", "helper-fast", "executor-cheap"];
11
12
  const models = Object.fromEntries(
12
- ["coordinator", "executor"].map((id) => [
13
+ modelIds.map((id) => [
13
14
  id,
14
- { id, provider: "fixture", api: "fixture", input: ["text"], contextWindow: 100000, maxTokens: 1000 },
15
+ {
16
+ id,
17
+ provider: "fixture",
18
+ api: "fixture",
19
+ input: ["text"],
20
+ reasoning: true,
21
+ contextWindow: 100000,
22
+ maxTokens: 1000,
23
+ },
15
24
  ]),
16
25
  );
17
26
  const cap = {
@@ -19,10 +28,17 @@ const cap = {
19
28
  enabled: true,
20
29
  projection: false,
21
30
  profiles: Object.fromEntries(
22
- Object.keys(models).map((id) => [id, { provider: "fixture", model: id, thinking: "off" }]),
31
+ ["coordinator", "executor"].map((id) => [id, { provider: "fixture", model: id, thinking: "off" }]),
23
32
  ),
24
33
  blockingReason: { code: "", message: "" },
25
34
  };
35
+ const helperCap = {
36
+ ...cap,
37
+ delegation: "helper",
38
+ profiles: Object.fromEntries(
39
+ ["coordinator", "helper"].map((id) => [id, { provider: "fixture", model: id, thinking: "off" }]),
40
+ ),
41
+ };
26
42
  async function environment(run) {
27
43
  const root = await mkdtemp(join(tmpdir(), "routing-runtime-"));
28
44
  let active;
@@ -41,6 +57,8 @@ async function environment(run) {
41
57
  sessionManager: manager,
42
58
  model: models.coordinator,
43
59
  thinkingLevel: "off",
60
+ failNextModelId: undefined,
61
+ failNextThinkingLevel: undefined,
44
62
  modelRegistry: { find: (_p, id) => models[id], getApiKeyAndHeaders: async () => ({ ok: true }) },
45
63
  getSystemPrompt: () => "",
46
64
  isIdle: () => true,
@@ -50,10 +68,18 @@ async function environment(run) {
50
68
  const pi = {
51
69
  appendEntry: (type, data) => active.sessionManager.appendCustomEntry(type, data),
52
70
  setModel: async (model) => {
71
+ if (active.failNextModelId === model.id) {
72
+ active.failNextModelId = undefined;
73
+ return false;
74
+ }
53
75
  active.model = model;
54
76
  return true;
55
77
  },
56
78
  setThinkingLevel: (level) => {
79
+ if (active.failNextThinkingLevel === level) {
80
+ active.failNextThinkingLevel = undefined;
81
+ throw new Error("fixture thinking application failed");
82
+ }
57
83
  active.thinkingLevel = level;
58
84
  },
59
85
  getAllTools: () => [],
@@ -114,6 +140,32 @@ test("delayed old control cannot change or poison a newly bound session", async
114
140
  }
115
141
  }));
116
142
 
143
+ for (const [name, firstCapability, heldWorker, secondCapability] of [
144
+ ["Helper to Executor", helperCap, "helper", cap],
145
+ ["Executor to Helper", cap, "executor", helperCap],
146
+ ]) {
147
+ test(`binding a fresh ${name} session does not inherit the prior manual hold`, async () =>
148
+ environment(async ({ runtime, make, activate }) => {
149
+ const first = make(`${name}-first`);
150
+ activate(first);
151
+ await runtime.bind(first, firstCapability);
152
+ assert.equal((await runtime.setManualProfile(heldWorker)).status, "active");
153
+ const firstEntryCount = first.sessionManager.getEntries().length;
154
+
155
+ const second = make(`${name}-second`);
156
+ activate(second);
157
+ await runtime.bind(second, secondCapability);
158
+
159
+ assert.equal(runtime.state().runtimeStatus, "active");
160
+ assert.equal(runtime.state().controlMode, "automatic");
161
+ assert.equal(runtime.state().activeProfile, "coordinator");
162
+ assert.equal(second.model.id, "coordinator");
163
+ assert.equal(first.sessionManager.getEntries().length, firstEntryCount);
164
+ assert.equal(replay(first.sessionManager.getBranch()).control, "manual");
165
+ assert.equal(replay(first.sessionManager.getBranch()).profile, heldWorker);
166
+ }));
167
+ }
168
+
117
169
  test("settled unbound attempt is retired truthfully and next request has a new identity", async () =>
118
170
  environment(async ({ runtime, make, activate }) => {
119
171
  const ctx = make("interrupt");
@@ -151,3 +203,221 @@ test("settled unbound attempt is retired truthfully and next request has a new i
151
203
  assert.notEqual(executions[1].id, old.id);
152
204
  assert.notEqual(executions[1].basisUserEntryId, old.basisUserEntryId);
153
205
  }));
206
+
207
+ test("session profile overrides apply only to the selected pair and restore configured pairs", async () =>
208
+ environment(async ({ runtime, make, activate }) => {
209
+ const ctx = make("session-profile");
210
+ activate(ctx);
211
+ await runtime.bind(ctx, cap);
212
+ assert.equal(ctx.model.id, "coordinator");
213
+
214
+ const coordinatorOverride = await runtime.setSessionProfileOverride("coordinator", {
215
+ provider: "fixture",
216
+ modelId: "coordinator-fast",
217
+ thinking: "high",
218
+ });
219
+ assert.equal(coordinatorOverride.status, "active");
220
+ assert.equal(ctx.model.id, "coordinator-fast");
221
+ assert.equal(ctx.thinkingLevel, "high");
222
+ assert.deepEqual(replay(ctx.sessionManager.getBranch()).profileOverrides.get("coordinator"), {
223
+ provider: "fixture",
224
+ modelId: "coordinator-fast",
225
+ thinking: "high",
226
+ });
227
+
228
+ const executorOverride = await runtime.setSessionProfileOverride("executor", {
229
+ provider: "fixture",
230
+ modelId: "executor-cheap",
231
+ thinking: "low",
232
+ });
233
+ assert.equal(executorOverride.status, "stored");
234
+ assert.equal(ctx.model.id, "coordinator-fast");
235
+ assert.equal(ctx.thinkingLevel, "high");
236
+
237
+ runtime.unbind();
238
+ ctx.model = models.coordinator;
239
+ ctx.thinkingLevel = "off";
240
+ activate(ctx);
241
+ await runtime.bind(ctx, cap);
242
+ assert.equal(ctx.model.id, "coordinator-fast");
243
+ assert.equal(ctx.thinkingLevel, "high");
244
+
245
+ const held = await runtime.setManualProfile("executor");
246
+ assert.equal(held.status, "active");
247
+ assert.equal(ctx.model.id, "executor-cheap");
248
+ assert.equal(ctx.thinkingLevel, "low");
249
+
250
+ ctx.model = models.executor;
251
+ ctx.thinkingLevel = "off";
252
+ await runtime.ancestryChanged(ctx, false);
253
+ assert.equal(ctx.model.id, "executor-cheap");
254
+ assert.equal(ctx.thinkingLevel, "low");
255
+ assert.equal(runtime.state().controlMode, "manual-executor");
256
+
257
+ const inheritedExecutor = await runtime.setSessionProfileOverride("executor", null);
258
+ assert.equal(inheritedExecutor.status, "active");
259
+ assert.equal(ctx.model.id, "executor");
260
+ assert.equal(ctx.thinkingLevel, "off");
261
+ assert.equal(replay(ctx.sessionManager.getBranch()).profileOverrides.has("coordinator"), true);
262
+ assert.equal(replay(ctx.sessionManager.getBranch()).profileOverrides.has("executor"), false);
263
+
264
+ const reset = await runtime.resetSessionProfileOverrides();
265
+ assert.equal(reset.status, "stored");
266
+ assert.equal(ctx.model.id, "executor");
267
+ assert.equal(ctx.thinkingLevel, "off");
268
+ assert.equal(replay(ctx.sessionManager.getBranch()).profileOverrides.size, 0);
269
+ assert.equal(runtime.state().controlMode, "manual-executor");
270
+
271
+ runtime.unbind();
272
+ activate(ctx);
273
+ await runtime.bind(ctx, cap);
274
+ assert.equal(ctx.model.id, "executor");
275
+ }));
276
+
277
+ test("Helper session presets apply through the same guarded profile path", async () =>
278
+ environment(async ({ runtime, make, activate }) => {
279
+ const ctx = make("session-helper-profile");
280
+ activate(ctx);
281
+ await runtime.bind(ctx, helperCap);
282
+ const helperOverride = { provider: "fixture", modelId: "helper-fast", thinking: "high" };
283
+ assert.equal((await runtime.setSessionProfileOverride("helper", helperOverride)).status, "stored");
284
+ assert.equal(ctx.model.id, "coordinator");
285
+ assert.equal((await runtime.setManualProfile("helper")).status, "active");
286
+ assert.equal(ctx.model.id, "helper-fast");
287
+ assert.equal(ctx.thinkingLevel, "high");
288
+ assert.deepEqual(replay(ctx.sessionManager.getBranch()).profileOverrides.get("helper"), helperOverride);
289
+ assert.equal((await runtime.resetSessionProfileOverrides()).status, "active");
290
+ assert.equal(ctx.model.id, "helper");
291
+ assert.equal(ctx.thinkingLevel, "off");
292
+ assert.equal(replay(ctx.sessionManager.getBranch()).profileOverrides.size, 0);
293
+ assert.equal(runtime.state().controlMode, "manual-helper");
294
+ }));
295
+
296
+ test("manual control rejects a disabled worker without poisoning routing", async () =>
297
+ environment(async ({ runtime, make, activate }) => {
298
+ const ctx = make("disabled-manual-worker");
299
+ activate(ctx);
300
+ await runtime.bind(ctx, helperCap);
301
+ const result = await runtime.setManualProfile("executor");
302
+ assert.equal(result.status, "blocked");
303
+ assert.match(result.reason, /not enabled/);
304
+ assert.equal(runtime.state().runtimeStatus, "active");
305
+ assert.equal(runtime.state().activeProfile, "coordinator");
306
+ assert.equal(ctx.model.id, "coordinator");
307
+ }));
308
+
309
+ test("invalid session profile overrides preserve the native pair and routing state", async () =>
310
+ environment(async ({ runtime, make, activate }) => {
311
+ const ctx = make("session-invalid");
312
+ activate(ctx);
313
+ await runtime.bind(ctx, cap);
314
+ const beforeEntries = ctx.sessionManager.getEntries().length;
315
+ const result = await runtime.setSessionProfileOverride("coordinator", {
316
+ provider: "fixture",
317
+ modelId: "missing-model",
318
+ thinking: "high",
319
+ });
320
+ assert.equal(result.status, "blocked");
321
+ assert.equal(ctx.model.id, "coordinator");
322
+ assert.equal(ctx.thinkingLevel, "off");
323
+ assert.equal(ctx.sessionManager.getEntries().length, beforeEntries);
324
+ assert.equal(replay(ctx.sessionManager.getBranch()).profileOverrides.size, 0);
325
+ }));
326
+
327
+ test("rebind rejects a persisted session override when its model is unavailable", async () =>
328
+ await environment(async ({ runtime, make, activate }) => {
329
+ const ctx = make("session-stale");
330
+ activate(ctx);
331
+ await runtime.bind(ctx, cap);
332
+ assert.equal(
333
+ (
334
+ await runtime.setSessionProfileOverride("coordinator", {
335
+ provider: "fixture",
336
+ modelId: "coordinator-fast",
337
+ thinking: "high",
338
+ })
339
+ ).status,
340
+ "active",
341
+ );
342
+ runtime.unbind();
343
+ const saved = models["coordinator-fast"];
344
+ delete models["coordinator-fast"];
345
+ try {
346
+ ctx.model = models.coordinator;
347
+ ctx.thinkingLevel = "off";
348
+ activate(ctx);
349
+ await runtime.bind(ctx, cap);
350
+ assert.equal(runtime.state().runtimeStatus, "blocked");
351
+ assert.match(runtime.state().runtimeReason, /profile_unavailable|Model unavailable/);
352
+ } finally {
353
+ models["coordinator-fast"] = saved;
354
+ }
355
+ const corrected = await runtime.setSessionProfileOverride("coordinator", null);
356
+ assert.equal(corrected.status, "active");
357
+ assert.equal(runtime.state().runtimeStatus, "active");
358
+ assert.equal(ctx.model.id, "coordinator");
359
+ assert.equal(replay(ctx.sessionManager.getBranch()).profileOverrides.size, 0);
360
+ }));
361
+
362
+ test("failed active session override restores the prior pair and event state", async () =>
363
+ await environment(async ({ runtime, make, activate }) => {
364
+ const ctx = make("session-rollback");
365
+ activate(ctx);
366
+ await runtime.bind(ctx, cap);
367
+ assert.equal(
368
+ (
369
+ await runtime.setSessionProfileOverride("coordinator", {
370
+ provider: "fixture",
371
+ modelId: "coordinator-fast",
372
+ thinking: "high",
373
+ })
374
+ ).status,
375
+ "active",
376
+ );
377
+ ctx.failNextModelId = "coordinator";
378
+ const result = await runtime.setSessionProfileOverride("coordinator", null);
379
+ assert.equal(result.status, "blocked");
380
+ assert.equal(ctx.model.id, "coordinator-fast");
381
+ assert.equal(ctx.thinkingLevel, "high");
382
+ assert.deepEqual(replay(ctx.sessionManager.getBranch()).profileOverrides.get("coordinator"), {
383
+ provider: "fixture",
384
+ modelId: "coordinator-fast",
385
+ thinking: "high",
386
+ });
387
+ }));
388
+
389
+ test("active session reset restores the configured pair and control mode", async () =>
390
+ await environment(async ({ runtime, make, activate }) => {
391
+ const ctx = make("session-active-reset");
392
+ activate(ctx);
393
+ await runtime.bind(ctx, cap);
394
+ const override = { provider: "fixture", modelId: "coordinator-fast", thinking: "high" };
395
+ assert.equal((await runtime.setSessionProfileOverride("coordinator", override)).status, "active");
396
+ const result = await runtime.resetSessionProfileOverrides();
397
+ assert.equal(result.status, "active");
398
+ assert.equal(ctx.model.id, "coordinator");
399
+ assert.equal(ctx.thinkingLevel, "off");
400
+ assert.equal(replay(ctx.sessionManager.getBranch()).profileOverrides.size, 0);
401
+ assert.equal(runtime.state().controlMode, "automatic");
402
+ }));
403
+
404
+ for (const operation of ["clear", "reset"]) {
405
+ test(`partial active ${operation} failure restores the prior native pair and override`, async () =>
406
+ await environment(async ({ runtime, make, activate }) => {
407
+ const ctx = make(`session-partial-${operation}`);
408
+ activate(ctx);
409
+ await runtime.bind(ctx, cap);
410
+ const override = { provider: "fixture", modelId: "coordinator-fast", thinking: "high" };
411
+ assert.equal((await runtime.setSessionProfileOverride("coordinator", override)).status, "active");
412
+ ctx.failNextThinkingLevel = "off";
413
+ const result =
414
+ operation === "clear"
415
+ ? await runtime.setSessionProfileOverride("coordinator", null)
416
+ : await runtime.resetSessionProfileOverrides();
417
+ assert.equal(result.status, "blocked");
418
+ assert.equal(ctx.model.id, "coordinator-fast");
419
+ assert.equal(ctx.thinkingLevel, "high");
420
+ assert.deepEqual(replay(ctx.sessionManager.getBranch()).profileOverrides.get("coordinator"), override);
421
+ assert.equal(runtime.state().runtimeStatus, "blocked");
422
+ }));
423
+ }
@@ -100,6 +100,64 @@ function started() {
100
100
  m.apply({ type: "handoff-state", handoffId: "d-a1", state: "configured", observedPair: pair });
101
101
  return m;
102
102
  }
103
+ const recoveryRequest = (assignmentId = "a1") => ({
104
+ type: "recovery-request-accepted",
105
+ recovery: {
106
+ id: "recovery",
107
+ assignmentId,
108
+ assessmentHandoffId: "return",
109
+ baseReportRevision: 1,
110
+ request: "recover evidence",
111
+ requestedPaths: ["recovery.txt"],
112
+ paths: ["/tmp/recovery.txt"],
113
+ state: "requested",
114
+ requestHandoffId: "recovery-request",
115
+ supplementRevision: 0,
116
+ },
117
+ handoff: {
118
+ id: "recovery-request",
119
+ kind: "recovery-request",
120
+ assignmentId,
121
+ text: "recover evidence",
122
+ from: "coordinator",
123
+ to: "executor",
124
+ executionId: "e3",
125
+ toolCallId: "recover-call",
126
+ basisUserEntryId: "user",
127
+ state: "pending",
128
+ reportRevision: 0,
129
+ limitations: [],
130
+ },
131
+ });
132
+ const recoverySupplement = (revision = 1) => ({
133
+ type: "recovery-supplement-accepted",
134
+ recoveryId: "recovery",
135
+ handoff: {
136
+ id: "recovery-return",
137
+ kind: "recovery-return",
138
+ assignmentId: "a1",
139
+ text: "recovered evidence",
140
+ from: "executor",
141
+ to: "coordinator",
142
+ executionId: "e4",
143
+ toolCallId: "supplement-call",
144
+ basisUserEntryId: "user",
145
+ state: "pending",
146
+ reportRevision: revision,
147
+ outcome: "completed",
148
+ limitations: [],
149
+ },
150
+ });
151
+ function recoveryReady() {
152
+ const m = started();
153
+ m.apply(opened("e2", "executor", "a1"));
154
+ m.apply(returned());
155
+ m.apply(bound("e2"));
156
+ m.apply({ type: "handoff-state", handoffId: "return", state: "configured", observedPair: pair });
157
+ m.apply(opened("e3", "coordinator", "a1"));
158
+ m.apply(recoveryRequest());
159
+ return m;
160
+ }
103
161
  const reservation = (revision) => ({
104
162
  id: "reservation",
105
163
  selectionRevision: revision,
@@ -183,7 +241,7 @@ test("replacement retains old assignment and requires old execution to be resolv
183
241
  });
184
242
  assert.throws(
185
243
  () => m.apply(replacement),
186
- (e) => e.code === "unresolved_executor_execution",
244
+ (e) => e.code === "unresolved_worker_execution",
187
245
  );
188
246
  m.apply(bound("e2"));
189
247
  m.apply(replacement);
@@ -192,6 +250,144 @@ test("replacement retains old assignment and requires old execution to be resolv
192
250
  assert.equal(m.state.assignments.get("a1").contract, "fixture contract");
193
251
  assert.equal(m.state.assignmentId, "a2");
194
252
  });
253
+ test("worker identity rejects foreign returns and recovery requests", () => {
254
+ const m = machine();
255
+ m.apply(control("coordinator"));
256
+ m.apply(opened("e1", "coordinator"));
257
+ const helperDelegate = delegated();
258
+ helperDelegate.handoff.to = "helper";
259
+ m.apply(helperDelegate);
260
+ m.apply(bound("e1"));
261
+ m.apply({ type: "handoff-state", handoffId: "d-a1", state: "configured", observedPair: pair });
262
+ m.apply(opened("e2", "helper", "a1"));
263
+ const foreignReturn = returned();
264
+ assert.throws(
265
+ () => m.apply(foreignReturn),
266
+ (error) => error.code === "return_identity_mismatch",
267
+ );
268
+ const helperReturn = returned();
269
+ helperReturn.handoff.from = "helper";
270
+ m.apply(helperReturn);
271
+ m.apply(bound("e2"));
272
+ m.apply({ type: "handoff-state", handoffId: "return", state: "configured", observedPair: pair });
273
+ m.apply(opened("e3", "coordinator", "a1"));
274
+ const foreignRecovery = recoveryRequest();
275
+ assert.throws(
276
+ () => m.apply(foreignRecovery),
277
+ (error) => error.code === "recovery_not_available",
278
+ );
279
+ foreignRecovery.handoff.to = "helper";
280
+ m.apply(foreignRecovery);
281
+ assert.equal(m.state.recoveryId, "recovery");
282
+ assert.equal(m.state.handoffs.get("recovery-request").to, "helper");
283
+ });
284
+
285
+ test("wrong-worker return, selection, supplement, and retry events are symmetric and state-preserving", () => {
286
+ const startedFor = (worker) => {
287
+ const m = machine();
288
+ m.apply(control("coordinator"));
289
+ m.apply(opened("e1", "coordinator"));
290
+ const delegation = delegated();
291
+ delegation.handoff.to = worker;
292
+ m.apply(delegation);
293
+ m.apply(bound("e1"));
294
+ m.apply({ type: "handoff-state", handoffId: "d-a1", state: "configured", observedPair: pair });
295
+ return m;
296
+ };
297
+ const returnFrom = (worker) => {
298
+ const value = returned();
299
+ value.handoff.from = worker;
300
+ return value;
301
+ };
302
+ const requestTo = (worker) => {
303
+ const value = recoveryRequest();
304
+ value.handoff.to = worker;
305
+ return value;
306
+ };
307
+ const supplementFrom = (worker) => {
308
+ const value = recoverySupplement();
309
+ value.handoff.from = worker;
310
+ return value;
311
+ };
312
+
313
+ for (const [worker, foreign] of [
314
+ ["executor", "helper"],
315
+ ["helper", "executor"],
316
+ ]) {
317
+ const returning = startedFor(worker);
318
+ returning.apply(opened("e2", worker, "a1"));
319
+ const beforeReturn = returning.state;
320
+ assert.throws(
321
+ () => returning.apply(returnFrom(foreign)),
322
+ (error) => error.code === "return_identity_mismatch",
323
+ );
324
+ assert.equal(returning.state, beforeReturn);
325
+ assert.equal(returning.state.assignments.get("a1").state, "outstanding");
326
+
327
+ const selecting = startedFor(worker);
328
+ selecting.apply(control(foreign));
329
+ const beforeSelection = selecting.state;
330
+ assert.throws(
331
+ () =>
332
+ selecting.apply({
333
+ type: "selection-changed",
334
+ assignmentId: "a1",
335
+ selection: { revision: 1, selected: [], unresolved: [], withdrawals: [] },
336
+ }),
337
+ (error) => error.code === "selection_wrong_phase",
338
+ );
339
+ assert.equal(selecting.state, beforeSelection);
340
+ assert.equal(selecting.state.selections.has("a1"), false);
341
+
342
+ const retrying = startedFor(worker);
343
+ retrying.apply(opened("e2", worker, "a1"));
344
+ retrying.apply(returnFrom(worker));
345
+ retrying.apply({ type: "handoff-state", handoffId: "return", state: "blocked", reason: "fixture gap" });
346
+ retrying.apply(control(foreign));
347
+ const beforeRetry = retrying.state;
348
+ assert.throws(
349
+ () =>
350
+ retrying.apply({
351
+ type: "handoff-retry-requested",
352
+ handoffId: "return",
353
+ attemptId: "retry",
354
+ executionId: "e2",
355
+ toolCallId: "retry-call",
356
+ reportRevision: 1,
357
+ selectionRevision: 0,
358
+ }),
359
+ (error) => error.code === "invalid_return_retry",
360
+ );
361
+ assert.equal(retrying.state, beforeRetry);
362
+ assert.equal(retrying.state.handoffs.get("return").state, "blocked");
363
+ assert.equal(retrying.state.attempts.has("return"), false);
364
+
365
+ const supplementing = startedFor(worker);
366
+ supplementing.apply(opened("e2", worker, "a1"));
367
+ supplementing.apply(returnFrom(worker));
368
+ supplementing.apply(bound("e2"));
369
+ supplementing.apply({ type: "handoff-state", handoffId: "return", state: "configured", observedPair: pair });
370
+ supplementing.apply(opened("e3", "coordinator", "a1"));
371
+ supplementing.apply(requestTo(worker));
372
+ supplementing.apply(bound("e3"));
373
+ supplementing.apply({
374
+ type: "handoff-state",
375
+ handoffId: "recovery-request",
376
+ state: "configured",
377
+ observedPair: pair,
378
+ });
379
+ supplementing.apply(opened("e4", worker, "a1"));
380
+ const beforeSupplement = supplementing.state;
381
+ assert.throws(
382
+ () => supplementing.apply(supplementFrom(foreign)),
383
+ (error) => error.code === "recovery_not_returnable",
384
+ );
385
+ assert.equal(supplementing.state, beforeSupplement);
386
+ assert.equal(supplementing.state.recoveries.get("recovery").state, "reading");
387
+ assert.equal(supplementing.state.handoffs.has("recovery-return"), false);
388
+ }
389
+ });
390
+
195
391
  test("saved report revisions and retries preserve exact earlier events", () => {
196
392
  const m = started();
197
393
  m.apply(opened("e2", "executor", "a1"));
@@ -213,6 +409,86 @@ test("saved report revisions and retries preserve exact earlier events", () => {
213
409
  assert.equal([...m.state.events.values()].find((e) => e.eventId === first.eventId).data.handoff.text, "exact report");
214
410
  assert.equal(m.state.assignments.size, 1);
215
411
  });
412
+ test("recovery transitions preserve the original report and reject foreign identities", () => {
413
+ const ready = () => {
414
+ const m = started();
415
+ m.apply(opened("e2", "executor", "a1"));
416
+ m.apply(returned());
417
+ m.apply(bound("e2"));
418
+ m.apply({ type: "handoff-state", handoffId: "return", state: "configured", observedPair: pair });
419
+ m.apply(opened("e3", "coordinator", "a1"));
420
+ return m;
421
+ };
422
+ const foreign = ready();
423
+ assert.throws(
424
+ () => foreign.apply(recoveryRequest("foreign")),
425
+ (error) => error.code === "recovery_not_available",
426
+ );
427
+
428
+ const m = ready();
429
+ const base = structuredClone(m.state.handoffs.get("return"));
430
+ const request = m.apply(recoveryRequest());
431
+ m.set(request);
432
+ assert.equal(m.state.recoveries.size, 1, "exact event replay is idempotent");
433
+ m.apply(bound("e3"));
434
+ m.apply({ type: "handoff-state", handoffId: "recovery-request", state: "configured", observedPair: pair });
435
+ assert.equal(m.state.recoveries.get("recovery").state, "reading");
436
+ m.apply(opened("e4", "executor", "a1"));
437
+ m.apply(recoverySupplement());
438
+ assert.equal(m.state.recoveries.get("recovery").state, "returning");
439
+ assert.deepEqual(m.state.handoffs.get("return"), base, "the base report remains immutable");
440
+ assert.throws(
441
+ () => m.apply(recoverySupplement(3)),
442
+ (error) => error.code === "invalid_report_revision",
443
+ );
444
+ });
445
+
446
+ test("assessment cannot resume while attached recovery remains unsettled", () => {
447
+ const m = recoveryReady();
448
+ const before = m.state;
449
+ assert.throws(
450
+ () =>
451
+ m.apply({
452
+ type: "assessment-resumed",
453
+ handoffId: "return",
454
+ basisUserEntryId: "new-user",
455
+ }),
456
+ (error) => error.code === "recovery_outstanding",
457
+ );
458
+ assert.equal(m.state, before);
459
+ assert.equal(m.state.assessment.view, "suspended");
460
+ assert.equal(m.state.recoveryId, "recovery");
461
+ });
462
+
463
+ for (const outcome of ["cancelled", "deferred"]) {
464
+ test(`${outcome} unit closure atomically disposes attached recovery`, () => {
465
+ const m = recoveryReady();
466
+ m.apply({ type: "unit-closed", unitId: "u1", outcome, assessment: `${outcome} with recovery` });
467
+ assert.equal(m.state.unitId, undefined);
468
+ assert.equal(m.state.assignmentId, undefined);
469
+ assert.equal(m.state.pendingId, undefined);
470
+ assert.equal(m.state.recoveryId, undefined);
471
+ assert.equal(m.state.recoveries.get("recovery").state, "cancelled");
472
+ assert.equal(m.state.recoveries.get("recovery").cancellationReason, `${outcome} with recovery`);
473
+ assert.equal(m.state.handoffs.get("recovery-request").state, "superseded");
474
+ assert.equal(m.state.handoffs.get("return").text, "exact report");
475
+ m.apply(opened(`e-${outcome}`, "coordinator"));
476
+ m.apply(delegated(`a-${outcome}`, `u-${outcome}`, [`a-${outcome}`], `e-${outcome}`));
477
+ assert.equal(m.state.unitId, `u-${outcome}`);
478
+ });
479
+ }
480
+
481
+ test("accepted unit closure rejects attached recovery without changing state", () => {
482
+ const m = recoveryReady();
483
+ const before = m.state;
484
+ assert.throws(
485
+ () => m.apply({ type: "unit-closed", unitId: "u1", outcome: "accepted", assessment: "incorrect" }),
486
+ (error) => error.code === "unfinished_work",
487
+ );
488
+ assert.equal(m.state, before);
489
+ assert.equal(m.state.recoveryId, "recovery");
490
+ });
491
+
216
492
  test("assessment resumption rejects stale selection and preserves suspension on failure", () => {
217
493
  const m = started();
218
494
  m.apply(opened("e2", "executor", "a1"));
@@ -273,3 +549,38 @@ test("accepted closure cannot conceal work; later work creates a new unit", () =
273
549
  assert.equal(m.state.unitId, "u2");
274
550
  assert.equal(m.state.units.size, 2);
275
551
  });
552
+ test("profile override events are complete-pair, branch-replayable session state", () => {
553
+ const m = machine();
554
+ const coordinator = { provider: "fixture", modelId: "coordinator-fast", thinking: "high" };
555
+ const executor = { provider: "fixture", modelId: "executor-cheap", thinking: "low" };
556
+ m.apply({
557
+ type: "profile-overrides",
558
+ overrides: { coordinator },
559
+ reason: "fixture session override",
560
+ });
561
+ assert.deepEqual(m.state.profileOverrides.get("coordinator"), coordinator);
562
+ assert.equal(m.state.profileOverrides.has("executor"), false);
563
+ m.apply({
564
+ type: "profile-overrides",
565
+ overrides: { executor },
566
+ reason: "fixture session override",
567
+ });
568
+ assert.deepEqual(m.state.profileOverrides.get("executor"), executor);
569
+ m.apply({
570
+ type: "profile-overrides",
571
+ overrides: { coordinator: null, executor: null },
572
+ reason: "fixture reset",
573
+ });
574
+ assert.equal(m.state.profileOverrides.size, 0);
575
+ assert.throws(
576
+ () =>
577
+ parseRoutingEvent(
578
+ m.event({
579
+ type: "profile-overrides",
580
+ overrides: { coordinator: { provider: "fixture", modelId: "bad", thinking: "unknown" } },
581
+ reason: "invalid",
582
+ }),
583
+ ),
584
+ (e) => e.code === "invalid_profile_overrides",
585
+ );
586
+ });