@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.
- 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 +153 -128
- package/capabilities/cognitive-routing/SKILL.md +126 -274
- package/command-surface.json +7 -1
- package/gemini-extension.json +1 -1
- package/package.json +4 -4
- package/pi-extension/dist/cognitive-routing-v2/budget.js +42 -0
- package/pi-extension/dist/cognitive-routing-v2/config.js +136 -0
- package/pi-extension/dist/cognitive-routing-v2/projection.js +330 -0
- package/pi-extension/dist/cognitive-routing-v2/render.js +252 -0
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +2181 -0
- package/pi-extension/dist/cognitive-routing-v2/schemas.js +173 -0
- package/pi-extension/dist/cognitive-routing-v2/state.js +828 -0
- package/pi-extension/dist/cognitive-routing-v2/tools.js +60 -0
- package/pi-extension/dist/cognitive-routing-v2/types.js +48 -0
- package/pi-extension/dist/context-virtualization/runtime.js +29 -7
- package/pi-extension/dist/freeflow-context/tool.js +12 -6
- package/pi-extension/dist/index.js +302 -626
- 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 +85 -16
- package/pi-extension/dist/session-sources/events.js +217 -0
- package/pi-extension/dist/session-sources/provenance.js +32 -0
- package/pi-extension/dist/session-sources/read-only-session.js +148 -0
- package/pi-extension/dist/session-sources/sources.js +277 -0
- package/pi-extension/dist/settings/settings-ui.js +274 -147
- package/pi-extension/src/cognitive-routing-v2/budget.ts +42 -0
- package/pi-extension/src/cognitive-routing-v2/config.ts +173 -0
- package/pi-extension/src/cognitive-routing-v2/projection.ts +375 -0
- package/pi-extension/src/cognitive-routing-v2/render.ts +277 -0
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +2241 -0
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +180 -0
- package/pi-extension/src/cognitive-routing-v2/state.ts +839 -0
- package/pi-extension/src/cognitive-routing-v2/tools.ts +62 -0
- package/pi-extension/src/cognitive-routing-v2/types.ts +254 -0
- package/pi-extension/src/context-virtualization/runtime.ts +30 -8
- package/pi-extension/src/freeflow-context/tool.ts +12 -6
- package/pi-extension/src/index.ts +308 -675
- 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 +100 -17
- package/pi-extension/src/session-sources/events.ts +230 -0
- package/pi-extension/src/session-sources/provenance.ts +39 -0
- package/pi-extension/src/session-sources/read-only-session.ts +170 -0
- package/pi-extension/src/session-sources/sources.ts +334 -0
- package/pi-extension/src/settings/settings-ui.ts +311 -158
- 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 +577 -0
- package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +183 -0
- package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +474 -0
- package/pi-extension/tests/cognitive-routing-v2/performance.test.js +87 -0
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +300 -0
- package/pi-extension/tests/cognitive-routing-v2/projection.test.js +241 -0
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +1034 -0
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +244 -0
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +423 -0
- package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +521 -0
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +586 -0
- package/pi-extension/tests/context-virtualization/extension.test.js +33 -7
- package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
- package/pi-extension/tests/fixtures/routing-native.js +191 -0
- package/pi-extension/tests/integration/mode-free-core.test.js +6 -4
- package/pi-extension/tests/integration/pi-extension.test.js +655 -59
- package/pi-extension/tests/integration/prompt-architecture.test.js +127 -30
- package/pi-extension/tests/integration/unaffected-regressions.test.js +78 -11
- 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 -5
- package/runtime/prompts/core.md +51 -45
- package/runtime/prompts/interaction-contract.md +5 -5
- package/skills/action-selection/SKILL.md +57 -97
- package/skills/design-for-depth/SKILL.md +58 -193
- package/skills/design-for-depth/references/implementation-decisions.md +76 -0
- package/skills/diagnose-failure/SKILL.md +64 -196
- package/skills/discuss/SKILL.md +64 -123
- package/skills/discuss/references/discussion-continuity.md +7 -3
- package/skills/execute-work/SKILL.md +69 -88
- package/skills/execute-work/references/execute-work-edges.md +9 -3
- package/skills/handoff/SKILL.md +44 -54
- package/skills/handoff/references/handoff-templates.md +15 -6
- package/skills/review-artifact/SKILL.md +80 -127
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
- package/skills/review-work/SKILL.md +65 -115
- package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
- package/skills/simplify-code/SKILL.md +47 -100
- package/skills/track-work/SKILL.md +128 -119
- package/skills/track-work/references/working-record-format.md +14 -2
- package/skills/verify-work/SKILL.md +58 -62
- package/skills/verify-work/references/integration-evidence.md +4 -2
- package/skills/verify-work/references/test-design.md +89 -0
- package/skills/workflow/SKILL.md +73 -119
- package/skills/write-plan/SKILL.md +46 -77
- package/skills/write-plan/references/plan-shapes.md +5 -5
- package/skills/write-spec/SKILL.md +51 -63
- package/skills/write-spec/references/artifact-standards.md +2 -2
- package/skills/write-spec/references/decision-records.md +3 -2
- package/skills/write-spec/references/spec-shapes.md +15 -0
- package/pi-extension/dist/cognitive-routing/commands.js +0 -96
- package/pi-extension/dist/cognitive-routing/config.js +0 -232
- package/pi-extension/dist/cognitive-routing/controller.js +0 -1292
- package/pi-extension/dist/cognitive-routing/history.js +0 -451
- package/pi-extension/dist/cognitive-routing/host.js +0 -188
- package/pi-extension/dist/cognitive-routing/pi-routing-session.js +0 -414
- package/pi-extension/dist/cognitive-routing/pi-session-control.js +0 -320
- package/pi-extension/dist/cognitive-routing/runtime.js +0 -43
- package/pi-extension/dist/cognitive-routing/tool.js +0 -212
- package/pi-extension/dist/cognitive-routing/types.js +0 -6
- package/pi-extension/src/cognitive-routing/commands.ts +0 -129
- package/pi-extension/src/cognitive-routing/config.ts +0 -303
- package/pi-extension/src/cognitive-routing/controller.ts +0 -1628
- package/pi-extension/src/cognitive-routing/history.ts +0 -650
- package/pi-extension/src/cognitive-routing/host.ts +0 -266
- package/pi-extension/src/cognitive-routing/pi-routing-session.ts +0 -538
- package/pi-extension/src/cognitive-routing/pi-session-control.ts +0 -444
- package/pi-extension/src/cognitive-routing/runtime.ts +0 -57
- package/pi-extension/src/cognitive-routing/tool.ts +0 -273
- package/pi-extension/src/cognitive-routing/types.ts +0 -89
- package/pi-extension/tests/cognitive-routing/commands.test.js +0 -134
- package/pi-extension/tests/cognitive-routing/controller.test.js +0 -1128
- package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +0 -1142
- package/pi-extension/tests/cognitive-routing/history.test.js +0 -274
- package/pi-extension/tests/cognitive-routing/host-identity.test.js +0 -63
- package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +0 -434
- package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +0 -174
- package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +0 -91
- package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +0 -279
- package/pi-extension/tests/cognitive-routing/tool.test.js +0 -230
- package/skills/tdd/SKILL.md +0 -143
- package/skills/tdd/references/test-design.md +0 -96
- /package/pi-extension/tests/{cognitive-routing/host-fixture.js → fixtures/pi-host.js} +0 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { resolveCognitiveRoutingState } from "../../dist/cognitive-routing-v2/config.js";
|
|
6
|
+
import { RoutingRuntime } from "../../dist/cognitive-routing-v2/runtime.js";
|
|
7
|
+
import { replay } from "../../dist/cognitive-routing-v2/state.js";
|
|
8
|
+
import { fixture } from "../fixtures/routing-native.js";
|
|
9
|
+
|
|
10
|
+
const coordinator = { provider: "openai", model: "gpt-4o", thinking: "off" };
|
|
11
|
+
const worker = { provider: "openai", model: "gpt-4.1-mini", thinking: "off" };
|
|
12
|
+
const alternateExecutor = { provider: "openai", model: "gpt-4.1", thinking: "off" };
|
|
13
|
+
const bothConfig = {
|
|
14
|
+
enabled: true,
|
|
15
|
+
delegation: "both",
|
|
16
|
+
projection: true,
|
|
17
|
+
profiles: { coordinator, helper: worker, executor: worker },
|
|
18
|
+
};
|
|
19
|
+
const helperConfig = {
|
|
20
|
+
enabled: true,
|
|
21
|
+
delegation: "helper",
|
|
22
|
+
projection: true,
|
|
23
|
+
profiles: { coordinator, helper: worker },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function registry(available = ["coordinator", "helper", "executor"]) {
|
|
27
|
+
const models = {
|
|
28
|
+
coordinator: { provider: "test", id: "coordinator", reasoning: true },
|
|
29
|
+
helper: { provider: "test", id: "helper", reasoning: true },
|
|
30
|
+
executor: { provider: "test", id: "executor", reasoning: true },
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
find(_provider, id) {
|
|
34
|
+
return available.includes(id) ? models[id] : undefined;
|
|
35
|
+
},
|
|
36
|
+
async getApiKeyAndHeaders() {
|
|
37
|
+
return { ok: true };
|
|
38
|
+
},
|
|
39
|
+
clampThinkingLevel(_model, level) {
|
|
40
|
+
return level;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const profile = (model) => ({ provider: "test", model, thinking: "off" });
|
|
46
|
+
|
|
47
|
+
test("delegation modes validate only enabled worker presets and preserve the executor default", async () => {
|
|
48
|
+
const legacy = await resolveCognitiveRoutingState(
|
|
49
|
+
{
|
|
50
|
+
cognitiveRouting: {
|
|
51
|
+
enabled: true,
|
|
52
|
+
profiles: {
|
|
53
|
+
coordinator: profile("coordinator"),
|
|
54
|
+
executor: profile("executor"),
|
|
55
|
+
helper: profile("missing-unused-helper"),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{},
|
|
60
|
+
{ modelRegistry: registry() },
|
|
61
|
+
);
|
|
62
|
+
assert.equal(legacy.delegation, "executor");
|
|
63
|
+
assert.equal(legacy.delegationSource, "default");
|
|
64
|
+
assert.equal(legacy.effective, true, "an unavailable unused Helper preset does not block executor-only mode");
|
|
65
|
+
|
|
66
|
+
const helperOnly = await resolveCognitiveRoutingState(
|
|
67
|
+
{
|
|
68
|
+
cognitiveRouting: {
|
|
69
|
+
enabled: true,
|
|
70
|
+
delegation: "helper",
|
|
71
|
+
profiles: { coordinator: profile("coordinator"), helper: profile("helper") },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{},
|
|
75
|
+
{ modelRegistry: registry() },
|
|
76
|
+
);
|
|
77
|
+
assert.equal(helperOnly.effective, true, "helper-only does not require Executor");
|
|
78
|
+
|
|
79
|
+
const missingBoth = await resolveCognitiveRoutingState(
|
|
80
|
+
{
|
|
81
|
+
cognitiveRouting: {
|
|
82
|
+
enabled: true,
|
|
83
|
+
delegation: "both",
|
|
84
|
+
profiles: { coordinator: profile("coordinator"), helper: profile("helper") },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{},
|
|
88
|
+
{ modelRegistry: registry() },
|
|
89
|
+
);
|
|
90
|
+
assert.equal(missingBoth.effective, false);
|
|
91
|
+
assert.equal(missingBoth.blockingReason.code, "profile_missing");
|
|
92
|
+
assert.match(missingBoth.blockingReason.message, /executor/i);
|
|
93
|
+
|
|
94
|
+
const personalMode = await resolveCognitiveRoutingState(
|
|
95
|
+
{
|
|
96
|
+
cognitiveRouting: {
|
|
97
|
+
enabled: true,
|
|
98
|
+
delegation: "executor",
|
|
99
|
+
profiles: { coordinator: profile("coordinator"), executor: profile("executor") },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
cognitiveRouting: {
|
|
104
|
+
delegation: "helper",
|
|
105
|
+
profiles: { helper: profile("helper") },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{ modelRegistry: registry() },
|
|
109
|
+
);
|
|
110
|
+
assert.equal(personalMode.effective, true);
|
|
111
|
+
assert.equal(personalMode.delegation, "helper");
|
|
112
|
+
assert.equal(personalMode.delegationSource, "personal");
|
|
113
|
+
|
|
114
|
+
const malformedUnused = await resolveCognitiveRoutingState(
|
|
115
|
+
{
|
|
116
|
+
cognitiveRouting: {
|
|
117
|
+
enabled: true,
|
|
118
|
+
profiles: {
|
|
119
|
+
coordinator: profile("coordinator"),
|
|
120
|
+
executor: profile("executor"),
|
|
121
|
+
helper: { provider: "test", model: "helper" },
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{},
|
|
126
|
+
{ modelRegistry: registry() },
|
|
127
|
+
);
|
|
128
|
+
assert.equal(malformedUnused.configValid, false, "malformed unused presets remain invalid configuration");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("a duplicate delegation operation cannot change its recorded worker", { timeout: 30000 }, async () => {
|
|
132
|
+
const originalInvoke = RoutingRuntime.prototype.invoke;
|
|
133
|
+
let checked = false;
|
|
134
|
+
RoutingRuntime.prototype.invoke = async function (name, id, input, signal, ctx) {
|
|
135
|
+
const result = await originalInvoke.call(this, name, id, input, signal, ctx);
|
|
136
|
+
if (!checked && name === "freeflow_delegate" && input.worker === "helper") {
|
|
137
|
+
checked = true;
|
|
138
|
+
this.receipts.clear();
|
|
139
|
+
const conflict = await originalInvoke.call(this, name, id, { ...input, worker: "executor" }, signal, ctx);
|
|
140
|
+
assert.equal(conflict.details.code, "operation_conflict");
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
const result = await fixture(
|
|
146
|
+
(n) =>
|
|
147
|
+
n === 1
|
|
148
|
+
? [
|
|
149
|
+
{
|
|
150
|
+
name: "freeflow_delegate",
|
|
151
|
+
args: { operation: "assign", worker: "helper", contract: "Keep the accepted Helper identity." },
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
: n === 2
|
|
155
|
+
? [
|
|
156
|
+
{
|
|
157
|
+
name: "freeflow_return",
|
|
158
|
+
args: { operation: "submit", report: "Helper identity retained.", outcome: "completed" },
|
|
159
|
+
},
|
|
160
|
+
]
|
|
161
|
+
: n === 3
|
|
162
|
+
? [
|
|
163
|
+
{
|
|
164
|
+
name: "freeflow_unit",
|
|
165
|
+
args: { operation: "close", outcome: "accepted", assessment: "Worker identity is stable." },
|
|
166
|
+
},
|
|
167
|
+
]
|
|
168
|
+
: [],
|
|
169
|
+
true,
|
|
170
|
+
undefined,
|
|
171
|
+
true,
|
|
172
|
+
{ cognitiveRouting: bothConfig, maxRequests: 5 },
|
|
173
|
+
);
|
|
174
|
+
assert.equal(checked, true);
|
|
175
|
+
assert.equal([...result.state.handoffs.values()].find((handoff) => handoff.kind === "delegate").to, "helper");
|
|
176
|
+
} finally {
|
|
177
|
+
RoutingRuntime.prototype.invoke = originalInvoke;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("both mode requires an explicit worker and shares Helper history with Executor", { timeout: 30000 }, async () => {
|
|
182
|
+
let helperReadId;
|
|
183
|
+
const result = await fixture(
|
|
184
|
+
(n, body, manager) => {
|
|
185
|
+
const wire = JSON.stringify(body);
|
|
186
|
+
if (n === 1) return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Ambiguous worker." } }];
|
|
187
|
+
if (n === 2) {
|
|
188
|
+
assert.equal(replay(manager.getBranch()).assignments.size, 0, "ambiguous delegation appends no assignment");
|
|
189
|
+
return [
|
|
190
|
+
{
|
|
191
|
+
name: "freeflow_delegate",
|
|
192
|
+
args: { operation: "assign", worker: "helper", contract: "Read the marker for later implementation." },
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
if (n === 3) {
|
|
197
|
+
assert.equal(body.model, "gpt-4.1-mini");
|
|
198
|
+
assert.equal(replay(manager.getBranch()).profile, "helper");
|
|
199
|
+
return [{ name: "read", args: { path: "unselected.txt" } }];
|
|
200
|
+
}
|
|
201
|
+
if (n === 4) {
|
|
202
|
+
const read = manager
|
|
203
|
+
.getBranch()
|
|
204
|
+
.filter((entry) => entry.message?.toolName === "read")
|
|
205
|
+
.at(-1);
|
|
206
|
+
helperReadId = read.id;
|
|
207
|
+
assert.equal(read.message.isError, false);
|
|
208
|
+
assert.match(wire, /UNSELECTED_PRIVATE_BODY_93/);
|
|
209
|
+
return [
|
|
210
|
+
{
|
|
211
|
+
name: "freeflow_return",
|
|
212
|
+
args: { operation: "submit", report: "Helper context gathered.", outcome: "completed" },
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
}
|
|
216
|
+
if (n === 5) {
|
|
217
|
+
assert.equal(body.model, "gpt-4o");
|
|
218
|
+
assert.equal(replay(manager.getBranch()).profile, "coordinator");
|
|
219
|
+
assert.doesNotMatch(
|
|
220
|
+
wire,
|
|
221
|
+
/UNSELECTED_PRIVATE_BODY_93/,
|
|
222
|
+
"unselected Helper result stays out of Coordinator view",
|
|
223
|
+
);
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
name: "freeflow_delegate",
|
|
227
|
+
args: { operation: "assign", worker: "executor", contract: "Use the gathered marker and report." },
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
if (n === 6) {
|
|
232
|
+
assert.equal(body.model, "gpt-4.1-mini");
|
|
233
|
+
assert.equal(replay(manager.getBranch()).profile, "executor");
|
|
234
|
+
assert.match(wire, /UNSELECTED_PRIVATE_BODY_93/, "Executor inherits ordinary Helper history");
|
|
235
|
+
assert.equal(
|
|
236
|
+
manager.getBranch().filter((entry) => entry.message?.toolName === "read").length,
|
|
237
|
+
1,
|
|
238
|
+
"Executor does not rerun Helper's read",
|
|
239
|
+
);
|
|
240
|
+
return [
|
|
241
|
+
{
|
|
242
|
+
name: "freeflow_project",
|
|
243
|
+
args: { operation: "add", refs: [`ctx:${helperReadId}`] },
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "freeflow_return",
|
|
247
|
+
args: { operation: "submit", report: "Executor used shared context.", outcome: "completed" },
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
if (n === 7) {
|
|
252
|
+
assert.equal(body.model, "gpt-4o");
|
|
253
|
+
assert.match(wire, /UNSELECTED_PRIVATE_BODY_93/);
|
|
254
|
+
assert.match(wire, /producer: helper/);
|
|
255
|
+
return [
|
|
256
|
+
{
|
|
257
|
+
name: "freeflow_unit",
|
|
258
|
+
args: { operation: "close", outcome: "accepted", assessment: "Shared worker context observed." },
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
return [];
|
|
263
|
+
},
|
|
264
|
+
true,
|
|
265
|
+
undefined,
|
|
266
|
+
true,
|
|
267
|
+
{ cognitiveRouting: bothConfig, maxRequests: 9 },
|
|
268
|
+
);
|
|
269
|
+
assert.equal(result.requests.length, 8);
|
|
270
|
+
assert.equal(result.state.units.values().next().value.state, "closed");
|
|
271
|
+
const delegates = [...result.state.handoffs.values()].filter((handoff) => handoff.kind === "delegate");
|
|
272
|
+
assert.deepEqual(
|
|
273
|
+
delegates.map((handoff) => handoff.to),
|
|
274
|
+
["helper", "executor"],
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test(
|
|
279
|
+
"helper-only rejects Executor delegation and recovers through the recorded Helper",
|
|
280
|
+
{ timeout: 30000 },
|
|
281
|
+
async () => {
|
|
282
|
+
let assignmentId, baseReportId;
|
|
283
|
+
const result = await fixture(
|
|
284
|
+
(n, body, manager) => {
|
|
285
|
+
if (n === 1)
|
|
286
|
+
return [
|
|
287
|
+
{
|
|
288
|
+
name: "freeflow_delegate",
|
|
289
|
+
args: { operation: "assign", worker: "executor", contract: "Disabled target." },
|
|
290
|
+
},
|
|
291
|
+
];
|
|
292
|
+
if (n === 2) {
|
|
293
|
+
assert.equal(replay(manager.getBranch()).assignments.size, 0);
|
|
294
|
+
return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Return then recover." } }];
|
|
295
|
+
}
|
|
296
|
+
if (n === 3) {
|
|
297
|
+
const state = replay(manager.getBranch());
|
|
298
|
+
assignmentId = state.assignmentId;
|
|
299
|
+
assert.equal(state.profile, "helper");
|
|
300
|
+
return [
|
|
301
|
+
{
|
|
302
|
+
name: "freeflow_return",
|
|
303
|
+
args: { operation: "submit", report: "Helper base report.", outcome: "completed" },
|
|
304
|
+
},
|
|
305
|
+
];
|
|
306
|
+
}
|
|
307
|
+
if (n === 4) {
|
|
308
|
+
const state = replay(manager.getBranch());
|
|
309
|
+
baseReportId = state.assignments.get(assignmentId).returnHandoffId;
|
|
310
|
+
return [
|
|
311
|
+
{
|
|
312
|
+
name: "freeflow_unit",
|
|
313
|
+
args: { operation: "recover", request: "Read the approved recovery file.", paths: ["recovery.txt"] },
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
if (n === 5) {
|
|
318
|
+
assert.equal(replay(manager.getBranch()).profile, "helper");
|
|
319
|
+
return [{ name: "read", args: { path: "recovery.txt" } }];
|
|
320
|
+
}
|
|
321
|
+
if (n === 6)
|
|
322
|
+
return [
|
|
323
|
+
{
|
|
324
|
+
name: "freeflow_return",
|
|
325
|
+
args: { operation: "supplement", report: "Helper recovery supplement.", outcome: "completed" },
|
|
326
|
+
},
|
|
327
|
+
];
|
|
328
|
+
if (n === 7) {
|
|
329
|
+
const state = replay(manager.getBranch());
|
|
330
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
331
|
+
assert.equal(state.assignments.get(assignmentId).returnHandoffId, baseReportId);
|
|
332
|
+
assert.equal(state.handoffs.get(baseReportId).text, "Helper base report.");
|
|
333
|
+
const completed = [...state.recoveries.values()].at(-1);
|
|
334
|
+
assert.equal(completed.state, "completed");
|
|
335
|
+
assert.equal(state.handoffs.get(completed.requestHandoffId).to, "helper");
|
|
336
|
+
assert.equal(state.handoffs.get(completed.supplementHandoffId).from, "helper");
|
|
337
|
+
return [
|
|
338
|
+
{
|
|
339
|
+
name: "freeflow_unit",
|
|
340
|
+
args: { operation: "close", outcome: "accepted", assessment: "Helper recovery retained lineage." },
|
|
341
|
+
},
|
|
342
|
+
];
|
|
343
|
+
}
|
|
344
|
+
return [];
|
|
345
|
+
},
|
|
346
|
+
true,
|
|
347
|
+
undefined,
|
|
348
|
+
true,
|
|
349
|
+
{ cognitiveRouting: helperConfig, maxRequests: 9 },
|
|
350
|
+
);
|
|
351
|
+
assert.equal(result.requests.length, 8);
|
|
352
|
+
},
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
test("new input routes through Coordinator before the same Helper assignment resumes", { timeout: 30000 }, async () => {
|
|
356
|
+
let assignmentId;
|
|
357
|
+
const result = await fixture(
|
|
358
|
+
(n, body, manager) => {
|
|
359
|
+
if (n === 1)
|
|
360
|
+
return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Keep the Helper task." } }];
|
|
361
|
+
if (n === 2) {
|
|
362
|
+
assignmentId = replay(manager.getBranch()).assignmentId;
|
|
363
|
+
assert.equal(replay(manager.getBranch()).profile, "helper");
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
if (n === 3) {
|
|
367
|
+
const state = replay(manager.getBranch());
|
|
368
|
+
assert.equal(body.model, "gpt-4o");
|
|
369
|
+
assert.equal(state.profile, "coordinator");
|
|
370
|
+
assert.equal(state.assignments.get(assignmentId).state, "outstanding");
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
373
|
+
if (n === 4) {
|
|
374
|
+
const state = replay(manager.getBranch());
|
|
375
|
+
assert.equal(state.profile, "helper");
|
|
376
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
377
|
+
assert.doesNotMatch(JSON.stringify(body), /New delivered user input requires Coordinator attention/);
|
|
378
|
+
return [
|
|
379
|
+
{
|
|
380
|
+
name: "freeflow_return",
|
|
381
|
+
args: { operation: "submit", report: "Helper resumed after Coordinator attention.", outcome: "completed" },
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
return [];
|
|
386
|
+
},
|
|
387
|
+
true,
|
|
388
|
+
async ({ session }) => {
|
|
389
|
+
await session.prompt("Consider this new direction before continuing.");
|
|
390
|
+
await session.waitForIdle();
|
|
391
|
+
await session.prompt("/freeflow resume");
|
|
392
|
+
await session.waitForIdle();
|
|
393
|
+
},
|
|
394
|
+
true,
|
|
395
|
+
{ cognitiveRouting: helperConfig, maxRequests: 6 },
|
|
396
|
+
);
|
|
397
|
+
assert.equal(result.requests.length, 5);
|
|
398
|
+
assert.equal(result.state.assignments.get(assignmentId).state, "returned");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("an accepted Helper assignment survives a later executor-only mode change", { timeout: 30000 }, async () => {
|
|
402
|
+
let assignmentId;
|
|
403
|
+
const result = await fixture(
|
|
404
|
+
(n, body, manager) => {
|
|
405
|
+
if (n === 1)
|
|
406
|
+
return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Keep this Helper assignment." } }];
|
|
407
|
+
if (n === 2) {
|
|
408
|
+
assignmentId = replay(manager.getBranch()).assignmentId;
|
|
409
|
+
assert.equal(replay(manager.getBranch()).profile, "helper");
|
|
410
|
+
return [];
|
|
411
|
+
}
|
|
412
|
+
if (n === 3) {
|
|
413
|
+
const state = replay(manager.getBranch());
|
|
414
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
415
|
+
assert.equal(state.profile, "helper");
|
|
416
|
+
assert.equal(body.model, "gpt-4.1-mini");
|
|
417
|
+
assert.notEqual(body.model, alternateExecutor.model, "resume does not use the newly enabled Executor pair");
|
|
418
|
+
return [
|
|
419
|
+
{
|
|
420
|
+
name: "freeflow_return",
|
|
421
|
+
args: { operation: "submit", report: "Original Helper assignment resumed.", outcome: "completed" },
|
|
422
|
+
},
|
|
423
|
+
];
|
|
424
|
+
}
|
|
425
|
+
if (n === 4)
|
|
426
|
+
return [
|
|
427
|
+
{
|
|
428
|
+
name: "freeflow_unit",
|
|
429
|
+
args: { operation: "close", outcome: "accepted", assessment: "Mode change did not retarget work." },
|
|
430
|
+
},
|
|
431
|
+
];
|
|
432
|
+
return [];
|
|
433
|
+
},
|
|
434
|
+
true,
|
|
435
|
+
async ({ session, cwd }) => {
|
|
436
|
+
await writeFile(
|
|
437
|
+
join(cwd, ".freeflow/config.json"),
|
|
438
|
+
JSON.stringify({
|
|
439
|
+
cognitiveRouting: {
|
|
440
|
+
enabled: true,
|
|
441
|
+
delegation: "executor",
|
|
442
|
+
projection: true,
|
|
443
|
+
profiles: { coordinator, helper: worker, executor: alternateExecutor },
|
|
444
|
+
},
|
|
445
|
+
}),
|
|
446
|
+
);
|
|
447
|
+
await session.reload();
|
|
448
|
+
await session.prompt("/freeflow resume");
|
|
449
|
+
await session.waitForIdle();
|
|
450
|
+
},
|
|
451
|
+
true,
|
|
452
|
+
{ cognitiveRouting: helperConfig, maxRequests: 6 },
|
|
453
|
+
);
|
|
454
|
+
assert.equal(result.requests.length, 5);
|
|
455
|
+
assert.equal(result.state.assignments.get(assignmentId).state, "returned");
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test(
|
|
459
|
+
"an outstanding Helper assignment fails closed when its preset becomes unavailable",
|
|
460
|
+
{ timeout: 30000 },
|
|
461
|
+
async () => {
|
|
462
|
+
let assignmentId, delegateHandoffId;
|
|
463
|
+
const result = await fixture(
|
|
464
|
+
(n, body, manager) => {
|
|
465
|
+
if (n === 1)
|
|
466
|
+
return [
|
|
467
|
+
{ name: "freeflow_delegate", args: { operation: "assign", contract: "Retain unavailable Helper work." } },
|
|
468
|
+
];
|
|
469
|
+
if (n === 2) {
|
|
470
|
+
const state = replay(manager.getBranch());
|
|
471
|
+
assignmentId = state.assignmentId;
|
|
472
|
+
delegateHandoffId = state.assignments.get(assignmentId).delegateHandoffId;
|
|
473
|
+
assert.equal(state.profile, "helper");
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
assert.fail(`blocked continuation unexpectedly reached the provider with ${body.model}`);
|
|
477
|
+
},
|
|
478
|
+
true,
|
|
479
|
+
async ({ session, cwd, requests, notices }) => {
|
|
480
|
+
await writeFile(
|
|
481
|
+
join(cwd, ".freeflow/config.json"),
|
|
482
|
+
JSON.stringify({
|
|
483
|
+
cognitiveRouting: {
|
|
484
|
+
enabled: true,
|
|
485
|
+
delegation: "executor",
|
|
486
|
+
projection: true,
|
|
487
|
+
profiles: { coordinator, executor: alternateExecutor },
|
|
488
|
+
},
|
|
489
|
+
}),
|
|
490
|
+
);
|
|
491
|
+
await session.reload();
|
|
492
|
+
const requestCount = requests.length;
|
|
493
|
+
await session.prompt("/freeflow resume");
|
|
494
|
+
await session.waitForIdle();
|
|
495
|
+
assert.equal(requests.length, requestCount, "blocked resume makes no provider request");
|
|
496
|
+
assert.equal(session.model.id, "gpt-4.1-mini", "no fallback setter applies the Executor pair");
|
|
497
|
+
assert.match(JSON.stringify(notices), /profile_missing|helper/i);
|
|
498
|
+
},
|
|
499
|
+
true,
|
|
500
|
+
{ cognitiveRouting: helperConfig, maxRequests: 4 },
|
|
501
|
+
);
|
|
502
|
+
assert.equal(result.requests.length, 2);
|
|
503
|
+
assert.equal(result.state.assignmentId, assignmentId);
|
|
504
|
+
assert.equal(result.state.assignments.get(assignmentId).state, "outstanding");
|
|
505
|
+
assert.equal(result.state.assignments.get(assignmentId).delegateHandoffId, delegateHandoffId);
|
|
506
|
+
assert.equal(result.state.handoffs.get(delegateHandoffId).to, "helper");
|
|
507
|
+
assert.equal(
|
|
508
|
+
[...result.state.handoffs.values()].some((handoff) => handoff.kind === "return"),
|
|
509
|
+
false,
|
|
510
|
+
);
|
|
511
|
+
},
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
test("compaction and explicit resume preserve a Helper assignment", { timeout: 30000 }, async () => {
|
|
515
|
+
let assignmentId, basis;
|
|
516
|
+
const result = await fixture(
|
|
517
|
+
(n, body, manager) => {
|
|
518
|
+
if (n === 1)
|
|
519
|
+
return [{ name: "freeflow_delegate", args: { operation: "assign", contract: "Preserve Helper work." } }];
|
|
520
|
+
if (n === 2) {
|
|
521
|
+
const state = replay(manager.getBranch());
|
|
522
|
+
assignmentId = state.assignmentId;
|
|
523
|
+
basis = state.assignments.get(assignmentId).basisUserEntryId;
|
|
524
|
+
return [{ name: "read", args: { path: "evidence.txt" } }];
|
|
525
|
+
}
|
|
526
|
+
if (n === 4) {
|
|
527
|
+
const state = replay(manager.getBranch());
|
|
528
|
+
assert.equal(state.profile, "helper");
|
|
529
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
530
|
+
assert.equal(state.resumeBasis.get(assignmentId), basis);
|
|
531
|
+
assert.doesNotMatch(JSON.stringify(body), /New delivered user input requires Coordinator attention/);
|
|
532
|
+
return [
|
|
533
|
+
{
|
|
534
|
+
name: "freeflow_return",
|
|
535
|
+
args: { operation: "submit", report: "Helper resumed after compaction.", outcome: "completed" },
|
|
536
|
+
},
|
|
537
|
+
];
|
|
538
|
+
}
|
|
539
|
+
return [];
|
|
540
|
+
},
|
|
541
|
+
true,
|
|
542
|
+
async ({ session, manager }) => {
|
|
543
|
+
await session.compact();
|
|
544
|
+
assert.equal(
|
|
545
|
+
manager.buildSessionContext().messages.some((message) => message.role === "user"),
|
|
546
|
+
false,
|
|
547
|
+
);
|
|
548
|
+
await session.reload();
|
|
549
|
+
await session.prompt("/freeflow resume");
|
|
550
|
+
await session.waitForIdle();
|
|
551
|
+
},
|
|
552
|
+
true,
|
|
553
|
+
{ cognitiveRouting: helperConfig, maxRequests: 6 },
|
|
554
|
+
);
|
|
555
|
+
assert.equal(result.requests.length, 5);
|
|
556
|
+
assert.equal(result.state.assignments.get(assignmentId).state, "returned");
|
|
557
|
+
});
|