@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,306 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { mkdtemp, rm, readFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { AstraAdapter, requestKey } from "../../dist/provider-support/astra/adapter.js";
|
|
8
|
+
import { ENTRY_TYPE, assemble } from "../../dist/provider-support/astra/history.js";
|
|
9
|
+
|
|
10
|
+
const model = {
|
|
11
|
+
id: "gpt-6-astra",
|
|
12
|
+
provider: "openai-codex",
|
|
13
|
+
api: "openai-codex-responses",
|
|
14
|
+
baseUrl: "https://chatgpt.com/backend-api",
|
|
15
|
+
};
|
|
16
|
+
const u = (text) => ({ role: "user", content: text });
|
|
17
|
+
const a = (text) => ({ role: "assistant", content: text });
|
|
18
|
+
const request = (input, effort = "low") => ({
|
|
19
|
+
model: model.id,
|
|
20
|
+
store: false,
|
|
21
|
+
input,
|
|
22
|
+
instructions: "stable",
|
|
23
|
+
reasoning: { effort, summary: "auto" },
|
|
24
|
+
prompt_cache_key: "unchanged-key",
|
|
25
|
+
tools: [],
|
|
26
|
+
});
|
|
27
|
+
function fixture(manager = SessionManager.inMemory("/tmp/astra-fixture")) {
|
|
28
|
+
const status = [];
|
|
29
|
+
const pi = { appendEntry: (type, data) => manager.appendCustomEntry(type, data) };
|
|
30
|
+
const ctx = { model, sessionManager: manager, ui: { setStatus: (...args) => status.push(args) } };
|
|
31
|
+
return { pi, ctx, manager, status, adapter: new AstraAdapter(pi) };
|
|
32
|
+
}
|
|
33
|
+
const updates = (p) =>
|
|
34
|
+
p.input.flatMap((item, index) =>
|
|
35
|
+
item.type === "configuration_update" ? [{ index, effort: item.reasoning.effort }] : [],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
test("native ancestry records replay Low High Low with original anchors and unchanged selected effort", async () => {
|
|
39
|
+
const f = fixture(),
|
|
40
|
+
h = [u("one")];
|
|
41
|
+
await f.adapter.adapt(request(h), f.ctx);
|
|
42
|
+
h.push(a("one"), u("two"));
|
|
43
|
+
const high = await f.adapter.adapt(request(h, "high"), f.ctx);
|
|
44
|
+
assert.equal(high.reasoning.effort, "low");
|
|
45
|
+
assert.deepEqual(updates(high), [{ index: 2, effort: "high" }]);
|
|
46
|
+
h.push(a("two"), u("three"));
|
|
47
|
+
const low = await new AstraAdapter(f.pi).adapt(request(h), f.ctx);
|
|
48
|
+
assert.deepEqual(low.input.slice(0, high.input.length), high.input);
|
|
49
|
+
assert.deepEqual(updates(low), [
|
|
50
|
+
{ index: 2, effort: "high" },
|
|
51
|
+
{ index: 5, effort: "low" },
|
|
52
|
+
]);
|
|
53
|
+
const count = f.manager.getEntries().length;
|
|
54
|
+
assert.deepEqual(await f.adapter.adapt(request(h), f.ctx), low);
|
|
55
|
+
assert.equal(f.manager.getEntries().length, count, "retry writes no duplicate record");
|
|
56
|
+
assert.equal(low.prompt_cache_key, "unchanged-key");
|
|
57
|
+
assert.ok(f.manager.getEntries().every((e) => !JSON.stringify(e.data).includes('"content"')));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("first High is baseline; tool-loop switch occurs after completed results, not before an old user", async () => {
|
|
61
|
+
const f = fixture(),
|
|
62
|
+
h = [u("one")];
|
|
63
|
+
await f.adapter.adapt(request(h, "high"), f.ctx);
|
|
64
|
+
h.push(
|
|
65
|
+
{ type: "reasoning", encrypted_content: "opaque-signed-value", summary: [] },
|
|
66
|
+
{ type: "function_call", call_id: "call_1", name: "read", arguments: "{}" },
|
|
67
|
+
{ type: "function_call_output", call_id: "call_1", output: "body" },
|
|
68
|
+
);
|
|
69
|
+
const p = await f.adapter.adapt(request(h, "low"), f.ctx);
|
|
70
|
+
assert.equal(p.reasoning.effort, "high");
|
|
71
|
+
assert.deepEqual(updates(p), [{ index: 4, effort: "low" }]);
|
|
72
|
+
assert.deepEqual(p.input.slice(0, 4), h);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("changed/omitted/expanded input safely rebases; restored compatible branch recovers its own updates", async () => {
|
|
76
|
+
const f = fixture();
|
|
77
|
+
const h = [u("source")];
|
|
78
|
+
await f.adapter.adapt(request(h), f.ctx);
|
|
79
|
+
h.push(a("one"), u("two"));
|
|
80
|
+
const high = await f.adapter.adapt(request(h, "high"), f.ctx);
|
|
81
|
+
const changed = [u("different")];
|
|
82
|
+
const p = await f.adapter.adapt(request(changed, "high"), f.ctx);
|
|
83
|
+
assert.equal(p.reasoning.effort, "low");
|
|
84
|
+
assert.deepEqual(updates(p), [{ index: 0, effort: "high" }]);
|
|
85
|
+
const restored = await f.adapter.adapt(request([...h, a("two"), u("three")], "high"), f.ctx);
|
|
86
|
+
assert.deepEqual(restored.input.slice(0, high.input.length), high.input);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("same payload effort toggles never manufacture adjacent updates; fallback retains desired effort", async () => {
|
|
90
|
+
const f = fixture(),
|
|
91
|
+
h = [u("one")];
|
|
92
|
+
await f.adapter.adapt(request(h), f.ctx);
|
|
93
|
+
const high = await f.adapter.adapt(request(h, "high"), f.ctx);
|
|
94
|
+
assert.deepEqual(updates(high), [{ index: 1, effort: "high" }]);
|
|
95
|
+
const original = request(h, "low");
|
|
96
|
+
assert.equal(await f.adapter.adapt(original, f.ctx), original);
|
|
97
|
+
assert.equal(original.reasoning.effort, "low");
|
|
98
|
+
assert.deepEqual(updates(original), []);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("unsupported requests and existing native updates pass through without persistence", async () => {
|
|
102
|
+
const f = fixture();
|
|
103
|
+
for (const change of [
|
|
104
|
+
(p) => (p.model = "gpt-5.6-luna"),
|
|
105
|
+
(p) => (p.previous_response_id = "resp_x"),
|
|
106
|
+
(p) => (p.conversation = "c"),
|
|
107
|
+
(p) => (p.context_management = []),
|
|
108
|
+
(p) => (p.truncation = "auto"),
|
|
109
|
+
(p) => (p.reasoning.mode = "pro"),
|
|
110
|
+
(p) => p.input.push({ type: "configuration_update", reasoning: { effort: "high" } }),
|
|
111
|
+
(p) => (p.reasoning.effort = "off"),
|
|
112
|
+
(p) => (p.store = true),
|
|
113
|
+
]) {
|
|
114
|
+
const p = request([u("one")]);
|
|
115
|
+
change(p);
|
|
116
|
+
assert.equal(await f.adapter.adapt(p, f.ctx), p);
|
|
117
|
+
}
|
|
118
|
+
assert.equal(requestKey(request([]), { ...model, provider: "proxy" }), undefined);
|
|
119
|
+
assert.equal(requestKey(request([]), { ...model, baseUrl: "https://example.com" }), undefined);
|
|
120
|
+
assert.ok(
|
|
121
|
+
requestKey(request([]), {
|
|
122
|
+
...model,
|
|
123
|
+
provider: "openai",
|
|
124
|
+
api: "openai-responses",
|
|
125
|
+
baseUrl: "https://api.openai.com/v1",
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
assert.equal(f.manager.getEntries().length, 0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("append failure before or after mutation keeps original request and latches fallback", async () => {
|
|
132
|
+
for (const after of [false, true]) {
|
|
133
|
+
const f = fixture();
|
|
134
|
+
await f.adapter.adapt(request([u("one")]), f.ctx);
|
|
135
|
+
const real = f.pi.appendEntry;
|
|
136
|
+
f.pi.appendEntry = (...args) => {
|
|
137
|
+
if (after) real(...args);
|
|
138
|
+
throw Error("disk fault");
|
|
139
|
+
};
|
|
140
|
+
const original = request([u("one"), a("one"), u("two")], "high");
|
|
141
|
+
assert.equal(await f.adapter.adapt(original, f.ctx), original);
|
|
142
|
+
f.pi.appendEntry = real;
|
|
143
|
+
assert.equal(await f.adapter.adapt(original, f.ctx), original);
|
|
144
|
+
assert.ok(f.status.some((x) => x[1]?.includes("native effort")));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("corrupt effective-effort metadata cannot silently pin the wrong effort", async () => {
|
|
149
|
+
const f = fixture();
|
|
150
|
+
await f.adapter.adapt(request([u("one")]), f.ctx);
|
|
151
|
+
f.manager.getEntries().find((e) => e.customType === ENTRY_TYPE).data.effective = "high";
|
|
152
|
+
const original = request([u("one"), a("one"), u("two")], "high");
|
|
153
|
+
assert.equal(await f.adapter.adapt(original, f.ctx), original);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("selected ancestry, new sessions, model changes and compaction isolate effort histories", async () => {
|
|
157
|
+
const f = fixture();
|
|
158
|
+
f.manager.appendModelChange(model.provider, model.id);
|
|
159
|
+
await f.adapter.adapt(request([u("one")]), f.ctx);
|
|
160
|
+
const baseLeaf = f.manager.getLeafId();
|
|
161
|
+
await f.adapter.adapt(request([u("one"), a("one"), u("two")], "high"), f.ctx);
|
|
162
|
+
f.manager.branch(baseLeaf);
|
|
163
|
+
const branch = await f.adapter.adapt(request([u("one"), a("other"), u("sibling")], "medium"), f.ctx);
|
|
164
|
+
assert.deepEqual(updates(branch), [{ index: 2, effort: "medium" }]);
|
|
165
|
+
f.manager.appendModelChange("openai-codex", "gpt-5.6-luna");
|
|
166
|
+
f.manager.appendModelChange(model.provider, model.id);
|
|
167
|
+
const reset = await f.adapter.adapt(request([u("new")], "high"), f.ctx);
|
|
168
|
+
assert.equal(reset.reasoning.effort, "low");
|
|
169
|
+
assert.deepEqual(updates(reset), [{ index: 0, effort: "high" }]);
|
|
170
|
+
assert.deepEqual(
|
|
171
|
+
reset.input.filter((x) => x.type !== "configuration_update"),
|
|
172
|
+
[u("new")],
|
|
173
|
+
);
|
|
174
|
+
f.adapter.setCompacting(true);
|
|
175
|
+
const summary = request([u("summary")], "low");
|
|
176
|
+
assert.equal(await f.adapter.adapt(summary, f.ctx), summary);
|
|
177
|
+
f.adapter.setCompacting(false);
|
|
178
|
+
const other = fixture();
|
|
179
|
+
assert.equal((await f.adapter.adapt(request([u("new")], "medium"), other.ctx)).reasoning.effort, "medium");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("persisted labeled fork and reload preserve an anchored effort transition", async () => {
|
|
183
|
+
const root = await mkdtemp(join(tmpdir(), "astra-persist-"));
|
|
184
|
+
try {
|
|
185
|
+
const manager = SessionManager.create(root, root),
|
|
186
|
+
f = fixture(manager);
|
|
187
|
+
manager.appendMessage({ role: "user", content: "one", timestamp: 1 });
|
|
188
|
+
await f.adapter.adapt(request([u("one")]), f.ctx);
|
|
189
|
+
manager.appendMessage({
|
|
190
|
+
role: "assistant",
|
|
191
|
+
content: [{ type: "text", text: "one" }],
|
|
192
|
+
api: model.api,
|
|
193
|
+
provider: model.provider,
|
|
194
|
+
model: model.id,
|
|
195
|
+
usage: {
|
|
196
|
+
input: 1,
|
|
197
|
+
output: 1,
|
|
198
|
+
cacheRead: 0,
|
|
199
|
+
cacheWrite: 0,
|
|
200
|
+
totalTokens: 2,
|
|
201
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
202
|
+
},
|
|
203
|
+
stopReason: "stop",
|
|
204
|
+
timestamp: 2,
|
|
205
|
+
});
|
|
206
|
+
manager.appendLabelChange(manager.getLeafId(), "before effort change");
|
|
207
|
+
const h = [u("one"), a("one"), u("two")];
|
|
208
|
+
const high = await f.adapter.adapt(request(h, "high"), f.ctx);
|
|
209
|
+
assert.ok((await readFile(manager.getSessionFile(), "utf8")).includes(ENTRY_TYPE));
|
|
210
|
+
const forkFile = manager.createBranchedSession(manager.getLeafId());
|
|
211
|
+
assert.ok(forkFile, "native fork file created");
|
|
212
|
+
const reloaded = fixture(SessionManager.open(forkFile));
|
|
213
|
+
const p = await reloaded.adapter.adapt(request([...h, a("two"), u("three")], "low"), reloaded.ctx);
|
|
214
|
+
assert.deepEqual(p.input.slice(0, high.input.length), high.input);
|
|
215
|
+
assert.equal(p.reasoning.effort, "low");
|
|
216
|
+
} finally {
|
|
217
|
+
await rm(root, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("queued request from a replaced session passes through without a native append", async () => {
|
|
222
|
+
const f = fixture(),
|
|
223
|
+
p = request([u("old")], "high");
|
|
224
|
+
const pending = f.adapter.adapt(p, f.ctx);
|
|
225
|
+
f.adapter.reset();
|
|
226
|
+
assert.equal(await pending, p);
|
|
227
|
+
assert.equal(f.manager.getEntries().length, 0);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
for (const returnEffort of ["high", "low"]) {
|
|
231
|
+
test(`model round trip preserves original Low anchor through ${returnEffort} return and reload`, async () => {
|
|
232
|
+
const f = fixture();
|
|
233
|
+
f.manager.appendModelChange(model.provider, model.id);
|
|
234
|
+
const h = [u("start")];
|
|
235
|
+
await f.adapter.adapt(request(h, "high"), f.ctx);
|
|
236
|
+
h.push(a("first"), u("low"));
|
|
237
|
+
const low = await f.adapter.adapt(request(h), f.ctx);
|
|
238
|
+
f.manager.appendModelChange(model.provider, "gpt-5.6-luna");
|
|
239
|
+
const luna = request(h);
|
|
240
|
+
luna.model = "gpt-5.6-luna";
|
|
241
|
+
assert.equal(await f.adapter.adapt(luna, { ...f.ctx, model: { ...model, id: luna.model } }), luna);
|
|
242
|
+
f.manager.appendModelChange(model.provider, model.id);
|
|
243
|
+
h.push(a("second"), u("return"));
|
|
244
|
+
const resumed = await new AstraAdapter(f.pi).adapt(request(h, returnEffort), f.ctx);
|
|
245
|
+
assert.equal(resumed.reasoning.effort, "high");
|
|
246
|
+
assert.deepEqual(resumed.input.slice(0, low.input.length), low.input);
|
|
247
|
+
h.push(a("third"), u("low again"));
|
|
248
|
+
const again = await f.adapter.adapt(request(h), f.ctx);
|
|
249
|
+
assert.deepEqual(again.input.slice(0, resumed.input.length), resumed.input);
|
|
250
|
+
assert.equal(updates(again)[0].index, 2);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
test("legacy model-generation records survive a round trip without rewriting persisted metadata", async () => {
|
|
255
|
+
const root = await mkdtemp(join(tmpdir(), "astra-legacy-"));
|
|
256
|
+
try {
|
|
257
|
+
const f = fixture(SessionManager.create(root, root));
|
|
258
|
+
f.manager.appendMessage({ role: "user", content: "legacy", timestamp: 1 });
|
|
259
|
+
f.manager.appendMessage({
|
|
260
|
+
role: "assistant",
|
|
261
|
+
content: [{ type: "text", text: "legacy" }],
|
|
262
|
+
api: model.api,
|
|
263
|
+
provider: model.provider,
|
|
264
|
+
model: model.id,
|
|
265
|
+
usage: {
|
|
266
|
+
input: 1,
|
|
267
|
+
output: 1,
|
|
268
|
+
cacheRead: 0,
|
|
269
|
+
cacheWrite: 0,
|
|
270
|
+
totalTokens: 2,
|
|
271
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
272
|
+
},
|
|
273
|
+
stopReason: "stop",
|
|
274
|
+
timestamp: 2,
|
|
275
|
+
});
|
|
276
|
+
const oldGeneration = f.manager.appendModelChange(model.provider, model.id);
|
|
277
|
+
const key = requestKey(request([]), model);
|
|
278
|
+
const h = [u("legacy")];
|
|
279
|
+
const first = assemble(request(h, "high"), key, oldGeneration, f.manager.getLeafId(), []);
|
|
280
|
+
f.manager.appendCustomEntry(ENTRY_TYPE, first.record);
|
|
281
|
+
h.push(a("one"), u("two"));
|
|
282
|
+
const second = assemble(request(h), key, oldGeneration, f.manager.getLeafId(), [first.record]);
|
|
283
|
+
f.manager.appendCustomEntry(ENTRY_TYPE, second.record);
|
|
284
|
+
const saved = JSON.stringify(f.manager.getEntries());
|
|
285
|
+
f.manager.appendModelChange(model.provider, "gpt-5.6-luna");
|
|
286
|
+
f.manager.appendModelChange(model.provider, model.id);
|
|
287
|
+
const loaded = fixture(SessionManager.open(f.manager.getSessionFile()));
|
|
288
|
+
const resumed = await loaded.adapter.adapt(request([...h, a("two"), u("three")]), loaded.ctx);
|
|
289
|
+
assert.equal(resumed.reasoning.effort, "high");
|
|
290
|
+
assert.deepEqual(resumed.input.slice(0, second.payload.input.length), second.payload.input);
|
|
291
|
+
assert.equal(JSON.stringify(f.manager.getEntries().slice(0, JSON.parse(saved).length)), saved);
|
|
292
|
+
const again = fixture(SessionManager.open(loaded.manager.getSessionFile()));
|
|
293
|
+
const reloaded = await again.adapter.adapt(request([...h, a("two"), u("three"), a("three"), u("four")]), again.ctx);
|
|
294
|
+
assert.deepEqual(reloaded.input.slice(0, resumed.input.length), resumed.input);
|
|
295
|
+
assert.equal(JSON.stringify(again.manager.getEntries().slice(0, JSON.parse(saved).length)), saved);
|
|
296
|
+
} finally {
|
|
297
|
+
await rm(root, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("session reset clears adapter status without changing the next requested effort", async () => {
|
|
302
|
+
const f = fixture();
|
|
303
|
+
await f.adapter.adapt(request([u("one")]), f.ctx);
|
|
304
|
+
f.adapter.reset(f.ctx);
|
|
305
|
+
assert.deepEqual(f.status.at(-1), ["freeflow-astra-effort", undefined]);
|
|
306
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { mkdtemp, mkdir, writeFile, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import { response } from "../fixtures/routing-native.js";
|
|
9
|
+
|
|
10
|
+
test(
|
|
11
|
+
"bundled Pi CLI loads Freeflow adapter and changes effort inside a native tool loop",
|
|
12
|
+
{ timeout: 30000 },
|
|
13
|
+
async () => {
|
|
14
|
+
const root = await mkdtemp(join(tmpdir(), "astra-cli-")),
|
|
15
|
+
agentDir = join(root, "agent");
|
|
16
|
+
try {
|
|
17
|
+
await mkdir(agentDir);
|
|
18
|
+
await writeFile(
|
|
19
|
+
join(agentDir, "auth.json"),
|
|
20
|
+
JSON.stringify({ openai: { type: "api_key", key: "fixture-not-real" } }),
|
|
21
|
+
);
|
|
22
|
+
await writeFile(
|
|
23
|
+
join(agentDir, "settings.json"),
|
|
24
|
+
JSON.stringify({
|
|
25
|
+
defaultProvider: "openai",
|
|
26
|
+
defaultModel: "gpt-6-astra",
|
|
27
|
+
defaultThinkingLevel: "low",
|
|
28
|
+
transport: "sse",
|
|
29
|
+
compaction: { enabled: false },
|
|
30
|
+
retry: { enabled: false, maxRetries: 0, provider: { maxRetries: 0 } },
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
const capture = join(root, "requests.json"),
|
|
34
|
+
extension = join(root, "fixture.mjs");
|
|
35
|
+
await writeFile(
|
|
36
|
+
extension,
|
|
37
|
+
`import {writeFileSync} from 'node:fs';\n${response.toString()}\nexport default function(pi){pi.registerTool({name:'probe_raise_effort',label:'Probe effort',description:'Fixture only',parameters:{type:'object',properties:{}},execute:async()=>{pi.setThinkingLevel('high');return {content:[{type:'text',text:'Changed desired effort.'}]};}});const bodies=[];globalThis.fetch=async(url,init)=>{if(String(url)!=='https://api.openai.com/v1/responses')throw Error('Unexpected network');const body=JSON.parse(String(init.body));bodies.push(body);writeFileSync(${JSON.stringify(capture)},JSON.stringify(bodies));if(bodies.length>2)throw Error('Unexpected extra request');return response(bodies.length,bodies.length===1?[{name:'probe_raise_effort',args:{}}]:[],'OK');};}`,
|
|
38
|
+
);
|
|
39
|
+
const cli = fileURLToPath(new URL("./bundle/cli.js", import.meta.resolve("@earendil-works/pi-coding-agent")));
|
|
40
|
+
const entry = fileURLToPath(new URL("../../freeflow/index.js", import.meta.url));
|
|
41
|
+
const child = spawn(
|
|
42
|
+
process.execPath,
|
|
43
|
+
[
|
|
44
|
+
cli,
|
|
45
|
+
"-p",
|
|
46
|
+
"--mode",
|
|
47
|
+
"json",
|
|
48
|
+
"--no-extensions",
|
|
49
|
+
"--no-skills",
|
|
50
|
+
"--no-context-files",
|
|
51
|
+
"--no-prompt-templates",
|
|
52
|
+
"--no-themes",
|
|
53
|
+
"-e",
|
|
54
|
+
extension,
|
|
55
|
+
"-e",
|
|
56
|
+
entry,
|
|
57
|
+
"Run the effort fixture.",
|
|
58
|
+
],
|
|
59
|
+
{
|
|
60
|
+
cwd: root,
|
|
61
|
+
env: {
|
|
62
|
+
PATH: process.env.PATH,
|
|
63
|
+
PI_CODING_AGENT_DIR: agentDir,
|
|
64
|
+
PI_OFFLINE: "1",
|
|
65
|
+
PI_TELEMETRY: "0",
|
|
66
|
+
PI_SKIP_VERSION_CHECK: "1",
|
|
67
|
+
},
|
|
68
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
let output = "";
|
|
72
|
+
child.stdout.on("data", (d) => (output += d));
|
|
73
|
+
child.stderr.on("data", (d) => (output += d));
|
|
74
|
+
const timer = setTimeout(() => child.kill("SIGKILL"), 20000);
|
|
75
|
+
const code = await new Promise((resolve, reject) => {
|
|
76
|
+
child.on("error", reject);
|
|
77
|
+
child.on("close", resolve);
|
|
78
|
+
});
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
assert.equal(code, 0, output.slice(-4000));
|
|
81
|
+
const bodies = JSON.parse(await readFile(capture, "utf8"));
|
|
82
|
+
assert.equal(bodies.length, 2);
|
|
83
|
+
assert.equal(bodies[1].reasoning.effort, "low");
|
|
84
|
+
const updates = bodies[1].input.filter((i) => i.type === "configuration_update");
|
|
85
|
+
assert.deepEqual(updates, [{ type: "configuration_update", reasoning: { effort: "high" } }]);
|
|
86
|
+
assert.ok(bodies[1].input.some((i) => i.type === "function_call_output"));
|
|
87
|
+
assert.match(output, /OK/);
|
|
88
|
+
} finally {
|
|
89
|
+
await rm(root, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
);
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { mkdtemp, mkdir, rm, readFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
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 { registerProviderSupport } from "../../dist/provider-support/index.js";
|
|
15
|
+
import { ENTRY_TYPE } from "../../dist/provider-support/astra/history.js";
|
|
16
|
+
import { response } from "../fixtures/routing-native.js";
|
|
17
|
+
|
|
18
|
+
async function native(extension, run) {
|
|
19
|
+
const root = await mkdtemp(join(tmpdir(), "astra-native-")),
|
|
20
|
+
agentDir = join(root, "agent");
|
|
21
|
+
await mkdir(agentDir);
|
|
22
|
+
const previousFetch = globalThis.fetch,
|
|
23
|
+
requests = [],
|
|
24
|
+
errors = [];
|
|
25
|
+
let session;
|
|
26
|
+
globalThis.fetch = async (url, init) => {
|
|
27
|
+
assert.match(String(url), /^https:\/\/api\.openai\.com\/v1\/responses$/);
|
|
28
|
+
requests.push(JSON.parse(String(init.body)));
|
|
29
|
+
assert.ok(requests.length <= 12);
|
|
30
|
+
return response(requests.length, [], "OK");
|
|
31
|
+
};
|
|
32
|
+
try {
|
|
33
|
+
const runtime = await ModelRuntime.create({
|
|
34
|
+
authPath: join(agentDir, "auth.json"),
|
|
35
|
+
modelsPath: null,
|
|
36
|
+
modelsStorePath: join(agentDir, "models.json"),
|
|
37
|
+
allowModelNetwork: false,
|
|
38
|
+
});
|
|
39
|
+
await runtime.setRuntimeApiKey("openai", "fixture-only-key");
|
|
40
|
+
const settings = SettingsManager.inMemory({
|
|
41
|
+
transport: "sse",
|
|
42
|
+
compaction: { enabled: false, keepRecentTokens: 1, reserveTokens: 1 },
|
|
43
|
+
retry: { enabled: false, maxRetries: 0, provider: { maxRetries: 0 } },
|
|
44
|
+
});
|
|
45
|
+
const manager = SessionManager.create(root, join(root, "sessions"));
|
|
46
|
+
let cancelCompaction = false;
|
|
47
|
+
const loader = new DefaultResourceLoader({
|
|
48
|
+
cwd: root,
|
|
49
|
+
agentDir,
|
|
50
|
+
settingsManager: settings,
|
|
51
|
+
noExtensions: true,
|
|
52
|
+
noSkills: true,
|
|
53
|
+
noPromptTemplates: true,
|
|
54
|
+
noThemes: true,
|
|
55
|
+
noContextFiles: true,
|
|
56
|
+
systemPrompt: "Reply OK.",
|
|
57
|
+
extensionFactories: [
|
|
58
|
+
extension,
|
|
59
|
+
(pi) =>
|
|
60
|
+
pi.on("session_before_compact", () =>
|
|
61
|
+
cancelCompaction
|
|
62
|
+
? { cancel: true }
|
|
63
|
+
: {
|
|
64
|
+
compaction: {
|
|
65
|
+
summary: "Older material summarized.",
|
|
66
|
+
firstKeptEntryId: manager.getLeafId(),
|
|
67
|
+
tokensBefore: 100,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
await loader.reload();
|
|
74
|
+
assert.deepEqual(loader.getExtensions().errors, []);
|
|
75
|
+
({ session } = await createAgentSession({
|
|
76
|
+
cwd: root,
|
|
77
|
+
agentDir,
|
|
78
|
+
modelRuntime: runtime,
|
|
79
|
+
model: runtime.getModel("openai", "gpt-6-astra"),
|
|
80
|
+
thinkingLevel: "low",
|
|
81
|
+
settingsManager: settings,
|
|
82
|
+
sessionManager: manager,
|
|
83
|
+
resourceLoader: loader,
|
|
84
|
+
tools: [],
|
|
85
|
+
}));
|
|
86
|
+
await session.bindExtensions({ mode: "print", onError: (e) => errors.push(e) });
|
|
87
|
+
const prompt = async (text, effort) => {
|
|
88
|
+
if (effort) session.setThinkingLevel(effort);
|
|
89
|
+
await session.prompt(text);
|
|
90
|
+
await session.waitForIdle();
|
|
91
|
+
assert.equal(session.messages.at(-1)?.stopReason, "stop");
|
|
92
|
+
assert.deepEqual(errors, []);
|
|
93
|
+
return requests.at(-1);
|
|
94
|
+
};
|
|
95
|
+
await run({
|
|
96
|
+
session,
|
|
97
|
+
manager,
|
|
98
|
+
requests,
|
|
99
|
+
prompt,
|
|
100
|
+
root,
|
|
101
|
+
cancelCompaction: () => {
|
|
102
|
+
cancelCompaction = true;
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
assert.deepEqual(errors, []);
|
|
106
|
+
} finally {
|
|
107
|
+
session?.dispose();
|
|
108
|
+
globalThis.fetch = previousFetch;
|
|
109
|
+
await rm(root, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const updates = (p) =>
|
|
113
|
+
p.input.flatMap((x, i) => (x.type === "configuration_update" ? [{ index: i, effort: x.reasoning.effort }] : []));
|
|
114
|
+
|
|
115
|
+
test("real Pi request dispatch, reload, native tree navigation and compaction", { timeout: 30000 }, async () => {
|
|
116
|
+
await native(registerProviderSupport, async ({ session, manager, requests, prompt }) => {
|
|
117
|
+
await prompt("first", "low");
|
|
118
|
+
const high = await prompt("second", "high");
|
|
119
|
+
assert.equal(high.reasoning.effort, "low");
|
|
120
|
+
assert.equal(session.thinkingLevel, "high");
|
|
121
|
+
assert.deepEqual(updates(high), [{ index: 3, effort: "high" }]);
|
|
122
|
+
await session.reload();
|
|
123
|
+
const low = await prompt("third", "low");
|
|
124
|
+
assert.deepEqual(low.input.slice(0, high.input.length), high.input);
|
|
125
|
+
assert.deepEqual(updates(low), [
|
|
126
|
+
{ index: 3, effort: "high" },
|
|
127
|
+
{ index: 6, effort: "low" },
|
|
128
|
+
]);
|
|
129
|
+
const branchPoint = manager
|
|
130
|
+
.getBranch()
|
|
131
|
+
.find((e) => e.message?.role === "user" && e.message.content?.some?.((b) => b.text === "second"));
|
|
132
|
+
assert.ok(branchPoint);
|
|
133
|
+
// Native tree navigation selects real ancestry; the discarded later Low update must not survive.
|
|
134
|
+
await session.navigateTree(branchPoint.id, { summarize: false });
|
|
135
|
+
const sibling = await prompt("sibling", "medium");
|
|
136
|
+
assert.ok(updates(sibling).every((x) => x.effort !== "low"));
|
|
137
|
+
await session.compact();
|
|
138
|
+
const compacted = await prompt("after summary", "high");
|
|
139
|
+
assert.equal(compacted.reasoning.effort, "high");
|
|
140
|
+
assert.deepEqual(updates(compacted), []);
|
|
141
|
+
const persisted = await readFile(manager.getSessionFile(), "utf8");
|
|
142
|
+
assert.ok(persisted.includes(ENTRY_TYPE));
|
|
143
|
+
assert.ok(!manager.getBranch().some((e) => e.customType === "freeflow-routing-v2"));
|
|
144
|
+
assert.equal(requests.length, 5);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test(
|
|
149
|
+
"the Freeflow extension adapts Astra without repository activation or Cognitive Routing",
|
|
150
|
+
{ timeout: 30000 },
|
|
151
|
+
async () => {
|
|
152
|
+
await native(freeflow, async ({ session, manager, prompt }) => {
|
|
153
|
+
await prompt("one", "low");
|
|
154
|
+
const p = await prompt("two", "high");
|
|
155
|
+
assert.equal(p.reasoning.effort, "low");
|
|
156
|
+
assert.equal(session.thinkingLevel, "high");
|
|
157
|
+
assert.ok(updates(p).some((x) => x.effort === "high"));
|
|
158
|
+
assert.ok(manager.getBranch().some((e) => e.customType === ENTRY_TYPE));
|
|
159
|
+
assert.ok(!manager.getBranch().some((e) => e.customType === "freeflow-routing-v2"));
|
|
160
|
+
assert.ok(!(p.tools ?? []).some((t) => t.name.startsWith("freeflow_")));
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
test(
|
|
166
|
+
"rapid native effort changes coalesce and cancelled compaction releases adaptation",
|
|
167
|
+
{ timeout: 30000 },
|
|
168
|
+
async () => {
|
|
169
|
+
await native(registerProviderSupport, async ({ session, prompt, cancelCompaction }) => {
|
|
170
|
+
await prompt("first", "low");
|
|
171
|
+
session.setThinkingLevel("high");
|
|
172
|
+
session.setThinkingLevel("max");
|
|
173
|
+
session.setThinkingLevel("medium");
|
|
174
|
+
const medium = await prompt("second");
|
|
175
|
+
assert.deepEqual(updates(medium), [{ index: 3, effort: "medium" }]);
|
|
176
|
+
cancelCompaction();
|
|
177
|
+
await assert.rejects(session.compact(), /cancelled/);
|
|
178
|
+
const high = await prompt("third", "high");
|
|
179
|
+
assert.equal(high.reasoning.effort, "low");
|
|
180
|
+
assert.equal(updates(high).at(-1).effort, "high");
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
);
|
package/plugin.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
## Cognitive Routing Cue
|
|
2
2
|
|
|
3
|
-
Cognitive Routing
|
|
3
|
+
Cognitive Routing places work across configured profiles in one agent/session to improve quality, continuity, responsiveness, and total cost together. Coordinator owns user interaction, governing direction, delegation, and acceptance. When enabled, Helper is the normal delegate for supporting work; Executor is commissioned for substantive or consequential results, including implementation and artifacts. Helper and Executor share ordinary active history; only Coordinator delegates and only one profile executes at a time.
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
- **Automatic:** each user interaction begins in Reasoning, which owns conversational and user-facing analysis, discussion, decisions, questions, assessment, and reporting. This work needs no route marker.
|
|
5
|
+
Use the latest Runtime State for control, profile, delegation mode, and current responsibility. In Automatic control, follow the selected mode: helper-only permits Coordinator substantive production with Helper support; executor-only delegates environment work to Executor; both uses Helper by default for support and Executor for substantive work. A mode change does not retarget an accepted assignment or its recovery. Under Manual control, the held Coordinator, Helper, or Executor follows ordinary unsplit Workflow without Automatic role restrictions. Do not release the user's hold. When routing is inactive, stop applying this split.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Before relying on Automatic routing, read the complete cognitive-routing skill if its body is absent. This bootstrap read is the only environment call in that response. If unavailable, stop and report the missing method. Reload it after context loss before applying remembered routing rules.
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
When projection is on, the active worker must project every completed skill and instructional-reference read, including this bootstrap read. Follow the skill for work placement, environment access, evidence, handoffs, and recorded-worker recovery. Under Automatic routing, a worker return or passing check is not Coordinator acceptance; completed Slice closure requires Coordinator's supported judgment and explicit closure instruction. Manual control follows ordinary Workflow and Track Work instead.
|