@retaw106/mmc-oc 1.2.2 → 2.0.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/cli.js +7095 -0
- package/dist/config/index.d.ts +0 -12
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/legacy-harness-blocks.d.ts +27 -0
- package/dist/config/legacy-harness-blocks.d.ts.map +1 -0
- package/dist/config/live-run-config.d.ts.map +1 -1
- package/dist/config/live-snapshot.d.ts +2 -1
- package/dist/config/live-snapshot.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +4 -5
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/raw-loader.d.ts +7 -26
- package/dist/config/raw-loader.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +164 -259
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/config/variable.d.ts +8 -0
- package/dist/config/variable.d.ts.map +1 -1
- package/dist/config-init.js +5595 -0
- package/dist/{context-a6px04s0.js → context-95zkksn3.js} +2471 -2438
- package/dist/{derive-budgets-fhk00zv8.js → derive-budgets-rjjz5f7y.js} +2 -2
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +2 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migration-v3-perf-schema.d.ts +8 -0
- package/dist/features/magic-context/migration-v3-perf-schema.d.ts.map +1 -0
- package/dist/features/magic-context/migration-v4-temporal-decisions.d.ts +12 -0
- package/dist/features/magic-context/migration-v4-temporal-decisions.d.ts.map +1 -0
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +6 -0
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
- package/dist/features/magic-context/reclaim-protection.d.ts +15 -10
- package/dist/features/magic-context/reclaim-protection.d.ts.map +1 -1
- package/dist/features/magic-context/session-cache-ttl.d.ts +4 -2
- package/dist/features/magic-context/session-cache-ttl.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-historian-runs.d.ts +2 -0
- package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -1
- package/dist/features/magic-context/storage-ops.d.ts +2 -0
- package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
- package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +15 -2
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +2 -2
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/temporal-decisions.d.ts +10 -0
- package/dist/features/magic-context/temporal-decisions.d.ts.map +1 -0
- package/dist/features/magic-context/tool-definition-tokens.d.ts +3 -0
- package/dist/features/magic-context/tool-definition-tokens.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 +15 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-deadline.d.ts +10 -3
- package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
- 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/caveman-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-cycle.d.ts +2 -0
- package/dist/hooks/magic-context/channel2-cycle.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts +8 -0
- package/dist/hooks/magic-context/compartment-render-epoch.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.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/ctx-reduce-availability.d.ts +12 -0
- package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
- package/dist/hooks/magic-context/decision-calibration.d.ts +3 -0
- package/dist/hooks/magic-context/decision-calibration.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts +2 -0
- package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts +21 -1
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +30 -1
- package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts +2 -0
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +10 -8
- package/dist/hooks/magic-context/inject-compartments.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/lkg-measured-request.d.ts +69 -0
- package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -0
- package/dist/hooks/magic-context/read-session-formatting.d.ts +3 -0
- package/dist/hooks/magic-context/read-session-formatting.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/recomp-orchestrator.d.ts +2 -0
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/sentinel.d.ts +2 -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/supersession-reclaim.d.ts +4 -0
- package/dist/hooks/magic-context/supersession-reclaim.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/tail-hygiene-walk.d.ts +5 -0
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
- package/dist/hooks/magic-context/temporal-awareness.d.ts +6 -3
- package/dist/hooks/magic-context/temporal-awareness.d.ts.map +1 -1
- package/dist/hooks/magic-context/temporal-served-projection.d.ts +4 -0
- package/dist/hooks/magic-context/temporal-served-projection.d.ts.map +1 -0
- package/dist/hooks/magic-context/tool-reclaim.d.ts +2 -0
- package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +34 -12
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +11 -5
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
- package/dist/{index-wgt4gamw.js → index-1mjhzqq7.js} +422 -31
- package/dist/index-33gprt9g.js +30 -0
- package/dist/{index-zx63j2e2.js → index-5cep2r9n.js} +3 -2
- package/dist/{index-by6h2pav.js → index-bdt9my9y.js} +374 -239
- package/dist/{index-3wagyrnj.js → index-x37vg474.js} +5 -1
- package/dist/{index-1vs6anwz.js → index-xt7y10x6.js} +2 -1
- package/dist/{index-me3zcj5t.js → index-z2g20msd.js} +4 -8
- package/dist/index.js +2 -2
- package/dist/{message-time-backfill-5ewk0axe.js → message-time-backfill-zfernxbw.js} +1 -1
- package/dist/{model-resolution-5haaxnpt.js → model-resolution-wazv26n0.js} +1 -1
- package/dist/plugin/rpc-handlers.d.ts +4 -0
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/{read-session-chunk-apjwrttc.js → read-session-chunk-hp464g13.js} +2 -2
- package/dist/{recomp-orchestrator-gyeevxaw.js → recomp-orchestrator-3qxeqwb9.js} +6 -6
- package/dist/shared/auto-search-lifecycle.d.ts +7 -0
- package/dist/shared/auto-search-lifecycle.d.ts.map +1 -0
- package/dist/shared/cache-ttl-display.d.ts +6 -4
- package/dist/shared/cache-ttl-display.d.ts.map +1 -1
- package/dist/shared/model-cache-ttl.d.ts +1 -1
- package/dist/shared/model-cache-ttl.d.ts.map +1 -1
- package/dist/shared/model-resolution.d.ts +5 -2
- package/dist/shared/model-resolution.d.ts.map +1 -1
- package/dist/shared/model-suggestion-retry.d.ts +2 -0
- package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
- package/dist/shared/protected-tools-policy.d.ts +6 -0
- package/dist/shared/protected-tools-policy.d.ts.map +1 -0
- package/dist/shared/provider-output-failure.d.ts +19 -0
- package/dist/shared/provider-output-failure.d.ts.map +1 -0
- package/dist/shared/temporal-upgrade-fixture.d.ts +19 -0
- package/dist/shared/temporal-upgrade-fixture.d.ts.map +1 -0
- package/dist/shared/window-geometry.d.ts +4 -0
- package/dist/shared/window-geometry.d.ts.map +1 -1
- package/dist/shared/write-transaction-timing.d.ts +8 -1
- package/dist/shared/write-transaction-timing.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/tools.d.ts +2 -0
- package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
- package/dist/v2/hidden-child-native.d.ts +7 -0
- package/dist/v2/hidden-child-native.d.ts.map +1 -1
- package/dist/v2/hidden-child-record.d.ts +13 -0
- package/dist/v2/hidden-child-record.d.ts.map +1 -1
- package/dist/v2/hidden-completion.d.ts.map +1 -1
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/hidden-child.d.ts +46 -2
- package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
- package/dist/v2/hooks/payload.d.ts.map +1 -1
- package/dist/v2/hooks/store.d.ts.map +1 -1
- package/dist/v2/hooks/tools.d.ts.map +1 -1
- package/dist/v2/hooks/types.d.ts +8 -0
- package/dist/v2/hooks/types.d.ts.map +1 -1
- package/dist/v2/server.js +4540 -3889
- package/dist/v2/tool-result.d.ts +8 -0
- package/dist/v2/tool-result.d.ts.map +1 -0
- package/dist/web/api.d.ts +6 -0
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/context-store.d.ts +13 -0
- package/dist/web/context-store.d.ts.map +1 -1
- package/dist/web/display-path.d.ts +28 -0
- package/dist/web/display-path.d.ts.map +1 -0
- package/dist/web/entry-args.d.ts +33 -0
- package/dist/web/entry-args.d.ts.map +1 -0
- package/dist/web/host-pi.d.ts +2 -0
- package/dist/web/host-pi.d.ts.map +1 -1
- package/dist/web/main.d.ts +4 -6
- package/dist/web/main.d.ts.map +1 -1
- package/dist/web/main.js +772 -77
- package/dist/web/message-stats.d.ts +45 -0
- package/dist/web/message-stats.d.ts.map +1 -0
- package/dist/web/ui/index.html +1 -0
- package/dist/web/ui/panel.css +5 -0
- package/dist/web/ui/panel.js +220 -70
- package/dist/web/views.d.ts +23 -0
- package/dist/web/views.d.ts.map +1 -1
- package/dist/{wrapup-orchestrator-z9sqpqjf.js → wrapup-orchestrator-wgb2nd19.js} +7 -6
- package/package.json +7 -2
- package/src/config/index.test.ts +257 -126
- package/src/config/index.ts +45 -39
- package/src/config/legacy-harness-blocks.test.ts +78 -0
- package/src/config/legacy-harness-blocks.ts +71 -0
- package/src/config/live-run-config.test.ts +49 -5
- package/src/config/live-run-config.ts +1 -0
- package/src/config/live-snapshot.ts +5 -1
- package/src/config/profiles.ts +1 -1
- package/src/config/project-config-trust-boundary.test.ts +2 -2
- package/src/config/project-security.hostile-matrix.test.ts +51 -65
- package/src/config/project-security.test.ts +6 -1
- package/src/config/project-security.ts +13 -12
- package/src/config/protected-tokens.test.ts +1 -1
- package/src/config/raw-loader.test.ts +29 -352
- package/src/config/raw-loader.ts +17 -406
- package/src/config/removed-config-keys.test.ts +1 -1
- package/src/config/schema/magic-context.test.ts +60 -64
- package/src/config/schema/magic-context.ts +105 -137
- package/src/config/variable.ts +21 -12
- package/src/shared/auto-search-lifecycle.ts +27 -0
- package/src/shared/cache-ttl-display.test.ts +1 -1
- package/src/shared/cache-ttl-display.ts +17 -16
- package/src/shared/model-cache-ttl.test.ts +2 -2
- package/src/shared/model-cache-ttl.ts +8 -4
- package/src/shared/model-resolution.test.ts +22 -32
- package/src/shared/model-resolution.ts +13 -18
- package/src/shared/model-suggestion-retry.ts +63 -7
- package/src/shared/protected-tools-policy.ts +25 -0
- package/src/shared/provider-output-failure.ts +176 -0
- package/src/shared/status-detail-text.test.ts +1 -1
- package/src/shared/status-view.test.ts +1 -1
- package/src/shared/temporal-upgrade-fixture.ts +50 -0
- package/src/shared/window-geometry.ts +10 -0
- package/src/shared/write-transaction-timing.ts +25 -1
- package/dist/features/magic-context/window-report-ledger.d.ts +0 -70
- package/dist/features/magic-context/window-report-ledger.d.ts.map +0 -1
- package/dist/index-e1q8002h.js +0 -16
- package/src/config/misplaced-agent-model.test.ts +0 -55
package/src/config/index.test.ts
CHANGED
|
@@ -116,6 +116,46 @@ function loadWithUserAndProjectConfig(
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Like loadWithUserAndProjectConfig, but returns the full detailed load result
|
|
121
|
+
* so tests can assert loadOutcome / configParseFailures (config rejection).
|
|
122
|
+
* Omit projectConfigText to load only the user tier.
|
|
123
|
+
*/
|
|
124
|
+
function loadDetailedWithConfigs(userConfigText: string, projectConfigText?: string) {
|
|
125
|
+
const xdg = createTestTempDirFromPath(join(tmpdir(), "mc-config-test-"));
|
|
126
|
+
const projectDir = createTestTempDirFromPath(join(tmpdir(), "mc-config-proj-"));
|
|
127
|
+
const fs = require("node:fs") as typeof import("node:fs");
|
|
128
|
+
const configDir = join(xdg, "opencode");
|
|
129
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
130
|
+
writeFileSync(join(configDir, "mmc.jsonc"), userConfigText, "utf-8");
|
|
131
|
+
if (projectConfigText !== undefined) {
|
|
132
|
+
fs.mkdirSync(join(projectDir, ".mmc"), { recursive: true });
|
|
133
|
+
writeFileSync(join(projectDir, ".mmc", "mmc.jsonc"), projectConfigText, "utf-8");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const origXdg = process.env.XDG_CONFIG_HOME;
|
|
137
|
+
process.env.XDG_CONFIG_HOME = xdg;
|
|
138
|
+
try {
|
|
139
|
+
return loadPluginConfigDetailed(projectDir);
|
|
140
|
+
} finally {
|
|
141
|
+
if (origXdg === undefined) {
|
|
142
|
+
delete process.env.XDG_CONFIG_HOME;
|
|
143
|
+
} else {
|
|
144
|
+
process.env.XDG_CONFIG_HOME = origXdg;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
rmSync(xdg, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
148
|
+
} catch {
|
|
149
|
+
/* Ignore EBUSY on Windows */
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
rmSync(projectDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
|
|
153
|
+
} catch {
|
|
154
|
+
/* Ignore EBUSY on Windows */
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
119
159
|
describe("loadPluginConfig — Fusiform overlay reload", () => {
|
|
120
160
|
it("does not invalidate a same-path overlay during routine config reads", () => {
|
|
121
161
|
const overlayDir = createTestTempDirFromPath(join(tmpdir(), "mc-config-overlay-"));
|
|
@@ -319,13 +359,13 @@ describe("loadPluginConfig — secret redaction", () => {
|
|
|
319
359
|
const result = loadWithUserAndProjectConfig(
|
|
320
360
|
JSON.stringify({
|
|
321
361
|
historian: {
|
|
322
|
-
|
|
362
|
+
model: "trusted/historian",
|
|
323
363
|
expand_tools: { ask: "Question ${input.question}", peer_send: false },
|
|
324
364
|
},
|
|
325
365
|
}),
|
|
326
366
|
JSON.stringify({
|
|
327
367
|
historian: {
|
|
328
|
-
|
|
368
|
+
model: "untrusted/historian",
|
|
329
369
|
expand_tools: { ask: false, board: '${input.ops.each("${op}")}' },
|
|
330
370
|
},
|
|
331
371
|
}),
|
|
@@ -335,41 +375,137 @@ describe("loadPluginConfig — secret redaction", () => {
|
|
|
335
375
|
peer_send: false,
|
|
336
376
|
board: '${input.ops.each("${op}")}',
|
|
337
377
|
});
|
|
338
|
-
expect(result.historian?.
|
|
378
|
+
expect(result.historian?.model).toBe("trusted/historian");
|
|
339
379
|
});
|
|
340
380
|
|
|
341
|
-
it("
|
|
381
|
+
it("accepts the flat qualifiers without treating them as an agent-config error", () => {
|
|
342
382
|
const result = loadWithUserConfig(
|
|
343
383
|
JSON.stringify({
|
|
344
384
|
historian: {
|
|
345
385
|
two_pass: true,
|
|
346
|
-
|
|
386
|
+
model: {
|
|
347
387
|
model: "anthropic/claude-sonnet",
|
|
348
388
|
variant: "high",
|
|
349
|
-
thinking_level: "minimal",
|
|
350
|
-
},
|
|
351
|
-
pi: {
|
|
352
|
-
model: "github-copilot/gpt-5",
|
|
353
389
|
thinking_level: "medium",
|
|
354
|
-
variant: "fast",
|
|
355
390
|
},
|
|
391
|
+
variant: "fast",
|
|
356
392
|
},
|
|
357
393
|
}),
|
|
358
394
|
);
|
|
359
395
|
|
|
360
396
|
expect(result.historian?.two_pass).toBe(true);
|
|
361
|
-
expect(result.historian?.
|
|
397
|
+
expect(result.historian?.model).toEqual({
|
|
362
398
|
model: "anthropic/claude-sonnet",
|
|
363
399
|
variant: "high",
|
|
364
|
-
});
|
|
365
|
-
expect(result.historian?.pi).toEqual({
|
|
366
|
-
model: "github-copilot/gpt-5",
|
|
367
400
|
thinking_level: "medium",
|
|
368
401
|
});
|
|
369
|
-
|
|
370
|
-
expect(
|
|
371
|
-
|
|
372
|
-
|
|
402
|
+
expect(result.historian?.variant).toBe("fast");
|
|
403
|
+
expect(result.configWarnings?.join("\n") ?? "").not.toContain(
|
|
404
|
+
"invalid agent configuration, ignoring",
|
|
405
|
+
);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("rejects a user config that still carries the removed per-harness historian blocks", () => {
|
|
409
|
+
const detailed = loadDetailedWithConfigs(
|
|
410
|
+
JSON.stringify({
|
|
411
|
+
historian: {
|
|
412
|
+
two_pass: true,
|
|
413
|
+
opencode: { model: "anthropic/claude-sonnet", variant: "high" },
|
|
414
|
+
pi: { model: "github-copilot/gpt-5", thinking_level: "medium" },
|
|
415
|
+
},
|
|
416
|
+
}),
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
// The whole file is rejected: a parse-failure outcome (defaults at boot,
|
|
420
|
+
// last-known-good on live reload), not a silent strip of the block.
|
|
421
|
+
expect(detailed.loadOutcome).toBe("project-file-parse-error");
|
|
422
|
+
expect(detailed.sources.userConfig).toBe("project-file-parse-error");
|
|
423
|
+
expect(detailed.config.historian?.two_pass).toBeUndefined();
|
|
424
|
+
const failures = detailed.configParseFailures.map((f) => f.message).join("\n");
|
|
425
|
+
expect(failures).toContain("historian.opencode");
|
|
426
|
+
expect(failures).toContain("historian.pi");
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it("rejects a project config that still carries the removed per-harness historian blocks", () => {
|
|
430
|
+
const detailed = loadDetailedWithConfigs(
|
|
431
|
+
JSON.stringify({ historian: { model: "anthropic/user-base" } }),
|
|
432
|
+
JSON.stringify({
|
|
433
|
+
historian: {
|
|
434
|
+
opencode: { model: "attacker/project-historian" },
|
|
435
|
+
pi: { model: "attacker/project-historian" },
|
|
436
|
+
},
|
|
437
|
+
}),
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
expect(detailed.loadOutcome).toBe("project-file-parse-error");
|
|
441
|
+
expect(detailed.sources.projectConfig).toBe("project-file-parse-error");
|
|
442
|
+
// The project file is rejected before tier hardening, so the block is
|
|
443
|
+
// reported (not pre-stripped and invisible); the user base still applies.
|
|
444
|
+
expect(detailed.config.historian?.model).toBe("anthropic/user-base");
|
|
445
|
+
const failures = detailed.configParseFailures.map((f) => f.message).join("\n");
|
|
446
|
+
expect(failures).toContain("historian.opencode");
|
|
447
|
+
expect(failures).toContain("historian.pi");
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("still strips the legal flat model fields from a project config with a warning", () => {
|
|
451
|
+
const result = loadWithUserAndProjectConfig(
|
|
452
|
+
JSON.stringify({ historian: { model: "anthropic/user-base" } }),
|
|
453
|
+
JSON.stringify({
|
|
454
|
+
historian: {
|
|
455
|
+
model: "attacker/project-model",
|
|
456
|
+
fallback_models: ["attacker/project-fallback"],
|
|
457
|
+
variant: "high",
|
|
458
|
+
thinking_level: "high",
|
|
459
|
+
},
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
expect(result.historian?.model).toBe("anthropic/user-base");
|
|
464
|
+
expect(result.configWarnings?.join("\n")).toContain("Ignoring historian.model");
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("rejects a user config whose legacy block survives JSONC partial recovery (no crash)", () => {
|
|
468
|
+
// A JSONC syntax error (`tru`) must not hide the removed block: recovery
|
|
469
|
+
// still yields historian.opencode, which must be rejected at load rather
|
|
470
|
+
// than handed to the parser (which would throw uncaught).
|
|
471
|
+
const detailed = loadDetailedWithConfigs(
|
|
472
|
+
'{"historian":{"opencode":{"model":"vendor/old"}},"enabled":tru}',
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
expect(detailed.loadOutcome).toBe("project-file-parse-error");
|
|
476
|
+
expect(detailed.sources.userConfig).toBe("project-file-parse-error");
|
|
477
|
+
expect(detailed.config.historian?.model).toBeUndefined();
|
|
478
|
+
expect(detailed.configParseFailures.every((f) => f.recovered === false)).toBe(true);
|
|
479
|
+
expect(detailed.configParseFailures.map((f) => f.message).join("\n")).toContain(
|
|
480
|
+
"historian.opencode",
|
|
481
|
+
);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("rejects a user config whose UNSELECTED profile legacy block survives partial recovery", () => {
|
|
485
|
+
const detailed = loadDetailedWithConfigs(
|
|
486
|
+
'{"profiles":{"work":{"historian":{"pi":{"model":"x/y"}}}},"enabled":tru}',
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
expect(detailed.loadOutcome).toBe("project-file-parse-error");
|
|
490
|
+
expect(detailed.configParseFailures.every((f) => f.recovered === false)).toBe(true);
|
|
491
|
+
expect(detailed.configParseFailures.map((f) => f.message).join("\n")).toContain(
|
|
492
|
+
"profiles.work.historian.pi",
|
|
493
|
+
);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("rejects a project config whose legacy block survives partial recovery while the user tier stays", () => {
|
|
497
|
+
const detailed = loadDetailedWithConfigs(
|
|
498
|
+
JSON.stringify({ historian: { model: "anthropic/user-base" } }),
|
|
499
|
+
'{"historian":{"opencode":{"model":"attacker/project"}},"enabled":tru}',
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
expect(detailed.loadOutcome).toBe("project-file-parse-error");
|
|
503
|
+
expect(detailed.sources.projectConfig).toBe("project-file-parse-error");
|
|
504
|
+
expect(detailed.config.historian?.model).toBe("anthropic/user-base");
|
|
505
|
+
expect(detailed.configParseFailures.every((f) => f.recovered === false)).toBe(true);
|
|
506
|
+
expect(detailed.configParseFailures.map((f) => f.message).join("\n")).toContain(
|
|
507
|
+
"historian.opencode",
|
|
508
|
+
);
|
|
373
509
|
});
|
|
374
510
|
|
|
375
511
|
it("still shows numeric and boolean invalid values (not secrets by nature)", () => {
|
|
@@ -476,14 +612,14 @@ describe("loadPluginConfig — legacy agent enabled migration", () => {
|
|
|
476
612
|
profiles: {
|
|
477
613
|
work: {
|
|
478
614
|
[removedKey]: { model: "example/profile-model" },
|
|
479
|
-
historian: {
|
|
615
|
+
historian: { model: "example/historian" },
|
|
480
616
|
},
|
|
481
617
|
},
|
|
482
618
|
}),
|
|
483
619
|
);
|
|
484
620
|
|
|
485
621
|
expect(removedKey in result).toBe(false);
|
|
486
|
-
expect(result.historian?.
|
|
622
|
+
expect(result.historian?.model).toBe("example/historian");
|
|
487
623
|
expect(result.configWarnings?.filter((warning) => warning.includes(removedKey))).toEqual([
|
|
488
624
|
`[config] ${REMOVED_AGENT_CONFIG_WARNING}`,
|
|
489
625
|
]);
|
|
@@ -590,43 +726,25 @@ describe("loadPluginConfig — user-only settings", () => {
|
|
|
590
726
|
const result = loadWithUserAndProjectConfig(
|
|
591
727
|
JSON.stringify({
|
|
592
728
|
historian: {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
fallback_models: ["anthropic/user-fallback"],
|
|
596
|
-
},
|
|
597
|
-
pi: {
|
|
598
|
-
model: "github-copilot/user-historian",
|
|
599
|
-
fallback_models: ["github-copilot/user-fallback"],
|
|
600
|
-
},
|
|
729
|
+
model: "anthropic/user-historian",
|
|
730
|
+
fallback_models: ["anthropic/user-fallback"],
|
|
601
731
|
},
|
|
602
732
|
}),
|
|
603
733
|
JSON.stringify({
|
|
604
734
|
historian: {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
fallback_models: ["anthropic/project-fallback"],
|
|
608
|
-
},
|
|
609
|
-
pi: {
|
|
610
|
-
model: "github-copilot/project-historian",
|
|
611
|
-
fallback_models: ["github-copilot/project-fallback"],
|
|
612
|
-
},
|
|
735
|
+
model: "anthropic/project-historian",
|
|
736
|
+
fallback_models: ["anthropic/project-fallback"],
|
|
613
737
|
temperature: 0.2,
|
|
614
738
|
},
|
|
615
739
|
}),
|
|
616
740
|
);
|
|
617
741
|
|
|
618
|
-
expect(result.historian?.
|
|
619
|
-
|
|
620
|
-
fallback_models: ["anthropic/user-fallback"],
|
|
621
|
-
});
|
|
622
|
-
expect(result.historian?.pi).toEqual({
|
|
623
|
-
model: "github-copilot/user-historian",
|
|
624
|
-
fallback_models: ["github-copilot/user-fallback"],
|
|
625
|
-
});
|
|
742
|
+
expect(result.historian?.model).toBe("anthropic/user-historian");
|
|
743
|
+
expect(result.historian?.fallback_models).toEqual(["anthropic/user-fallback"]);
|
|
626
744
|
expect(result.historian?.temperature).toBe(0.2);
|
|
627
745
|
const warnings = result.configWarnings?.join("\n") ?? "";
|
|
628
|
-
expect(warnings).toContain("historian.
|
|
629
|
-
expect(warnings).toContain("historian.
|
|
746
|
+
expect(warnings).toContain("historian.model");
|
|
747
|
+
expect(warnings).toContain("historian.fallback_models");
|
|
630
748
|
});
|
|
631
749
|
});
|
|
632
750
|
|
|
@@ -796,6 +914,7 @@ describe("loadPluginConfig — raw merge preserves user fields not set in projec
|
|
|
796
914
|
const result = loadWithUserAndProjectConfig(userConfig, projectConfig);
|
|
797
915
|
|
|
798
916
|
expect(result.memory.enabled).toBe(false);
|
|
917
|
+
// Deprecated key still passes through the raw merge; runtime ignores it.
|
|
799
918
|
expect(result.smart_drops).toBe(true);
|
|
800
919
|
});
|
|
801
920
|
|
|
@@ -812,7 +931,7 @@ describe("loadPluginConfig — raw merge preserves user fields not set in projec
|
|
|
812
931
|
});
|
|
813
932
|
|
|
814
933
|
it("project boolean override beats user default", () => {
|
|
815
|
-
//
|
|
934
|
+
// Deprecated smart_drops no longer gates supersession reclaim.
|
|
816
935
|
const result = loadWithUserAndProjectConfig(
|
|
817
936
|
JSON.stringify({ enabled: true }),
|
|
818
937
|
JSON.stringify({ smart_drops: true }),
|
|
@@ -848,24 +967,18 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
848
967
|
profile: "personal",
|
|
849
968
|
historian: {
|
|
850
969
|
two_pass: true,
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
fallback_models: ["anthropic/base-fallback"],
|
|
854
|
-
},
|
|
970
|
+
model: "anthropic/base-historian",
|
|
971
|
+
fallback_models: ["anthropic/base-fallback"],
|
|
855
972
|
},
|
|
856
973
|
profiles: {
|
|
857
974
|
work: {
|
|
858
975
|
historian: {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
fallback_models: [
|
|
862
|
-
{ model: "openai/work-fallback", variant: "low" },
|
|
863
|
-
],
|
|
864
|
-
},
|
|
976
|
+
model: { model: "anthropic/work-historian", variant: "high" },
|
|
977
|
+
fallback_models: [{ model: "openai/work-fallback", variant: "low" }],
|
|
865
978
|
},
|
|
866
979
|
},
|
|
867
980
|
personal: {
|
|
868
|
-
historian: {
|
|
981
|
+
historian: { model: "anthropic/personal-historian" },
|
|
869
982
|
},
|
|
870
983
|
},
|
|
871
984
|
}),
|
|
@@ -878,19 +991,22 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
878
991
|
expect(result.profile).toBe("work");
|
|
879
992
|
expect(result.memory.enabled).toBe(false);
|
|
880
993
|
expect(result.historian?.two_pass).toBe(true);
|
|
881
|
-
expect(result.historian?.
|
|
882
|
-
model:
|
|
883
|
-
|
|
994
|
+
expect(result.historian?.model).toEqual({
|
|
995
|
+
model: "anthropic/work-historian",
|
|
996
|
+
variant: "high",
|
|
884
997
|
});
|
|
998
|
+
expect(result.historian?.fallback_models).toEqual([
|
|
999
|
+
{ model: "openai/work-fallback", variant: "low" },
|
|
1000
|
+
]);
|
|
885
1001
|
});
|
|
886
1002
|
|
|
887
1003
|
it("uses project selection over user selection and falls back to the base on an unknown name", () => {
|
|
888
1004
|
const userConfig = JSON.stringify({
|
|
889
1005
|
profile: "personal",
|
|
890
|
-
historian: {
|
|
1006
|
+
historian: { model: "anthropic/base" },
|
|
891
1007
|
profiles: {
|
|
892
|
-
personal: { historian: {
|
|
893
|
-
work: { historian: {
|
|
1008
|
+
personal: { historian: { model: "anthropic/personal" } },
|
|
1009
|
+
work: { historian: { model: "anthropic/work" } },
|
|
894
1010
|
},
|
|
895
1011
|
});
|
|
896
1012
|
|
|
@@ -899,12 +1015,12 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
899
1015
|
const unknownProject = loadWithUserAndProjectConfig(userConfig, '{"profile":"missing"}');
|
|
900
1016
|
|
|
901
1017
|
expect(userDefault.profile).toBe("personal");
|
|
902
|
-
expect(userDefault.historian?.
|
|
1018
|
+
expect(userDefault.historian?.model).toBe("anthropic/personal");
|
|
903
1019
|
expect(projectOverride.profile).toBe("work");
|
|
904
|
-
expect(projectOverride.historian?.
|
|
1020
|
+
expect(projectOverride.historian?.model).toBe("anthropic/work");
|
|
905
1021
|
expect(unknownProject.profile).toBeUndefined();
|
|
906
1022
|
expect(unknownProject.enabled).toBe(true);
|
|
907
|
-
expect(unknownProject.historian?.
|
|
1023
|
+
expect(unknownProject.historian?.model).toBe("anthropic/base");
|
|
908
1024
|
expect(unknownProject.configWarnings?.join("\n")).toContain(
|
|
909
1025
|
'Unknown profile "missing" selected by project config',
|
|
910
1026
|
);
|
|
@@ -914,7 +1030,7 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
914
1030
|
const result = loadWithUserConfig(
|
|
915
1031
|
JSON.stringify({
|
|
916
1032
|
profile: "unsafe",
|
|
917
|
-
historian: {
|
|
1033
|
+
historian: { model: "anthropic/base" },
|
|
918
1034
|
profiles: {
|
|
919
1035
|
unsafe: {
|
|
920
1036
|
historian: { two_pass: true },
|
|
@@ -924,29 +1040,46 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
924
1040
|
);
|
|
925
1041
|
|
|
926
1042
|
expect(result.profile).toBeUndefined();
|
|
927
|
-
expect(result.historian?.
|
|
1043
|
+
expect(result.historian?.model).toBe("anthropic/base");
|
|
928
1044
|
// two_pass defaults to false; the profile's `true` must not leak through.
|
|
929
1045
|
expect(result.historian?.two_pass).toBe(false);
|
|
930
1046
|
expect(result.configWarnings?.join("\n")).toContain("Ignoring profiles from user config");
|
|
931
1047
|
});
|
|
932
1048
|
|
|
1049
|
+
it("rejects a user config whose profile still carries a removed per-harness block", () => {
|
|
1050
|
+
const detailed = loadDetailedWithConfigs(
|
|
1051
|
+
JSON.stringify({
|
|
1052
|
+
profile: "work",
|
|
1053
|
+
profiles: {
|
|
1054
|
+
work: { historian: { opencode: { model: "anthropic/x" } } },
|
|
1055
|
+
},
|
|
1056
|
+
}),
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
// No silent fallback to the base config: the whole file is rejected.
|
|
1060
|
+
expect(detailed.loadOutcome).toBe("project-file-parse-error");
|
|
1061
|
+
expect(detailed.config.profile).toBeUndefined();
|
|
1062
|
+
const failures = detailed.configParseFailures.map((f) => f.message).join("\n");
|
|
1063
|
+
expect(failures).toContain("profiles.work.historian.opencode");
|
|
1064
|
+
});
|
|
1065
|
+
|
|
933
1066
|
it("strips hostile project profile definitions while allowing only user-owned definitions", () => {
|
|
934
1067
|
const result = loadWithUserAndProjectConfig(
|
|
935
1068
|
JSON.stringify({
|
|
936
|
-
historian: {
|
|
1069
|
+
historian: { model: "anthropic/user-base" },
|
|
937
1070
|
}),
|
|
938
1071
|
JSON.stringify({
|
|
939
1072
|
profile: "work",
|
|
940
1073
|
profiles: {
|
|
941
1074
|
work: {
|
|
942
|
-
historian: {
|
|
1075
|
+
historian: { model: "attacker/project-profile" },
|
|
943
1076
|
},
|
|
944
1077
|
},
|
|
945
1078
|
}),
|
|
946
1079
|
);
|
|
947
1080
|
|
|
948
1081
|
expect(result.profile).toBeUndefined();
|
|
949
|
-
expect(result.historian?.
|
|
1082
|
+
expect(result.historian?.model).toBe("anthropic/user-base");
|
|
950
1083
|
expect(result.configWarnings?.join("\n")).toContain(
|
|
951
1084
|
"Ignoring profiles from project config",
|
|
952
1085
|
);
|
|
@@ -954,8 +1087,8 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
954
1087
|
|
|
955
1088
|
it("falls back loudly for inherited profile names without changing the resolved base config", () => {
|
|
956
1089
|
const userConfig = JSON.stringify({
|
|
957
|
-
historian: {
|
|
958
|
-
profiles: { known: { historian: {
|
|
1090
|
+
historian: { model: "anthropic/base", fallback_models: ["openai/base"] },
|
|
1091
|
+
profiles: { known: { historian: { model: "anthropic/known" } } },
|
|
959
1092
|
});
|
|
960
1093
|
const base = loadWithUserConfig(userConfig);
|
|
961
1094
|
const { configWarnings: _baseWarnings, ...baseWithoutWarnings } = base;
|
|
@@ -986,13 +1119,10 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
986
1119
|
it("ignores a profile inherited from a polluted Object prototype", () => {
|
|
987
1120
|
const profileName = "profile_overlay_pollution_canary";
|
|
988
1121
|
const previousDescriptor = Object.getOwnPropertyDescriptor(Object.prototype, profileName);
|
|
989
|
-
const userBase = { historian: {
|
|
1122
|
+
const userBase = { historian: { model: "anthropic/base" } };
|
|
990
1123
|
// Null-prototype input isolates lookup behavior from strict schema validation of inherited data.
|
|
991
|
-
const profileOpenCode = Object.assign(Object.create(null), {
|
|
992
|
-
model: "anthropic/known",
|
|
993
|
-
});
|
|
994
1124
|
const profileHistorian = Object.assign(Object.create(null), {
|
|
995
|
-
|
|
1125
|
+
model: "anthropic/known",
|
|
996
1126
|
});
|
|
997
1127
|
const knownProfile = Object.assign(Object.create(null), {
|
|
998
1128
|
historian: profileHistorian,
|
|
@@ -1001,7 +1131,7 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
1001
1131
|
known: knownProfile,
|
|
1002
1132
|
}) as Record<string, unknown>;
|
|
1003
1133
|
Object.defineProperty(Object.prototype, profileName, {
|
|
1004
|
-
value: { historian: {
|
|
1134
|
+
value: { historian: { model: "attacker/inherited-overlay" } },
|
|
1005
1135
|
enumerable: true,
|
|
1006
1136
|
configurable: true,
|
|
1007
1137
|
});
|
|
@@ -1034,9 +1164,9 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
1034
1164
|
it("treats empty, null, and non-string project selectors as no selection", () => {
|
|
1035
1165
|
const userConfig = JSON.stringify({
|
|
1036
1166
|
profile: "personal",
|
|
1037
|
-
historian: {
|
|
1167
|
+
historian: { model: "anthropic/base" },
|
|
1038
1168
|
profiles: {
|
|
1039
|
-
personal: { historian: {
|
|
1169
|
+
personal: { historian: { model: "anthropic/personal" } },
|
|
1040
1170
|
},
|
|
1041
1171
|
});
|
|
1042
1172
|
const invalidProjectSelectors = ["", null, { name: "work" }];
|
|
@@ -1045,39 +1175,40 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
1045
1175
|
const result = loadWithUserAndProjectConfig(userConfig, JSON.stringify({ profile }));
|
|
1046
1176
|
|
|
1047
1177
|
expect(result.profile).toBe("personal");
|
|
1048
|
-
expect(result.historian?.
|
|
1178
|
+
expect(result.historian?.model).toBe("anthropic/personal");
|
|
1049
1179
|
expect(result.configWarnings?.join("\n")).toContain(
|
|
1050
1180
|
"Ignoring invalid profile selection from project config",
|
|
1051
1181
|
);
|
|
1052
1182
|
}
|
|
1053
1183
|
});
|
|
1054
1184
|
|
|
1055
|
-
it("
|
|
1056
|
-
const piBase = {
|
|
1057
|
-
model: { model: "github-copilot/base", thinking_level: "high" },
|
|
1058
|
-
fallback_models: [
|
|
1059
|
-
{ model: "openai/base-fallback", thinking_level: "minimal" },
|
|
1060
|
-
{ model: "github-copilot/second-base-fallback", thinking_level: "medium" },
|
|
1061
|
-
],
|
|
1062
|
-
thinking_level: "high",
|
|
1063
|
-
};
|
|
1185
|
+
it("keeps base historian metadata when a profile overlays the model", () => {
|
|
1064
1186
|
const result = loadWithUserConfig(
|
|
1065
1187
|
JSON.stringify({
|
|
1066
1188
|
profile: "work",
|
|
1067
1189
|
historian: {
|
|
1068
|
-
|
|
1069
|
-
|
|
1190
|
+
two_pass: true,
|
|
1191
|
+
model: "anthropic/base",
|
|
1192
|
+
expand_tools: { read: "Read ${input.path}" },
|
|
1070
1193
|
},
|
|
1071
1194
|
profiles: {
|
|
1072
1195
|
work: {
|
|
1073
|
-
historian: {
|
|
1196
|
+
historian: {
|
|
1197
|
+
model: { model: "github-copilot/work", thinking_level: "high" },
|
|
1198
|
+
thinking_level: "medium",
|
|
1199
|
+
},
|
|
1074
1200
|
},
|
|
1075
1201
|
},
|
|
1076
1202
|
}),
|
|
1077
1203
|
);
|
|
1078
1204
|
|
|
1079
|
-
expect(result.historian?.
|
|
1080
|
-
expect(result.historian?.
|
|
1205
|
+
expect(result.historian?.two_pass).toBe(true);
|
|
1206
|
+
expect(result.historian?.expand_tools).toEqual({ read: "Read ${input.path}" });
|
|
1207
|
+
expect(result.historian?.model).toEqual({
|
|
1208
|
+
model: "github-copilot/work",
|
|
1209
|
+
thinking_level: "high",
|
|
1210
|
+
});
|
|
1211
|
+
expect(result.historian?.thinking_level).toBe("medium");
|
|
1081
1212
|
});
|
|
1082
1213
|
|
|
1083
1214
|
it("replaces a base fallback_models array wholesale when a profile provides one", () => {
|
|
@@ -1085,27 +1216,23 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
1085
1216
|
JSON.stringify({
|
|
1086
1217
|
profile: "work",
|
|
1087
1218
|
historian: {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
],
|
|
1094
|
-
},
|
|
1219
|
+
model: "anthropic/base",
|
|
1220
|
+
fallback_models: [
|
|
1221
|
+
{ model: "openai/base-first", variant: "low" },
|
|
1222
|
+
{ model: "anthropic/base-second", variant: "high" },
|
|
1223
|
+
],
|
|
1095
1224
|
},
|
|
1096
1225
|
profiles: {
|
|
1097
1226
|
work: {
|
|
1098
1227
|
historian: {
|
|
1099
|
-
|
|
1100
|
-
fallback_models: [{ model: "google/work-only", variant: "medium" }],
|
|
1101
|
-
},
|
|
1228
|
+
fallback_models: [{ model: "google/work-only", variant: "medium" }],
|
|
1102
1229
|
},
|
|
1103
1230
|
},
|
|
1104
1231
|
},
|
|
1105
1232
|
}),
|
|
1106
1233
|
);
|
|
1107
1234
|
|
|
1108
|
-
expect(result.historian?.
|
|
1235
|
+
expect(result.historian?.fallback_models).toEqual([
|
|
1109
1236
|
{ model: "google/work-only", variant: "medium" },
|
|
1110
1237
|
]);
|
|
1111
1238
|
});
|
|
@@ -1122,9 +1249,9 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
1122
1249
|
join(xdg, "opencode", "mmc.jsonc"),
|
|
1123
1250
|
JSON.stringify({
|
|
1124
1251
|
profiles: {
|
|
1125
|
-
work: { historian: {
|
|
1252
|
+
work: { historian: { model: "anthropic/work" } },
|
|
1126
1253
|
personal: {
|
|
1127
|
-
historian: {
|
|
1254
|
+
historian: { model: "anthropic/personal" },
|
|
1128
1255
|
},
|
|
1129
1256
|
},
|
|
1130
1257
|
}),
|
|
@@ -1139,9 +1266,9 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
1139
1266
|
expect(work.profile).toBe("work");
|
|
1140
1267
|
expect(personal.profile).toBe("personal");
|
|
1141
1268
|
expect(workAgain.profile).toBe("work");
|
|
1142
|
-
expect(work.historian?.
|
|
1143
|
-
expect(personal.historian?.
|
|
1144
|
-
expect(workAgain.historian?.
|
|
1269
|
+
expect(work.historian?.model).toBe("anthropic/work");
|
|
1270
|
+
expect(personal.historian?.model).toBe("anthropic/personal");
|
|
1271
|
+
expect(workAgain.historian?.model).toBe("anthropic/work");
|
|
1145
1272
|
} finally {
|
|
1146
1273
|
if (previousXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
|
1147
1274
|
else process.env.XDG_CONFIG_HOME = previousXdg;
|
|
@@ -1157,12 +1284,8 @@ describe("loadPluginConfig — user-owned model profiles", () => {
|
|
|
1157
1284
|
profiles: {
|
|
1158
1285
|
work: {
|
|
1159
1286
|
historian: {
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
fallback_models: [
|
|
1163
|
-
{ model: "openai/work-fallback", variant: "low" },
|
|
1164
|
-
],
|
|
1165
|
-
},
|
|
1287
|
+
model: { model: "anthropic/work", variant: "high" },
|
|
1288
|
+
fallback_models: [{ model: "openai/work-fallback", variant: "low" }],
|
|
1166
1289
|
},
|
|
1167
1290
|
},
|
|
1168
1291
|
},
|
|
@@ -1233,20 +1356,28 @@ describe("loadPluginConfigDetailed — prompt-surface registration owner", () =>
|
|
|
1233
1356
|
});
|
|
1234
1357
|
});
|
|
1235
1358
|
|
|
1236
|
-
describe("shared
|
|
1237
|
-
it("
|
|
1359
|
+
describe("shared flat historian config loading", () => {
|
|
1360
|
+
it("resolves one flat entry for both harnesses without schema warnings", () => {
|
|
1238
1361
|
const config = loadWithUserConfig(
|
|
1239
1362
|
JSON.stringify({
|
|
1240
1363
|
historian: {
|
|
1241
|
-
|
|
1242
|
-
|
|
1364
|
+
model: {
|
|
1365
|
+
model: "anthropic/shared-historian",
|
|
1366
|
+
variant: "high",
|
|
1367
|
+
thinking_level: "high",
|
|
1368
|
+
},
|
|
1243
1369
|
},
|
|
1244
1370
|
}),
|
|
1245
1371
|
);
|
|
1246
1372
|
|
|
1247
1373
|
expect(config.configWarnings).toBeUndefined();
|
|
1248
|
-
expect(resolveHistorianModel(config, "opencode").primary
|
|
1249
|
-
"anthropic/
|
|
1250
|
-
|
|
1374
|
+
expect(resolveHistorianModel(config, "opencode").primary).toEqual({
|
|
1375
|
+
model: "anthropic/shared-historian",
|
|
1376
|
+
qualifier: "high",
|
|
1377
|
+
});
|
|
1378
|
+
expect(resolveHistorianModel(config, "pi").primary).toEqual({
|
|
1379
|
+
model: "anthropic/shared-historian",
|
|
1380
|
+
qualifier: "high",
|
|
1381
|
+
});
|
|
1251
1382
|
});
|
|
1252
1383
|
});
|