@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +147 -127
- package/capabilities/cognitive-routing/SKILL.md +162 -93
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pi-extension/dist/cognitive-routing-v2/config.js +35 -19
- package/pi-extension/dist/cognitive-routing-v2/projection.js +53 -44
- package/pi-extension/dist/cognitive-routing-v2/render.js +31 -10
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +701 -105
- package/pi-extension/dist/cognitive-routing-v2/schemas.js +90 -24
- package/pi-extension/dist/cognitive-routing-v2/state.js +243 -27
- package/pi-extension/dist/cognitive-routing-v2/tools.js +19 -50
- package/pi-extension/dist/cognitive-routing-v2/types.js +5 -1
- package/pi-extension/dist/context-virtualization/runtime.js +29 -7
- package/pi-extension/dist/freeflow-context/tool.js +7 -6
- package/pi-extension/dist/index.js +36 -15
- package/pi-extension/dist/provider-support/astra/adapter.js +110 -0
- package/pi-extension/dist/provider-support/astra/history.js +125 -0
- package/pi-extension/dist/provider-support/astra/index.js +11 -0
- package/pi-extension/dist/provider-support/astra/session-state.js +114 -0
- package/pi-extension/dist/provider-support/index.js +4 -0
- package/pi-extension/dist/runtime/request-history.js +216 -0
- package/pi-extension/dist/runtime/runtime-context.js +29 -10
- package/pi-extension/dist/session-sources/events.js +22 -0
- package/pi-extension/dist/session-sources/provenance.js +32 -0
- package/pi-extension/dist/session-sources/sources.js +51 -7
- package/pi-extension/dist/settings/settings-ui.js +247 -47
- package/pi-extension/src/cognitive-routing-v2/config.ts +49 -19
- package/pi-extension/src/cognitive-routing-v2/projection.ts +53 -43
- package/pi-extension/src/cognitive-routing-v2/render.ts +31 -10
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +712 -105
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +95 -24
- package/pi-extension/src/cognitive-routing-v2/state.ts +244 -27
- package/pi-extension/src/cognitive-routing-v2/tools.ts +19 -50
- package/pi-extension/src/cognitive-routing-v2/types.ts +35 -3
- package/pi-extension/src/context-virtualization/runtime.ts +30 -8
- package/pi-extension/src/freeflow-context/tool.ts +7 -6
- package/pi-extension/src/index.ts +36 -14
- package/pi-extension/src/provider-support/astra/adapter.ts +108 -0
- package/pi-extension/src/provider-support/astra/history.ts +145 -0
- package/pi-extension/src/provider-support/astra/index.ts +12 -0
- package/pi-extension/src/provider-support/astra/session-state.ts +113 -0
- package/pi-extension/src/provider-support/index.ts +5 -0
- package/pi-extension/src/runtime/request-history.ts +243 -0
- package/pi-extension/src/runtime/runtime-context.ts +32 -10
- package/pi-extension/src/session-sources/events.ts +22 -0
- package/pi-extension/src/session-sources/provenance.ts +39 -0
- package/pi-extension/src/session-sources/sources.ts +77 -6
- package/pi-extension/src/settings/settings-ui.ts +276 -46
- package/pi-extension/tests/cache-reuse/history.test.js +117 -0
- package/pi-extension/tests/cache-reuse/native.test.js +91 -0
- package/pi-extension/tests/cache-reuse/settings.test.js +211 -0
- package/pi-extension/tests/cognitive-routing-v2/communication.test.js +94 -1
- package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +34 -1
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +55 -0
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +800 -2
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +15 -1
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +273 -3
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +312 -1
- package/pi-extension/tests/context-virtualization/extension.test.js +20 -10
- package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
- package/pi-extension/tests/fixtures/routing-native.js +4 -1
- package/pi-extension/tests/integration/mode-free-core.test.js +4 -3
- package/pi-extension/tests/integration/pi-extension.test.js +508 -29
- package/pi-extension/tests/integration/prompt-architecture.test.js +56 -40
- package/pi-extension/tests/integration/unaffected-regressions.test.js +3 -3
- package/pi-extension/tests/provider-support/astra-boundaries.test.js +75 -0
- package/pi-extension/tests/provider-support/astra.test.js +306 -0
- package/pi-extension/tests/provider-support/cli.test.js +92 -0
- package/pi-extension/tests/provider-support/native.test.js +183 -0
- package/plugin.json +1 -1
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/skills/design-for-depth/SKILL.md +1 -1
- package/skills/design-for-depth/references/implementation-decisions.md +3 -3
- package/skills/discuss/SKILL.md +2 -2
|
@@ -1,7 +1,796 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
|
+
import { access, readFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
3
5
|
import { fixture } from "../fixtures/routing-native.js";
|
|
4
6
|
import { replay } from "../../dist/cognitive-routing-v2/state.js";
|
|
7
|
+
import { RoutingRuntime } from "../../dist/cognitive-routing-v2/runtime.js";
|
|
8
|
+
|
|
9
|
+
for (const recoveryWorker of ["executor", "helper"]) {
|
|
10
|
+
const cognitiveRouting =
|
|
11
|
+
recoveryWorker === "helper"
|
|
12
|
+
? {
|
|
13
|
+
enabled: true,
|
|
14
|
+
delegation: "helper",
|
|
15
|
+
projection: true,
|
|
16
|
+
profiles: {
|
|
17
|
+
coordinator: { provider: "openai", model: "gpt-4o", thinking: "off" },
|
|
18
|
+
helper: { provider: "openai", model: "gpt-4.1-mini", thinking: "off" },
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
: undefined;
|
|
22
|
+
test(
|
|
23
|
+
`attached ${recoveryWorker} recovery reuses omitted evidence, gates exact native reads, and blocks returned work`,
|
|
24
|
+
{ timeout: 30000 },
|
|
25
|
+
async () => {
|
|
26
|
+
let assignmentId, baseReportId, baseReportRevision, omittedRef, supplementRef, recoveryRef, cwd;
|
|
27
|
+
const originalInvoke = RoutingRuntime.prototype.invoke;
|
|
28
|
+
const replayed = new Set();
|
|
29
|
+
RoutingRuntime.prototype.invoke = async function (name, id, input, signal, ctx) {
|
|
30
|
+
const intercepted =
|
|
31
|
+
name === "freeflow_unit" && input.operation === "recover"
|
|
32
|
+
? { ...input, request: `${input.request} [accepted]` }
|
|
33
|
+
: name === "freeflow_return" && input.operation === "supplement"
|
|
34
|
+
? { ...input, report: `${input.report} [accepted]` }
|
|
35
|
+
: input;
|
|
36
|
+
const first = await originalInvoke.call(this, name, id, intercepted, signal, ctx);
|
|
37
|
+
const key = `${name}:${input.operation}`;
|
|
38
|
+
if (
|
|
39
|
+
((name === "freeflow_unit" && input.operation === "recover") ||
|
|
40
|
+
(name === "freeflow_return" && input.operation === "supplement")) &&
|
|
41
|
+
!replayed.has(key)
|
|
42
|
+
) {
|
|
43
|
+
replayed.add(key);
|
|
44
|
+
this.receipts.clear();
|
|
45
|
+
const repeated = await originalInvoke.call(this, name, id, intercepted, signal, ctx);
|
|
46
|
+
assert.deepEqual(repeated, first, "same accepted operation returns the recorded receipt");
|
|
47
|
+
this.receipts.clear();
|
|
48
|
+
const changed = await originalInvoke.call(
|
|
49
|
+
this,
|
|
50
|
+
name,
|
|
51
|
+
id,
|
|
52
|
+
{ ...intercepted, [input.operation === "recover" ? "request" : "report"]: "CHANGED_PAYLOAD" },
|
|
53
|
+
signal,
|
|
54
|
+
ctx,
|
|
55
|
+
);
|
|
56
|
+
assert.equal(changed.details.code, "operation_conflict");
|
|
57
|
+
}
|
|
58
|
+
return first;
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
await fixture(
|
|
62
|
+
async (n, body, manager) => {
|
|
63
|
+
if (n === 1)
|
|
64
|
+
return [
|
|
65
|
+
{ name: "freeflow_delegate", args: { operation: "assign", contract: "Read two files and report." } },
|
|
66
|
+
];
|
|
67
|
+
if (n === 2) {
|
|
68
|
+
assert.equal(replay(manager.getBranch()).profile, recoveryWorker);
|
|
69
|
+
return [
|
|
70
|
+
{ name: "read", args: { path: "evidence.txt" } },
|
|
71
|
+
{ name: "read", args: { path: "unselected.txt" } },
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
if (n === 3) {
|
|
75
|
+
const reads = manager.getBranch().filter((e) => e.message?.toolName === "read" && !e.message.isError);
|
|
76
|
+
omittedRef = `ctx:${reads[1].id}`;
|
|
77
|
+
return [
|
|
78
|
+
{ name: "freeflow_project", args: { operation: "add", refs: [`ctx:${reads[0].id}`] } },
|
|
79
|
+
{
|
|
80
|
+
name: "freeflow_return",
|
|
81
|
+
args: { operation: "submit", report: "BASE_REPORT_UNCHANGED_41", outcome: "completed" },
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
if (n === 4) {
|
|
86
|
+
const state = replay(manager.getBranch());
|
|
87
|
+
assignmentId = state.assignmentId;
|
|
88
|
+
baseReportId = state.assignments.get(assignmentId).returnHandoffId;
|
|
89
|
+
baseReportRevision = state.handoffs.get(baseReportId).reportRevision;
|
|
90
|
+
const wire = JSON.stringify(body);
|
|
91
|
+
assert.match(wire, /BASE_REPORT_UNCHANGED_41/);
|
|
92
|
+
assert.match(wire, /EXACT_EVIDENCE_BODY_81/);
|
|
93
|
+
assert.doesNotMatch(wire, /UNSELECTED_PRIVATE_BODY_93/);
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
name: "freeflow_unit",
|
|
97
|
+
args: {
|
|
98
|
+
operation: "recover",
|
|
99
|
+
request: "Select the omitted result and read the approved third file only.",
|
|
100
|
+
paths: ["recovery.txt"],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
if (n === 5) {
|
|
106
|
+
assert.equal(body.model, "gpt-4.1-mini");
|
|
107
|
+
assert.equal(replay(manager.getBranch()).profile, recoveryWorker);
|
|
108
|
+
assert.match(
|
|
109
|
+
JSON.stringify(body),
|
|
110
|
+
/Select the omitted result and read the approved third file only\. \[accepted\]/,
|
|
111
|
+
);
|
|
112
|
+
return [
|
|
113
|
+
{ name: "read", args: { path: "recovery.txt" } },
|
|
114
|
+
{ name: "read", args: { path: "@recovery.txt" } },
|
|
115
|
+
{ name: "read", args: { path: "evidence.txt" } },
|
|
116
|
+
{ name: "write", args: { path: "recovery-write.txt", content: "forbidden" } },
|
|
117
|
+
{ name: "bash", args: { command: "touch recovery-bash.txt" } },
|
|
118
|
+
];
|
|
119
|
+
}
|
|
120
|
+
if (n === 6) {
|
|
121
|
+
const recent = manager
|
|
122
|
+
.getBranch()
|
|
123
|
+
.filter((e) => ["read", "write", "bash"].includes(e.message?.toolName))
|
|
124
|
+
.slice(-5);
|
|
125
|
+
const allowed = recent.find(
|
|
126
|
+
(e) =>
|
|
127
|
+
e.message.toolName === "read" &&
|
|
128
|
+
!e.message.isError &&
|
|
129
|
+
JSON.stringify(e.message).includes("FRESH_RECOVERY_BODY_57"),
|
|
130
|
+
);
|
|
131
|
+
assert.ok(allowed, "the exact approved native read executes");
|
|
132
|
+
assert.equal(
|
|
133
|
+
recent.filter((e) => e !== allowed).every((e) => e.message.isError),
|
|
134
|
+
true,
|
|
135
|
+
);
|
|
136
|
+
await assert.rejects(access(join(cwd, "recovery-write.txt")));
|
|
137
|
+
await assert.rejects(access(join(cwd, "recovery-bash.txt")));
|
|
138
|
+
assert.equal(await readFile(join(cwd, "@recovery.txt"), "utf8"), "LITERAL_AT_FILE_BODY_68");
|
|
139
|
+
assert.equal(
|
|
140
|
+
recent.some((e) => JSON.stringify(e.message).includes("LITERAL_AT_FILE_BODY_68")),
|
|
141
|
+
false,
|
|
142
|
+
"Pi never reaches either @-transformed or literal resource for a rejected alias",
|
|
143
|
+
);
|
|
144
|
+
return [
|
|
145
|
+
{
|
|
146
|
+
name: "freeflow_project",
|
|
147
|
+
args: { operation: "add", refs: [omittedRef, `ctx:${allowed.id}`, "ctx:missing"] },
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "freeflow_return",
|
|
151
|
+
args: { operation: "supplement", report: "RECOVERY_SUPPLEMENT_73", outcome: "completed" },
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
if (n === 7) return [{ name: "read", args: { path: "recovery.txt" } }];
|
|
156
|
+
if (n === 8) {
|
|
157
|
+
const postSupplement = manager
|
|
158
|
+
.getBranch()
|
|
159
|
+
.filter((e) => e.message?.toolName === "read")
|
|
160
|
+
.at(-1);
|
|
161
|
+
assert.equal(postSupplement.message.isError, true, "a saved supplement ends recovery read permission");
|
|
162
|
+
assert.equal(postSupplement.message.content[0].text, "worker_task_phase_ended");
|
|
163
|
+
return [
|
|
164
|
+
{
|
|
165
|
+
name: "freeflow_project",
|
|
166
|
+
args: {
|
|
167
|
+
operation: "remove",
|
|
168
|
+
refs: ["ctx:missing"],
|
|
169
|
+
reason: "Fixture removes the unresolved request.",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{ name: "freeflow_return", args: { operation: "retry" } },
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
assert.equal(body.model, "gpt-4o");
|
|
176
|
+
const wire = JSON.stringify(body);
|
|
177
|
+
const state = replay(manager.getBranch());
|
|
178
|
+
if (n === 14) {
|
|
179
|
+
assert.equal(state.assessment.view, "suspended");
|
|
180
|
+
assert.equal(state.assessment.suspensionReason, "user-attention");
|
|
181
|
+
assert.doesNotMatch(wire, /EXACT_EVIDENCE_BODY_81/);
|
|
182
|
+
assert.match(wire, /NEW_USER_ATTENTION_29/);
|
|
183
|
+
return [{ name: "freeflow_unit", args: { operation: "assess" } }];
|
|
184
|
+
}
|
|
185
|
+
for (const value of [
|
|
186
|
+
"BASE_REPORT_UNCHANGED_41",
|
|
187
|
+
"RECOVERY_SUPPLEMENT_73 [accepted]",
|
|
188
|
+
"EXACT_EVIDENCE_BODY_81",
|
|
189
|
+
"UNSELECTED_PRIVATE_BODY_93",
|
|
190
|
+
"FRESH_RECOVERY_BODY_57",
|
|
191
|
+
])
|
|
192
|
+
assert.ok(wire.includes(value), `missing ${value}`);
|
|
193
|
+
assert.match(wire, new RegExp(`producer: ${recoveryWorker}`));
|
|
194
|
+
if (n === 15) {
|
|
195
|
+
assert.equal(state.assessment.view, "active");
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
199
|
+
assert.equal(state.assignments.get(assignmentId).state, "returned");
|
|
200
|
+
assert.equal(state.assignments.get(assignmentId).returnHandoffId, baseReportId);
|
|
201
|
+
assert.equal(state.handoffs.get(baseReportId).text, "BASE_REPORT_UNCHANGED_41");
|
|
202
|
+
assert.equal(state.handoffs.get(baseReportId).outcome, "completed");
|
|
203
|
+
assert.equal(state.handoffs.get(baseReportId).reportRevision, baseReportRevision);
|
|
204
|
+
assert.equal(state.assessment.handoffId, baseReportId);
|
|
205
|
+
assert.equal(state.assessment.view, "active");
|
|
206
|
+
assert.equal(state.recoveryId, undefined);
|
|
207
|
+
const completedRecovery = [...state.recoveries.values()].at(-1);
|
|
208
|
+
assert.equal(completedRecovery.state, "completed");
|
|
209
|
+
supplementRef = `supplement:${completedRecovery.id}:${completedRecovery.supplementRevision}`;
|
|
210
|
+
recoveryRef = `recovery:${completedRecovery.id}`;
|
|
211
|
+
assert.equal(
|
|
212
|
+
state.selections.get(assignmentId).selected.includes(omittedRef),
|
|
213
|
+
true,
|
|
214
|
+
"the originally omitted result is selected",
|
|
215
|
+
);
|
|
216
|
+
for (const marker of ["EXACT_EVIDENCE_BODY_81", "UNSELECTED_PRIVATE_BODY_93"])
|
|
217
|
+
assert.equal(
|
|
218
|
+
manager
|
|
219
|
+
.getBranch()
|
|
220
|
+
.filter(
|
|
221
|
+
(e) =>
|
|
222
|
+
e.message?.toolName === "read" &&
|
|
223
|
+
!e.message.isError &&
|
|
224
|
+
JSON.stringify(e.message).includes(marker),
|
|
225
|
+
).length,
|
|
226
|
+
1,
|
|
227
|
+
`${marker} was not replayed`,
|
|
228
|
+
);
|
|
229
|
+
if (n === 9)
|
|
230
|
+
return [{ name: "freeflow_unit", args: { operation: "inspect", view: "detail", ref: supplementRef } }];
|
|
231
|
+
if (n === 10) {
|
|
232
|
+
const detail = manager
|
|
233
|
+
.getBranch()
|
|
234
|
+
.filter((e) => e.message?.toolName === "freeflow_unit")
|
|
235
|
+
.at(-1).message.details;
|
|
236
|
+
assert.equal(detail.supplement, "RECOVERY_SUPPLEMENT_73 [accepted]");
|
|
237
|
+
assert.equal(detail.report, "BASE_REPORT_UNCHANGED_41");
|
|
238
|
+
assert.equal(detail.reportRef, `report:${baseReportId}:${baseReportRevision}`);
|
|
239
|
+
return [{ name: "freeflow_unit", args: { operation: "inspect", view: "detail", ref: recoveryRef } }];
|
|
240
|
+
}
|
|
241
|
+
const detail = manager
|
|
242
|
+
.getBranch()
|
|
243
|
+
.filter((e) => e.message?.toolName === "freeflow_unit")
|
|
244
|
+
.at(-1).message.details;
|
|
245
|
+
assert.equal(detail.recovery.state, "completed");
|
|
246
|
+
assert.equal(detail.supplementRef, supplementRef);
|
|
247
|
+
return [];
|
|
248
|
+
},
|
|
249
|
+
true,
|
|
250
|
+
async ({ session, manager, requests }) => {
|
|
251
|
+
await session.prompt("/freeflow resume");
|
|
252
|
+
await session.waitForIdle();
|
|
253
|
+
assert.equal(requests.length, 12);
|
|
254
|
+
await session.compact();
|
|
255
|
+
assert.equal(
|
|
256
|
+
manager.buildSessionContext().messages.some((message) => message.role === "user"),
|
|
257
|
+
false,
|
|
258
|
+
);
|
|
259
|
+
await session.reload();
|
|
260
|
+
await session.prompt("/freeflow resume");
|
|
261
|
+
await session.waitForIdle();
|
|
262
|
+
await session.prompt("NEW_USER_ATTENTION_29");
|
|
263
|
+
await session.waitForIdle();
|
|
264
|
+
assert.equal(requests.length, 15);
|
|
265
|
+
},
|
|
266
|
+
true,
|
|
267
|
+
{ beforePrompt: (fixtureState) => (cwd = fixtureState.cwd), cognitiveRouting },
|
|
268
|
+
);
|
|
269
|
+
} finally {
|
|
270
|
+
RoutingRuntime.prototype.invoke = originalInvoke;
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
for (const returnedWorker of ["executor", "helper"]) {
|
|
277
|
+
const cognitiveRouting =
|
|
278
|
+
returnedWorker === "helper"
|
|
279
|
+
? {
|
|
280
|
+
enabled: true,
|
|
281
|
+
delegation: "helper",
|
|
282
|
+
projection: true,
|
|
283
|
+
profiles: {
|
|
284
|
+
coordinator: { provider: "openai", model: "gpt-4o", thinking: "off" },
|
|
285
|
+
helper: { provider: "openai", model: "gpt-4.1-mini", thinking: "off" },
|
|
286
|
+
},
|
|
287
|
+
}
|
|
288
|
+
: undefined;
|
|
289
|
+
test(`a blocked ${returnedWorker} return denies ordinary work before retry`, { timeout: 30000 }, async () => {
|
|
290
|
+
let assignmentId;
|
|
291
|
+
const result = await fixture(
|
|
292
|
+
(n, body, manager) => {
|
|
293
|
+
if (n === 1)
|
|
294
|
+
return [
|
|
295
|
+
{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return with a delivery gap." } },
|
|
296
|
+
];
|
|
297
|
+
if (n === 2) {
|
|
298
|
+
const state = replay(manager.getBranch());
|
|
299
|
+
assignmentId = state.assignmentId;
|
|
300
|
+
assert.equal(state.profile, returnedWorker);
|
|
301
|
+
return [
|
|
302
|
+
{ name: "freeflow_project", args: { operation: "add", refs: ["ctx:missing"] } },
|
|
303
|
+
{
|
|
304
|
+
name: "freeflow_return",
|
|
305
|
+
args: { operation: "submit", report: "RETURNED_WORKER_REPORT_67", outcome: "completed" },
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
if (n === 3) {
|
|
310
|
+
const state = replay(manager.getBranch());
|
|
311
|
+
assert.equal(state.profile, returnedWorker);
|
|
312
|
+
assert.equal(state.assignments.get(assignmentId).state, "returned");
|
|
313
|
+
assert.equal(state.handoffs.get(state.assignments.get(assignmentId).returnHandoffId).state, "blocked");
|
|
314
|
+
return [{ name: "read", args: { path: "evidence.txt" } }];
|
|
315
|
+
}
|
|
316
|
+
if (n === 4) {
|
|
317
|
+
const denied = manager
|
|
318
|
+
.getBranch()
|
|
319
|
+
.filter((entry) => entry.message?.toolName === "read")
|
|
320
|
+
.at(-1);
|
|
321
|
+
assert.equal(denied.message.isError, true);
|
|
322
|
+
assert.equal(denied.message.content[0].text, "worker_task_phase_ended");
|
|
323
|
+
return [
|
|
324
|
+
{
|
|
325
|
+
name: "freeflow_project",
|
|
326
|
+
args: { operation: "remove", refs: ["ctx:missing"], reason: "Resolve the fixture delivery gap." },
|
|
327
|
+
},
|
|
328
|
+
{ name: "freeflow_return", args: { operation: "retry" } },
|
|
329
|
+
];
|
|
330
|
+
}
|
|
331
|
+
assert.equal(body.model, "gpt-4o");
|
|
332
|
+
const state = replay(manager.getBranch());
|
|
333
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
334
|
+
assert.equal(state.assignments.get(assignmentId).state, "returned");
|
|
335
|
+
assert.equal(state.handoffs.get(state.assignments.get(assignmentId).returnHandoffId).state, "configured");
|
|
336
|
+
assert.match(JSON.stringify(body), /RETURNED_WORKER_REPORT_67/);
|
|
337
|
+
return [];
|
|
338
|
+
},
|
|
339
|
+
true,
|
|
340
|
+
undefined,
|
|
341
|
+
true,
|
|
342
|
+
{ cognitiveRouting, maxRequests: 7 },
|
|
343
|
+
);
|
|
344
|
+
assert.equal(result.requests.length, 5);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
test(
|
|
349
|
+
"partial recovery supplement delivers communication while evidence remains suspended",
|
|
350
|
+
{ timeout: 30000 },
|
|
351
|
+
async () => {
|
|
352
|
+
let baseReportId;
|
|
353
|
+
await fixture((n, body, manager) => {
|
|
354
|
+
if (n === 1)
|
|
355
|
+
return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return a base report." } }];
|
|
356
|
+
if (n === 2)
|
|
357
|
+
return [
|
|
358
|
+
{
|
|
359
|
+
name: "freeflow_return",
|
|
360
|
+
args: { operation: "submit", report: "FALLBACK_BASE_REPORT_19", outcome: "completed" },
|
|
361
|
+
},
|
|
362
|
+
];
|
|
363
|
+
if (n === 3) {
|
|
364
|
+
const state = replay(manager.getBranch());
|
|
365
|
+
baseReportId = state.assignments.get(state.assignmentId).returnHandoffId;
|
|
366
|
+
return [
|
|
367
|
+
{
|
|
368
|
+
name: "freeflow_unit",
|
|
369
|
+
args: { operation: "recover", request: "Report unavailable evidence honestly." },
|
|
370
|
+
},
|
|
371
|
+
];
|
|
372
|
+
}
|
|
373
|
+
if (n === 4)
|
|
374
|
+
return [
|
|
375
|
+
{ name: "freeflow_project", args: { operation: "add", refs: ["ctx:missing"] } },
|
|
376
|
+
{
|
|
377
|
+
name: "freeflow_return",
|
|
378
|
+
args: { operation: "supplement", report: "FALLBACK_SUPPLEMENT_23", outcome: "partial" },
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
assert.equal(body.model, "gpt-4o");
|
|
382
|
+
const wire = JSON.stringify(body);
|
|
383
|
+
assert.match(wire, /FALLBACK_BASE_REPORT_19/);
|
|
384
|
+
assert.match(wire, /FALLBACK_SUPPLEMENT_23/);
|
|
385
|
+
assert.match(wire, /source_unavailable|Exact source is unavailable/);
|
|
386
|
+
const state = replay(manager.getBranch());
|
|
387
|
+
assert.equal(state.assessment.handoffId, baseReportId);
|
|
388
|
+
assert.equal(state.assessment.view, "suspended");
|
|
389
|
+
assert.equal(state.assessment.suspensionReason, "delivery-gap");
|
|
390
|
+
assert.equal(state.recoveryId, undefined);
|
|
391
|
+
assert.equal([...state.recoveries.values()].at(-1).state, "completed");
|
|
392
|
+
return [];
|
|
393
|
+
});
|
|
394
|
+
},
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
test(
|
|
398
|
+
"recovery cancellation preserves the report and restores the same assessment explicitly",
|
|
399
|
+
{ timeout: 30000 },
|
|
400
|
+
async () => {
|
|
401
|
+
let baseReportId;
|
|
402
|
+
await fixture(
|
|
403
|
+
(n, _body, manager) => {
|
|
404
|
+
if (n === 1)
|
|
405
|
+
return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return a base report." } }];
|
|
406
|
+
if (n === 2)
|
|
407
|
+
return [
|
|
408
|
+
{ name: "freeflow_return", args: { operation: "submit", report: "CANCEL_BASE_31", outcome: "completed" } },
|
|
409
|
+
];
|
|
410
|
+
if (n === 3) {
|
|
411
|
+
const state = replay(manager.getBranch());
|
|
412
|
+
baseReportId = state.assignments.get(state.assignmentId).returnHandoffId;
|
|
413
|
+
return [{ name: "freeflow_unit", args: { operation: "recover", request: "Wait for cancellation." } }];
|
|
414
|
+
}
|
|
415
|
+
if (n === 4) return [];
|
|
416
|
+
if (n === 5)
|
|
417
|
+
return [{ name: "freeflow_unit", args: { operation: "cancel-recovery", reason: "No longer needed." } }];
|
|
418
|
+
if (n === 6) {
|
|
419
|
+
const state = replay(manager.getBranch());
|
|
420
|
+
assert.equal(state.recoveryId, undefined);
|
|
421
|
+
assert.equal([...state.recoveries.values()].at(-1).state, "cancelled");
|
|
422
|
+
assert.equal(state.handoffs.get(baseReportId).text, "CANCEL_BASE_31");
|
|
423
|
+
assert.equal(state.assessment.handoffId, baseReportId);
|
|
424
|
+
assert.equal(state.assessment.view, "suspended");
|
|
425
|
+
return [{ name: "freeflow_unit", args: { operation: "assess" } }];
|
|
426
|
+
}
|
|
427
|
+
assert.equal(replay(manager.getBranch()).assessment.view, "active");
|
|
428
|
+
return [];
|
|
429
|
+
},
|
|
430
|
+
true,
|
|
431
|
+
async ({ session }) => {
|
|
432
|
+
await session.prompt("Cancel the evidence recovery without changing the report.");
|
|
433
|
+
await session.waitForIdle();
|
|
434
|
+
},
|
|
435
|
+
);
|
|
436
|
+
},
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
test(
|
|
440
|
+
"fresh user recovery uses its own input basis without a changed-direction warning",
|
|
441
|
+
{ timeout: 30000 },
|
|
442
|
+
async () => {
|
|
443
|
+
await fixture(
|
|
444
|
+
(n, body) => {
|
|
445
|
+
if (n === 1) return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return once." } }];
|
|
446
|
+
if (n === 2)
|
|
447
|
+
return [{ name: "freeflow_return", args: { operation: "submit", report: "base", outcome: "completed" } }];
|
|
448
|
+
if (n === 3) return [];
|
|
449
|
+
if (n === 4)
|
|
450
|
+
return [
|
|
451
|
+
{
|
|
452
|
+
name: "freeflow_unit",
|
|
453
|
+
args: { operation: "recover", request: "Recover evidence for this assessment." },
|
|
454
|
+
},
|
|
455
|
+
];
|
|
456
|
+
if (n === 5) {
|
|
457
|
+
const wire = JSON.stringify(body);
|
|
458
|
+
assert.equal(body.model, "gpt-4.1-mini");
|
|
459
|
+
assert.match(wire, /Recovery request: Recover evidence for this assessment\./);
|
|
460
|
+
assert.doesNotMatch(wire, /Current input differs from the active Executor responsibility/);
|
|
461
|
+
return [
|
|
462
|
+
{
|
|
463
|
+
name: "freeflow_return",
|
|
464
|
+
args: { operation: "supplement", report: "fresh-basis supplement", outcome: "completed" },
|
|
465
|
+
},
|
|
466
|
+
];
|
|
467
|
+
}
|
|
468
|
+
return [];
|
|
469
|
+
},
|
|
470
|
+
true,
|
|
471
|
+
async ({ session }) => {
|
|
472
|
+
await session.prompt("Please recover omitted evidence now.");
|
|
473
|
+
await session.waitForIdle();
|
|
474
|
+
},
|
|
475
|
+
);
|
|
476
|
+
},
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
test("assessment waits for recovery cancellation before resuming", { timeout: 30000 }, async () => {
|
|
480
|
+
let selection;
|
|
481
|
+
await fixture(
|
|
482
|
+
(n, body, manager) => {
|
|
483
|
+
if (n === 1) return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return once." } }];
|
|
484
|
+
if (n === 2)
|
|
485
|
+
return [{ name: "freeflow_return", args: { operation: "submit", report: "base", outcome: "completed" } }];
|
|
486
|
+
if (n === 3) return [{ name: "freeflow_unit", args: { operation: "recover", request: "Recover later." } }];
|
|
487
|
+
if (n === 4) {
|
|
488
|
+
const state = replay(manager.getBranch());
|
|
489
|
+
selection = structuredClone(state.selections.get(state.assignmentId));
|
|
490
|
+
return [];
|
|
491
|
+
}
|
|
492
|
+
if (n === 5) {
|
|
493
|
+
const wire = JSON.stringify(body);
|
|
494
|
+
assert.match(
|
|
495
|
+
wire,
|
|
496
|
+
/Finish and deliver the recovery supplement or cancel recovery before using freeflow_unit assess/,
|
|
497
|
+
);
|
|
498
|
+
assert.doesNotMatch(wire, /Use freeflow_unit assess to restore the assessment/);
|
|
499
|
+
return [{ name: "freeflow_unit", args: { operation: "assess" } }];
|
|
500
|
+
}
|
|
501
|
+
if (n === 6) {
|
|
502
|
+
const state = replay(manager.getBranch());
|
|
503
|
+
const result = manager
|
|
504
|
+
.getBranch()
|
|
505
|
+
.filter((entry) => entry.message?.toolName === "freeflow_unit")
|
|
506
|
+
.at(-1).message;
|
|
507
|
+
assert.equal(result.details.code, "recovery_outstanding");
|
|
508
|
+
assert.equal(state.assessment.view, "suspended");
|
|
509
|
+
assert.equal(state.assessment.suspensionReason, "user-attention");
|
|
510
|
+
assert.equal(state.recoveries.get(state.recoveryId).state, "reading");
|
|
511
|
+
assert.deepEqual(state.selections.get(state.assignmentId), selection);
|
|
512
|
+
return [{ name: "freeflow_unit", args: { operation: "cancel-recovery", reason: "No longer needed." } }];
|
|
513
|
+
}
|
|
514
|
+
if (n === 7) {
|
|
515
|
+
const state = replay(manager.getBranch());
|
|
516
|
+
const wire = JSON.stringify(body);
|
|
517
|
+
assert.equal(state.recoveryId, undefined);
|
|
518
|
+
assert.equal(state.assessment.view, "suspended");
|
|
519
|
+
assert.match(wire, /Use freeflow_unit assess to restore the assessment/);
|
|
520
|
+
assert.doesNotMatch(
|
|
521
|
+
JSON.stringify(
|
|
522
|
+
body.input.filter((item) => JSON.stringify(item).includes("# Cognitive Routing Runtime State")).at(-1),
|
|
523
|
+
),
|
|
524
|
+
/Finish and deliver the recovery supplement or cancel recovery/,
|
|
525
|
+
);
|
|
526
|
+
return [{ name: "freeflow_unit", args: { operation: "assess" } }];
|
|
527
|
+
}
|
|
528
|
+
const state = replay(manager.getBranch());
|
|
529
|
+
assert.equal(state.assessment.view, "active");
|
|
530
|
+
assert.equal(state.recoveryId, undefined);
|
|
531
|
+
return [];
|
|
532
|
+
},
|
|
533
|
+
true,
|
|
534
|
+
async ({ session }) => {
|
|
535
|
+
await session.prompt("Assess before the open recovery is settled.");
|
|
536
|
+
await session.waitForIdle();
|
|
537
|
+
},
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("cancelled unit closure atomically abandons attached recovery", { timeout: 30000 }, async () => {
|
|
542
|
+
let baseReportId, selectionSnapshot;
|
|
543
|
+
await fixture(
|
|
544
|
+
(n, _body, manager) => {
|
|
545
|
+
if (n === 1) return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return once." } }];
|
|
546
|
+
if (n === 2)
|
|
547
|
+
return [{ name: "freeflow_return", args: { operation: "submit", report: "close-base", outcome: "completed" } }];
|
|
548
|
+
if (n === 3) {
|
|
549
|
+
const state = replay(manager.getBranch());
|
|
550
|
+
baseReportId = state.assignments.get(state.assignmentId).returnHandoffId;
|
|
551
|
+
return [{ name: "freeflow_unit", args: { operation: "recover", request: "Recover later." } }];
|
|
552
|
+
}
|
|
553
|
+
if (n === 4) return [];
|
|
554
|
+
if (n === 5) {
|
|
555
|
+
selectionSnapshot = structuredClone([...replay(manager.getBranch()).selections]);
|
|
556
|
+
return [
|
|
557
|
+
{
|
|
558
|
+
name: "freeflow_unit",
|
|
559
|
+
args: { operation: "close", outcome: "cancelled", assessment: "Stop unit and recovery." },
|
|
560
|
+
},
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
const state = replay(manager.getBranch());
|
|
564
|
+
const recovery = [...state.recoveries.values()].at(-1);
|
|
565
|
+
assert.equal(state.unitId, undefined);
|
|
566
|
+
assert.equal(state.assignmentId, undefined);
|
|
567
|
+
assert.equal(state.pendingId, undefined);
|
|
568
|
+
assert.equal(state.recoveryId, undefined);
|
|
569
|
+
assert.equal(recovery.state, "cancelled");
|
|
570
|
+
assert.equal(recovery.cancellationReason, "Stop unit and recovery.");
|
|
571
|
+
assert.equal(state.handoffs.get(baseReportId).text, "close-base");
|
|
572
|
+
assert.deepEqual([...state.selections], selectionSnapshot);
|
|
573
|
+
return [];
|
|
574
|
+
},
|
|
575
|
+
true,
|
|
576
|
+
async ({ session }) => {
|
|
577
|
+
await session.prompt("Cancel the unit and its recovery.");
|
|
578
|
+
await session.waitForIdle();
|
|
579
|
+
},
|
|
580
|
+
);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
test("projection-off recovery can read packaged instructions and resume assessment", { timeout: 30000 }, async () => {
|
|
584
|
+
const packageRoot = process.cwd();
|
|
585
|
+
await fixture(
|
|
586
|
+
(n, body, manager) => {
|
|
587
|
+
if (n === 1)
|
|
588
|
+
return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return a base report." } }];
|
|
589
|
+
if (n === 2)
|
|
590
|
+
return [
|
|
591
|
+
{
|
|
592
|
+
name: "freeflow_return",
|
|
593
|
+
args: { operation: "submit", report: "NO_PROJECTION_BASE_43", outcome: "completed" },
|
|
594
|
+
},
|
|
595
|
+
];
|
|
596
|
+
if (n === 3)
|
|
597
|
+
return [
|
|
598
|
+
{ name: "freeflow_unit", args: { operation: "recover", request: "Read the packaged Workflow method." } },
|
|
599
|
+
];
|
|
600
|
+
if (n === 4)
|
|
601
|
+
return [{ name: "read", args: { path: join(packageRoot, "skills/workflow/SKILL.md"), offset: 1, limit: 8 } }];
|
|
602
|
+
if (n === 5) {
|
|
603
|
+
const read = manager
|
|
604
|
+
.getBranch()
|
|
605
|
+
.filter((e) => e.message?.toolName === "read")
|
|
606
|
+
.at(-1);
|
|
607
|
+
assert.equal(read.message.isError, false);
|
|
608
|
+
assert.match(JSON.stringify(read.message), /name: workflow|# Workflow/);
|
|
609
|
+
return [
|
|
610
|
+
{
|
|
611
|
+
name: "freeflow_return",
|
|
612
|
+
args: { operation: "supplement", report: "PACKAGED_METHOD_READ_47", outcome: "completed" },
|
|
613
|
+
},
|
|
614
|
+
];
|
|
615
|
+
}
|
|
616
|
+
assert.equal(body.model, "gpt-4o");
|
|
617
|
+
const state = replay(manager.getBranch());
|
|
618
|
+
assert.equal(state.assessment.view, "active");
|
|
619
|
+
assert.equal(state.recoveryId, undefined);
|
|
620
|
+
assert.match(JSON.stringify(body), /NO_PROJECTION_BASE_43/);
|
|
621
|
+
assert.match(JSON.stringify(body), /PACKAGED_METHOD_READ_47/);
|
|
622
|
+
return [];
|
|
623
|
+
},
|
|
624
|
+
false,
|
|
625
|
+
undefined,
|
|
626
|
+
true,
|
|
627
|
+
);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test(
|
|
631
|
+
"new input during recovery blocks further reads and a supplement preserves user attention",
|
|
632
|
+
{ timeout: 30000 },
|
|
633
|
+
async () => {
|
|
634
|
+
let active, assignmentId, baseReportId;
|
|
635
|
+
await fixture(
|
|
636
|
+
async (n, body, manager) => {
|
|
637
|
+
if (n === 1)
|
|
638
|
+
return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return a base report." } }];
|
|
639
|
+
if (n === 2)
|
|
640
|
+
return [
|
|
641
|
+
{
|
|
642
|
+
name: "freeflow_return",
|
|
643
|
+
args: { operation: "submit", report: "INTERRUPTED_RECOVERY_BASE_59", outcome: "completed" },
|
|
644
|
+
},
|
|
645
|
+
];
|
|
646
|
+
if (n === 3) {
|
|
647
|
+
const state = replay(manager.getBranch());
|
|
648
|
+
assignmentId = state.assignmentId;
|
|
649
|
+
baseReportId = state.assignments.get(assignmentId).returnHandoffId;
|
|
650
|
+
return [
|
|
651
|
+
{
|
|
652
|
+
name: "freeflow_unit",
|
|
653
|
+
args: { operation: "recover", request: "Read only recovery.txt.", paths: ["recovery.txt"] },
|
|
654
|
+
},
|
|
655
|
+
];
|
|
656
|
+
}
|
|
657
|
+
if (n === 4) {
|
|
658
|
+
assert.equal(body.model, "gpt-4.1-mini");
|
|
659
|
+
await active.steer("NEW_RECOVERY_ATTENTION_61");
|
|
660
|
+
return [{ name: "read", args: { path: "recovery.txt" } }];
|
|
661
|
+
}
|
|
662
|
+
if (n === 5) {
|
|
663
|
+
const state = replay(manager.getBranch());
|
|
664
|
+
assert.equal(state.assessment.view, "suspended");
|
|
665
|
+
assert.equal(state.assessment.suspensionReason, "user-attention");
|
|
666
|
+
assert.match(JSON.stringify(body), /New delivered user input requires Coordinator attention/);
|
|
667
|
+
return [{ name: "read", args: { path: "recovery.txt" } }];
|
|
668
|
+
}
|
|
669
|
+
if (n === 6) {
|
|
670
|
+
const lastRead = manager
|
|
671
|
+
.getBranch()
|
|
672
|
+
.filter((entry) => entry.message?.toolName === "read")
|
|
673
|
+
.at(-1);
|
|
674
|
+
assert.equal(lastRead.message.isError, true, "new input revokes recovery-read permission");
|
|
675
|
+
return [
|
|
676
|
+
{
|
|
677
|
+
name: "freeflow_return",
|
|
678
|
+
args: { operation: "supplement", report: "INTERRUPTED_RECOVERY_SUPPLEMENT_67", outcome: "partial" },
|
|
679
|
+
},
|
|
680
|
+
];
|
|
681
|
+
}
|
|
682
|
+
assert.equal(body.model, "gpt-4o");
|
|
683
|
+
assert.match(JSON.stringify(body), /NEW_RECOVERY_ATTENTION_61/);
|
|
684
|
+
assert.match(JSON.stringify(body), /INTERRUPTED_RECOVERY_SUPPLEMENT_67/);
|
|
685
|
+
const state = replay(manager.getBranch());
|
|
686
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
687
|
+
assert.equal(state.assignments.get(assignmentId).state, "returned");
|
|
688
|
+
assert.equal(state.assignments.get(assignmentId).returnHandoffId, baseReportId);
|
|
689
|
+
assert.equal(state.handoffs.get(baseReportId).text, "INTERRUPTED_RECOVERY_BASE_59");
|
|
690
|
+
assert.equal(state.assessment.handoffId, baseReportId);
|
|
691
|
+
assert.equal(state.assessment.view, "suspended");
|
|
692
|
+
assert.equal(state.assessment.suspensionReason, "user-attention");
|
|
693
|
+
assert.equal(state.recoveryId, undefined);
|
|
694
|
+
assert.equal([...state.recoveries.values()].at(-1).state, "completed");
|
|
695
|
+
return [];
|
|
696
|
+
},
|
|
697
|
+
true,
|
|
698
|
+
undefined,
|
|
699
|
+
true,
|
|
700
|
+
{ onSession: (session) => (active = session) },
|
|
701
|
+
);
|
|
702
|
+
},
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
test("manual hold and navigation preserve an unchanged recovery for explicit resume", { timeout: 30000 }, async () => {
|
|
706
|
+
let assignmentId, baseReportId, recoveryId, requestHandoffId;
|
|
707
|
+
await fixture(
|
|
708
|
+
(n, body, manager) => {
|
|
709
|
+
if (n === 1)
|
|
710
|
+
return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return a base report." } }];
|
|
711
|
+
if (n === 2)
|
|
712
|
+
return [
|
|
713
|
+
{
|
|
714
|
+
name: "freeflow_return",
|
|
715
|
+
args: { operation: "submit", report: "MANUAL_RECOVERY_BASE_71", outcome: "completed" },
|
|
716
|
+
},
|
|
717
|
+
];
|
|
718
|
+
if (n === 3) {
|
|
719
|
+
const state = replay(manager.getBranch());
|
|
720
|
+
assignmentId = state.assignmentId;
|
|
721
|
+
baseReportId = state.assignments.get(assignmentId).returnHandoffId;
|
|
722
|
+
return [{ name: "freeflow_unit", args: { operation: "recover", request: "Resume this lookup unchanged." } }];
|
|
723
|
+
}
|
|
724
|
+
if (n === 4) {
|
|
725
|
+
const state = replay(manager.getBranch());
|
|
726
|
+
recoveryId = state.recoveryId;
|
|
727
|
+
requestHandoffId = state.recoveries.get(recoveryId).requestHandoffId;
|
|
728
|
+
assert.equal(state.recoveries.get(recoveryId).state, "reading");
|
|
729
|
+
return [];
|
|
730
|
+
}
|
|
731
|
+
if (n === 5) {
|
|
732
|
+
const state = replay(manager.getBranch());
|
|
733
|
+
assert.equal(body.model, "gpt-4.1-mini");
|
|
734
|
+
assert.equal(state.recoveryId, recoveryId);
|
|
735
|
+
assert.equal(state.recoveries.get(recoveryId).state, "reading");
|
|
736
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
737
|
+
assert.equal(state.assignments.get(assignmentId).state, "returned");
|
|
738
|
+
return [
|
|
739
|
+
{
|
|
740
|
+
name: "freeflow_return",
|
|
741
|
+
args: { operation: "supplement", report: "MANUAL_RECOVERY_SUPPLEMENT_73", outcome: "completed" },
|
|
742
|
+
},
|
|
743
|
+
];
|
|
744
|
+
}
|
|
745
|
+
const state = replay(manager.getBranch());
|
|
746
|
+
assert.equal(body.model, "gpt-4o");
|
|
747
|
+
assert.equal(state.control, "automatic");
|
|
748
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
749
|
+
assert.equal(state.assignments.get(assignmentId).returnHandoffId, baseReportId);
|
|
750
|
+
assert.equal(state.handoffs.get(baseReportId).text, "MANUAL_RECOVERY_BASE_71");
|
|
751
|
+
assert.equal(state.assessment.handoffId, baseReportId);
|
|
752
|
+
assert.equal(state.assessment.view, "active");
|
|
753
|
+
assert.equal(state.recoveries.get(recoveryId).state, "completed");
|
|
754
|
+
assert.equal(state.recoveries.size, 1);
|
|
755
|
+
assert.equal(state.assignments.size, 1);
|
|
756
|
+
return [];
|
|
757
|
+
},
|
|
758
|
+
true,
|
|
759
|
+
async ({ session, manager, requests }) => {
|
|
760
|
+
const reads = manager.getEntries().filter((entry) => entry.message?.toolName === "read").length;
|
|
761
|
+
await session.prompt("/freeflow profile executor");
|
|
762
|
+
const configured = manager
|
|
763
|
+
.getEntries()
|
|
764
|
+
.find(
|
|
765
|
+
(entry) =>
|
|
766
|
+
entry.data?.data?.type === "handoff-state" &&
|
|
767
|
+
entry.data.data.handoffId === requestHandoffId &&
|
|
768
|
+
entry.data.data.state === "configured",
|
|
769
|
+
);
|
|
770
|
+
await session.navigateTree(configured.id, { summarize: false });
|
|
771
|
+
const before = requests.length;
|
|
772
|
+
await session.prompt("/freeflow resume");
|
|
773
|
+
assert.equal(requests.length, before, "resume cannot release a manual hold");
|
|
774
|
+
let state = replay(manager.getBranch());
|
|
775
|
+
assert.equal(state.control, "manual");
|
|
776
|
+
assert.equal(state.profile, "executor");
|
|
777
|
+
assert.equal(state.recoveryId, recoveryId);
|
|
778
|
+
assert.equal(state.recoveries.get(recoveryId).state, "reading");
|
|
779
|
+
await session.prompt("/freeflow profile auto");
|
|
780
|
+
state = replay(manager.getBranch());
|
|
781
|
+
assert.equal(state.control, "automatic");
|
|
782
|
+
assert.equal(state.profile, "coordinator");
|
|
783
|
+
await session.prompt("/freeflow resume");
|
|
784
|
+
await session.waitForIdle();
|
|
785
|
+
assert.equal(requests.length, before + 2);
|
|
786
|
+
assert.equal(
|
|
787
|
+
manager.getEntries().filter((entry) => entry.message?.toolName === "read").length,
|
|
788
|
+
reads,
|
|
789
|
+
"navigation and resume do not replay task reads",
|
|
790
|
+
);
|
|
791
|
+
},
|
|
792
|
+
);
|
|
793
|
+
});
|
|
5
794
|
|
|
6
795
|
test(
|
|
7
796
|
"resume after attention restores saved-return correction without reopening task work",
|
|
@@ -197,9 +986,16 @@ test(
|
|
|
197
986
|
if (n === 3) {
|
|
198
987
|
assert.equal(body.model, "gpt-4.1-mini");
|
|
199
988
|
assert.match(JSON.stringify(body), /New delivered user input requires Coordinator attention/);
|
|
200
|
-
assert.ok(
|
|
989
|
+
assert.ok(
|
|
990
|
+
body.tools.some((t) => t.name === "freeflow_delegate"),
|
|
991
|
+
"stable catalog retains definitions; runtime gates still deny wrong-role effects",
|
|
992
|
+
);
|
|
201
993
|
assert.deepEqual(body.tools.find((t) => t.name === "freeflow_unit").parameters.properties.operation.enum, [
|
|
202
994
|
"inspect",
|
|
995
|
+
"assess",
|
|
996
|
+
"recover",
|
|
997
|
+
"cancel-recovery",
|
|
998
|
+
"close",
|
|
203
999
|
]);
|
|
204
1000
|
return [{ name: "read", args: { path: "unselected.txt" } }];
|
|
205
1001
|
}
|
|
@@ -219,7 +1015,9 @@ test(
|
|
|
219
1015
|
];
|
|
220
1016
|
}
|
|
221
1017
|
assert.equal(body.model, "gpt-4o");
|
|
222
|
-
assert.ok(
|
|
1018
|
+
assert.ok(
|
|
1019
|
+
body.tools.some((t) => t.name === "freeflow_return") && body.tools.some((t) => t.name === "freeflow_project"),
|
|
1020
|
+
);
|
|
223
1021
|
assert.match(JSON.stringify(body), /Stop further reads/);
|
|
224
1022
|
return [];
|
|
225
1023
|
},
|