@hassangameryt/freeflow 0.7.2 → 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 +147 -127
- package/capabilities/cognitive-routing/SKILL.md +162 -93
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pi-extension/dist/cognitive-routing-v2/config.js +35 -19
- package/pi-extension/dist/cognitive-routing-v2/projection.js +53 -44
- package/pi-extension/dist/cognitive-routing-v2/render.js +31 -10
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +701 -105
- package/pi-extension/dist/cognitive-routing-v2/schemas.js +90 -24
- package/pi-extension/dist/cognitive-routing-v2/state.js +243 -27
- package/pi-extension/dist/cognitive-routing-v2/tools.js +19 -50
- package/pi-extension/dist/cognitive-routing-v2/types.js +5 -1
- package/pi-extension/dist/context-virtualization/runtime.js +29 -7
- package/pi-extension/dist/freeflow-context/tool.js +7 -6
- package/pi-extension/dist/index.js +36 -15
- 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 +29 -10
- package/pi-extension/dist/session-sources/events.js +22 -0
- package/pi-extension/dist/session-sources/provenance.js +32 -0
- package/pi-extension/dist/session-sources/sources.js +51 -7
- package/pi-extension/dist/settings/settings-ui.js +247 -47
- package/pi-extension/src/cognitive-routing-v2/config.ts +49 -19
- package/pi-extension/src/cognitive-routing-v2/projection.ts +53 -43
- package/pi-extension/src/cognitive-routing-v2/render.ts +31 -10
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +712 -105
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +95 -24
- package/pi-extension/src/cognitive-routing-v2/state.ts +244 -27
- package/pi-extension/src/cognitive-routing-v2/tools.ts +19 -50
- package/pi-extension/src/cognitive-routing-v2/types.ts +35 -3
- package/pi-extension/src/context-virtualization/runtime.ts +30 -8
- package/pi-extension/src/freeflow-context/tool.ts +7 -6
- package/pi-extension/src/index.ts +36 -14
- 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 +32 -10
- package/pi-extension/src/session-sources/events.ts +22 -0
- package/pi-extension/src/session-sources/provenance.ts +39 -0
- package/pi-extension/src/session-sources/sources.ts +77 -6
- package/pi-extension/src/settings/settings-ui.ts +276 -46
- 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 +94 -1
- package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +557 -0
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +34 -1
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +55 -0
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +800 -2
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +15 -1
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +273 -3
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +312 -1
- package/pi-extension/tests/context-virtualization/extension.test.js +20 -10
- package/pi-extension/tests/context-virtualization/runtime.test.js +17 -3
- package/pi-extension/tests/fixtures/routing-native.js +4 -1
- package/pi-extension/tests/integration/mode-free-core.test.js +4 -3
- package/pi-extension/tests/integration/pi-extension.test.js +508 -29
- package/pi-extension/tests/integration/prompt-architecture.test.js +56 -40
- package/pi-extension/tests/integration/unaffected-regressions.test.js +3 -3
- 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 -4
- package/skills/design-for-depth/SKILL.md +1 -1
- package/skills/design-for-depth/references/implementation-decisions.md +3 -3
- package/skills/discuss/SKILL.md +2 -2
|
@@ -83,27 +83,38 @@ test("re-entry recovery is stable and capability-neutral", async () => {
|
|
|
83
83
|
assert.match(core, /apply its method and gather or produce evidence/);
|
|
84
84
|
assert.match(core, /read the complete `full` record/);
|
|
85
85
|
assert.match(core, /Make material gaps, contradictions, deferrals, and user-owned decisions explicit/);
|
|
86
|
-
assert.match(cognitiveRouting, /Before relying on
|
|
86
|
+
assert.match(cognitiveRouting, /Before relying on Automatic routing, read the complete cognitive-routing skill/);
|
|
87
87
|
assert.match(cognitiveRouting, /This bootstrap read is the only environment call/);
|
|
88
88
|
assert.match(cognitiveRouting, /If unavailable, stop and report the missing method/);
|
|
89
89
|
// The approved compact cue keeps bootstrap/control boundaries; the loaded
|
|
90
90
|
// method owns detailed role and handoff policy. Keep both obligations checked.
|
|
91
91
|
assert.match(
|
|
92
92
|
cognitiveRouting,
|
|
93
|
-
/Under Manual
|
|
93
|
+
/Under Manual control, the held Coordinator, Helper, or Executor follows ordinary unsplit Workflow/,
|
|
94
94
|
);
|
|
95
|
-
assert.match(cognitiveRouting, /
|
|
96
|
-
assert.match(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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/);
|
|
100
104
|
assert.match(cognitiveRouting, /project every completed skill and instructional-reference read/);
|
|
101
|
-
assert.match(routingSkill, /Attach recovery to the existing responsibility/);
|
|
102
105
|
assert.match(
|
|
103
106
|
routingSkill,
|
|
104
|
-
/
|
|
107
|
+
/For the current returned assessment, Coordinator uses `freeflow_unit\(operation: "recover"\)`/,
|
|
105
108
|
);
|
|
106
|
-
assert.match(routingSkill, /
|
|
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/);
|
|
107
118
|
assert.doesNotMatch(
|
|
108
119
|
conversationHistory,
|
|
109
120
|
/Current user direction, live source truth, and present runtime state remain authoritative/,
|
|
@@ -178,7 +189,7 @@ test("composes the mandatory core fragments, optional capabilities, discovery, a
|
|
|
178
189
|
}
|
|
179
190
|
});
|
|
180
191
|
|
|
181
|
-
test("subagents
|
|
192
|
+
test("subagents retain reference definitions while optional capabilities stay inactive", async () => {
|
|
182
193
|
const cwd = await configuredRepo({
|
|
183
194
|
contextVirtualization: true,
|
|
184
195
|
conversationHistory: true,
|
|
@@ -198,13 +209,13 @@ test("subagents keep Freeflow core and base skills without optional capabilities
|
|
|
198
209
|
const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
199
210
|
assert.match(before.systemPrompt, /# Freeflow Stable Guidance/);
|
|
200
211
|
assert.match(before.systemPrompt, /# Freeflow Interaction Contract/);
|
|
201
|
-
assert.
|
|
202
|
-
assert.
|
|
203
|
-
assert.
|
|
212
|
+
assert.match(before.systemPrompt, /## Cognitive Routing Cue/);
|
|
213
|
+
assert.match(before.systemPrompt, /## Context Virtualization Cue/);
|
|
214
|
+
assert.match(before.systemPrompt, /## Conversation History Cue/);
|
|
204
215
|
|
|
205
216
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
206
217
|
assert.ok(resources.skillPaths.some((path) => path.endsWith("/skills/action-selection/SKILL.md")));
|
|
207
|
-
assert.
|
|
218
|
+
assert.equal(resources.skillPaths.filter((path) => path.includes("/capabilities/")).length, 3);
|
|
208
219
|
|
|
209
220
|
const providerContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
210
221
|
const runtimeState = lastRuntimeState(providerContext.messages);
|
|
@@ -212,7 +223,7 @@ test("subagents keep Freeflow core and base skills without optional capabilities
|
|
|
212
223
|
assert.match(runtimeState.content, /Context Virtualization: inactive/);
|
|
213
224
|
assert.match(runtimeState.content, /Conversation History: inactive/);
|
|
214
225
|
assert.match(runtimeState.content, /Cognitive Routing: inactive/);
|
|
215
|
-
assert.ok(
|
|
226
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
216
227
|
} finally {
|
|
217
228
|
await rm(cwd, { recursive: true, force: true });
|
|
218
229
|
}
|
|
@@ -233,7 +244,7 @@ test("provider context reuses the before-agent surface until the next provider t
|
|
|
233
244
|
assert.match(lastRuntimeState(sameTurn.messages).content, /Context Virtualization: active/);
|
|
234
245
|
|
|
235
246
|
const next = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
236
|
-
assert.
|
|
247
|
+
assert.equal(next.systemPrompt, before.systemPrompt, "settings preserve the complete reference surface");
|
|
237
248
|
assert.match(next.systemPrompt, /# Freeflow Interaction Contract/);
|
|
238
249
|
assert.match(next.systemPrompt, /## Shared Terms/);
|
|
239
250
|
} finally {
|
|
@@ -278,7 +289,7 @@ test("missing optional prompt fragments preserve the mandatory core surface", as
|
|
|
278
289
|
}
|
|
279
290
|
});
|
|
280
291
|
|
|
281
|
-
test("missing child prompt
|
|
292
|
+
test("missing child prompt marks that capability unavailable while retaining its definitions", async () => {
|
|
282
293
|
const root = await mkdtemp(join(tmpdir(), "freeflow-child-prompt-failure-"));
|
|
283
294
|
const cwd = await configuredRepo({ contextVirtualization: true });
|
|
284
295
|
try {
|
|
@@ -298,13 +309,13 @@ test("missing child prompt removes only that capability from every model-facing
|
|
|
298
309
|
assert.doesNotMatch(before.systemPrompt, /## Context Virtualization Cue/);
|
|
299
310
|
assert.match(before.systemPrompt, /# Freeflow Interaction Contract/);
|
|
300
311
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
301
|
-
assert.ok(
|
|
302
|
-
assert.ok(
|
|
312
|
+
assert.ok(resources.skillPaths.some((path) => path.endsWith("/capabilities/context-virtualization/SKILL.md")));
|
|
313
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
303
314
|
|
|
304
315
|
await handlers.get("session_tree")({}, ctx);
|
|
305
|
-
assert.ok(
|
|
316
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
306
317
|
await handlers.get("session_compact")({}, ctx);
|
|
307
|
-
assert.ok(
|
|
318
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
308
319
|
|
|
309
320
|
const providerContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
310
321
|
assert.match(providerContext.messages.at(-1).content, /Context Virtualization: unavailable/);
|
|
@@ -314,9 +325,9 @@ test("missing child prompt removes only that capability from every model-facing
|
|
|
314
325
|
assert.doesNotMatch(whitespaceBefore.systemPrompt, /## Context Virtualization Cue/);
|
|
315
326
|
const whitespaceResources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
316
327
|
assert.ok(
|
|
317
|
-
|
|
328
|
+
whitespaceResources.skillPaths.some((path) => path.endsWith("/capabilities/context-virtualization/SKILL.md")),
|
|
318
329
|
);
|
|
319
|
-
assert.ok(
|
|
330
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
320
331
|
const whitespaceContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
321
332
|
assert.match(whitespaceContext.messages.at(-1).content, /Context Virtualization: unavailable/);
|
|
322
333
|
} finally {
|
|
@@ -325,7 +336,7 @@ test("missing child prompt removes only that capability from every model-facing
|
|
|
325
336
|
}
|
|
326
337
|
});
|
|
327
338
|
|
|
328
|
-
test("missing mandatory Interaction Contract
|
|
339
|
+
test("missing mandatory Interaction Contract retains a dormant surface with unavailable state", async () => {
|
|
329
340
|
const root = await mkdtemp(join(tmpdir(), "freeflow-interaction-contract-failure-"));
|
|
330
341
|
const cwd = await configuredRepo();
|
|
331
342
|
try {
|
|
@@ -342,19 +353,21 @@ test("missing mandatory Interaction Contract preserves the host prompt and hides
|
|
|
342
353
|
const { handlers, activeToolNames } = loadExtension(extension);
|
|
343
354
|
const ctx = context(cwd);
|
|
344
355
|
await handlers.get("session_start")({}, ctx);
|
|
345
|
-
assert.ok(
|
|
356
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
346
357
|
const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
347
|
-
assert.
|
|
358
|
+
assert.ok(before.systemPrompt.startsWith("base prompt\n\n"));
|
|
359
|
+
assert.match(before.systemPrompt, /guidance is dormant/);
|
|
348
360
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
349
|
-
assert.
|
|
361
|
+
assert.equal(resources.skillPaths.length, 27);
|
|
350
362
|
await handlers.get("session_tree")({}, ctx);
|
|
351
|
-
assert.ok(
|
|
363
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
352
364
|
|
|
353
365
|
await writeFile(join(root, "runtime", "prompts", "interaction-contract.md"), " \n\t", "utf8");
|
|
354
366
|
const whitespaceBefore = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
355
|
-
assert.
|
|
367
|
+
assert.ok(whitespaceBefore.systemPrompt.startsWith("base prompt\n\n"));
|
|
368
|
+
assert.match(whitespaceBefore.systemPrompt, /guidance is dormant/);
|
|
356
369
|
const whitespaceResources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
357
|
-
assert.
|
|
370
|
+
assert.equal(whitespaceResources.skillPaths.length, 27);
|
|
358
371
|
const whitespaceContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
359
372
|
assert.match(whitespaceContext.messages.at(-1).content, /Freeflow: unavailable/);
|
|
360
373
|
} finally {
|
|
@@ -363,7 +376,7 @@ test("missing mandatory Interaction Contract preserves the host prompt and hides
|
|
|
363
376
|
}
|
|
364
377
|
});
|
|
365
378
|
|
|
366
|
-
test("missing mandatory core prompt
|
|
379
|
+
test("missing mandatory core prompt retains a dormant surface with unavailable state", async () => {
|
|
367
380
|
const root = await mkdtemp(join(tmpdir(), "freeflow-core-prompt-failure-"));
|
|
368
381
|
const cwd = await configuredRepo({ contextVirtualization: true });
|
|
369
382
|
try {
|
|
@@ -380,9 +393,10 @@ test("missing mandatory core prompt preserves the host prompt and hides base ski
|
|
|
380
393
|
await handlers.get("session_start")({ type: "session_start" }, ctx);
|
|
381
394
|
|
|
382
395
|
const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
383
|
-
assert.
|
|
396
|
+
assert.ok(before.systemPrompt.startsWith("base prompt\n\n"));
|
|
397
|
+
assert.match(before.systemPrompt, /guidance is dormant/);
|
|
384
398
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
385
|
-
assert.
|
|
399
|
+
assert.equal(resources.skillPaths.length, 27);
|
|
386
400
|
const providerContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
387
401
|
assert.match(providerContext.messages.at(-1).content, /Freeflow: unavailable/);
|
|
388
402
|
assert.doesNotMatch(
|
|
@@ -391,15 +405,16 @@ test("missing mandatory core prompt preserves the host prompt and hides base ski
|
|
|
391
405
|
);
|
|
392
406
|
|
|
393
407
|
await handlers.get("session_tree")({}, ctx);
|
|
394
|
-
assert.ok(
|
|
408
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
395
409
|
await handlers.get("session_compact")({}, ctx);
|
|
396
|
-
assert.ok(
|
|
410
|
+
assert.ok(activeToolNames().includes("freeflow_context"));
|
|
397
411
|
|
|
398
412
|
await writeFile(join(root, "runtime", "prompts", "core.md"), " \n\t", "utf8");
|
|
399
413
|
const whitespaceBefore = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
400
|
-
assert.
|
|
414
|
+
assert.ok(whitespaceBefore.systemPrompt.startsWith("base prompt\n\n"));
|
|
415
|
+
assert.match(whitespaceBefore.systemPrompt, /guidance is dormant/);
|
|
401
416
|
const whitespaceResources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
402
|
-
assert.
|
|
417
|
+
assert.equal(whitespaceResources.skillPaths.length, 27);
|
|
403
418
|
const whitespaceContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
404
419
|
assert.match(whitespaceContext.messages.at(-1).content, /Freeflow: unavailable/);
|
|
405
420
|
} finally {
|
|
@@ -414,7 +429,8 @@ test("Runtime State remains present while Freeflow is disabled without optional
|
|
|
414
429
|
const { handlers } = loadExtension();
|
|
415
430
|
const ctx = context(cwd);
|
|
416
431
|
const before = await handlers.get("before_agent_start")({ systemPrompt: "base prompt" }, ctx);
|
|
417
|
-
assert.
|
|
432
|
+
assert.ok(before.systemPrompt.startsWith("base prompt\n\n"));
|
|
433
|
+
assert.match(before.systemPrompt, /guidance is dormant/);
|
|
418
434
|
assert.equal(before.message, undefined);
|
|
419
435
|
|
|
420
436
|
const providerContext = await handlers.get("context")({ messages: [] }, ctx);
|
|
@@ -424,7 +440,7 @@ test("Runtime State remains present while Freeflow is disabled without optional
|
|
|
424
440
|
assert.doesNotMatch(runtimeState.content, /Default mode|Active mode|Interaction Contract|Skills/);
|
|
425
441
|
|
|
426
442
|
const resources = await handlers.get("resources_discover")({ cwd }, ctx);
|
|
427
|
-
assert.
|
|
443
|
+
assert.equal(resources.skillPaths.length, 27);
|
|
428
444
|
} finally {
|
|
429
445
|
await rm(cwd, { recursive: true, force: true });
|
|
430
446
|
}
|
|
@@ -290,7 +290,7 @@ test("Cognitive Routing preset cancellation preserves the previous repository va
|
|
|
290
290
|
});
|
|
291
291
|
component.handleInput("\u001b[B");
|
|
292
292
|
component.handleInput("\r");
|
|
293
|
-
component.handleInput("\u001b[B");
|
|
293
|
+
for (let index = 0; index < 4; index += 1) component.handleInput("\u001b[B");
|
|
294
294
|
component.handleInput("\r");
|
|
295
295
|
component.handleInput("\u001b");
|
|
296
296
|
component.handleInput("\u001b");
|
|
@@ -339,7 +339,7 @@ test("Cognitive Routing settings refresh after enabling the capability", async (
|
|
|
339
339
|
component.handleInput("\r");
|
|
340
340
|
await component.waitForWrites();
|
|
341
341
|
component.handleInput("\u001b");
|
|
342
|
-
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
343
|
component.handleInput("\u001b");
|
|
344
344
|
return result;
|
|
345
345
|
};
|
|
@@ -383,7 +383,7 @@ test("Cognitive Routing settings can disable projection without disabling routin
|
|
|
383
383
|
});
|
|
384
384
|
component.handleInput("\u001b[B");
|
|
385
385
|
component.handleInput("\r");
|
|
386
|
-
for (let index = 0; index <
|
|
386
|
+
for (let index = 0; index < 5; index += 1) component.handleInput("\u001b[B");
|
|
387
387
|
component.handleInput("\r");
|
|
388
388
|
component.handleInput("\u001b[B");
|
|
389
389
|
component.handleInput("\r");
|
|
@@ -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
|
+
});
|
|
@@ -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
|
+
});
|