@hassangameryt/freeflow 0.7.1 → 0.7.2
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 +28 -23
- package/capabilities/cognitive-routing/SKILL.md +94 -311
- 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 +120 -0
- package/pi-extension/dist/cognitive-routing-v2/projection.js +321 -0
- package/pi-extension/dist/cognitive-routing-v2/render.js +231 -0
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +1585 -0
- package/pi-extension/dist/cognitive-routing-v2/schemas.js +107 -0
- package/pi-extension/dist/cognitive-routing-v2/state.js +612 -0
- package/pi-extension/dist/cognitive-routing-v2/tools.js +91 -0
- package/pi-extension/dist/cognitive-routing-v2/types.js +44 -0
- package/pi-extension/dist/freeflow-context/tool.js +5 -0
- package/pi-extension/dist/index.js +279 -624
- package/pi-extension/dist/runtime/runtime-context.js +63 -13
- package/pi-extension/dist/session-sources/events.js +195 -0
- package/pi-extension/dist/session-sources/read-only-session.js +148 -0
- package/pi-extension/dist/session-sources/sources.js +233 -0
- package/pi-extension/dist/settings/settings-ui.js +44 -117
- package/pi-extension/src/cognitive-routing-v2/budget.ts +42 -0
- package/pi-extension/src/cognitive-routing-v2/config.ts +143 -0
- package/pi-extension/src/cognitive-routing-v2/projection.ts +365 -0
- package/pi-extension/src/cognitive-routing-v2/render.ts +256 -0
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +1634 -0
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +109 -0
- package/pi-extension/src/cognitive-routing-v2/state.ts +622 -0
- package/pi-extension/src/cognitive-routing-v2/tools.ts +93 -0
- package/pi-extension/src/cognitive-routing-v2/types.ts +222 -0
- package/pi-extension/src/freeflow-context/tool.ts +5 -0
- package/pi-extension/src/index.ts +285 -674
- package/pi-extension/src/runtime/runtime-context.ts +75 -14
- package/pi-extension/src/session-sources/events.ts +208 -0
- package/pi-extension/src/session-sources/read-only-session.ts +170 -0
- package/pi-extension/src/session-sources/sources.ts +263 -0
- package/pi-extension/src/settings/settings-ui.ts +54 -131
- package/pi-extension/tests/cognitive-routing-v2/communication.test.js +484 -0
- package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +183 -0
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +441 -0
- package/pi-extension/tests/cognitive-routing-v2/performance.test.js +87 -0
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +245 -0
- package/pi-extension/tests/cognitive-routing-v2/projection.test.js +241 -0
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +236 -0
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +230 -0
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +153 -0
- package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +521 -0
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +275 -0
- package/pi-extension/tests/context-virtualization/extension.test.js +16 -0
- package/pi-extension/tests/fixtures/routing-native.js +188 -0
- package/pi-extension/tests/integration/mode-free-core.test.js +3 -2
- package/pi-extension/tests/integration/pi-extension.test.js +154 -37
- package/pi-extension/tests/integration/prompt-architecture.test.js +87 -6
- package/pi-extension/tests/integration/unaffected-regressions.test.js +77 -10
- 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,484 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { fixture } from "../fixtures/routing-native.js";
|
|
4
|
+
import { Sources } from "../../dist/session-sources/sources.js";
|
|
5
|
+
import { initialState } from "../../dist/cognitive-routing-v2/state.js";
|
|
6
|
+
import { prepareView, changeSelection } from "../../dist/cognitive-routing-v2/projection.js";
|
|
7
|
+
import { RoutingRuntime } from "../../dist/cognitive-routing-v2/runtime.js";
|
|
8
|
+
|
|
9
|
+
const call = (name, args) => [{ name, args }];
|
|
10
|
+
const receipt = (m, name) =>
|
|
11
|
+
m
|
|
12
|
+
.getBranch()
|
|
13
|
+
.filter((e) => e.message?.toolName === name)
|
|
14
|
+
.at(-1).message.details;
|
|
15
|
+
const model = {
|
|
16
|
+
id: "receiver",
|
|
17
|
+
provider: "fixture",
|
|
18
|
+
api: "fixture",
|
|
19
|
+
input: ["text"],
|
|
20
|
+
contextWindow: 100000,
|
|
21
|
+
maxTokens: 1000,
|
|
22
|
+
};
|
|
23
|
+
const assistant = (text, calls = []) => ({
|
|
24
|
+
role: "assistant",
|
|
25
|
+
content: [{ type: "text", text }, ...calls],
|
|
26
|
+
model: "receiver",
|
|
27
|
+
provider: "fixture",
|
|
28
|
+
api: "fixture",
|
|
29
|
+
timestamp: 1,
|
|
30
|
+
stopReason: calls.length ? "toolUse" : "stop",
|
|
31
|
+
});
|
|
32
|
+
const entry = (id, parentId, message) => ({ type: "message", id, parentId, message });
|
|
33
|
+
function prepare(state, entries, messages = entries.map((e) => e.message), view = "executor") {
|
|
34
|
+
return prepareView({
|
|
35
|
+
state,
|
|
36
|
+
sources: new Sources(entries, state),
|
|
37
|
+
messages,
|
|
38
|
+
view,
|
|
39
|
+
projection: true,
|
|
40
|
+
model,
|
|
41
|
+
pair: { provider: "fixture", modelId: "receiver", thinking: "off" },
|
|
42
|
+
systemPrompt: "",
|
|
43
|
+
tools: [],
|
|
44
|
+
runtimeMessage: RoutingRuntime.prototype.runtimeMessage.call(
|
|
45
|
+
{ projectionEnabled: true, token: "test", evidenceFacts: () => ({ selected: [] }) },
|
|
46
|
+
state,
|
|
47
|
+
),
|
|
48
|
+
instance: "test",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test("provenance covers old sources and complete exchanges, with stable prefixes and unchanged originals", () => {
|
|
53
|
+
const state = initialState();
|
|
54
|
+
const entries = Array.from({ length: 40 }, (_, i) =>
|
|
55
|
+
entry(`m${i}`, i ? `m${i - 1}` : null, assistant(`Observation ${i}`)),
|
|
56
|
+
);
|
|
57
|
+
for (const [i, e] of entries.entries())
|
|
58
|
+
state.authors.set(e.id, {
|
|
59
|
+
profile: i % 2 ? "executor" : "coordinator",
|
|
60
|
+
assignmentId: i % 2 ? "earlier-assignment" : undefined,
|
|
61
|
+
});
|
|
62
|
+
entries.push(entry("unknown", "m39", assistant("Imported observation")));
|
|
63
|
+
entries.push(
|
|
64
|
+
entry(
|
|
65
|
+
"calls",
|
|
66
|
+
"unknown",
|
|
67
|
+
assistant("Read observations", [{ type: "toolCall", id: "c", name: "read", arguments: { path: "x" } }]),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
entries.push(
|
|
71
|
+
entry("result", "calls", {
|
|
72
|
+
role: "toolResult",
|
|
73
|
+
toolCallId: "c",
|
|
74
|
+
toolName: "read",
|
|
75
|
+
content: [{ type: "text", text: "FULL_RESULT" }],
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
state.authors.set("calls", { profile: "executor", assignmentId: "current" });
|
|
79
|
+
state.authors.set("result", { profile: "executor", assignmentId: "current" });
|
|
80
|
+
const before = JSON.stringify(entries);
|
|
81
|
+
const prepared = prepare(state, entries);
|
|
82
|
+
const annotations = prepared.messages.filter((m) => m.customType === "freeflow-routing-v2-refs");
|
|
83
|
+
for (let i = 0; i < 40; i++)
|
|
84
|
+
assert.ok(
|
|
85
|
+
annotations.some((m) => m.content.includes(`ctx:m${i} | producer: ${i % 2 ? "executor" : "coordinator"}`)),
|
|
86
|
+
);
|
|
87
|
+
assert.ok(annotations.some((m) => m.content.includes("ctx:unknown | producer: unknown/common")));
|
|
88
|
+
const callIndex = prepared.messages.findIndex((m) => m.role === "assistant" && m.content.some((b) => b.id === "c"));
|
|
89
|
+
assert.equal(prepared.messages[callIndex + 1].toolCallId, "c", "no annotation splits a call/result exchange");
|
|
90
|
+
assert.match(prepared.messages[callIndex - 1].content, /ctx:result.*producer: executor/);
|
|
91
|
+
const extended = prepare(state, [...entries, entry("later", "result", assistant("Later observation"))]);
|
|
92
|
+
assert.deepEqual(
|
|
93
|
+
extended.messages.slice(0, prepared.messages.length - 1),
|
|
94
|
+
prepared.messages.slice(0, -1),
|
|
95
|
+
"appends leave old provenance stable",
|
|
96
|
+
);
|
|
97
|
+
assert.equal(JSON.stringify(entries), before);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("new control selections are rejected while old selections and mixed assistant text remain usable", () => {
|
|
101
|
+
const state = initialState();
|
|
102
|
+
const entries = [
|
|
103
|
+
entry(
|
|
104
|
+
"control",
|
|
105
|
+
null,
|
|
106
|
+
assistant("Substantive executor finding", [
|
|
107
|
+
{ type: "toolCall", id: "c", name: "freeflow_project", arguments: { operation: "inspect" } },
|
|
108
|
+
]),
|
|
109
|
+
),
|
|
110
|
+
entry("receipt", "control", {
|
|
111
|
+
role: "toolResult",
|
|
112
|
+
toolCallId: "c",
|
|
113
|
+
toolName: "freeflow_project",
|
|
114
|
+
content: [{ type: "text", text: '{"status":"unchanged"}' }],
|
|
115
|
+
}),
|
|
116
|
+
];
|
|
117
|
+
for (const e of entries) state.authors.set(e.id, { profile: "executor", assignmentId: "a" });
|
|
118
|
+
const sources = new Sources(entries, state);
|
|
119
|
+
for (const s of sources.byRef.values()) state.exposure.set(s.ref, s.hash);
|
|
120
|
+
const empty = { revision: 0, selected: [], unresolved: [], withdrawals: [] };
|
|
121
|
+
const attempted = changeSelection(
|
|
122
|
+
empty,
|
|
123
|
+
{ operation: "add", refs: ["ctx:control", "ctx:receipt", "ctx:control#text"] },
|
|
124
|
+
sources,
|
|
125
|
+
state,
|
|
126
|
+
);
|
|
127
|
+
assert.deepEqual(attempted.selected, ["ctx:control#text"]);
|
|
128
|
+
assert.deepEqual(
|
|
129
|
+
attempted.unresolved.map((p) => p.code),
|
|
130
|
+
["routing_source", "routing_source"],
|
|
131
|
+
);
|
|
132
|
+
const prior = { ...empty, revision: 1, selected: ["ctx:receipt"] };
|
|
133
|
+
assert.equal(changeSelection(prior, { operation: "add", refs: ["ctx:receipt"] }, sources, state), prior);
|
|
134
|
+
state.assignmentId = "a";
|
|
135
|
+
state.selections.set("a", attempted);
|
|
136
|
+
const projected = prepare(
|
|
137
|
+
state,
|
|
138
|
+
entries,
|
|
139
|
+
entries.map((e) => e.message),
|
|
140
|
+
"coordinator",
|
|
141
|
+
);
|
|
142
|
+
assert.ok(
|
|
143
|
+
projected.messages.some(
|
|
144
|
+
(m) => m.customType === "freeflow-routing-v2-refs" && m.content.includes("ctx:control#text | producer: executor"),
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
assert.ok(projected.messages.some((m) => m.customType === "freeflow-assistant-text"));
|
|
148
|
+
state.selections.set("a", prior);
|
|
149
|
+
assert.ok(
|
|
150
|
+
prepare(
|
|
151
|
+
state,
|
|
152
|
+
entries,
|
|
153
|
+
entries.map((e) => e.message),
|
|
154
|
+
"coordinator",
|
|
155
|
+
).messages.some((m) => m.toolName === "freeflow_project"),
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("restoration preserves full report metadata and deduplicates by accepted occurrence", () => {
|
|
160
|
+
const state = initialState();
|
|
161
|
+
state.profile = "coordinator";
|
|
162
|
+
state.assignmentId = "a";
|
|
163
|
+
state.assignments.set("a", { id: "a", state: "returned", contract: "CONTRACT", delegateHandoffId: "d" });
|
|
164
|
+
state.handoffs.set("d", { id: "d", kind: "delegate", toolCallId: "dc" });
|
|
165
|
+
const h = {
|
|
166
|
+
id: "h",
|
|
167
|
+
assignmentId: "a",
|
|
168
|
+
kind: "return",
|
|
169
|
+
state: "configured",
|
|
170
|
+
toolCallId: "rc",
|
|
171
|
+
text: "SAME_REPORT",
|
|
172
|
+
outcome: "partial",
|
|
173
|
+
limitations: ["UNRUN_INTEGRATION"],
|
|
174
|
+
reportRevision: 2,
|
|
175
|
+
};
|
|
176
|
+
state.handoffs.set("h", h);
|
|
177
|
+
state.assessment = { handoffId: "h", view: "active", problems: [] };
|
|
178
|
+
const restored = JSON.stringify(prepare(state, [], [], "coordinator").messages);
|
|
179
|
+
for (const value of ["SAME_REPORT", "UNRUN_INTEGRATION", "partial", "report:h:2", "assignment:a"])
|
|
180
|
+
assert.ok(restored.includes(value));
|
|
181
|
+
const old = {
|
|
182
|
+
role: "toolResult",
|
|
183
|
+
toolName: "freeflow_return",
|
|
184
|
+
toolCallId: "other",
|
|
185
|
+
content: [{ type: "text", text: JSON.stringify({ report: h.text, outcome: "completed", limitations: [] }) }],
|
|
186
|
+
};
|
|
187
|
+
const oldResult = prepare(state, [], [old], "coordinator");
|
|
188
|
+
assert.ok(
|
|
189
|
+
oldResult.messages.some(
|
|
190
|
+
(m) => m.customType === "freeflow-routing-communication" && m.content.includes("SAME_REPORT"),
|
|
191
|
+
),
|
|
192
|
+
"equal text from another call cannot satisfy current report",
|
|
193
|
+
);
|
|
194
|
+
const incomplete = { ...old, toolCallId: "rc" };
|
|
195
|
+
const corrected = prepare(state, [], [incomplete], "coordinator");
|
|
196
|
+
assert.equal(JSON.stringify(corrected.messages).split("SAME_REPORT").length - 1, 1);
|
|
197
|
+
assert.ok(JSON.stringify(corrected.messages).includes("UNRUN_INTEGRATION"));
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
for (const projection of [false, true])
|
|
201
|
+
test(`native report arrives complete without inspection and survives close, projection ${projection}`, async () => {
|
|
202
|
+
await fixture((n, body, m) => {
|
|
203
|
+
if (n === 1) return call("freeflow_delegate", { operation: "assign", contract: "Return a partial report." });
|
|
204
|
+
if (n === 2)
|
|
205
|
+
return call("freeflow_return", {
|
|
206
|
+
operation: "submit",
|
|
207
|
+
report: "REPORT_DIRECT_91",
|
|
208
|
+
outcome: "partial",
|
|
209
|
+
limitations: ["LIMIT_DIRECT_92"],
|
|
210
|
+
});
|
|
211
|
+
if (n === 3) {
|
|
212
|
+
const r = receipt(m, "freeflow_return");
|
|
213
|
+
assert.equal(r.outcome, "partial");
|
|
214
|
+
assert.deepEqual(r.limitations, ["LIMIT_DIRECT_92"]);
|
|
215
|
+
assert.match(r.assignmentRef, /^assignment:/);
|
|
216
|
+
assert.match(r.reportRef, /^report:.*:1$/);
|
|
217
|
+
for (const text of ["REPORT_DIRECT_91", "LIMIT_DIRECT_92", r.reportRef])
|
|
218
|
+
assert.ok(JSON.stringify(body).includes(text));
|
|
219
|
+
return call("freeflow_unit", {
|
|
220
|
+
operation: "close",
|
|
221
|
+
outcome: "deferred",
|
|
222
|
+
assessment: "Partial result acknowledged.",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
if (n === 4)
|
|
226
|
+
for (const text of ["REPORT_DIRECT_91", "LIMIT_DIRECT_92"]) assert.ok(JSON.stringify(body).includes(text));
|
|
227
|
+
return [];
|
|
228
|
+
}, projection);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("native inspection snapshots distinguish page/scope totals, exclude controls and explain invalid work refs", async () => {
|
|
232
|
+
let first;
|
|
233
|
+
await fixture((n, body, m) => {
|
|
234
|
+
if (n === 1) return call("freeflow_delegate", { operation: "assign", contract: "Read observations." });
|
|
235
|
+
if (n === 2) return Array.from({ length: 35 }, () => ({ name: "read", args: { path: "evidence.txt" } }));
|
|
236
|
+
if (n === 3) return call("freeflow_project", { operation: "inspect", scope: "history" });
|
|
237
|
+
if (n === 4) {
|
|
238
|
+
first = receipt(m, "freeflow_project");
|
|
239
|
+
assert.equal(first.pageCounts.candidates, 30);
|
|
240
|
+
assert.ok(first.scopeCounts.candidates > 30);
|
|
241
|
+
assert.equal(first.scopeCounts.eligible, first.scopeCounts.candidates);
|
|
242
|
+
assert.ok(first.candidates.every((c) => c.producer === "executor" && !c.toolName?.startsWith("freeflow_")));
|
|
243
|
+
return call("freeflow_project", { operation: "inspect", scope: "history", cursor: first.nextCursor });
|
|
244
|
+
}
|
|
245
|
+
if (n === 5) {
|
|
246
|
+
const second = receipt(m, "freeflow_project");
|
|
247
|
+
assert.deepEqual(second.scopeCounts, first.scopeCounts);
|
|
248
|
+
assert.equal(
|
|
249
|
+
new Set([...first.candidates, ...second.candidates].map((c) => c.ref)).size,
|
|
250
|
+
first.scopeCounts.candidates,
|
|
251
|
+
);
|
|
252
|
+
return call("freeflow_return", { operation: "submit", report: "Done", outcome: "completed" });
|
|
253
|
+
}
|
|
254
|
+
if (n === 6)
|
|
255
|
+
return call("freeflow_unit", {
|
|
256
|
+
operation: "inspect",
|
|
257
|
+
view: "detail",
|
|
258
|
+
ref: receipt(m, "freeflow_return").handoff,
|
|
259
|
+
});
|
|
260
|
+
if (n === 7) {
|
|
261
|
+
assert.equal(receipt(m, "freeflow_unit").code, "invalid_work_ref");
|
|
262
|
+
return call("freeflow_unit", {
|
|
263
|
+
operation: "inspect",
|
|
264
|
+
view: "detail",
|
|
265
|
+
ref: receipt(m, "freeflow_return").assignmentRef,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (n === 8) {
|
|
269
|
+
assert.equal(receipt(m, "freeflow_unit").report, "Done");
|
|
270
|
+
return call("freeflow_unit", { operation: "close", outcome: "accepted", assessment: "Read work assessed." });
|
|
271
|
+
}
|
|
272
|
+
return [];
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("native compaction restores report limitations and revision into the same assessment", async () => {
|
|
277
|
+
let saved;
|
|
278
|
+
await fixture(
|
|
279
|
+
(n, body, m) => {
|
|
280
|
+
if (n === 1)
|
|
281
|
+
return call("freeflow_delegate", { operation: "assign", contract: "Return a report for assessment." });
|
|
282
|
+
if (n === 2)
|
|
283
|
+
return call("freeflow_return", {
|
|
284
|
+
operation: "submit",
|
|
285
|
+
report: "COMPACT_REPORT_61",
|
|
286
|
+
outcome: "partial",
|
|
287
|
+
limitations: ["COMPACT_LIMIT_62"],
|
|
288
|
+
});
|
|
289
|
+
if (n === 3) {
|
|
290
|
+
saved = receipt(m, "freeflow_return");
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
if (n === 4) return call("freeflow_unit", { operation: "assess" });
|
|
294
|
+
if (n === 5) {
|
|
295
|
+
const wire = JSON.stringify(body);
|
|
296
|
+
for (const value of ["COMPACT_REPORT_61", "COMPACT_LIMIT_62", "partial", saved.assignmentRef, saved.reportRef])
|
|
297
|
+
assert.ok(wire.includes(value), value);
|
|
298
|
+
}
|
|
299
|
+
return [];
|
|
300
|
+
},
|
|
301
|
+
true,
|
|
302
|
+
async ({ session }) => {
|
|
303
|
+
await session.compact();
|
|
304
|
+
await session.prompt("Resume assessment of the existing saved report.");
|
|
305
|
+
await session.waitForIdle();
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("native retry delivers the unchanged complete report without a follow-up inspect", async () => {
|
|
311
|
+
let saved;
|
|
312
|
+
await fixture((n, body, m) => {
|
|
313
|
+
if (n === 1) return call("freeflow_delegate", { operation: "assign", contract: "Return available findings." });
|
|
314
|
+
if (n === 2) return call("freeflow_project", { operation: "add", refs: ["ctx:unavailable"] });
|
|
315
|
+
if (n === 3)
|
|
316
|
+
return call("freeflow_return", {
|
|
317
|
+
operation: "submit",
|
|
318
|
+
report: "RETRY_REPORT_11",
|
|
319
|
+
outcome: "partial",
|
|
320
|
+
limitations: ["RETRY_LIMIT_12"],
|
|
321
|
+
});
|
|
322
|
+
if (n === 4) {
|
|
323
|
+
saved = receipt(m, "freeflow_return");
|
|
324
|
+
assert.equal(saved.ready, false);
|
|
325
|
+
return call("freeflow_project", {
|
|
326
|
+
operation: "remove",
|
|
327
|
+
refs: ["ctx:unavailable"],
|
|
328
|
+
reason: "Original capture unavailable; retain limitation.",
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
if (n === 5) return call("freeflow_return", { operation: "retry" });
|
|
332
|
+
if (n === 6) {
|
|
333
|
+
const retried = receipt(m, "freeflow_return");
|
|
334
|
+
for (const key of ["report", "reportRef", "reportRevision", "assignmentRef", "outcome", "limitations"])
|
|
335
|
+
assert.deepEqual(retried[key], saved[key]);
|
|
336
|
+
assert.equal(retried.reportUnchanged, true);
|
|
337
|
+
for (const value of ["RETRY_REPORT_11", "RETRY_LIMIT_12", saved.reportRef])
|
|
338
|
+
assert.ok(JSON.stringify(body).includes(value));
|
|
339
|
+
return call("freeflow_unit", { operation: "close", outcome: "deferred", assessment: "Partial report assessed." });
|
|
340
|
+
}
|
|
341
|
+
return [];
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("empty new assignment advertises earlier evidence and selected receipts retain its producer", async () => {
|
|
346
|
+
let ref, oldAssignment;
|
|
347
|
+
await fixture((n, body, m) => {
|
|
348
|
+
if (n === 1 || n === 5)
|
|
349
|
+
return call("freeflow_delegate", {
|
|
350
|
+
operation: "assign",
|
|
351
|
+
contract: n === 1 ? "Read an observation." : "Reuse the earlier observation.",
|
|
352
|
+
});
|
|
353
|
+
if (n === 2) return call("read", { path: "evidence.txt" });
|
|
354
|
+
if (n === 3) {
|
|
355
|
+
ref = `ctx:${m.getBranch().find((e) => e.message?.toolName === "read").id}`;
|
|
356
|
+
return call("freeflow_return", { operation: "submit", report: "Observation captured.", outcome: "completed" });
|
|
357
|
+
}
|
|
358
|
+
if (n === 4)
|
|
359
|
+
return call("freeflow_unit", { operation: "close", outcome: "accepted", assessment: "Supported result." });
|
|
360
|
+
if (n === 6) return call("freeflow_project", { operation: "inspect" });
|
|
361
|
+
if (n === 7) {
|
|
362
|
+
const r = receipt(m, "freeflow_project");
|
|
363
|
+
assert.equal(r.count, 0);
|
|
364
|
+
assert.ok(r.otherAssignments > 0);
|
|
365
|
+
assert.match(r.historyHint, /scope history/);
|
|
366
|
+
return call("freeflow_project", { operation: "inspect", scope: "history" });
|
|
367
|
+
}
|
|
368
|
+
if (n === 8) {
|
|
369
|
+
const candidate = receipt(m, "freeflow_project").candidates.find((c) => c.ref === ref);
|
|
370
|
+
assert.equal(candidate.producer, "executor");
|
|
371
|
+
assert.equal(candidate.eligible, true);
|
|
372
|
+
oldAssignment = candidate.assignment;
|
|
373
|
+
return call("freeflow_project", { operation: "add", refs: [ref] });
|
|
374
|
+
}
|
|
375
|
+
if (n === 9) {
|
|
376
|
+
const selected = receipt(m, "freeflow_project").evidence.selected[0];
|
|
377
|
+
assert.equal(selected.producer, "executor");
|
|
378
|
+
assert.equal(selected.assignment, oldAssignment);
|
|
379
|
+
return call("freeflow_return", {
|
|
380
|
+
operation: "submit",
|
|
381
|
+
report: "Earlier evidence selected.",
|
|
382
|
+
outcome: "completed",
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
if (n === 10) {
|
|
386
|
+
const wire = JSON.stringify(body);
|
|
387
|
+
assert.ok(wire.includes(`${ref} | producer: executor`));
|
|
388
|
+
assert.ok(wire.includes("EXACT_EVIDENCE_BODY_81"));
|
|
389
|
+
return call("freeflow_unit", { operation: "inspect", view: "detail", ref: "assignment:absent" });
|
|
390
|
+
}
|
|
391
|
+
if (n === 11) {
|
|
392
|
+
assert.equal(receipt(m, "freeflow_unit").code, "work_unavailable");
|
|
393
|
+
return call("freeflow_unit", { operation: "close", outcome: "accepted", assessment: "Supported result." });
|
|
394
|
+
}
|
|
395
|
+
return [];
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("normal inspection hides delivery gaps while selected inspection preserves their diagnostics", async () => {
|
|
400
|
+
let ref;
|
|
401
|
+
const result = await fixture(
|
|
402
|
+
(n, body, m) => {
|
|
403
|
+
if (n === 1) return call("freeflow_delegate", { operation: "assign", contract: "Read and inspect evidence." });
|
|
404
|
+
if (n === 2) return call("read", { path: "evidence.txt" });
|
|
405
|
+
if (n === 3) {
|
|
406
|
+
ref = `ctx:${m.getBranch().find((e) => e.message?.content?.some?.((b) => b.type === "toolCall" && b.name === "read")).id}`;
|
|
407
|
+
return call("freeflow_project", { operation: "inspect", scope: "assignment" });
|
|
408
|
+
}
|
|
409
|
+
if ([4, 5, 6].includes(n)) {
|
|
410
|
+
const r = receipt(m, "freeflow_project");
|
|
411
|
+
assert.ok(r.candidates.every((c) => c.eligible && c.targetReady));
|
|
412
|
+
assert.ok(r.candidates.every((c) => !Object.hasOwn(c, "preview")));
|
|
413
|
+
assert.ok(!JSON.stringify(r).includes("EXACT_EVIDENCE_BODY_81"), "inspection does not repeat source text");
|
|
414
|
+
assert.equal(
|
|
415
|
+
r.candidates.find((c) => c.ref === ref),
|
|
416
|
+
undefined,
|
|
417
|
+
"incompatible whole message is not a candidate",
|
|
418
|
+
);
|
|
419
|
+
assert.ok(
|
|
420
|
+
r.candidates.some((c) => c.ref === `${ref}#text`),
|
|
421
|
+
"usable visible text remains offered",
|
|
422
|
+
);
|
|
423
|
+
assert.equal(r.scopeCounts.candidates, r.scopeCounts.targetReady);
|
|
424
|
+
assert.equal(r.pageCounts.candidates, r.pageCounts.targetReady);
|
|
425
|
+
if (n === 4) return call("freeflow_project", { operation: "inspect", scope: "active" });
|
|
426
|
+
if (n === 5) return call("freeflow_project", { operation: "inspect", scope: "history" });
|
|
427
|
+
return call("freeflow_project", { operation: "add", refs: [ref] });
|
|
428
|
+
}
|
|
429
|
+
if (n === 7) {
|
|
430
|
+
const r = receipt(m, "freeflow_project");
|
|
431
|
+
assert.equal(r.ready, false);
|
|
432
|
+
assert.ok(r.problems.some((p) => p.code === "target_representation"));
|
|
433
|
+
return call("freeflow_project", { operation: "inspect", scope: "selected" });
|
|
434
|
+
}
|
|
435
|
+
if (n === 8) {
|
|
436
|
+
const r = receipt(m, "freeflow_project");
|
|
437
|
+
assert.deepEqual(r.selected, [ref]);
|
|
438
|
+
const selected = r.candidates.find((c) => c.ref === ref);
|
|
439
|
+
assert.equal(Object.hasOwn(selected, "preview"), false);
|
|
440
|
+
assert.equal(selected.targetReady, false);
|
|
441
|
+
assert.ok(selected.limitations.some((p) => p.code === "target_representation"));
|
|
442
|
+
assert.equal(r.scopeCounts.candidates, 1);
|
|
443
|
+
assert.equal(r.scopeCounts.targetReady, 0);
|
|
444
|
+
}
|
|
445
|
+
return [];
|
|
446
|
+
},
|
|
447
|
+
true,
|
|
448
|
+
undefined,
|
|
449
|
+
true,
|
|
450
|
+
{
|
|
451
|
+
extensions: [
|
|
452
|
+
(pi) =>
|
|
453
|
+
pi.on("message_end", (e) => {
|
|
454
|
+
if (
|
|
455
|
+
e.message.role === "assistant" &&
|
|
456
|
+
e.message.content.some((b) => b.type === "toolCall" && b.name === "read")
|
|
457
|
+
)
|
|
458
|
+
return {
|
|
459
|
+
message: {
|
|
460
|
+
...e.message,
|
|
461
|
+
content: [
|
|
462
|
+
...e.message.content,
|
|
463
|
+
{
|
|
464
|
+
type: "thinking",
|
|
465
|
+
thinking: "Fixture signed reasoning",
|
|
466
|
+
thinkingSignature: JSON.stringify({
|
|
467
|
+
type: "reasoning",
|
|
468
|
+
id: "rs_candidate",
|
|
469
|
+
summary: [{ type: "summary_text", text: "Fixture signed reasoning" }],
|
|
470
|
+
}),
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}),
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
);
|
|
479
|
+
assert.deepEqual(
|
|
480
|
+
[...result.state.selections.values()].at(-1).selected,
|
|
481
|
+
[ref],
|
|
482
|
+
"inspection does not silently withdraw evidence",
|
|
483
|
+
);
|
|
484
|
+
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { fixture, response } from "../fixtures/routing-native.js";
|
|
4
|
+
import { replay } from "../../dist/cognitive-routing-v2/state.js";
|
|
5
|
+
import { RoutingRuntime } from "../../dist/cognitive-routing-v2/runtime.js";
|
|
6
|
+
import { Sources } from "../../dist/session-sources/sources.js";
|
|
7
|
+
import { initialState } from "../../dist/cognitive-routing-v2/state.js";
|
|
8
|
+
|
|
9
|
+
test("delivery identity uses observed ancestry, not unseen storage or an older retained user", () => {
|
|
10
|
+
const state = initialState();
|
|
11
|
+
const entries = ["old", "delivered", "queued"].map((id, i) => ({
|
|
12
|
+
id,
|
|
13
|
+
parentId: i ? ["old", "delivered"][i - 1] : null,
|
|
14
|
+
type: "message",
|
|
15
|
+
message: { role: "user", content: id, timestamp: i },
|
|
16
|
+
}));
|
|
17
|
+
state.executions.set("observed", { basisUserEntryId: "delivered" });
|
|
18
|
+
const read = (branch, active) =>
|
|
19
|
+
RoutingRuntime.prototype.lastDeliveredUser.call({ stateData: () => state }, new Sources(branch, state), active);
|
|
20
|
+
assert.equal(read(entries, []), "delivered", "unseen queued entry is not delivered");
|
|
21
|
+
assert.equal(read(entries, [entries[0].message]), "delivered", "older retained user does not regress delivery");
|
|
22
|
+
assert.equal(read(entries, [entries[2].message]), "queued", "newly represented user advances delivery");
|
|
23
|
+
assert.equal(read(entries.slice(0, 1), []), null, "a basis outside selected ancestry is not inherited");
|
|
24
|
+
assert.equal(read(entries.slice(0, 1), [entries[0].message]), "old");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("reload and explicit resume after compaction keep the outstanding assignment", { timeout: 30000 }, async () => {
|
|
28
|
+
let assignmentId, basis;
|
|
29
|
+
const result = await fixture(
|
|
30
|
+
(n, body, manager) => {
|
|
31
|
+
if (n === 1)
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
name: "freeflow_delegate",
|
|
35
|
+
args: { operation: "assign", contract: "Read and preserve this assignment through a technical pause." },
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
if (n === 2) {
|
|
39
|
+
const state = replay(manager.getBranch());
|
|
40
|
+
assignmentId = state.assignmentId;
|
|
41
|
+
basis = state.assignments.get(assignmentId).basisUserEntryId;
|
|
42
|
+
return [{ name: "read", args: { path: "evidence.txt" } }];
|
|
43
|
+
}
|
|
44
|
+
if (n === 4) {
|
|
45
|
+
const state = replay(manager.getBranch());
|
|
46
|
+
assert.equal(body.model, "gpt-4.1-mini");
|
|
47
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
48
|
+
assert.equal(state.assignments.size, 1);
|
|
49
|
+
assert.equal(state.resumeBasis.get(assignmentId), basis);
|
|
50
|
+
assert.doesNotMatch(JSON.stringify(body), /New delivered user input requires Coordinator attention/);
|
|
51
|
+
return [{ name: "read", args: { path: "unselected.txt" } }];
|
|
52
|
+
}
|
|
53
|
+
if (n === 5)
|
|
54
|
+
assert.equal(
|
|
55
|
+
manager
|
|
56
|
+
.getBranch()
|
|
57
|
+
.filter((e) => e.message?.toolName === "read")
|
|
58
|
+
.at(-1).message.isError,
|
|
59
|
+
false,
|
|
60
|
+
);
|
|
61
|
+
return [];
|
|
62
|
+
},
|
|
63
|
+
true,
|
|
64
|
+
async ({ session, manager }) => {
|
|
65
|
+
await session.compact();
|
|
66
|
+
assert.equal(
|
|
67
|
+
manager.buildSessionContext().messages.some((m) => m.role === "user"),
|
|
68
|
+
false,
|
|
69
|
+
);
|
|
70
|
+
await session.reload();
|
|
71
|
+
await session.prompt("/freeflow resume");
|
|
72
|
+
await session.waitForIdle();
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
assert.equal(result.requests.length, 5);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
for (const projection of [false, true]) {
|
|
79
|
+
for (const newInput of [false, true]) {
|
|
80
|
+
test(
|
|
81
|
+
`native overflow compaction preserves input identity; projection=${projection}, new input=${newInput}`,
|
|
82
|
+
{ timeout: 30000 },
|
|
83
|
+
async () => {
|
|
84
|
+
let session, assignmentId, basis;
|
|
85
|
+
const result = await fixture(
|
|
86
|
+
async (n, body, manager) => {
|
|
87
|
+
const call = (name, args) => [{ name, args }];
|
|
88
|
+
if (n === 1)
|
|
89
|
+
return call("freeflow_delegate", {
|
|
90
|
+
operation: "assign",
|
|
91
|
+
contract: "Read evidence, then continue the same assignment after recovery.",
|
|
92
|
+
});
|
|
93
|
+
if (n === 2) {
|
|
94
|
+
const state = replay(manager.getBranch());
|
|
95
|
+
assignmentId = state.assignmentId;
|
|
96
|
+
basis = state.assignments.get(assignmentId).basisUserEntryId;
|
|
97
|
+
return call("read", { path: "evidence.txt" });
|
|
98
|
+
}
|
|
99
|
+
// The fixture returns one actual provider overflow here. Pi owns the
|
|
100
|
+
// compaction and automatic retry, not a manually invoked routing hook.
|
|
101
|
+
if (n === 3) return [];
|
|
102
|
+
if (n === 4) {
|
|
103
|
+
assert.ok(manager.getEntries().some((e) => e.type === "compaction"));
|
|
104
|
+
assert.equal(
|
|
105
|
+
manager.buildSessionContext().messages.some((m) => m.role === "user"),
|
|
106
|
+
false,
|
|
107
|
+
);
|
|
108
|
+
const state = replay(manager.getBranch());
|
|
109
|
+
assert.equal(state.assignmentId, assignmentId);
|
|
110
|
+
assert.equal(state.assignments.size, 1);
|
|
111
|
+
assert.equal([...state.executions.values()].at(-1).basisUserEntryId, basis);
|
|
112
|
+
assert.doesNotMatch(JSON.stringify(body), /New delivered user input requires Coordinator attention/);
|
|
113
|
+
assert.match(JSON.stringify(body), /Read evidence, then continue the same assignment after recovery/);
|
|
114
|
+
if (newInput) await session.steer("Stop further task reads; report the partial result.");
|
|
115
|
+
return call("read", { path: "unselected.txt" });
|
|
116
|
+
}
|
|
117
|
+
if (n === 5) {
|
|
118
|
+
assert.equal(
|
|
119
|
+
manager
|
|
120
|
+
.getBranch()
|
|
121
|
+
.filter((e) => e.message?.toolName === "read")
|
|
122
|
+
.at(-1).message.isError,
|
|
123
|
+
false,
|
|
124
|
+
);
|
|
125
|
+
if (newInput) {
|
|
126
|
+
assert.match(JSON.stringify(body), /New delivered user input requires Coordinator attention/);
|
|
127
|
+
assert.notEqual([...replay(manager.getBranch()).executions.values()].at(-1).basisUserEntryId, basis);
|
|
128
|
+
return call("read", { path: "evidence.txt" });
|
|
129
|
+
}
|
|
130
|
+
return call("freeflow_return", {
|
|
131
|
+
operation: "submit",
|
|
132
|
+
report: "Same assignment completed after compaction.",
|
|
133
|
+
outcome: "completed",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (newInput && n === 6) {
|
|
137
|
+
assert.equal(
|
|
138
|
+
manager
|
|
139
|
+
.getBranch()
|
|
140
|
+
.filter((e) => e.message?.toolName === "read")
|
|
141
|
+
.at(-1).message.isError,
|
|
142
|
+
true,
|
|
143
|
+
);
|
|
144
|
+
return call("freeflow_return", {
|
|
145
|
+
operation: "submit",
|
|
146
|
+
report: "Stopped for genuine new input.",
|
|
147
|
+
outcome: "partial",
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
assert.equal(body.model, "gpt-4o");
|
|
151
|
+
assert.equal(replay(manager.getBranch()).assignments.get(assignmentId).state, "returned");
|
|
152
|
+
return [];
|
|
153
|
+
},
|
|
154
|
+
projection,
|
|
155
|
+
async ({ manager }) => {
|
|
156
|
+
assert.equal(manager.getEntries().filter((e) => e.type === "compaction").length, 1);
|
|
157
|
+
},
|
|
158
|
+
true,
|
|
159
|
+
{
|
|
160
|
+
onSession: (s) => {
|
|
161
|
+
session = s;
|
|
162
|
+
},
|
|
163
|
+
settings: { compaction: { enabled: true, keepRecentTokens: 1, reserveTokens: 1 } },
|
|
164
|
+
response: (n, calls) =>
|
|
165
|
+
n === 3
|
|
166
|
+
? new Response(
|
|
167
|
+
JSON.stringify({
|
|
168
|
+
error: {
|
|
169
|
+
message: "Your input exceeds the context window of this model.",
|
|
170
|
+
type: "invalid_request_error",
|
|
171
|
+
code: "context_length_exceeded",
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
{ status: 400, headers: { "content-type": "application/json" } },
|
|
175
|
+
)
|
|
176
|
+
: response(n, calls),
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
assert.equal(result.requests.length, newInput ? 7 : 6);
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|