@cortexkit/opencode-magic-context 0.46.1 → 0.47.0
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/dist/auto-search-worker.js +115 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/live-run-config.d.ts.map +1 -1
- package/dist/config/migrate-config-location.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +11 -3
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-7fj2b9jw.js → conflict-warning-hook-z44ynfz7.js} +5 -4
- package/dist/{context-sh2m2vde.js → context-wq4ffwyb.js} +108 -23
- package/dist/{derive-budgets-7nj7r1rv.js → derive-budgets-ke12jnnh.js} +1 -1
- package/dist/embedding-worker.js +2 -2
- package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +2 -0
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +7 -0
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/search.d.ts +2 -0
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/session-project-storage.d.ts +8 -2
- package/dist/features/magic-context/session-project-storage.d.ts.map +1 -1
- package/dist/features/magic-context/storage-historian-runs.d.ts +2 -5
- package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +12 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +7 -2
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -0
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts +3 -1
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/hooks/magic-context/active-anthropic-turn.d.ts +8 -0
- package/dist/hooks/magic-context/active-anthropic-turn.d.ts.map +1 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-deadline.d.ts +7 -0
- package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts +3 -0
- package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts +4 -0
- package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts +3 -0
- package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-runner.d.ts +1 -1
- package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts +4 -0
- package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts +69 -0
- package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-worker.d.ts +2 -0
- package/dist/hooks/magic-context/auto-search-worker.d.ts.map +1 -0
- package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +3 -5
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts +2 -1
- package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts +3 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/historian-drain-gate.d.ts +11 -0
- package/dist/hooks/magic-context/historian-drain-gate.d.ts.map +1 -0
- package/dist/hooks/magic-context/hook.d.ts +2 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/latest-assistant-turn.d.ts +25 -0
- package/dist/hooks/magic-context/latest-assistant-turn.d.ts.map +1 -0
- package/dist/hooks/magic-context/latest-thinking-recovery.d.ts +61 -0
- package/dist/hooks/magic-context/latest-thinking-recovery.d.ts.map +1 -0
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
- package/dist/hooks/magic-context/opencode-proven-floor.d.ts +6 -0
- package/dist/hooks/magic-context/opencode-proven-floor.d.ts.map +1 -0
- package/dist/hooks/magic-context/pass-outcome.d.ts.map +1 -1
- package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/reasoning-budget-status.d.ts +15 -0
- package/dist/hooks/magic-context/reasoning-budget-status.d.ts.map +1 -0
- package/dist/hooks/magic-context/reasoning-budget.d.ts +40 -0
- package/dist/hooks/magic-context/reasoning-budget.d.ts.map +1 -0
- package/dist/hooks/magic-context/reasoning-removal.d.ts +6 -6
- package/dist/hooks/magic-context/reasoning-removal.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/sentinel.d.ts +4 -0
- package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-content.d.ts +7 -3
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +13 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tool-drop-target.d.ts +5 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -28
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +28 -6
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +3 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-0d3g3461.js → index-0c82743p.js} +2 -1
- package/dist/{index-5p8jdswx.js → index-5a2r0p9f.js} +3 -3
- package/dist/{index-sjthx78w.js → index-6d9e1m08.js} +4647 -11000
- package/dist/{index-majxda46.js → index-6wkcswft.js} +1 -1
- package/dist/index-92bp57hg.js +2063 -0
- package/dist/{index-sfnj6h8h.js → index-9pk9exrb.js} +3 -3
- package/dist/{index-7qw742ev.js → index-adwfkpex.js} +5 -24
- package/dist/{index-3h0ybwah.js → index-bb36vgr3.js} +80 -12
- package/dist/{index-pkbcrdqs.js → index-cwf6s3ay.js} +1072 -2422
- package/dist/index-fnn5pjtt.js +6554 -0
- package/dist/{index-sgthv3f7.js → index-gg8j07zz.js} +1 -1
- package/dist/{index-8x2v7gg9.js → index-m5fe6jch.js} +7 -3
- package/dist/{v2/server-g0fjrkta.js → index-rrbbspwh.js} +42 -27
- package/dist/index-rrrsepyw.js +3464 -0
- package/dist/{index-hpecdzec.js → index-w2t4ah5j.js} +948 -501
- package/dist/{v2/server-fg34g6a5.js → index-wx8a6bw3.js} +48 -8
- package/dist/{index-r5hmbx3t.js → index-x3kwrmqk.js} +146 -3571
- package/dist/index-yr3zj7e7.js +23 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +624 -65
- package/dist/{message-time-backfill-68gj6t3e.js → message-time-backfill-r5epkzj3.js} +1 -1
- package/dist/migration-worker.js +7 -5
- package/dist/plugin/hooks/create-session-hooks.d.ts +2 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/host-stall-profiler-report.d.ts +67 -0
- package/dist/plugin/host-stall-profiler-report.d.ts.map +1 -0
- package/dist/plugin/host-stall-profiler.d.ts +96 -0
- package/dist/plugin/host-stall-profiler.d.ts.map +1 -0
- package/dist/plugin/messages-transform.d.ts +21 -6
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/{read-session-chunk-egz7r68d.js → read-session-chunk-4jey7hpt.js} +4 -4
- package/dist/{recomp-orchestrator-y15j0rb9.js → recomp-orchestrator-rervg6q4.js} +13 -10
- package/dist/{send-session-notification-dyw42dtr.js → send-session-notification-0m68ms3n.js} +4 -3
- package/dist/shared/auto-search-lifecycle.d.ts +7 -0
- package/dist/shared/auto-search-lifecycle.d.ts.map +1 -0
- package/dist/shared/historian-tool-expansions.d.ts +1 -1
- package/dist/shared/historian-tool-expansions.d.ts.map +1 -1
- package/dist/shared/historian-tool-template.d.ts +2 -2
- package/dist/shared/historian-tool-template.d.ts.map +1 -1
- package/dist/shared/sqlite.d.ts +16 -1
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/status-view.d.ts.map +1 -1
- package/dist/shared/window-geometry.d.ts +4 -0
- package/dist/shared/window-geometry.d.ts.map +1 -1
- package/dist/tools/ctx-expand/render.d.ts.map +1 -1
- package/dist/{transform-postprocess-phase-bqhz6vdr.js → transform-postprocess-phase-z5hzpsxv.js} +13 -10
- package/dist/v2/auto-search-worker.js +115 -0
- package/dist/v2/{context-zecmzk5s.js → context-14v1hr8b.js} +3783 -4668
- package/dist/v2/{derive-budgets-kdgp4s3r.js → derive-budgets-nc9avxz8.js} +2 -2
- package/dist/v2/embedding-worker.js +3 -3
- package/dist/v2/hidden-completion.d.ts.map +1 -1
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/{message-time-backfill-r6qkem1x.js → message-time-backfill-1f9tzvcs.js} +1 -1
- package/dist/v2/migration-worker.js +7 -5
- package/dist/v2/{read-session-chunk-8vjp6581.js → read-session-chunk-gyyv7w2r.js} +4 -4
- package/dist/v2/{recomp-orchestrator-8ejxvrzz.js → recomp-orchestrator-42vjba7p.js} +17 -14
- package/dist/v2/{send-session-notification-qs0j78v6.js → send-session-notification-c0wtscea.js} +5 -4
- package/dist/v2/server-3trjk1hx.js +6360 -0
- package/dist/v2/{server-e29d7p7c.js → server-50hvkg3v.js} +7 -3
- package/dist/v2/{server-y8maxeft.js → server-cpepxy2q.js} +2 -1
- package/dist/v2/server-d0sraw7m.js +3403 -0
- package/dist/v2/server-e0c1b7a7.js +2063 -0
- package/dist/v2/{server-mcfawjgc.js → server-efhwzx05.js} +5 -24
- package/dist/v2/{server-3n4q9xme.js → server-fcdehk1s.js} +1 -1
- package/dist/{index-q4yp4b52.js → v2/server-fq7kq32k.js} +42 -27
- package/dist/v2/{server-jc7y02q0.js → server-nf82q6pt.js} +1117 -7290
- package/dist/v2/{server-frbm99q5.js → server-njt3np1k.js} +99 -3465
- package/dist/v2/{server-qp5hx1ad.js → server-nm9n3aqq.js} +107 -12
- package/dist/v2/{server-c5gz8wph.js → server-rz514f14.js} +7 -2
- package/dist/v2/{server-wvjvfhck.js → server-vd4e7174.js} +1 -1
- package/dist/{index-fg34g6a5.js → v2/server-wx8a6bw3.js} +48 -8
- package/dist/v2/{server-xa4xe3rv.js → server-y10vbq6y.js} +1 -1
- package/dist/v2/server-yr3zj7e7.js +23 -0
- package/dist/v2/server.js +5 -5
- package/dist/v2/{wrapup-orchestrator-0snhvwcv.js → wrapup-orchestrator-nbz854vc.js} +16 -13
- package/dist/wrapup-orchestrator-b2dztrar.js +29 -0
- package/package.json +3 -3
- package/src/config/index.ts +6 -0
- package/src/config/live-run-config.test.ts +37 -1
- package/src/config/live-run-config.ts +28 -0
- package/src/config/migrate-config-location.ts +10 -3
- package/src/config/schema/magic-context.test.ts +4 -3
- package/src/config/schema/magic-context.ts +18 -6
- package/src/shared/auto-search-lifecycle.ts +27 -0
- package/src/shared/historian-tool-defaults.json +6 -6
- package/src/shared/historian-tool-expansions.ts +65 -8
- package/src/shared/historian-tool-preview-defaults.json +13 -0
- package/src/shared/historian-tool-template.ts +48 -14
- package/src/shared/sqlite-640-review-r2.test.ts +136 -0
- package/src/shared/sqlite-640-review.test.ts +183 -0
- package/src/shared/sqlite-acquisition-routing.test.ts +18 -0
- package/src/shared/sqlite.ts +84 -14
- package/src/shared/status-view.test.ts +6 -1
- package/src/shared/status-view.ts +15 -1
- package/src/shared/tag-transcript.ts +6 -0
- package/src/shared/window-geometry.ts +10 -0
- package/dist/wrapup-orchestrator-39amhv9s.js +0 -26
- /package/dist/{index-en1k8h8m.js → index-5prbx026.js} +0 -0
- /package/dist/v2/{index-en1k8h8m.js → index-5prbx026.js} +0 -0
|
@@ -11,6 +11,14 @@ export function sampleLiveConfig<T extends MagicContextConfig>(boot: T, fresh: T
|
|
|
11
11
|
const parts = path.split(".");
|
|
12
12
|
let value: unknown = fresh;
|
|
13
13
|
for (const part of parts) value = (value as Record<string, unknown> | undefined)?.[part];
|
|
14
|
+
if (value === undefined) {
|
|
15
|
+
// An unset live path must not create its parent objects. An empty `omp`
|
|
16
|
+
// block, for example, shadows the `pi` block it would otherwise fall back
|
|
17
|
+
// to, and the historian and dreamer lose their configured model. Only
|
|
18
|
+
// clear a leaf the boot config actually has, so removing a key live works.
|
|
19
|
+
removeLeaf(result, parts);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
14
22
|
let target = result;
|
|
15
23
|
for (const part of parts.slice(0, -1)) {
|
|
16
24
|
const child = target[part];
|
|
@@ -27,6 +35,26 @@ export function sampleLiveConfig<T extends MagicContextConfig>(boot: T, fresh: T
|
|
|
27
35
|
return result as T;
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
function removeLeaf(root: Record<string, unknown>, parts: string[]): void {
|
|
39
|
+
const parents = parts.slice(0, -1);
|
|
40
|
+
let node: Record<string, unknown> = root;
|
|
41
|
+
for (const part of parents) {
|
|
42
|
+
const child = node[part];
|
|
43
|
+
if (!child || typeof child !== "object" || Array.isArray(child)) return;
|
|
44
|
+
node = child as Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
const leaf = parts.at(-1);
|
|
47
|
+
if (leaf === undefined || !Object.hasOwn(node, leaf)) return;
|
|
48
|
+
// Copy the parent chain before deleting so the boot config object is never mutated.
|
|
49
|
+
let target = root;
|
|
50
|
+
for (const part of parents) {
|
|
51
|
+
const copy = { ...(target[part] as Record<string, unknown>) };
|
|
52
|
+
target[part] = copy;
|
|
53
|
+
target = copy;
|
|
54
|
+
}
|
|
55
|
+
delete target[leaf];
|
|
56
|
+
}
|
|
57
|
+
|
|
30
58
|
export function changedLiveKeys(previous: MagicContextConfig, next: MagicContextConfig): string[] {
|
|
31
59
|
const at = (config: MagicContextConfig, path: string): unknown =>
|
|
32
60
|
path
|
|
@@ -278,7 +278,7 @@ function sortJson(value: unknown): unknown {
|
|
|
278
278
|
if (value && typeof value === "object") {
|
|
279
279
|
const sorted: Record<string, unknown> = {};
|
|
280
280
|
for (const key of Object.keys(value as Record<string, unknown>).sort()) {
|
|
281
|
-
if (key === "protected_tags") continue;
|
|
281
|
+
if (key === "protected_tags" || key === "clear_reasoning_age") continue;
|
|
282
282
|
sorted[key] = sortJson((value as Record<string, unknown>)[key]);
|
|
283
283
|
}
|
|
284
284
|
return sorted;
|
|
@@ -509,7 +509,9 @@ export function migrateConfigFile(opts: ConfigFileMigrationOptions): ConfigFileM
|
|
|
509
509
|
let migratedContent = first.content;
|
|
510
510
|
let strippedProtectedTags = false;
|
|
511
511
|
try {
|
|
512
|
-
const stripped = removeJsoncValue(first.content, ["protected_tags"])
|
|
512
|
+
const stripped = removeJsoncValue(removeJsoncValue(first.content, ["protected_tags"]), [
|
|
513
|
+
"clear_reasoning_age",
|
|
514
|
+
]);
|
|
513
515
|
if (stripped !== first.content) {
|
|
514
516
|
migratedContent = stripped;
|
|
515
517
|
strippedProtectedTags = true;
|
|
@@ -520,7 +522,12 @@ export function migrateConfigFile(opts: ConfigFileMigrationOptions): ConfigFileM
|
|
|
520
522
|
|
|
521
523
|
atomicWriteConfigFile(opts.targetPath, migratedContent);
|
|
522
524
|
if (strippedProtectedTags) {
|
|
523
|
-
|
|
525
|
+
if (first.content.includes('"protected_tags"'))
|
|
526
|
+
info?.(`Stripped deprecated "protected_tags" key during config location migration`);
|
|
527
|
+
if (first.content.includes('"clear_reasoning_age"'))
|
|
528
|
+
info?.(
|
|
529
|
+
`Stripped deprecated "clear_reasoning_age" key during config location migration`,
|
|
530
|
+
);
|
|
524
531
|
}
|
|
525
532
|
info?.(
|
|
526
533
|
`Migrated Magic Context ${opts.scope} config from ${first.path} to ${opts.targetPath}`,
|
|
@@ -42,7 +42,6 @@ describe("MagicContextConfigSchema", () => {
|
|
|
42
42
|
cache_ttl: "5m",
|
|
43
43
|
prompt_surface: { default: "full" },
|
|
44
44
|
execute_threshold_percentage: 65,
|
|
45
|
-
clear_reasoning_age: 50,
|
|
46
45
|
history_budget_percentage: DEFAULT_HISTORY_BUDGET_PERCENTAGE,
|
|
47
46
|
historian_timeout_ms: DEFAULT_HISTORIAN_TIMEOUT_MS,
|
|
48
47
|
embedding: {
|
|
@@ -701,8 +700,10 @@ describe("MagicContextConfigSchema", () => {
|
|
|
701
700
|
).toBe(30000);
|
|
702
701
|
});
|
|
703
702
|
|
|
704
|
-
it("
|
|
705
|
-
expect(
|
|
703
|
+
it("accepts deprecated clear_reasoning_age without applying its old validation", () => {
|
|
704
|
+
expect(
|
|
705
|
+
MagicContextConfigSchema.parse({ clear_reasoning_age: 9 }).keep_reasoning_tokens,
|
|
706
|
+
).toBeUndefined();
|
|
706
707
|
});
|
|
707
708
|
|
|
708
709
|
it("rejects historian_timeout_ms below minimum", () => {
|
|
@@ -682,7 +682,7 @@ export const HistorianConfigSchema = AgentMetadataSchema.extend({
|
|
|
682
682
|
)
|
|
683
683
|
.optional()
|
|
684
684
|
.describe(
|
|
685
|
-
'Readable historian tool expansions, keyed by exact host tool name. Templates override built-in defaults; false disables an expansion. Supports ${input.path}, ${output.path}, bare ${output}, array [N], [*].field, .each("${field}"), .join("separator"), .count and final .truncate(N). Missing fields are empty;
|
|
685
|
+
'Readable historian tool expansions, keyed by exact host tool name. Templates override built-in defaults; false disables an expansion. Supports ${input.path}, ${output.path}, bare ${output}, array [N], [*].field, .each("${field}"), .join("separator"), .count and final .truncate(N). Missing fields are empty. Historian text and lists have no implicit caps; explicit .truncate(N) keeps complete sentences and marks omitted characters, or emits only the marker if no sentence fits. When the ctx_expand history-recovery tool is called in verbose mode, its tool-call previews retain their original built-in templates and limits (300 characters per placeholder, 10 list elements, 1000 characters per expansion). Valid in user and project config; never changes the wire or default ctx_expand transcript.',
|
|
686
686
|
),
|
|
687
687
|
opencode: OpenCodeHarnessBlockSchema.optional(),
|
|
688
688
|
pi: PiHarnessBlockSchema.optional(),
|
|
@@ -963,7 +963,8 @@ export interface MagicContextConfig {
|
|
|
963
963
|
protected_tokens?: number;
|
|
964
964
|
protected_tags?: number;
|
|
965
965
|
protected_tools: Record<string, number>;
|
|
966
|
-
clear_reasoning_age
|
|
966
|
+
clear_reasoning_age?: unknown;
|
|
967
|
+
keep_reasoning_tokens?: number | Record<string, number>;
|
|
967
968
|
history_budget_percentage: number;
|
|
968
969
|
historian_timeout_ms: number;
|
|
969
970
|
commit_cluster_trigger: {
|
|
@@ -1263,10 +1264,21 @@ export const MagicContextConfigSchema = z
|
|
|
1263
1264
|
)
|
|
1264
1265
|
.meta({ deprecated: true }),
|
|
1265
1266
|
clear_reasoning_age: z
|
|
1266
|
-
.
|
|
1267
|
-
.
|
|
1268
|
-
.
|
|
1269
|
-
.
|
|
1267
|
+
.unknown()
|
|
1268
|
+
.optional()
|
|
1269
|
+
.describe("Deprecated and ignored. Use keep_reasoning_tokens instead.")
|
|
1270
|
+
.meta({ deprecated: true }),
|
|
1271
|
+
keep_reasoning_tokens: z
|
|
1272
|
+
.union([
|
|
1273
|
+
z.number().int().min(0).max(1_000_000),
|
|
1274
|
+
z
|
|
1275
|
+
.object({ default: z.number().int().min(0).max(1_000_000).optional() })
|
|
1276
|
+
.catchall(z.number().int().min(0).max(1_000_000)),
|
|
1277
|
+
])
|
|
1278
|
+
.optional()
|
|
1279
|
+
.describe(
|
|
1280
|
+
"Reasoning tokens to keep on rebuilding passes. Number or per-model object; exact, shorter model keys, provider/*, then default. Omitted: fixed 10,000. 0 removes all eligible historical reasoning; newest and exempt steps always stay.",
|
|
1281
|
+
),
|
|
1270
1282
|
history_budget_percentage: z
|
|
1271
1283
|
.number()
|
|
1272
1284
|
.min(0.05)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const pending = new Map<string, Set<AbortController>>();
|
|
2
|
+
|
|
3
|
+
/** Cleanup cancels optional searches and backfill, not durable served decisions. */
|
|
4
|
+
export function registerAutoSearchWork(sessionId: string): {
|
|
5
|
+
signal: AbortSignal;
|
|
6
|
+
done: () => void;
|
|
7
|
+
} {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const work = pending.get(sessionId) ?? new Set<AbortController>();
|
|
10
|
+
pending.set(sessionId, work);
|
|
11
|
+
work.add(controller);
|
|
12
|
+
return {
|
|
13
|
+
signal: controller.signal,
|
|
14
|
+
done: () => {
|
|
15
|
+
work.delete(controller);
|
|
16
|
+
if (pending.get(sessionId) === work && work.size === 0) pending.delete(sessionId);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function cancelAutoSearchWork(sessionId?: string): void {
|
|
22
|
+
for (const [id, work] of pending) {
|
|
23
|
+
if (sessionId !== undefined && id !== sessionId) continue;
|
|
24
|
+
pending.delete(id);
|
|
25
|
+
for (const controller of work) controller.abort();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"ctx_memory": "Memory ${input.action} ${input.category} ${input.ids}: ${input.content} → ${output.truncate(160)}",
|
|
4
4
|
"todowrite": "Todos (${input.todos.count}): ${input.todos.each(\"${status}: ${content}\")}",
|
|
5
5
|
"question": "Asked user: ${input.questions[*].question.join(\" / \")} → ${output}",
|
|
6
|
-
"task": "Task ${input.subagent_type}: ${input.prompt} → ${output.truncate(200)}",
|
|
7
|
-
"ask": "Asked: ${input.question
|
|
8
|
-
"peer_send": "PM
|
|
9
|
-
"board": "Board ${input.verb}: ${input.ops.each(\"${op} ${item}${lane.
|
|
10
|
-
"room": "Room ${input.action} ${input.
|
|
11
|
-
"work": "Work ${input.action} ${input.
|
|
6
|
+
"task": "Task ${input.subagent_type}: ${input.prompt.truncate(600)} → ${output.truncate(200)}",
|
|
7
|
+
"ask": "Asked: ${input.question} [options: ${input.options.join(\" / \")}] ${input.text}${input.resolution} → ${output.truncate(300)}",
|
|
8
|
+
"peer_send": "PM ${input.recipient}: ${input.message}",
|
|
9
|
+
"board": "Board ${input.verb}${input.board_lane}: ${input.ops.each(\"${op} ${lane_label} ${item}${props.text}${lane.items} ${state}${props.state}${lane.status}${id}${title}\")}",
|
|
10
|
+
"room": "Room ${input.action} ${input.room_label}: ${input.room_detail}",
|
|
11
|
+
"work": "Work ${input.action} ${input.title}${input.id}: ${input.prompt.truncate(600)}${input.notes.truncate(600)} → ${output.truncate(160)}",
|
|
12
12
|
"knowhow": "Looked up how-to: ${input.query}${input.id}"
|
|
13
13
|
}
|
|
@@ -1,18 +1,75 @@
|
|
|
1
1
|
import defaults from "./historian-tool-defaults.json";
|
|
2
|
+
import previewDefaults from "./historian-tool-preview-defaults.json";
|
|
2
3
|
import { renderToolTemplate, type ToolExpansionMap } from "./historian-tool-template";
|
|
3
4
|
|
|
4
5
|
export { defaults as DEFAULT_TOOL_EXPANSIONS };
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
// Room actions, private-message addresses and board lanes have multiple input
|
|
8
|
+
// shapes. Derive readable labels only for built-ins; configured template overrides
|
|
9
|
+
// keep the unmodified tool input.
|
|
10
|
+
function builtinInput(tool: string, input: unknown): unknown {
|
|
11
|
+
if (!input || typeof input !== "object") return input;
|
|
12
|
+
const i = input as Record<string, unknown>;
|
|
13
|
+
if (tool === "peer_send") {
|
|
14
|
+
const external = i.external as Record<string, unknown> | undefined;
|
|
15
|
+
const recipient = i.reply_to_pmid
|
|
16
|
+
? `reply to ${i.reply_to_pmid}`
|
|
17
|
+
: i.agent || i.agent_id
|
|
18
|
+
? `to ${i.agent || i.agent_id}`
|
|
19
|
+
: external?.session_id || i.session_id
|
|
20
|
+
? `to session ${external?.session_id || i.session_id}`
|
|
21
|
+
: "to unknown recipient";
|
|
22
|
+
return { ...i, recipient };
|
|
23
|
+
}
|
|
24
|
+
if (tool === "room") {
|
|
25
|
+
return {
|
|
26
|
+
...i,
|
|
27
|
+
room_label: i.room_id || i.title,
|
|
28
|
+
room_detail:
|
|
29
|
+
i.action === "create" || i.action === "invite"
|
|
30
|
+
? `invitees: ${renderToolTemplate("${input.invitees}", i)}`
|
|
31
|
+
: i.text,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (tool === "board") {
|
|
35
|
+
return {
|
|
36
|
+
...i,
|
|
37
|
+
board_lane: i.lane ? ` ${i.lane}` : "",
|
|
38
|
+
ops: Array.isArray(i.ops)
|
|
39
|
+
? i.ops.map((op) =>
|
|
40
|
+
op && typeof op === "object"
|
|
41
|
+
? {
|
|
42
|
+
...op,
|
|
43
|
+
lane_label:
|
|
44
|
+
typeof op.lane === "string"
|
|
45
|
+
? op.lane
|
|
46
|
+
: op.lane?.title || op.lane?.id,
|
|
47
|
+
}
|
|
48
|
+
: op,
|
|
49
|
+
)
|
|
50
|
+
: i.ops,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return input;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function expandToolPart(
|
|
57
|
+
part: unknown,
|
|
58
|
+
overrides?: ToolExpansionMap,
|
|
59
|
+
legacyPreview = false,
|
|
60
|
+
): string | null {
|
|
6
61
|
if (!part || typeof part !== "object") return null;
|
|
7
62
|
const p = part as Record<string, unknown>;
|
|
8
63
|
if (p.type !== "tool" || typeof p.tool !== "string") return null;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
64
|
+
const builtinDefaults = legacyPreview ? previewDefaults : defaults;
|
|
65
|
+
const overridden = overrides && Object.hasOwn(overrides, p.tool);
|
|
66
|
+
const template = overridden
|
|
67
|
+
? overrides[p.tool]
|
|
68
|
+
: Object.hasOwn(builtinDefaults, p.tool)
|
|
69
|
+
? builtinDefaults[p.tool as keyof typeof defaults]
|
|
70
|
+
: undefined;
|
|
15
71
|
if (typeof template !== "string") return null;
|
|
16
72
|
const state = p.state as Record<string, unknown> | undefined;
|
|
17
|
-
|
|
73
|
+
const input = overridden || legacyPreview ? state?.input : builtinInput(p.tool, state?.input);
|
|
74
|
+
return renderToolTemplate(template, input, state?.output ?? state?.error, legacyPreview);
|
|
18
75
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ctx_note": "Note ${input.action} ${input.note_ids}: ${input.content} → ${output.truncate(160)}",
|
|
3
|
+
"ctx_memory": "Memory ${input.action} ${input.category} ${input.ids}: ${input.content} → ${output.truncate(160)}",
|
|
4
|
+
"todowrite": "Todos (${input.todos.count}): ${input.todos.each(\"${status}: ${content}\")}",
|
|
5
|
+
"question": "Asked user: ${input.questions[*].question.join(\" / \")} → ${output}",
|
|
6
|
+
"task": "Task ${input.subagent_type}: ${input.prompt} → ${output.truncate(200)}",
|
|
7
|
+
"ask": "Asked: ${input.question.truncate(250)} [options: ${input.options.join(\" / \")}] ${input.resolution} → ${output.truncate(300)}",
|
|
8
|
+
"peer_send": "PM to ${input.agent}${input.agent_id}: ${input.message.truncate(400)}",
|
|
9
|
+
"board": "Board ${input.verb}: ${input.ops.each(\"${op} ${item}${lane.title} ${state}${lane.status}${props.text} ${lane.items[*].text}\")}",
|
|
10
|
+
"room": "Room ${input.action} ${input.room_id}: ${input.text.truncate(400)}",
|
|
11
|
+
"work": "Work ${input.action} ${input.id}: ${input.prompt_file}${input.prompt.truncate(200)}${input.notes.truncate(200)} → ${output.truncate(160)}",
|
|
12
|
+
"knowhow": "Looked up how-to: ${input.query}${input.id}"
|
|
13
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A deliberately non-executable language for readable
|
|
1
|
+
/** A deliberately non-executable language for readable historian tool lines. */
|
|
2
2
|
export type ToolExpansionMap = Record<string, string | false>;
|
|
3
3
|
type Step = { field: string } | { index: number } | { project: string };
|
|
4
4
|
type Expression = {
|
|
@@ -6,7 +6,7 @@ type Expression = {
|
|
|
6
6
|
each?: Template;
|
|
7
7
|
join?: string;
|
|
8
8
|
count?: boolean;
|
|
9
|
-
cap
|
|
9
|
+
cap?: number;
|
|
10
10
|
};
|
|
11
11
|
type Template = Array<string | Expression>;
|
|
12
12
|
|
|
@@ -78,7 +78,7 @@ class Parser {
|
|
|
78
78
|
if (this.source[this.pos] === "(") return this.error();
|
|
79
79
|
} else break;
|
|
80
80
|
}
|
|
81
|
-
const expression: Expression = { steps
|
|
81
|
+
const expression: Expression = { steps };
|
|
82
82
|
if (this.take(".each(")) {
|
|
83
83
|
if (relative) return this.error();
|
|
84
84
|
expression.each = parseTemplate(this.quoted(), true);
|
|
@@ -145,9 +145,24 @@ function compiled(source: string): Template | null {
|
|
|
145
145
|
function oneLine(value: string): string {
|
|
146
146
|
return value.replace(/[\r\n\u2028\u2029]+/g, " ");
|
|
147
147
|
}
|
|
148
|
-
function truncate(value: string, cap
|
|
148
|
+
function truncate(value: string, cap?: number, legacyPreview = false): string {
|
|
149
149
|
const chars = Array.from(oneLine(value));
|
|
150
|
-
|
|
150
|
+
if (cap === undefined || chars.length <= cap) return chars.join("");
|
|
151
|
+
if (legacyPreview) return `${chars.slice(0, cap).join("")}…`;
|
|
152
|
+
// Explicit caps stop after sentence-ending punctuation so a cut is not
|
|
153
|
+
// mistaken for an intact tool message. If no sentence fits, show only the marker.
|
|
154
|
+
let end = 0;
|
|
155
|
+
for (let i = 0; i < Math.min(cap, chars.length); i++) {
|
|
156
|
+
if (!".!?。!?".includes(chars[i])) continue;
|
|
157
|
+
let next = i + 1;
|
|
158
|
+
while (next < chars.length && "\"'”’)]".includes(chars[next])) next++;
|
|
159
|
+
if (
|
|
160
|
+
next <= cap &&
|
|
161
|
+
("。!?".includes(chars[i]) || next === chars.length || /[ \t]/.test(chars[next]))
|
|
162
|
+
)
|
|
163
|
+
end = next;
|
|
164
|
+
}
|
|
165
|
+
return `${end ? `${chars.slice(0, end).join("")} ` : ""}[… ${chars.length - end} more characters]`;
|
|
151
166
|
}
|
|
152
167
|
function scalar(value: unknown): string {
|
|
153
168
|
if (value === undefined) return "";
|
|
@@ -174,10 +189,11 @@ function field(value: unknown, key: string): unknown {
|
|
|
174
189
|
? (value as Record<string, unknown>)[key]
|
|
175
190
|
: undefined;
|
|
176
191
|
}
|
|
177
|
-
function render(nodes: Template, root: unknown): string {
|
|
192
|
+
function render(nodes: Template, root: unknown, legacyPreview: boolean): string {
|
|
178
193
|
return nodes
|
|
179
194
|
.map((node) => {
|
|
180
195
|
if (typeof node === "string") return node;
|
|
196
|
+
const cap = node.cap ?? (legacyPreview ? 300 : undefined);
|
|
181
197
|
let value = root;
|
|
182
198
|
let list = false;
|
|
183
199
|
for (const step of node.steps) {
|
|
@@ -196,18 +212,31 @@ function render(nodes: Template, root: unknown): string {
|
|
|
196
212
|
}
|
|
197
213
|
}
|
|
198
214
|
if (node.count)
|
|
199
|
-
return truncate(
|
|
215
|
+
return truncate(
|
|
216
|
+
Array.isArray(value) ? String(value.length) : "",
|
|
217
|
+
cap,
|
|
218
|
+
legacyPreview,
|
|
219
|
+
);
|
|
200
220
|
if (node.each || list || node.join !== undefined) {
|
|
201
221
|
if (!Array.isArray(value)) return "";
|
|
202
222
|
const elements = value
|
|
203
|
-
.slice(0, 10)
|
|
223
|
+
.slice(0, legacyPreview ? 10 : undefined)
|
|
204
224
|
.map((item) =>
|
|
205
|
-
truncate(
|
|
225
|
+
truncate(
|
|
226
|
+
node.each ? render(node.each, item, legacyPreview) : scalar(item),
|
|
227
|
+
legacyPreview ? 300 : undefined,
|
|
228
|
+
legacyPreview,
|
|
229
|
+
),
|
|
206
230
|
);
|
|
207
|
-
if (
|
|
208
|
-
|
|
231
|
+
if (legacyPreview && value.length > 10)
|
|
232
|
+
elements.push(`… +${value.length - 10} more`);
|
|
233
|
+
return truncate(
|
|
234
|
+
elements.join(node.join ?? (node.each ? "; " : ", ")),
|
|
235
|
+
cap,
|
|
236
|
+
legacyPreview,
|
|
237
|
+
);
|
|
209
238
|
}
|
|
210
|
-
return truncate(scalar(value),
|
|
239
|
+
return truncate(scalar(value), cap, legacyPreview);
|
|
211
240
|
})
|
|
212
241
|
.join("");
|
|
213
242
|
}
|
|
@@ -216,6 +245,7 @@ export function renderToolTemplate(
|
|
|
216
245
|
source: string,
|
|
217
246
|
input: unknown,
|
|
218
247
|
output?: unknown,
|
|
248
|
+
legacyPreview = false,
|
|
219
249
|
): string | null {
|
|
220
250
|
const nodes = compiled(source);
|
|
221
251
|
if (!nodes) return null;
|
|
@@ -241,8 +271,12 @@ export function renderToolTemplate(
|
|
|
241
271
|
!node.each &&
|
|
242
272
|
!node.count &&
|
|
243
273
|
node.join === undefined
|
|
244
|
-
? truncate(text ?? "", node.cap)
|
|
274
|
+
? truncate(text ?? "", node.cap ?? (legacyPreview ? 300 : undefined), legacyPreview)
|
|
245
275
|
: node,
|
|
246
276
|
);
|
|
247
|
-
return truncate(
|
|
277
|
+
return truncate(
|
|
278
|
+
render(bareOutputNodes, root, legacyPreview),
|
|
279
|
+
legacyPreview ? 1000 : undefined,
|
|
280
|
+
legacyPreview,
|
|
281
|
+
);
|
|
248
282
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { expect, spyOn, test } from "bun:test";
|
|
2
|
+
import { openDatabase } from "../features/magic-context/storage-db";
|
|
3
|
+
import { getOrCreateSessionMeta, updateSessionMeta } from "../features/magic-context/storage-meta";
|
|
4
|
+
import { captureLkgSlot, replayLkg } from "../hooks/magic-context/lkg-replay";
|
|
5
|
+
import { resetLkgSlotsForTest } from "../hooks/magic-context/lkg-slot";
|
|
6
|
+
import type { RustLkgReplayParticipant } from "../hooks/magic-context/rust-lkg-freeze-registry";
|
|
7
|
+
import type { MessageLike } from "../hooks/magic-context/transform-operations";
|
|
8
|
+
import { createMessagesTransformHandler } from "../plugin/messages-transform";
|
|
9
|
+
|
|
10
|
+
for (const change of ["content", "reorder", "interior deletion"] as const) {
|
|
11
|
+
test(`r2: OpenCode admission validates current ${change} after a backed-off wait`, async () => {
|
|
12
|
+
resetLkgSlotsForTest();
|
|
13
|
+
const db = openDatabase();
|
|
14
|
+
if (!db) throw new Error("throwaway database unavailable");
|
|
15
|
+
const sessionId = `r2-stale-${change}`;
|
|
16
|
+
getOrCreateSessionMeta(db, sessionId);
|
|
17
|
+
const model = { providerID: "openai", modelID: "gpt-4.1" };
|
|
18
|
+
const raw = ["u0", "u1", "u2"].map((id) => ({
|
|
19
|
+
info: { id, role: "user", sessionID: sessionId, model },
|
|
20
|
+
parts: [{ type: "text", text: id }],
|
|
21
|
+
})) as MessageLike[];
|
|
22
|
+
const saved = structuredClone(raw);
|
|
23
|
+
(saved[0].parts[0] as { text: string }).text = "§1§ saved managed prefix";
|
|
24
|
+
const capture = () =>
|
|
25
|
+
captureLkgSlot({
|
|
26
|
+
sessionId,
|
|
27
|
+
input: raw,
|
|
28
|
+
output: saved,
|
|
29
|
+
modelKey: "openai/gpt-4.1",
|
|
30
|
+
providerKey: "openai",
|
|
31
|
+
});
|
|
32
|
+
expect(capture()).toBe(true);
|
|
33
|
+
const output = { messages: structuredClone(raw) };
|
|
34
|
+
let fits = 0;
|
|
35
|
+
let callbacks = 0;
|
|
36
|
+
let changed: MessageLike[] | undefined;
|
|
37
|
+
const participant: RustLkgReplayParticipant = {
|
|
38
|
+
lastPassStamp: () => 1,
|
|
39
|
+
enterFreezeFromExternalServe: () => {},
|
|
40
|
+
emergencyFailClosed: () => false,
|
|
41
|
+
stripPersistedReasoning: () => {},
|
|
42
|
+
replayFits: () => ++fits > 1,
|
|
43
|
+
};
|
|
44
|
+
const exec = db.exec.bind(db);
|
|
45
|
+
let attempts = 0;
|
|
46
|
+
const spy = spyOn(db, "exec").mockImplementation((sql) => {
|
|
47
|
+
if (sql === "BEGIN IMMEDIATE" && ++attempts <= 2) {
|
|
48
|
+
if (attempts === 1)
|
|
49
|
+
setImmediate(() => {
|
|
50
|
+
if (change === "content")
|
|
51
|
+
(output.messages[0].parts[0] as { text: string }).text =
|
|
52
|
+
"history edited while waiting";
|
|
53
|
+
else if (change === "reorder")
|
|
54
|
+
[output.messages[0], output.messages[1]] = [
|
|
55
|
+
output.messages[1],
|
|
56
|
+
output.messages[0],
|
|
57
|
+
];
|
|
58
|
+
else output.messages.splice(1, 1);
|
|
59
|
+
changed = structuredClone(output.messages);
|
|
60
|
+
});
|
|
61
|
+
throw Object.assign(new Error("writer busy"), { code: "SQLITE_BUSY" });
|
|
62
|
+
}
|
|
63
|
+
return exec(sql);
|
|
64
|
+
});
|
|
65
|
+
try {
|
|
66
|
+
const handler = createMessagesTransformHandler({
|
|
67
|
+
rustReplayParticipant: () => participant,
|
|
68
|
+
magicContext: {
|
|
69
|
+
"experimental.chat.messages.transform": async () => {
|
|
70
|
+
callbacks++;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
await handler({}, output as Parameters<typeof handler>[1]);
|
|
75
|
+
expect(changed).toBeDefined();
|
|
76
|
+
// A fresh validator rejects this exact input, independently of the wrapper's snapshot.
|
|
77
|
+
expect(capture()).toBe(true);
|
|
78
|
+
expect(
|
|
79
|
+
replayLkg({
|
|
80
|
+
sessionId,
|
|
81
|
+
messages: changed!,
|
|
82
|
+
modelKey: "openai/gpt-4.1",
|
|
83
|
+
providerKey: "openai",
|
|
84
|
+
}).ok,
|
|
85
|
+
).toBe(false);
|
|
86
|
+
expect(callbacks).toBe(1);
|
|
87
|
+
expect(output.messages).toEqual(changed!);
|
|
88
|
+
} finally {
|
|
89
|
+
spy.mockRestore();
|
|
90
|
+
resetLkgSlotsForTest();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
test("r2: writer callback runs once after three failed acquisitions with no saved request", async () => {
|
|
96
|
+
resetLkgSlotsForTest();
|
|
97
|
+
const db = openDatabase();
|
|
98
|
+
if (!db) throw new Error("throwaway database unavailable");
|
|
99
|
+
getOrCreateSessionMeta(db, "r2-three-busy");
|
|
100
|
+
updateSessionMeta(db, "r2-three-busy", { systemPromptTokens: 100 });
|
|
101
|
+
const exec = db.exec.bind(db);
|
|
102
|
+
let attempts = 0;
|
|
103
|
+
let callbacks = 0;
|
|
104
|
+
const spy = spyOn(db, "exec").mockImplementation((sql) => {
|
|
105
|
+
if (sql === "BEGIN IMMEDIATE" && ++attempts <= 3)
|
|
106
|
+
throw Object.assign(new Error("busy"), { code: "SQLITE_BUSY" });
|
|
107
|
+
return exec(sql);
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
const handler = createMessagesTransformHandler({
|
|
111
|
+
magicContext: {
|
|
112
|
+
"experimental.chat.messages.transform": async () => {
|
|
113
|
+
callbacks++;
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
const output = {
|
|
118
|
+
messages: [
|
|
119
|
+
{
|
|
120
|
+
info: { id: "u", sessionID: "r2-three-busy", role: "user" },
|
|
121
|
+
parts: [{ type: "text", text: "§1§ managed" }],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
const bytes = JSON.stringify(output);
|
|
126
|
+
await handler({}, output as Parameters<typeof handler>[1]);
|
|
127
|
+
expect({ attempts, callbacks, bytes: JSON.stringify(output) }).toEqual({
|
|
128
|
+
attempts: 4,
|
|
129
|
+
callbacks: 1,
|
|
130
|
+
bytes,
|
|
131
|
+
});
|
|
132
|
+
} finally {
|
|
133
|
+
spy.mockRestore();
|
|
134
|
+
resetLkgSlotsForTest();
|
|
135
|
+
}
|
|
136
|
+
});
|