@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
|
@@ -6,11 +6,12 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
import test from "node:test";
|
|
7
7
|
|
|
8
8
|
import freeflowExtension from "../../dist/index.js";
|
|
9
|
-
import { PIFLOW_HOST } from "../
|
|
9
|
+
import { PIFLOW_HOST } from "../fixtures/pi-host.js";
|
|
10
10
|
|
|
11
|
-
function context(cwd) {
|
|
11
|
+
function context(cwd, systemPrompt = "") {
|
|
12
12
|
return {
|
|
13
13
|
cwd,
|
|
14
|
+
getSystemPrompt: () => systemPrompt,
|
|
14
15
|
sessionManager: {
|
|
15
16
|
getEntries: () => [],
|
|
16
17
|
getBranch: () => [],
|
|
@@ -65,20 +66,55 @@ function lastRuntimeState(messages) {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
test("re-entry recovery is stable and capability-neutral", async () => {
|
|
68
|
-
const [core, cognitiveRouting, conversationHistory] = await Promise.all([
|
|
69
|
+
const [core, cognitiveRouting, conversationHistory, routingSkill] = await Promise.all([
|
|
69
70
|
readFile(join(process.cwd(), "runtime", "prompts", "core.md"), "utf8"),
|
|
70
71
|
readFile(join(process.cwd(), "runtime", "prompts", "cognitive-routing.md"), "utf8"),
|
|
71
72
|
readFile(join(process.cwd(), "runtime", "prompts", "conversation-history.md"), "utf8"),
|
|
73
|
+
readFile(join(process.cwd(), "capabilities", "cognitive-routing", "SKILL.md"), "utf8"),
|
|
72
74
|
]);
|
|
73
75
|
|
|
76
|
+
assert.match(core, /## Load The Selected Method/);
|
|
77
|
+
assert.match(core, /Before applying a selected skill, read its current body/);
|
|
74
78
|
assert.match(core, /## Recover After Context Loss/);
|
|
75
79
|
assert.match(core, /latest Freeflow Runtime State/);
|
|
76
|
-
assert.
|
|
77
|
-
assert.match(
|
|
80
|
+
assert.match(core, /recovery is not a bypass/);
|
|
81
|
+
assert.match(core, /evidence of prior approval; check that the approval still applies/);
|
|
82
|
+
assert.match(core, /choose or retain the current owner/);
|
|
83
|
+
assert.match(core, /apply its method and gather or produce evidence/);
|
|
84
|
+
assert.match(core, /read the complete `full` record/);
|
|
85
|
+
assert.match(core, /Make material gaps, contradictions, deferrals, and user-owned decisions explicit/);
|
|
86
|
+
assert.match(cognitiveRouting, /Before relying on Automatic routing, read the complete cognitive-routing skill/);
|
|
87
|
+
assert.match(cognitiveRouting, /This bootstrap read is the only environment call/);
|
|
88
|
+
assert.match(cognitiveRouting, /If unavailable, stop and report the missing method/);
|
|
89
|
+
// The approved compact cue keeps bootstrap/control boundaries; the loaded
|
|
90
|
+
// method owns detailed role and handoff policy. Keep both obligations checked.
|
|
78
91
|
assert.match(
|
|
79
92
|
cognitiveRouting,
|
|
80
|
-
|
|
93
|
+
/Under Manual control, the held Coordinator, Helper, or Executor follows ordinary unsplit Workflow/,
|
|
81
94
|
);
|
|
95
|
+
assert.match(cognitiveRouting, /When routing is inactive, stop applying this split/);
|
|
96
|
+
assert.match(
|
|
97
|
+
cognitiveRouting,
|
|
98
|
+
/Use the latest Runtime State for control, profile, delegation mode, and current responsibility/,
|
|
99
|
+
);
|
|
100
|
+
assert.match(cognitiveRouting, /configured profiles in one agent\/session/);
|
|
101
|
+
assert.match(routingSkill, /Automatic role restrictions, delegation, and projection do not apply/);
|
|
102
|
+
assert.match(routingSkill, /Put the actual contract in `freeflow_delegate/);
|
|
103
|
+
assert.match(routingSkill, /A worker return ends its ordinary task work/);
|
|
104
|
+
assert.match(cognitiveRouting, /project every completed skill and instructional-reference read/);
|
|
105
|
+
assert.match(
|
|
106
|
+
routingSkill,
|
|
107
|
+
/For the current returned assessment, Coordinator uses `freeflow_unit\(operation: "recover"\)`/,
|
|
108
|
+
);
|
|
109
|
+
assert.match(routingSkill, /freeflow_unit\(operation: "recover"\)/);
|
|
110
|
+
assert.match(
|
|
111
|
+
routingSkill,
|
|
112
|
+
/Preserve the unit, assignment, original report, outcome, revision, selections, and assessment/,
|
|
113
|
+
);
|
|
114
|
+
assert.match(routingSkill, /read only exact admitted task paths/);
|
|
115
|
+
assert.match(routingSkill, /freeflow_return\(operation: "supplement"\)/);
|
|
116
|
+
assert.match(routingSkill, /Deliver the supplement or cancel recovery before `assess`/);
|
|
117
|
+
assert.match(routingSkill, /Do not simulate recovery by replacing an assignment\/report or opening a new unit/);
|
|
82
118
|
assert.doesNotMatch(
|
|
83
119
|
conversationHistory,
|
|
84
120
|
/Current user direction, live source truth, and present runtime state remain authoritative/,
|
|
@@ -98,8 +134,10 @@ test("composes the mandatory core fragments, optional capabilities, discovery, a
|
|
|
98
134
|
const order = [
|
|
99
135
|
"# Freeflow Stable Guidance",
|
|
100
136
|
"## Shared Terms",
|
|
137
|
+
"## Load The Selected Method",
|
|
101
138
|
"## Recover After Context Loss",
|
|
102
139
|
"## Three Nested Loops",
|
|
140
|
+
"## Evidence And Judgment",
|
|
103
141
|
"## Workflow Cue",
|
|
104
142
|
"## Action Selection Cue",
|
|
105
143
|
"## Supported Exit",
|
|
@@ -112,6 +150,18 @@ test("composes the mandatory core fragments, optional capabilities, discovery, a
|
|
|
112
150
|
order,
|
|
113
151
|
[...order].sort((a, b) => a - b),
|
|
114
152
|
);
|
|
153
|
+
assert.match(
|
|
154
|
+
prompt,
|
|
155
|
+
/Before applying a selected skill, read its current body when its exact method is absent from context/,
|
|
156
|
+
);
|
|
157
|
+
assert.match(prompt, /Verification establishes what direct evidence proves at the observed boundary/);
|
|
158
|
+
assert.match(
|
|
159
|
+
prompt,
|
|
160
|
+
/Review judges whether work or an artifact is aligned, correct, suitable, and sufficiently evidenced/,
|
|
161
|
+
);
|
|
162
|
+
assert.match(prompt, /Interpret requested intent, not sentence form/);
|
|
163
|
+
assert.match(prompt, /answer the question before any action that depends on it/);
|
|
164
|
+
assert.match(prompt, /An unresolved question does not automatically suspend independent, clearly authorized work/);
|
|
115
165
|
assert.doesNotMatch(prompt, /## Mode\b|strict-workflow|conversation mode|workflow mode/);
|
|
116
166
|
assert.doesNotMatch(prompt, /Skills prompt/);
|
|
117
167
|
assert.doesNotMatch(prompt, /# Workflow\n/);
|
|
@@ -124,6 +174,8 @@ test("composes the mandatory core fragments, optional capabilities, discovery, a
|
|
|
124
174
|
assert.match(runtimeState.content, /Context Virtualization: active/);
|
|
125
175
|
assert.match(runtimeState.content, /Conversation History: active/);
|
|
126
176
|
assert.match(runtimeState.content, /Cognitive Routing: inactive/);
|
|
177
|
+
assert.match(runtimeState.content, /Control: `unavailable`/);
|
|
178
|
+
assert.match(runtimeState.content, /Projection: `disabled`/);
|
|
127
179
|
assert.doesNotMatch(runtimeState.content, /Default mode|Active mode|Interaction Contract|Skills/);
|
|
128
180
|
|
|
129
181
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
@@ -137,6 +189,46 @@ test("composes the mandatory core fragments, optional capabilities, discovery, a
|
|
|
137
189
|
}
|
|
138
190
|
});
|
|
139
191
|
|
|
192
|
+
test("subagents retain reference definitions while optional capabilities stay inactive", async () => {
|
|
193
|
+
const cwd = await configuredRepo({
|
|
194
|
+
contextVirtualization: true,
|
|
195
|
+
conversationHistory: true,
|
|
196
|
+
cognitiveRouting: {
|
|
197
|
+
enabled: true,
|
|
198
|
+
profiles: {
|
|
199
|
+
coordinator: { provider: "test", model: "coordinator", thinking: "high" },
|
|
200
|
+
executor: { provider: "test", model: "executor", thinking: "max" },
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
try {
|
|
205
|
+
const { handlers, activeToolNames } = loadExtension(freeflowExtension, null);
|
|
206
|
+
const ctx = context(cwd, "<!-- freeflow-subagent-capabilities: disabled -->");
|
|
207
|
+
await handlers.get("session_start")({ type: "session_start" }, ctx);
|
|
208
|
+
|
|
209
|
+
const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
210
|
+
assert.match(before.systemPrompt, /# Freeflow Stable Guidance/);
|
|
211
|
+
assert.match(before.systemPrompt, /# Freeflow Interaction Contract/);
|
|
212
|
+
assert.match(before.systemPrompt, /## Cognitive Routing Cue/);
|
|
213
|
+
assert.match(before.systemPrompt, /## Context Virtualization Cue/);
|
|
214
|
+
assert.match(before.systemPrompt, /## Conversation History Cue/);
|
|
215
|
+
|
|
216
|
+
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
217
|
+
assert.ok(resources.skillPaths.some((path) => path.endsWith("/skills/action-selection/SKILL.md")));
|
|
218
|
+
assert.equal(resources.skillPaths.filter((path) => path.includes("/capabilities/")).length, 3);
|
|
219
|
+
|
|
220
|
+
const providerContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
221
|
+
const runtimeState = lastRuntimeState(providerContext.messages);
|
|
222
|
+
assert.match(runtimeState.content, /Freeflow: active/);
|
|
223
|
+
assert.match(runtimeState.content, /Context Virtualization: inactive/);
|
|
224
|
+
assert.match(runtimeState.content, /Conversation History: inactive/);
|
|
225
|
+
assert.match(runtimeState.content, /Cognitive Routing: inactive/);
|
|
226
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
227
|
+
} finally {
|
|
228
|
+
await rm(cwd, { recursive: true, force: true });
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
140
232
|
test("provider context reuses the before-agent surface until the next provider turn", async () => {
|
|
141
233
|
const cwd = await configuredRepo({ contextVirtualization: true });
|
|
142
234
|
try {
|
|
@@ -152,7 +244,7 @@ test("provider context reuses the before-agent surface until the next provider t
|
|
|
152
244
|
assert.match(lastRuntimeState(sameTurn.messages).content, /Context Virtualization: active/);
|
|
153
245
|
|
|
154
246
|
const next = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
155
|
-
assert.
|
|
247
|
+
assert.equal(next.systemPrompt, before.systemPrompt, "settings preserve the complete reference surface");
|
|
156
248
|
assert.match(next.systemPrompt, /# Freeflow Interaction Contract/);
|
|
157
249
|
assert.match(next.systemPrompt, /## Shared Terms/);
|
|
158
250
|
} finally {
|
|
@@ -197,7 +289,7 @@ test("missing optional prompt fragments preserve the mandatory core surface", as
|
|
|
197
289
|
}
|
|
198
290
|
});
|
|
199
291
|
|
|
200
|
-
test("missing child prompt
|
|
292
|
+
test("missing child prompt marks that capability unavailable while retaining its definitions", async () => {
|
|
201
293
|
const root = await mkdtemp(join(tmpdir(), "freeflow-child-prompt-failure-"));
|
|
202
294
|
const cwd = await configuredRepo({ contextVirtualization: true });
|
|
203
295
|
try {
|
|
@@ -217,13 +309,13 @@ test("missing child prompt removes only that capability from every model-facing
|
|
|
217
309
|
assert.doesNotMatch(before.systemPrompt, /## Context Virtualization Cue/);
|
|
218
310
|
assert.match(before.systemPrompt, /# Freeflow Interaction Contract/);
|
|
219
311
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
220
|
-
assert.ok(
|
|
221
|
-
assert.ok(
|
|
312
|
+
assert.ok(resources.skillPaths.some((path) => path.endsWith("/capabilities/context-virtualization/SKILL.md")));
|
|
313
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
222
314
|
|
|
223
315
|
await handlers.get("session_tree")({}, ctx);
|
|
224
|
-
assert.ok(
|
|
316
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
225
317
|
await handlers.get("session_compact")({}, ctx);
|
|
226
|
-
assert.ok(
|
|
318
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
227
319
|
|
|
228
320
|
const providerContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
229
321
|
assert.match(providerContext.messages.at(-1).content, /Context Virtualization: unavailable/);
|
|
@@ -233,9 +325,9 @@ test("missing child prompt removes only that capability from every model-facing
|
|
|
233
325
|
assert.doesNotMatch(whitespaceBefore.systemPrompt, /## Context Virtualization Cue/);
|
|
234
326
|
const whitespaceResources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
235
327
|
assert.ok(
|
|
236
|
-
|
|
328
|
+
whitespaceResources.skillPaths.some((path) => path.endsWith("/capabilities/context-virtualization/SKILL.md")),
|
|
237
329
|
);
|
|
238
|
-
assert.ok(
|
|
330
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
239
331
|
const whitespaceContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
240
332
|
assert.match(whitespaceContext.messages.at(-1).content, /Context Virtualization: unavailable/);
|
|
241
333
|
} finally {
|
|
@@ -244,7 +336,7 @@ test("missing child prompt removes only that capability from every model-facing
|
|
|
244
336
|
}
|
|
245
337
|
});
|
|
246
338
|
|
|
247
|
-
test("missing mandatory Interaction Contract
|
|
339
|
+
test("missing mandatory Interaction Contract retains a dormant surface with unavailable state", async () => {
|
|
248
340
|
const root = await mkdtemp(join(tmpdir(), "freeflow-interaction-contract-failure-"));
|
|
249
341
|
const cwd = await configuredRepo();
|
|
250
342
|
try {
|
|
@@ -261,19 +353,21 @@ test("missing mandatory Interaction Contract preserves the host prompt and hides
|
|
|
261
353
|
const { handlers, activeToolNames } = loadExtension(extension);
|
|
262
354
|
const ctx = context(cwd);
|
|
263
355
|
await handlers.get("session_start")({}, ctx);
|
|
264
|
-
assert.ok(
|
|
356
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
265
357
|
const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
266
|
-
assert.
|
|
358
|
+
assert.ok(before.systemPrompt.startsWith("base prompt\n\n"));
|
|
359
|
+
assert.match(before.systemPrompt, /guidance is dormant/);
|
|
267
360
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
268
|
-
assert.
|
|
361
|
+
assert.equal(resources.skillPaths.length, 27);
|
|
269
362
|
await handlers.get("session_tree")({}, ctx);
|
|
270
|
-
assert.ok(
|
|
363
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
271
364
|
|
|
272
365
|
await writeFile(join(root, "runtime", "prompts", "interaction-contract.md"), " \n\t", "utf8");
|
|
273
366
|
const whitespaceBefore = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
274
|
-
assert.
|
|
367
|
+
assert.ok(whitespaceBefore.systemPrompt.startsWith("base prompt\n\n"));
|
|
368
|
+
assert.match(whitespaceBefore.systemPrompt, /guidance is dormant/);
|
|
275
369
|
const whitespaceResources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
276
|
-
assert.
|
|
370
|
+
assert.equal(whitespaceResources.skillPaths.length, 27);
|
|
277
371
|
const whitespaceContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
278
372
|
assert.match(whitespaceContext.messages.at(-1).content, /Freeflow: unavailable/);
|
|
279
373
|
} finally {
|
|
@@ -282,7 +376,7 @@ test("missing mandatory Interaction Contract preserves the host prompt and hides
|
|
|
282
376
|
}
|
|
283
377
|
});
|
|
284
378
|
|
|
285
|
-
test("missing mandatory core prompt
|
|
379
|
+
test("missing mandatory core prompt retains a dormant surface with unavailable state", async () => {
|
|
286
380
|
const root = await mkdtemp(join(tmpdir(), "freeflow-core-prompt-failure-"));
|
|
287
381
|
const cwd = await configuredRepo({ contextVirtualization: true });
|
|
288
382
|
try {
|
|
@@ -299,9 +393,10 @@ test("missing mandatory core prompt preserves the host prompt and hides base ski
|
|
|
299
393
|
await handlers.get("session_start")({ type: "session_start" }, ctx);
|
|
300
394
|
|
|
301
395
|
const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
302
|
-
assert.
|
|
396
|
+
assert.ok(before.systemPrompt.startsWith("base prompt\n\n"));
|
|
397
|
+
assert.match(before.systemPrompt, /guidance is dormant/);
|
|
303
398
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
304
|
-
assert.
|
|
399
|
+
assert.equal(resources.skillPaths.length, 27);
|
|
305
400
|
const providerContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
306
401
|
assert.match(providerContext.messages.at(-1).content, /Freeflow: unavailable/);
|
|
307
402
|
assert.doesNotMatch(
|
|
@@ -310,15 +405,16 @@ test("missing mandatory core prompt preserves the host prompt and hides base ski
|
|
|
310
405
|
);
|
|
311
406
|
|
|
312
407
|
await handlers.get("session_tree")({}, ctx);
|
|
313
|
-
assert.ok(
|
|
408
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
314
409
|
await handlers.get("session_compact")({}, ctx);
|
|
315
|
-
assert.ok(
|
|
410
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
316
411
|
|
|
317
412
|
await writeFile(join(root, "runtime", "prompts", "core.md"), " \n\t", "utf8");
|
|
318
413
|
const whitespaceBefore = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
319
|
-
assert.
|
|
414
|
+
assert.ok(whitespaceBefore.systemPrompt.startsWith("base prompt\n\n"));
|
|
415
|
+
assert.match(whitespaceBefore.systemPrompt, /guidance is dormant/);
|
|
320
416
|
const whitespaceResources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
321
|
-
assert.
|
|
417
|
+
assert.equal(whitespaceResources.skillPaths.length, 27);
|
|
322
418
|
const whitespaceContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
323
419
|
assert.match(whitespaceContext.messages.at(-1).content, /Freeflow: unavailable/);
|
|
324
420
|
} finally {
|
|
@@ -333,7 +429,8 @@ test("Runtime State remains present while Freeflow is disabled without optional
|
|
|
333
429
|
const { handlers } = loadExtension();
|
|
334
430
|
const ctx = context(cwd);
|
|
335
431
|
const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
336
|
-
assert.
|
|
432
|
+
assert.ok(before.systemPrompt.startsWith("base prompt\n\n"));
|
|
433
|
+
assert.match(before.systemPrompt, /guidance is dormant/);
|
|
337
434
|
assert.equal(before.message, undefined);
|
|
338
435
|
|
|
339
436
|
const providerContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
@@ -343,7 +440,7 @@ test("Runtime State remains present while Freeflow is disabled without optional
|
|
|
343
440
|
assert.doesNotMatch(runtimeState.content, /Default mode|Active mode|Interaction Contract|Skills/);
|
|
344
441
|
|
|
345
442
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
346
|
-
assert.
|
|
443
|
+
assert.equal(resources.skillPaths.length, 27);
|
|
347
444
|
} finally {
|
|
348
445
|
await rm(cwd, { recursive: true, force: true });
|
|
349
446
|
}
|
|
@@ -8,7 +8,7 @@ import { promisify } from "node:util";
|
|
|
8
8
|
|
|
9
9
|
import freeflowExtension from "../../dist/index.js";
|
|
10
10
|
import { resetSessionOverrides, setSessionCoreOverride } from "../../dist/runtime/runtime-context.js";
|
|
11
|
-
import { PIFLOW_HOST } from "../
|
|
11
|
+
import { PIFLOW_HOST } from "../fixtures/pi-host.js";
|
|
12
12
|
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
14
|
|
|
@@ -51,12 +51,12 @@ function context(cwd, options = {}) {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function loadExtension() {
|
|
54
|
+
function loadExtension(host = PIFLOW_HOST, runtimeApi = {}) {
|
|
55
55
|
const handlers = new Map();
|
|
56
56
|
const commands = [];
|
|
57
57
|
const tools = [];
|
|
58
58
|
const pi = {
|
|
59
|
-
host
|
|
59
|
+
host,
|
|
60
60
|
registerTool(tool) {
|
|
61
61
|
tools.push(tool);
|
|
62
62
|
},
|
|
@@ -77,6 +77,7 @@ function loadExtension() {
|
|
|
77
77
|
},
|
|
78
78
|
setActiveTools() {},
|
|
79
79
|
};
|
|
80
|
+
Object.assign(pi, runtimeApi);
|
|
80
81
|
freeflowExtension(pi);
|
|
81
82
|
return { handlers, commands, pi };
|
|
82
83
|
}
|
|
@@ -263,14 +264,22 @@ test("Cognitive Routing preset cancellation preserves the previous repository va
|
|
|
263
264
|
cognitiveRouting: {
|
|
264
265
|
enabled: true,
|
|
265
266
|
profiles: {
|
|
266
|
-
|
|
267
|
-
|
|
267
|
+
coordinator: { provider: "test", model: "model-a", thinking: "low" },
|
|
268
|
+
executor: { provider: "test", model: "model-b", thinking: "high" },
|
|
268
269
|
},
|
|
269
270
|
},
|
|
270
271
|
});
|
|
271
272
|
const original = await readFile(join(cwd, ".freeflow/config.json"), "utf8");
|
|
272
273
|
try {
|
|
273
|
-
const { commands } = loadExtension(
|
|
274
|
+
const { commands } = loadExtension(
|
|
275
|
+
{},
|
|
276
|
+
{
|
|
277
|
+
setModel() {
|
|
278
|
+
return true;
|
|
279
|
+
},
|
|
280
|
+
setThinkingLevel() {},
|
|
281
|
+
},
|
|
282
|
+
);
|
|
274
283
|
const command = freeflowCommand(commands);
|
|
275
284
|
const settings = context(cwd, { isIdle: () => true });
|
|
276
285
|
settings.modelRegistry = cognitiveRoutingModelRegistry();
|
|
@@ -281,7 +290,7 @@ test("Cognitive Routing preset cancellation preserves the previous repository va
|
|
|
281
290
|
});
|
|
282
291
|
component.handleInput("\u001b[B");
|
|
283
292
|
component.handleInput("\r");
|
|
284
|
-
component.handleInput("\u001b[B");
|
|
293
|
+
for (let index = 0; index < 4; index += 1) component.handleInput("\u001b[B");
|
|
285
294
|
component.handleInput("\r");
|
|
286
295
|
component.handleInput("\u001b");
|
|
287
296
|
component.handleInput("\u001b");
|
|
@@ -300,13 +309,21 @@ test("Cognitive Routing settings refresh after enabling the capability", async (
|
|
|
300
309
|
cognitiveRouting: {
|
|
301
310
|
enabled: false,
|
|
302
311
|
profiles: {
|
|
303
|
-
|
|
304
|
-
|
|
312
|
+
coordinator: { provider: "test", model: "model-a", thinking: "low" },
|
|
313
|
+
executor: { provider: "test", model: "model-b", thinking: "high" },
|
|
305
314
|
},
|
|
306
315
|
},
|
|
307
316
|
});
|
|
308
317
|
try {
|
|
309
|
-
const { commands } = loadExtension(
|
|
318
|
+
const { commands } = loadExtension(
|
|
319
|
+
{},
|
|
320
|
+
{
|
|
321
|
+
setModel() {
|
|
322
|
+
return true;
|
|
323
|
+
},
|
|
324
|
+
setThinkingLevel() {},
|
|
325
|
+
},
|
|
326
|
+
);
|
|
310
327
|
const command = freeflowCommand(commands);
|
|
311
328
|
const settings = context(cwd, { isIdle: () => true });
|
|
312
329
|
settings.modelRegistry = cognitiveRoutingModelRegistry();
|
|
@@ -322,13 +339,63 @@ test("Cognitive Routing settings refresh after enabling the capability", async (
|
|
|
322
339
|
component.handleInput("\r");
|
|
323
340
|
await component.waitForWrites();
|
|
324
341
|
component.handleInput("\u001b");
|
|
325
|
-
assert.match(component.render(120).join("\n"), /Cognitive Routing\s+enabled \(
|
|
342
|
+
assert.match(component.render(120).join("\n"), /Cognitive Routing\s+enabled \(6\) configured/);
|
|
343
|
+
component.handleInput("\u001b");
|
|
344
|
+
return result;
|
|
345
|
+
};
|
|
346
|
+
await command.definition.handler("settings repo", settings);
|
|
347
|
+
const saved = JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8"));
|
|
348
|
+
assert.equal(saved.cognitiveRouting.enabled, true);
|
|
349
|
+
assert.equal(settings.reloads.length, 1);
|
|
350
|
+
} finally {
|
|
351
|
+
await rm(cwd, { recursive: true, force: true });
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("Cognitive Routing settings can disable projection without disabling routing", async () => {
|
|
356
|
+
const cwd = await configuredRepo({
|
|
357
|
+
cognitiveRouting: {
|
|
358
|
+
enabled: true,
|
|
359
|
+
projection: true,
|
|
360
|
+
profiles: {
|
|
361
|
+
coordinator: { provider: "test", model: "model-a", thinking: "low" },
|
|
362
|
+
executor: { provider: "test", model: "model-b", thinking: "high" },
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
try {
|
|
367
|
+
const { commands } = loadExtension(
|
|
368
|
+
{},
|
|
369
|
+
{
|
|
370
|
+
setModel() {
|
|
371
|
+
return true;
|
|
372
|
+
},
|
|
373
|
+
setThinkingLevel() {},
|
|
374
|
+
},
|
|
375
|
+
);
|
|
376
|
+
const command = freeflowCommand(commands);
|
|
377
|
+
const settings = context(cwd, { isIdle: () => true });
|
|
378
|
+
settings.modelRegistry = cognitiveRoutingModelRegistry();
|
|
379
|
+
settings.ui.custom = async (factory) => {
|
|
380
|
+
let result;
|
|
381
|
+
const component = factory({ requestRender() {} }, theme, {}, (value) => {
|
|
382
|
+
result = value;
|
|
383
|
+
});
|
|
384
|
+
component.handleInput("\u001b[B");
|
|
385
|
+
component.handleInput("\r");
|
|
386
|
+
for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
|
|
387
|
+
component.handleInput("\r");
|
|
388
|
+
component.handleInput("\u001b[B");
|
|
389
|
+
component.handleInput("\r");
|
|
390
|
+
await component.waitForWrites();
|
|
391
|
+
component.handleInput("\u001b");
|
|
326
392
|
component.handleInput("\u001b");
|
|
327
393
|
return result;
|
|
328
394
|
};
|
|
329
395
|
await command.definition.handler("settings repo", settings);
|
|
330
396
|
const saved = JSON.parse(await readFile(join(cwd, ".freeflow/config.json"), "utf8"));
|
|
331
397
|
assert.equal(saved.cognitiveRouting.enabled, true);
|
|
398
|
+
assert.equal(saved.cognitiveRouting.projection ?? false, false);
|
|
332
399
|
assert.equal(settings.reloads.length, 1);
|
|
333
400
|
} finally {
|
|
334
401
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { SessionState } from "../../dist/provider-support/astra/session-state.js";
|
|
4
|
+
import { assemble, ENTRY_TYPE } from "../../dist/provider-support/astra/history.js";
|
|
5
|
+
|
|
6
|
+
function fixture() {
|
|
7
|
+
const branch = [];
|
|
8
|
+
const append = (type, fields = {}) => {
|
|
9
|
+
const entry = { id: `entry-${branch.length}`, parentId: branch.at(-1)?.id ?? null, type, ...fields };
|
|
10
|
+
branch.push(entry);
|
|
11
|
+
return entry.id;
|
|
12
|
+
};
|
|
13
|
+
const reader = {
|
|
14
|
+
getBranch: () => branch,
|
|
15
|
+
getLeafId: () => branch.at(-1)?.id ?? null,
|
|
16
|
+
getSessionFile: () => undefined,
|
|
17
|
+
};
|
|
18
|
+
const store = new SessionState({ appendEntry: (customType, data) => append("custom", { customType, data }) }, reader);
|
|
19
|
+
return { branch, append, reader, store };
|
|
20
|
+
}
|
|
21
|
+
const payload = (effort = "high") => ({
|
|
22
|
+
model: "astra",
|
|
23
|
+
instructions: "same",
|
|
24
|
+
tools: [],
|
|
25
|
+
reasoning: { effort },
|
|
26
|
+
input: [{ role: "user", content: "same" }],
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
for (const boundary of ["compaction", "branch_summary"]) {
|
|
30
|
+
test(`${boundary} excludes pre-boundary history even with identical input`, async () => {
|
|
31
|
+
const f = fixture();
|
|
32
|
+
const old = assemble(payload(), "astra", f.store.generation(), f.reader.getLeafId(), []);
|
|
33
|
+
f.store.append(old.record);
|
|
34
|
+
const epoch = f.append(boundary);
|
|
35
|
+
f.append("model_change", { provider: "p", modelId: "luna" });
|
|
36
|
+
f.append("model_change", { provider: "p", modelId: "astra" });
|
|
37
|
+
assert.equal(f.store.generation(), epoch);
|
|
38
|
+
const current = assemble(payload("low"), "astra", epoch, f.reader.getLeafId(), await f.store.records());
|
|
39
|
+
assert.equal(current.baseline, "low");
|
|
40
|
+
assert.equal(current.record.parent, null);
|
|
41
|
+
// A persisted cross-boundary parent must not become valid through normalization.
|
|
42
|
+
f.store.append({ ...current.record, parent: old.record.id, baseline: "high", effective: "high" });
|
|
43
|
+
await assert.rejects(f.store.records(), /Invalid Astra history parent/);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test("unknown generation and invalid native ancestry are rejected, not normalized into eligibility", async () => {
|
|
48
|
+
const f = fixture();
|
|
49
|
+
const old = assemble(payload(), "astra", "unrelated-model-entry", null, []);
|
|
50
|
+
f.store.append(old.record);
|
|
51
|
+
await assert.rejects(f.store.records(), /generation unavailable/);
|
|
52
|
+
f.branch[0].parentId = "missing";
|
|
53
|
+
await assert.rejects(f.store.records(), /Invalid Astra session ancestry/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("changed envelope or earlier projected content cannot replay an old effort update", async () => {
|
|
57
|
+
const high = payload();
|
|
58
|
+
const first = assemble(high, "astra", "root", null, []);
|
|
59
|
+
const low = { ...payload("low"), input: [...high.input, { role: "user", content: "later" }] };
|
|
60
|
+
const second = assemble(low, "astra", "root", null, [first.record]);
|
|
61
|
+
for (const changed of [
|
|
62
|
+
{ ...low, instructions: "different" },
|
|
63
|
+
{ ...low, input: [{ role: "user", content: "structural omission" }] },
|
|
64
|
+
]) {
|
|
65
|
+
const result = assemble(changed, "astra", "root", null, [first.record, second.record]);
|
|
66
|
+
assert.equal(result.record.parent, null);
|
|
67
|
+
assert.deepEqual(
|
|
68
|
+
result.payload.input.filter((x) => x.type !== "configuration_update"),
|
|
69
|
+
changed.input,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const differentModel = assemble(low, "other-model", "root", null, [first.record, second.record]);
|
|
73
|
+
assert.equal(differentModel.record.parent, null);
|
|
74
|
+
assert.equal(differentModel.baseline, "low");
|
|
75
|
+
});
|