@hassangameryt/freeflow 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.cursor-plugin/plugin.json +1 -1
  5. package/README.md +153 -128
  6. package/capabilities/cognitive-routing/SKILL.md +126 -274
  7. package/command-surface.json +7 -1
  8. package/gemini-extension.json +1 -1
  9. package/package.json +4 -4
  10. package/pi-extension/dist/cognitive-routing-v2/budget.js +42 -0
  11. package/pi-extension/dist/cognitive-routing-v2/config.js +136 -0
  12. package/pi-extension/dist/cognitive-routing-v2/projection.js +330 -0
  13. package/pi-extension/dist/cognitive-routing-v2/render.js +252 -0
  14. package/pi-extension/dist/cognitive-routing-v2/runtime.js +2181 -0
  15. package/pi-extension/dist/cognitive-routing-v2/schemas.js +173 -0
  16. package/pi-extension/dist/cognitive-routing-v2/state.js +828 -0
  17. package/pi-extension/dist/cognitive-routing-v2/tools.js +60 -0
  18. package/pi-extension/dist/cognitive-routing-v2/types.js +48 -0
  19. package/pi-extension/dist/context-virtualization/runtime.js +29 -7
  20. package/pi-extension/dist/freeflow-context/tool.js +12 -6
  21. package/pi-extension/dist/index.js +302 -626
  22. package/pi-extension/dist/provider-support/astra/adapter.js +110 -0
  23. package/pi-extension/dist/provider-support/astra/history.js +125 -0
  24. package/pi-extension/dist/provider-support/astra/index.js +11 -0
  25. package/pi-extension/dist/provider-support/astra/session-state.js +114 -0
  26. package/pi-extension/dist/provider-support/index.js +4 -0
  27. package/pi-extension/dist/runtime/request-history.js +216 -0
  28. package/pi-extension/dist/runtime/runtime-context.js +85 -16
  29. package/pi-extension/dist/session-sources/events.js +217 -0
  30. package/pi-extension/dist/session-sources/provenance.js +32 -0
  31. package/pi-extension/dist/session-sources/read-only-session.js +148 -0
  32. package/pi-extension/dist/session-sources/sources.js +277 -0
  33. package/pi-extension/dist/settings/settings-ui.js +274 -147
  34. package/pi-extension/src/cognitive-routing-v2/budget.ts +42 -0
  35. package/pi-extension/src/cognitive-routing-v2/config.ts +173 -0
  36. package/pi-extension/src/cognitive-routing-v2/projection.ts +375 -0
  37. package/pi-extension/src/cognitive-routing-v2/render.ts +277 -0
  38. package/pi-extension/src/cognitive-routing-v2/runtime.ts +2241 -0
  39. package/pi-extension/src/cognitive-routing-v2/schemas.ts +180 -0
  40. package/pi-extension/src/cognitive-routing-v2/state.ts +839 -0
  41. package/pi-extension/src/cognitive-routing-v2/tools.ts +62 -0
  42. package/pi-extension/src/cognitive-routing-v2/types.ts +254 -0
  43. package/pi-extension/src/context-virtualization/runtime.ts +30 -8
  44. package/pi-extension/src/freeflow-context/tool.ts +12 -6
  45. package/pi-extension/src/index.ts +308 -675
  46. package/pi-extension/src/provider-support/astra/adapter.ts +108 -0
  47. package/pi-extension/src/provider-support/astra/history.ts +145 -0
  48. package/pi-extension/src/provider-support/astra/index.ts +12 -0
  49. package/pi-extension/src/provider-support/astra/session-state.ts +113 -0
  50. package/pi-extension/src/provider-support/index.ts +5 -0
  51. package/pi-extension/src/runtime/request-history.ts +243 -0
  52. package/pi-extension/src/runtime/runtime-context.ts +100 -17
  53. package/pi-extension/src/session-sources/events.ts +230 -0
  54. package/pi-extension/src/session-sources/provenance.ts +39 -0
  55. package/pi-extension/src/session-sources/read-only-session.ts +170 -0
  56. package/pi-extension/src/session-sources/sources.ts +334 -0
  57. package/pi-extension/src/settings/settings-ui.ts +311 -158
  58. package/pi-extension/tests/cache-reuse/history.test.js +117 -0
  59. package/pi-extension/tests/cache-reuse/native.test.js +91 -0
  60. package/pi-extension/tests/cache-reuse/settings.test.js +211 -0
  61. package/pi-extension/tests/cognitive-routing-v2/communication.test.js +577 -0
  62. package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +183 -0
  63. package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
  64. package/pi-extension/tests/cognitive-routing-v2/native.test.js +474 -0
  65. package/pi-extension/tests/cognitive-routing-v2/performance.test.js +87 -0
  66. package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +300 -0
  67. package/pi-extension/tests/cognitive-routing-v2/projection.test.js +241 -0
  68. package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +1034 -0
  69. package/pi-extension/tests/cognitive-routing-v2/render.test.js +244 -0
  70. package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +423 -0
  71. package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +521 -0
  72. package/pi-extension/tests/cognitive-routing-v2/state.test.js +586 -0
  73. package/pi-extension/tests/context-virtualization/extension.test.js +33 -7
  74. package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
  75. package/pi-extension/tests/fixtures/routing-native.js +191 -0
  76. package/pi-extension/tests/integration/mode-free-core.test.js +6 -4
  77. package/pi-extension/tests/integration/pi-extension.test.js +655 -59
  78. package/pi-extension/tests/integration/prompt-architecture.test.js +127 -30
  79. package/pi-extension/tests/integration/unaffected-regressions.test.js +78 -11
  80. package/pi-extension/tests/provider-support/astra-boundaries.test.js +75 -0
  81. package/pi-extension/tests/provider-support/astra.test.js +306 -0
  82. package/pi-extension/tests/provider-support/cli.test.js +92 -0
  83. package/pi-extension/tests/provider-support/native.test.js +183 -0
  84. package/plugin.json +1 -1
  85. package/runtime/prompts/cognitive-routing.md +4 -5
  86. package/runtime/prompts/core.md +51 -45
  87. package/runtime/prompts/interaction-contract.md +5 -5
  88. package/skills/action-selection/SKILL.md +57 -97
  89. package/skills/design-for-depth/SKILL.md +58 -193
  90. package/skills/design-for-depth/references/implementation-decisions.md +76 -0
  91. package/skills/diagnose-failure/SKILL.md +64 -196
  92. package/skills/discuss/SKILL.md +64 -123
  93. package/skills/discuss/references/discussion-continuity.md +7 -3
  94. package/skills/execute-work/SKILL.md +69 -88
  95. package/skills/execute-work/references/execute-work-edges.md +9 -3
  96. package/skills/handoff/SKILL.md +44 -54
  97. package/skills/handoff/references/handoff-templates.md +15 -6
  98. package/skills/review-artifact/SKILL.md +80 -127
  99. package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +1 -1
  100. package/skills/review-work/SKILL.md +65 -115
  101. package/skills/review-work/references/independent-work-reviewer-contract.md +1 -1
  102. package/skills/simplify-code/SKILL.md +47 -100
  103. package/skills/track-work/SKILL.md +128 -119
  104. package/skills/track-work/references/working-record-format.md +14 -2
  105. package/skills/verify-work/SKILL.md +58 -62
  106. package/skills/verify-work/references/integration-evidence.md +4 -2
  107. package/skills/verify-work/references/test-design.md +89 -0
  108. package/skills/workflow/SKILL.md +73 -119
  109. package/skills/write-plan/SKILL.md +46 -77
  110. package/skills/write-plan/references/plan-shapes.md +5 -5
  111. package/skills/write-spec/SKILL.md +51 -63
  112. package/skills/write-spec/references/artifact-standards.md +2 -2
  113. package/skills/write-spec/references/decision-records.md +3 -2
  114. package/skills/write-spec/references/spec-shapes.md +15 -0
  115. package/pi-extension/dist/cognitive-routing/commands.js +0 -96
  116. package/pi-extension/dist/cognitive-routing/config.js +0 -232
  117. package/pi-extension/dist/cognitive-routing/controller.js +0 -1292
  118. package/pi-extension/dist/cognitive-routing/history.js +0 -451
  119. package/pi-extension/dist/cognitive-routing/host.js +0 -188
  120. package/pi-extension/dist/cognitive-routing/pi-routing-session.js +0 -414
  121. package/pi-extension/dist/cognitive-routing/pi-session-control.js +0 -320
  122. package/pi-extension/dist/cognitive-routing/runtime.js +0 -43
  123. package/pi-extension/dist/cognitive-routing/tool.js +0 -212
  124. package/pi-extension/dist/cognitive-routing/types.js +0 -6
  125. package/pi-extension/src/cognitive-routing/commands.ts +0 -129
  126. package/pi-extension/src/cognitive-routing/config.ts +0 -303
  127. package/pi-extension/src/cognitive-routing/controller.ts +0 -1628
  128. package/pi-extension/src/cognitive-routing/history.ts +0 -650
  129. package/pi-extension/src/cognitive-routing/host.ts +0 -266
  130. package/pi-extension/src/cognitive-routing/pi-routing-session.ts +0 -538
  131. package/pi-extension/src/cognitive-routing/pi-session-control.ts +0 -444
  132. package/pi-extension/src/cognitive-routing/runtime.ts +0 -57
  133. package/pi-extension/src/cognitive-routing/tool.ts +0 -273
  134. package/pi-extension/src/cognitive-routing/types.ts +0 -89
  135. package/pi-extension/tests/cognitive-routing/commands.test.js +0 -134
  136. package/pi-extension/tests/cognitive-routing/controller.test.js +0 -1128
  137. package/pi-extension/tests/cognitive-routing/extension-lifecycle.test.js +0 -1142
  138. package/pi-extension/tests/cognitive-routing/history.test.js +0 -274
  139. package/pi-extension/tests/cognitive-routing/host-identity.test.js +0 -63
  140. package/pi-extension/tests/cognitive-routing/pi-normal-lifecycle.test.js +0 -434
  141. package/pi-extension/tests/cognitive-routing/pi-session-control.test.js +0 -174
  142. package/pi-extension/tests/cognitive-routing/pi-session-history.test.js +0 -91
  143. package/pi-extension/tests/cognitive-routing/profile-resolution.test.js +0 -279
  144. package/pi-extension/tests/cognitive-routing/tool.test.js +0 -230
  145. package/skills/tdd/SKILL.md +0 -143
  146. package/skills/tdd/references/test-design.md +0 -96
  147. /package/pi-extension/tests/{cognitive-routing/host-fixture.js → fixtures/pi-host.js} +0 -0
@@ -0,0 +1,117 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import assert from "node:assert/strict";
5
+ import test from "node:test";
6
+ import { SessionManager } from "@earendil-works/pi-coding-agent";
7
+ import { RequestHistory } from "../../dist/runtime/request-history.js";
8
+ const state = (text) => ({ role: "custom", customType: "freeflow-runtime-state", content: text, display: false });
9
+ const communication = (text) => ({
10
+ role: "custom",
11
+ customType: "freeflow-routing-communication",
12
+ content: text,
13
+ display: false,
14
+ });
15
+ const user = (text) => ({ role: "user", content: [{ type: "text", text }], timestamp: 1 });
16
+ function fixture(manager = SessionManager.inMemory("/tmp/freeflow-cache")) {
17
+ const pi = { appendEntry: (type, data) => manager.appendCustomEntry(type, data) };
18
+ return { manager, pi, ctx: { sessionManager: manager, ui: { setStatus() {} } }, history: new RequestHistory(pi) };
19
+ }
20
+ test("generated state stays at its original position and changed state appends", async () => {
21
+ const f = fixture(),
22
+ one = await f.history.assemble([state("enabled"), user("one")], "ordinary", f.ctx);
23
+ const two = await f.history.assemble([user("one"), state("disabled"), user("two")], "ordinary", f.ctx);
24
+ assert.deepEqual(two.slice(0, one.length), one);
25
+ assert.equal(two.at(-1).content, "disabled");
26
+ const count = f.manager.getEntries().length;
27
+ assert.deepEqual(await f.history.assemble([state("disabled"), user("one"), user("two")], "ordinary", f.ctx), two);
28
+ assert.equal(f.manager.getEntries().length, count);
29
+ const reload = new RequestHistory(f.pi);
30
+ const three = await reload.assemble([state("disabled"), user("one"), user("two"), user("three")], "ordinary", f.ctx);
31
+ assert.deepEqual(three.slice(0, two.length), two);
32
+ });
33
+ test("view changes never import excluded bodies or stale required communication", async () => {
34
+ const f = fixture();
35
+ await f.history.assemble([user("SECRET"), state("worker"), communication("required report")], "ordinary", f.ctx);
36
+ const coordinator = await f.history.assemble([user("selected"), state("coordinator")], "coordinator", f.ctx);
37
+ assert.ok(!JSON.stringify(coordinator).includes("SECRET"));
38
+ const reduced = await f.history.assemble([user("new visible"), state("worker")], "ordinary", f.ctx);
39
+ assert.ok(!JSON.stringify(reduced).includes("SECRET"));
40
+ assert.ok(!JSON.stringify(reduced).includes("required report"));
41
+ });
42
+ test("context persistence failure returns the current qualified view without old bodies", async () => {
43
+ const f = fixture();
44
+ await f.history.assemble([user("old"), state("first")], "ordinary", f.ctx);
45
+ f.pi.appendEntry = () => {
46
+ throw Error("disk");
47
+ };
48
+ const current = [user("current"), state("second")];
49
+ assert.equal(await f.history.assemble(current, "ordinary", f.ctx), current);
50
+ });
51
+
52
+ test("changing only the view label retains the exact permitted prefix", async () => {
53
+ const f = fixture();
54
+ const first = await f.history.assemble([user("common"), state("ordinary")], "ordinary", f.ctx);
55
+ const next = await f.history.assemble([user("common"), state("coordinator")], "coordinator", f.ctx);
56
+ assert.deepEqual(next.slice(0, first.length), first);
57
+ });
58
+
59
+ test("native labeled forks preserve generated-state positions", async () => {
60
+ const root = await mkdtemp(join(tmpdir(), "freeflow-cache-fork-"));
61
+ try {
62
+ const manager = SessionManager.create(root, root),
63
+ f = fixture(manager);
64
+ const id = manager.appendMessage(user("one"));
65
+ manager.appendLabelChange(id, "named");
66
+ const first = await f.history.assemble([user("one"), state("enabled")], "ordinary", f.ctx);
67
+ manager.appendMessage({
68
+ role: "assistant",
69
+ content: [{ type: "text", text: "done" }],
70
+ timestamp: 2,
71
+ stopReason: "stop",
72
+ });
73
+ const path = manager.createBranchedSession(manager.getLeafId());
74
+ const nextFixture = fixture(SessionManager.open(path));
75
+ const next = await nextFixture.history.assemble(
76
+ [user("one"), user("two"), state("disabled")],
77
+ "ordinary",
78
+ nextFixture.ctx,
79
+ );
80
+ assert.deepEqual(next.slice(0, first.length), first);
81
+ } finally {
82
+ await rm(root, { recursive: true, force: true });
83
+ }
84
+ });
85
+
86
+ test("budget notices see assembled historical overhead and clear without rewriting old notices", async () => {
87
+ const f = fixture();
88
+ let length;
89
+ const first = await f.history.assemble([user("one"), state("enabled")], "ordinary", f.ctx, (messages) => {
90
+ length = messages.length;
91
+ return { role: "custom", customType: "freeflow-routing-budget", content: "Large input", display: false };
92
+ });
93
+ assert.equal(length, 2);
94
+ const next = await f.history.assemble([user("one"), user("two"), state("enabled")], "ordinary", f.ctx, (messages) => {
95
+ length = messages.length;
96
+ });
97
+ assert.equal(length, 4);
98
+ assert.deepEqual(next.slice(0, first.length), first);
99
+ assert.match(next.at(-1).content, /warning cleared/);
100
+ });
101
+
102
+ test("ending a restoration obligation retains already admitted communication until context changes", async () => {
103
+ const f = fixture();
104
+ const first = await f.history.assemble(
105
+ [user("one"), communication("accepted report"), state("active")],
106
+ "ordinary",
107
+ f.ctx,
108
+ );
109
+ const closed = await f.history.assemble([user("one"), user("two"), state("closed")], "ordinary", f.ctx);
110
+ assert.deepEqual(closed.slice(0, first.length), first);
111
+ const again = await f.history.assemble(
112
+ [user("one"), user("two"), communication("accepted report"), state("closed")],
113
+ "ordinary",
114
+ f.ctx,
115
+ );
116
+ assert.equal(again.filter((message) => message.content === "accepted report").length, 1);
117
+ });
@@ -0,0 +1,91 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { fixture } from "../fixtures/routing-native.js";
4
+ const profiles = Object.fromEntries(
5
+ ["coordinator", "helper", "executor"].map((p) => [
6
+ p,
7
+ { provider: "openai", model: "gpt-6-astra", thinking: p === "coordinator" ? "high" : "low" },
8
+ ]),
9
+ );
10
+ for (const projection of [false, true])
11
+ test(`native routing preserves per-view prefixes with projection=${projection}`, { timeout: 30000 }, async () => {
12
+ const last = new Map();
13
+ let ref;
14
+ const result = await fixture(
15
+ (n, body, manager) => {
16
+ const runtimeItems = body.input.filter((x) => JSON.stringify(x).includes("# Cognitive Routing Runtime State"));
17
+ const current = JSON.stringify(runtimeItems.at(-1));
18
+ const profile = current.includes("Profile: coordinator") ? "coordinator" : "worker";
19
+ const view = projection ? profile : "ordinary";
20
+ const previous = last.get(view);
21
+ if (previous) {
22
+ assert.deepEqual(body.tools, previous.tools, "fixed routing tool definitions");
23
+ assert.deepEqual(
24
+ body.input.slice(0, previous.input.length),
25
+ previous.input,
26
+ `prior ${view} prefix survives request ${n}`,
27
+ );
28
+ }
29
+ last.set(view, body);
30
+ if (n === 1)
31
+ return [
32
+ {
33
+ name: "freeflow_delegate",
34
+ args: { operation: "assign", worker: "helper", contract: "Read evidence.txt and select its result." },
35
+ },
36
+ ];
37
+ if (n === 2) return [{ name: "read", args: { path: "evidence.txt" } }];
38
+ if (n === 3) {
39
+ ref = "ctx:" + manager.getBranch().find((e) => e.message?.toolName === "read").id;
40
+ return [
41
+ ...(projection ? [{ name: "freeflow_project", args: { operation: "add", refs: [ref] } }] : []),
42
+ { name: "freeflow_return", args: { operation: "submit", report: "Evidence read.", outcome: "completed" } },
43
+ ];
44
+ }
45
+ if (n === 4)
46
+ return [
47
+ {
48
+ name: "freeflow_unit",
49
+ args: { operation: "close", outcome: "accepted", assessment: "Exact evidence observed." },
50
+ },
51
+ ];
52
+ if (n === 5)
53
+ return [
54
+ {
55
+ name: "freeflow_delegate",
56
+ args: { operation: "assign", worker: "executor", contract: "Read evidence.txt and return." },
57
+ },
58
+ ];
59
+ if (n === 6) return [{ name: "read", args: { path: "evidence.txt" } }];
60
+ if (n === 7) {
61
+ const latest = manager
62
+ .getBranch()
63
+ .filter((e) => e.message?.toolName === "read")
64
+ .at(-1);
65
+ return [
66
+ ...(projection
67
+ ? [{ name: "freeflow_project", args: { operation: "add", refs: ["ctx:" + latest.id] } }]
68
+ : []),
69
+ {
70
+ name: "freeflow_return",
71
+ args: { operation: "submit", report: "Executor evidence read.", outcome: "completed" },
72
+ },
73
+ ];
74
+ }
75
+ if (n === 8)
76
+ return [
77
+ {
78
+ name: "freeflow_unit",
79
+ args: { operation: "close", outcome: "accepted", assessment: "Second exact result checked." },
80
+ },
81
+ ];
82
+ return [];
83
+ },
84
+ projection,
85
+ undefined,
86
+ false,
87
+ { cognitiveRouting: { enabled: true, projection, delegation: "both", profiles } },
88
+ );
89
+ assert.equal(result.requests.length, 9);
90
+ assert.ok(result.entries.some((e) => e.customType === "freeflow-request-history-v1"));
91
+ });
@@ -0,0 +1,211 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { mkdtemp, mkdir, writeFile, rm } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import {
7
+ createAgentSession,
8
+ DefaultResourceLoader,
9
+ ModelRuntime,
10
+ SessionManager,
11
+ SettingsManager,
12
+ } from "@earendil-works/pi-coding-agent";
13
+ import freeflow from "../../dist/index.js";
14
+ import { response } from "../fixtures/routing-native.js";
15
+
16
+ test(
17
+ "native Freeflow settings retain the exact prefix; archive and restore change only real evidence",
18
+ { timeout: 30000 },
19
+ async () => {
20
+ const root = await mkdtemp(join(tmpdir(), "freeflow-settings-cache-")),
21
+ agentDir = join(root, "agent");
22
+ await mkdir(agentDir);
23
+ await mkdir(join(root, ".freeflow"));
24
+ const config = join(root, ".freeflow/config.json");
25
+ await writeFile(config, JSON.stringify({ enabled: true }));
26
+ await writeFile(join(root, "evidence.txt"), "PRIVATE_RAW_EVIDENCE_716");
27
+ let session, action, ref;
28
+ const requests = [],
29
+ errors = [],
30
+ prior = globalThis.fetch;
31
+ const manager = SessionManager.create(root, join(root, "sessions"));
32
+ globalThis.fetch = async (url, init) => {
33
+ assert.equal(String(url), "https://api.openai.com/v1/responses");
34
+ const body = JSON.parse(String(init.body));
35
+ requests.push(body);
36
+ assert.ok(requests.length <= 20);
37
+ let calls = [];
38
+ if (action === "read") {
39
+ action = undefined;
40
+ calls = [{ name: "read", args: { path: "evidence.txt" } }];
41
+ }
42
+ if (action === "archive") {
43
+ action = undefined;
44
+ calls = [
45
+ {
46
+ name: "freeflow_context",
47
+ args: { operation: "archive", targets: [{ ref, retained: "The completed evidence was checked." }] },
48
+ },
49
+ ];
50
+ }
51
+ if (action === "restore") {
52
+ action = undefined;
53
+ calls = [{ name: "freeflow_context", args: { operation: "restore", refs: [ref] } }];
54
+ }
55
+ if (action === "denied") {
56
+ action = undefined;
57
+ calls = [
58
+ { name: "freeflow_context", args: { operation: "search", query: "private" } },
59
+ { name: "freeflow_delegate", args: { operation: "assign", contract: "Must not execute" } },
60
+ ];
61
+ }
62
+ return response(requests.length, calls, "OK");
63
+ };
64
+ try {
65
+ const runtime = await ModelRuntime.create({
66
+ authPath: join(agentDir, "auth.json"),
67
+ modelsPath: null,
68
+ modelsStorePath: join(agentDir, "models.json"),
69
+ allowModelNetwork: false,
70
+ });
71
+ await runtime.setRuntimeApiKey("openai", "fixture-key");
72
+ const settings = SettingsManager.inMemory({
73
+ transport: "sse",
74
+ compaction: { enabled: false },
75
+ retry: { enabled: false, maxRetries: 0, provider: { maxRetries: 0 } },
76
+ });
77
+ const loader = new DefaultResourceLoader({
78
+ cwd: root,
79
+ agentDir,
80
+ settingsManager: settings,
81
+ noExtensions: true,
82
+ noSkills: true,
83
+ noPromptTemplates: true,
84
+ noThemes: true,
85
+ noContextFiles: true,
86
+ systemPrompt: "Synthetic fixture.",
87
+ extensionFactories: [freeflow],
88
+ });
89
+ await loader.reload();
90
+ ({ session } = await createAgentSession({
91
+ cwd: root,
92
+ agentDir,
93
+ modelRuntime: runtime,
94
+ model: runtime.getModel("openai", "gpt-6-astra"),
95
+ thinkingLevel: "high",
96
+ settingsManager: settings,
97
+ sessionManager: manager,
98
+ resourceLoader: loader,
99
+ }));
100
+ await session.bindExtensions({ mode: "print", onError: (e) => errors.push(e) });
101
+ const prompt = async () => {
102
+ await session.prompt("Continue the synthetic fixture.");
103
+ await session.waitForIdle();
104
+ assert.equal(session.messages.at(-1).stopReason, "stop");
105
+ assert.deepEqual(errors, []);
106
+ return requests.at(-1);
107
+ };
108
+ action = "read";
109
+ await prompt();
110
+ const read = manager.getBranch().find((e) => e.message?.toolName === "read");
111
+ ref = "ctx:" + read.id;
112
+ const original = JSON.stringify(read);
113
+ let previous = requests.at(-1);
114
+ const stable = async () => {
115
+ const body = await prompt();
116
+ assert.deepEqual(body.tools, previous.tools);
117
+ assert.deepEqual(body.input.slice(0, previous.input.length), previous.input);
118
+ previous = body;
119
+ };
120
+ await session.prompt("/freeflow disable");
121
+ await stable();
122
+ action = "denied";
123
+ await stable();
124
+ assert.ok(
125
+ manager
126
+ .getBranch()
127
+ .filter((e) => ["freeflow_context", "freeflow_delegate"].includes(e.message?.toolName))
128
+ .every((e) => e.message.isError),
129
+ );
130
+ assert.ok(!manager.getBranch().some((e) => e.customType === "freeflow-routing-v2"));
131
+ await session.prompt("/freeflow enable");
132
+ await stable();
133
+ await writeFile(config, JSON.stringify({ enabled: true, conversationHistory: true }));
134
+ await session.reload();
135
+ await stable();
136
+ await writeFile(
137
+ config,
138
+ JSON.stringify({ enabled: true, conversationHistory: true, contextVirtualization: true }),
139
+ );
140
+ await session.reload();
141
+ await stable();
142
+ await writeFile(join(root, ".freeflow/local.json"), JSON.stringify({ enabled: true }));
143
+ await session.reload();
144
+ await stable();
145
+ const routing = {
146
+ enabled: true,
147
+ projection: false,
148
+ profiles: {
149
+ coordinator: { provider: "openai", model: "gpt-6-astra", thinking: "high" },
150
+ executor: { provider: "openai", model: "gpt-6-astra", thinking: "low" },
151
+ },
152
+ };
153
+ await writeFile(
154
+ config,
155
+ JSON.stringify({
156
+ enabled: true,
157
+ conversationHistory: true,
158
+ contextVirtualization: true,
159
+ cognitiveRouting: routing,
160
+ }),
161
+ );
162
+ await session.reload();
163
+ await stable();
164
+ await session.prompt("/freeflow profile executor");
165
+ await stable();
166
+ await session.prompt("/freeflow profile auto");
167
+ await stable();
168
+ routing.profiles.executor.thinking = "medium";
169
+ await writeFile(
170
+ config,
171
+ JSON.stringify({
172
+ enabled: true,
173
+ conversationHistory: true,
174
+ contextVirtualization: true,
175
+ cognitiveRouting: routing,
176
+ }),
177
+ );
178
+ await session.reload();
179
+ await stable();
180
+ routing.projection = true;
181
+ await writeFile(config, JSON.stringify({ enabled: true, cognitiveRouting: routing }));
182
+ await session.reload();
183
+ await stable();
184
+ routing.projection = false;
185
+ await writeFile(
186
+ config,
187
+ JSON.stringify({
188
+ enabled: true,
189
+ conversationHistory: true,
190
+ contextVirtualization: true,
191
+ cognitiveRouting: routing,
192
+ }),
193
+ );
194
+ await session.reload();
195
+ await stable();
196
+ action = "archive";
197
+ const archived = await prompt();
198
+ assert.ok(!JSON.stringify(archived).includes("PRIVATE_RAW_EVIDENCE_716"));
199
+ assert.deepEqual(archived.tools, previous.tools);
200
+ action = "restore";
201
+ const restored = await prompt();
202
+ assert.ok(JSON.stringify(restored).includes("PRIVATE_RAW_EVIDENCE_716"));
203
+ assert.deepEqual(restored.tools, previous.tools);
204
+ assert.equal(JSON.stringify(manager.getEntry(read.id)), original, "canonical evidence untouched");
205
+ } finally {
206
+ session?.dispose();
207
+ globalThis.fetch = prior;
208
+ await rm(root, { recursive: true, force: true });
209
+ }
210
+ },
211
+ );