@retaw106/mmc-oc 1.2.3 → 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 +1 -1
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE,
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
HISTORIAN_MODEL_RESOLUTION_FIELDS,
|
|
4
|
+
LEGACY_HARNESS_BLOCK_KEYS,
|
|
5
5
|
} from "./schema/magic-context";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -19,11 +19,13 @@ import {
|
|
|
19
19
|
|
|
20
20
|
/** Hidden agents that run with elevated/autonomous capability. */
|
|
21
21
|
const HIDDEN_AGENT_KEYS = ["historian"] as const;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
/** Removed per-harness historian blocks; iterated only to keep stripping
|
|
23
|
+
* escalation fields out of a legacy block a cloned repo might still supply. */
|
|
24
|
+
const HARNESS_KEYS = LEGACY_HARNESS_BLOCK_KEYS;
|
|
25
|
+
/** Every flat historian model-resolution field, including the per-harness
|
|
26
|
+
* qualifiers. Variant and thinking_level merge onto the user's historian model
|
|
27
|
+
* at resolve time, so leaving them would let a cloned repo force extra spend. */
|
|
28
|
+
const HISTORIAN_USER_ONLY_FIELDS = HISTORIAN_MODEL_RESOLUTION_FIELDS;
|
|
27
29
|
const PROMPT_SURFACE_USER_ONLY_FIELDS = ["guidance_override_path", "tool_descriptions"] as const;
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -316,11 +318,10 @@ function makeProjectThresholdWarning(field: string, reason: string): string {
|
|
|
316
318
|
* owner-private to group-readable changes every session and memory's local
|
|
317
319
|
* confidentiality. Only the machine operator's user config may opt into an
|
|
318
320
|
* externally managed trusted-group deployment.
|
|
319
|
-
* - historian model-resolution fields (model, fallback_models, variant,
|
|
320
|
-
* thinking_level),
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* variant cost.
|
|
321
|
+
* - historian flat model-resolution fields (model, fallback_models, variant,
|
|
322
|
+
* thinking_level), plus any legacy per-harness block — historian model spend
|
|
323
|
+
* is user-level only. Qualifiers merge onto the user's historian model at
|
|
324
|
+
* resolve time, so a cloned repo cannot force extra thinking or variant cost.
|
|
324
325
|
* - `pi.subagent_extensions` — a cloned repo must not choose which extensions
|
|
325
326
|
* the user's Pi child processes load.
|
|
326
327
|
* - `prompt_surface.guidance_override_path` / `tool_descriptions` — a repository
|
|
@@ -177,7 +177,7 @@ describe("protected_tokens config and derivation", () => {
|
|
|
177
177
|
|
|
178
178
|
describe("root configuration reference", () => {
|
|
179
179
|
const reference = readFileSync(
|
|
180
|
-
resolve(import.meta.dir, "../../../../
|
|
180
|
+
resolve(import.meta.dir, "../../../../docs/guides/配置说明.md"),
|
|
181
181
|
"utf8",
|
|
182
182
|
);
|
|
183
183
|
|
|
@@ -1,389 +1,66 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
chmodSync,
|
|
4
|
-
lstatSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
readlinkSync,
|
|
8
|
-
rmSync,
|
|
9
|
-
statSync,
|
|
10
|
-
symlinkSync,
|
|
11
|
-
writeFileSync,
|
|
12
|
-
} from "node:fs";
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
13
3
|
import { tmpdir } from "node:os";
|
|
14
4
|
import { join } from "node:path";
|
|
15
5
|
|
|
16
|
-
import { parseJsonc } from "../shared/jsonc-parser";
|
|
17
6
|
import { createTestTempDirFromPath } from "../shared/test-temp-dir";
|
|
18
|
-
import {
|
|
19
|
-
import { PER_HARNESS_MIGRATION_INVENTORY } from "./schema/magic-context";
|
|
7
|
+
import { loadRawConfigFile } from "./raw-loader";
|
|
20
8
|
|
|
21
9
|
const temporaryDirectories: string[] = [];
|
|
22
10
|
|
|
23
11
|
function temporaryDirectory(): string {
|
|
24
|
-
const directory = createTestTempDirFromPath(join(tmpdir(), "mc-
|
|
12
|
+
const directory = createTestTempDirFromPath(join(tmpdir(), "mc-raw-loader-"));
|
|
25
13
|
temporaryDirectories.push(directory);
|
|
26
14
|
return directory;
|
|
27
15
|
}
|
|
28
16
|
|
|
29
|
-
function parse(text: string): Record<string, unknown> {
|
|
30
|
-
const withoutBom = text.startsWith("\uFEFF") ? text.slice(1) : text;
|
|
31
|
-
return parseJsonc<Record<string, unknown>>(withoutBom);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
17
|
afterEach(() => {
|
|
35
18
|
for (const directory of temporaryDirectories.splice(0)) {
|
|
36
19
|
rmSync(directory, { recursive: true, force: true });
|
|
37
20
|
}
|
|
38
21
|
});
|
|
39
22
|
|
|
40
|
-
describe("
|
|
41
|
-
it("
|
|
42
|
-
const input = `\uFEFF{\r
|
|
43
|
-
// preserve this comment\r
|
|
44
|
-
"historian": {\r
|
|
45
|
-
"model": { "model": "provider/historian", "variant": "fast", "thinking_level": "high" }, // historian model note\r
|
|
46
|
-
"fallback_models": [{ "model": "provider/fallback", "variant": "careful", "thinking_level": "low" }],\r
|
|
47
|
-
"variant": "block-variant",\r
|
|
48
|
-
"thinking_level": "medium"\r
|
|
49
|
-
},\r
|
|
50
|
-
"unrelated": { "keep": true }\r
|
|
51
|
-
}\r
|
|
52
|
-
`;
|
|
53
|
-
|
|
54
|
-
const migrated = migrateFlatDetailed(Buffer.from(input, "utf-8"));
|
|
55
|
-
const repeated = migrateFlatDetailed(Buffer.from(input, "utf-8"));
|
|
56
|
-
const idempotent = migrateFlatDetailed(migrated.bytes);
|
|
57
|
-
const output = migrated.bytes.toString("utf-8");
|
|
58
|
-
const config = parse(output);
|
|
59
|
-
const historian = config.historian as Record<string, unknown>;
|
|
60
|
-
const opencodeHistorian = historian.opencode as Record<string, unknown>;
|
|
61
|
-
const piHistorian = historian.pi as Record<string, unknown>;
|
|
62
|
-
|
|
63
|
-
expect(migrated.hasFlatKeys).toBe(true);
|
|
64
|
-
expect(repeated.bytes).toEqual(migrated.bytes);
|
|
65
|
-
expect(repeated.diagnostics).toEqual(migrated.diagnostics);
|
|
66
|
-
expect(idempotent.bytes).toEqual(migrated.bytes);
|
|
67
|
-
expect(idempotent.hasFlatKeys).toBe(false);
|
|
68
|
-
expect(idempotent.diagnostics).toEqual([]);
|
|
69
|
-
expect(hasFlatKeys(migrated.bytes)).toBe(false);
|
|
70
|
-
expect(output.startsWith("\uFEFF")).toBe(true);
|
|
71
|
-
expect(output).toContain("// preserve this comment");
|
|
72
|
-
expect(output).toContain("// historian model note");
|
|
73
|
-
expect(output).toContain("\r\n");
|
|
74
|
-
expect(config.unrelated).toEqual({ keep: true });
|
|
75
|
-
expect(historian.model).toBeUndefined();
|
|
76
|
-
expect(opencodeHistorian).toEqual({
|
|
77
|
-
model: { model: "provider/historian", variant: "fast" },
|
|
78
|
-
fallback_models: [{ model: "provider/fallback", variant: "careful" }],
|
|
79
|
-
variant: "block-variant",
|
|
80
|
-
});
|
|
81
|
-
expect(piHistorian).toEqual({
|
|
82
|
-
model: { model: "provider/historian", thinking_level: "high" },
|
|
83
|
-
fallback_models: [{ model: "provider/fallback", thinking_level: "low" }],
|
|
84
|
-
thinking_level: "medium",
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("turns a singleton fallback into one entry without changing its spelling", () => {
|
|
89
|
-
const migrated = migrateFlatDetailed(
|
|
90
|
-
Buffer.from(
|
|
91
|
-
JSON.stringify({
|
|
92
|
-
historian: { fallback_models: "Vendor/Model:Exact-Spelling" },
|
|
93
|
-
}),
|
|
94
|
-
),
|
|
95
|
-
);
|
|
96
|
-
const historian = parse(migrated.bytes.toString("utf-8")).historian as Record<
|
|
97
|
-
string,
|
|
98
|
-
Record<string, unknown>
|
|
99
|
-
>;
|
|
100
|
-
|
|
101
|
-
expect(historian.opencode.fallback_models).toEqual(["Vendor/Model:Exact-Spelling"]);
|
|
102
|
-
expect(historian.pi.fallback_models).toEqual(["Vendor/Model:Exact-Spelling"]);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it("preserves every schema-enumerated retained field at its original level", () => {
|
|
106
|
-
const historianRetained = {
|
|
107
|
-
temperature: 0.1,
|
|
108
|
-
top_p: 0.9,
|
|
109
|
-
prompt: "historian prompt",
|
|
110
|
-
tools: { read: true },
|
|
111
|
-
disable: false,
|
|
112
|
-
description: "historian description",
|
|
113
|
-
mode: "subagent",
|
|
114
|
-
color: "blue",
|
|
115
|
-
maxSteps: 11,
|
|
116
|
-
permission: { edit: "deny" },
|
|
117
|
-
maxTokens: 4096,
|
|
118
|
-
two_pass: true,
|
|
119
|
-
disallowed_tools: ["bash"],
|
|
120
|
-
};
|
|
121
|
-
expect(Object.keys(historianRetained)).toEqual(
|
|
122
|
-
PER_HARNESS_MIGRATION_INVENTORY.historian.retained,
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
const migrated = migrateFlatDetailed(
|
|
126
|
-
Buffer.from(
|
|
127
|
-
JSON.stringify({
|
|
128
|
-
historian: { ...historianRetained, model: "provider/historian" },
|
|
129
|
-
}),
|
|
130
|
-
),
|
|
131
|
-
);
|
|
132
|
-
const config = parse(migrated.bytes.toString("utf-8"));
|
|
133
|
-
const historian = config.historian as Record<string, unknown>;
|
|
134
|
-
|
|
135
|
-
for (const [field, value] of Object.entries(historianRetained)) {
|
|
136
|
-
expect(historian[field]).toEqual(value);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("fills absent destinations and silently removes equal flat sources", () => {
|
|
141
|
-
const migrated = migrateFlatDetailed(
|
|
142
|
-
Buffer.from(`{
|
|
143
|
-
"historian": {
|
|
144
|
-
"model": "provider/same",
|
|
145
|
-
"opencode": { "model": "provider/same" },
|
|
146
|
-
"pi": {}
|
|
147
|
-
}
|
|
148
|
-
}`),
|
|
149
|
-
);
|
|
150
|
-
const historian = parse(migrated.bytes.toString("utf-8")).historian as Record<
|
|
151
|
-
string,
|
|
152
|
-
unknown
|
|
153
|
-
>;
|
|
154
|
-
|
|
155
|
-
expect(historian.model).toBeUndefined();
|
|
156
|
-
expect(historian.opencode).toEqual({ model: "provider/same" });
|
|
157
|
-
expect(historian.pi).toEqual({ model: "provider/same" });
|
|
158
|
-
expect(migrated.diagnostics).toEqual([]);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("keeps new-shape destinations on conflicts and names both paths in warnings", () => {
|
|
162
|
-
const input = `{
|
|
163
|
-
"historian": {
|
|
164
|
-
"model": "provider/flat",
|
|
165
|
-
"fallback_models": "provider/fallback",
|
|
166
|
-
"opencode": { "model": "provider/opencode", "fallback_models": ["provider/fallback"] },
|
|
167
|
-
"pi": { "model": "provider/pi", "fallback_models": ["provider/fallback"] }
|
|
168
|
-
}
|
|
169
|
-
}`;
|
|
170
|
-
|
|
171
|
-
const migrated = migrateFlatDetailed(Buffer.from(input));
|
|
172
|
-
const config = parse(migrated.bytes.toString("utf-8"));
|
|
173
|
-
const historian = config.historian as Record<string, unknown>;
|
|
174
|
-
|
|
175
|
-
expect(historian.model).toBeUndefined();
|
|
176
|
-
expect(historian.fallback_models).toBeUndefined();
|
|
177
|
-
expect(historian.opencode).toEqual({
|
|
178
|
-
model: "provider/opencode",
|
|
179
|
-
fallback_models: ["provider/fallback"],
|
|
180
|
-
});
|
|
181
|
-
expect(historian.pi).toEqual({
|
|
182
|
-
model: "provider/pi",
|
|
183
|
-
fallback_models: ["provider/fallback"],
|
|
184
|
-
});
|
|
185
|
-
expect(migrated.diagnostics.map((diagnostic) => diagnostic.path)).toEqual([
|
|
186
|
-
"historian.model",
|
|
187
|
-
"historian.model",
|
|
188
|
-
]);
|
|
189
|
-
expect(migrated.diagnostics[0]?.message).toContain("historian.opencode.model");
|
|
190
|
-
expect(migrated.diagnostics[1]?.message).toContain("historian.pi.model");
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("processes flat keys re-added after migration while existing blocks keep winning", () => {
|
|
194
|
-
const first = migrateFlatDetailed(
|
|
195
|
-
Buffer.from(JSON.stringify({ historian: { model: "provider/original" } })),
|
|
196
|
-
);
|
|
197
|
-
const reintroduced = parse(first.bytes.toString("utf-8"));
|
|
198
|
-
(reintroduced.historian as Record<string, unknown>).model = "provider/re-added";
|
|
199
|
-
|
|
200
|
-
const second = migrateFlatDetailed(Buffer.from(JSON.stringify(reintroduced)));
|
|
201
|
-
const historian = parse(second.bytes.toString("utf-8")).historian as Record<
|
|
202
|
-
string,
|
|
203
|
-
unknown
|
|
204
|
-
>;
|
|
205
|
-
|
|
206
|
-
expect(historian.model).toBeUndefined();
|
|
207
|
-
expect(historian.opencode).toEqual({ model: "provider/original" });
|
|
208
|
-
expect(historian.pi).toEqual({ model: "provider/original" });
|
|
209
|
-
expect(second.diagnostics.map((diagnostic) => diagnostic.message)).toEqual([
|
|
210
|
-
expect.stringContaining("historian.opencode.model"),
|
|
211
|
-
expect.stringContaining("historian.pi.model"),
|
|
212
|
-
]);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("rewrites user config atomically with one exact-byte restricted backup", () => {
|
|
23
|
+
describe("loadRawConfigFile", () => {
|
|
24
|
+
it("returns the file byte-for-byte without rewriting it", () => {
|
|
216
25
|
const directory = temporaryDirectory();
|
|
217
26
|
const configPath = join(directory, "mmc.jsonc");
|
|
218
|
-
const original =
|
|
219
|
-
writeFileSync(configPath, original);
|
|
220
|
-
|
|
221
|
-
const expected = migrateFlatDetailed(original).bytes;
|
|
222
|
-
let temporaryNamesDuringWrite: string[] = [];
|
|
223
|
-
|
|
224
|
-
const first = loadRawConfigFile({
|
|
225
|
-
configPath,
|
|
226
|
-
tier: "user",
|
|
227
|
-
afterTemporaryWrite: () => {
|
|
228
|
-
expect(readFileSync(configPath)).toEqual(original);
|
|
229
|
-
temporaryNamesDuringWrite = readdirSync(directory).filter((name) =>
|
|
230
|
-
name.endsWith(".tmp"),
|
|
231
|
-
);
|
|
232
|
-
expect(temporaryNamesDuringWrite).toHaveLength(1);
|
|
233
|
-
expect(
|
|
234
|
-
readFileSync(join(directory, temporaryNamesDuringWrite[0] as string)),
|
|
235
|
-
).toEqual(expected);
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
const second = loadRawConfigFile({ configPath, tier: "user" });
|
|
239
|
-
const backupPath = `${configPath}.pre-per-harness.bak`;
|
|
240
|
-
|
|
241
|
-
expect(first?.migrated).toBe(true);
|
|
242
|
-
expect(first?.warnings).toContain(
|
|
243
|
-
"Migrated flat historian model config to per-harness blocks.",
|
|
244
|
-
);
|
|
245
|
-
expect(readFileSync(backupPath)).toEqual(original);
|
|
246
|
-
expect(readFileSync(configPath)).toEqual(first?.bytes);
|
|
247
|
-
expect(temporaryNamesDuringWrite).toHaveLength(1);
|
|
248
|
-
expect(readdirSync(directory).filter((name) => name.endsWith(".tmp"))).toEqual([]);
|
|
249
|
-
expect(statSync(configPath).mode & 0o777).toBe(0o600);
|
|
250
|
-
expect(statSync(backupPath).mode & 0o777).toBe(0o600);
|
|
251
|
-
expect(second?.migrated).toBe(false);
|
|
252
|
-
expect(second?.warnings).toEqual([]);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it("repairs a truncated migration backup left by the direct-write protocol", () => {
|
|
256
|
-
const directory = temporaryDirectory();
|
|
257
|
-
const configPath = join(directory, "mmc.jsonc");
|
|
258
|
-
const backupPath = `${configPath}.pre-per-harness.bak`;
|
|
259
|
-
const original = Buffer.from('{ "historian": { "model": "provider/model" } }\n');
|
|
260
|
-
writeFileSync(configPath, original);
|
|
261
|
-
writeFileSync(backupPath, original.subarray(0, 17));
|
|
27
|
+
const original = `{\n // keep this comment\n "historian": { "model": "provider/model" }\n}\n`;
|
|
28
|
+
writeFileSync(configPath, original, "utf-8");
|
|
29
|
+
const before = readFileSync(configPath);
|
|
262
30
|
|
|
263
31
|
const loaded = loadRawConfigFile({ configPath, tier: "user" });
|
|
264
32
|
|
|
265
|
-
expect(loaded
|
|
266
|
-
expect(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const directory = temporaryDirectory();
|
|
271
|
-
const configPath = join(directory, "mmc.jsonc");
|
|
272
|
-
const backupPath = `${configPath}.pre-per-harness.bak`;
|
|
273
|
-
const sentinel = Buffer.from("not config: backup is opaque and permanent\n");
|
|
274
|
-
writeFileSync(configPath, '{ "historian": { "model": "provider/model" } }');
|
|
275
|
-
writeFileSync(backupPath, sentinel);
|
|
276
|
-
|
|
277
|
-
const first = loadRawConfigFile({ configPath, tier: "user" });
|
|
278
|
-
const second = loadRawConfigFile({ configPath, tier: "user" });
|
|
279
|
-
|
|
280
|
-
expect(first?.migrated).toBe(true);
|
|
281
|
-
expect(second?.migrated).toBe(false);
|
|
282
|
-
expect(readFileSync(backupPath)).toEqual(sentinel);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it("accepts a matching migration backup without rewriting it", () => {
|
|
286
|
-
const directory = temporaryDirectory();
|
|
287
|
-
const configPath = join(directory, "mmc.jsonc");
|
|
288
|
-
const backupPath = `${configPath}.pre-per-harness.bak`;
|
|
289
|
-
const original = Buffer.from('{ "historian": { "model": "provider/model" } }\n');
|
|
290
|
-
writeFileSync(configPath, original);
|
|
291
|
-
writeFileSync(backupPath, original);
|
|
292
|
-
const backupInode = statSync(backupPath).ino;
|
|
293
|
-
|
|
294
|
-
const loaded = loadRawConfigFile({ configPath, tier: "user" });
|
|
295
|
-
|
|
296
|
-
expect(loaded?.migrated).toBe(true);
|
|
297
|
-
expect(readFileSync(backupPath)).toEqual(original);
|
|
298
|
-
expect(statSync(backupPath).ino).toBe(backupInode);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it("creates user migration backups and replacement configs owner-only", () => {
|
|
302
|
-
if (process.platform === "win32") return;
|
|
303
|
-
const directory = temporaryDirectory();
|
|
304
|
-
const configPath = join(directory, "mmc.jsonc");
|
|
305
|
-
const original = Buffer.from('{ "historian": { "model": "provider/model" } }\n');
|
|
306
|
-
writeFileSync(configPath, original);
|
|
307
|
-
chmodSync(configPath, 0o644);
|
|
308
|
-
|
|
309
|
-
const loaded = loadRawConfigFile({ configPath, tier: "user" });
|
|
33
|
+
expect(loaded).not.toBeNull();
|
|
34
|
+
expect(loaded?.text).toBe(original);
|
|
35
|
+
expect(loaded?.bytes.equals(before)).toBe(true);
|
|
36
|
+
expect(loaded?.warnings).toEqual([]);
|
|
37
|
+
expect(loaded?.migrated).toBe(false);
|
|
310
38
|
|
|
311
|
-
|
|
312
|
-
expect(
|
|
313
|
-
expect(
|
|
39
|
+
// No rewrite, no `.pre-per-harness.bak` backup, no temp file left behind.
|
|
40
|
+
expect(readFileSync(configPath).equals(before)).toBe(true);
|
|
41
|
+
expect(existsSync(`${configPath}.pre-per-harness.bak`)).toBe(false);
|
|
42
|
+
expect(readdirSync(directory)).toEqual(["mmc.jsonc"]);
|
|
314
43
|
});
|
|
315
44
|
|
|
316
|
-
|
|
317
|
-
// Replacing the link with a regular file would silently stop later edits
|
|
318
|
-
// from reaching that repo, so the migration writes through to the target.
|
|
319
|
-
it("keeps a symlinked user config a symlink and migrates its target", () => {
|
|
45
|
+
it("does not rewrite a file that still uses the removed per-harness blocks", () => {
|
|
320
46
|
const directory = temporaryDirectory();
|
|
321
|
-
const dotfiles = temporaryDirectory();
|
|
322
|
-
const targetPath = join(dotfiles, "mmc.jsonc");
|
|
323
47
|
const configPath = join(directory, "mmc.jsonc");
|
|
324
|
-
const original =
|
|
325
|
-
writeFileSync(
|
|
326
|
-
|
|
327
|
-
symlinkSync(targetPath, configPath);
|
|
48
|
+
const original = `{\n "historian": { "opencode": { "model": "provider/model" } }\n}\n`;
|
|
49
|
+
writeFileSync(configPath, original, "utf-8");
|
|
50
|
+
const before = readFileSync(configPath);
|
|
328
51
|
|
|
329
52
|
const loaded = loadRawConfigFile({ configPath, tier: "user" });
|
|
330
53
|
|
|
331
|
-
expect(loaded?.
|
|
332
|
-
expect(
|
|
333
|
-
expect(
|
|
334
|
-
expect(
|
|
335
|
-
expect(hasFlatKeys(readFileSync(targetPath))).toBe(false);
|
|
336
|
-
expect(statSync(targetPath).mode & 0o777).toBe(0o600);
|
|
337
|
-
expect(readFileSync(`${configPath}.pre-per-harness.bak`)).toEqual(original);
|
|
338
|
-
expect(readdirSync(dotfiles).filter((name) => name.endsWith(".tmp"))).toEqual([]);
|
|
339
|
-
expect(readdirSync(directory).filter((name) => name.endsWith(".tmp"))).toEqual([]);
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it("reloads the winning candidate when a concurrent loader replaces the target", () => {
|
|
343
|
-
const directory = temporaryDirectory();
|
|
344
|
-
const configPath = join(directory, "mmc.jsonc");
|
|
345
|
-
const original = Buffer.from('{\n "historian": { "model": "provider/model" }\n}\n');
|
|
346
|
-
writeFileSync(configPath, original);
|
|
347
|
-
|
|
348
|
-
let competing: ReturnType<typeof loadRawConfigFile> | undefined;
|
|
349
|
-
const first = loadRawConfigFile({
|
|
350
|
-
configPath,
|
|
351
|
-
tier: "user",
|
|
352
|
-
afterTemporaryWrite: () => {
|
|
353
|
-
competing ??= loadRawConfigFile({ configPath, tier: "user" });
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
expect([first?.migrated, competing?.migrated].filter(Boolean)).toHaveLength(1);
|
|
358
|
-
expect(competing?.migrated).toBe(true);
|
|
359
|
-
expect(first?.migrated).toBe(false);
|
|
360
|
-
expect(first?.warnings).toEqual([]);
|
|
361
|
-
expect(competing?.warnings).toEqual([
|
|
362
|
-
"Migrated flat historian model config to per-harness blocks.",
|
|
363
|
-
]);
|
|
364
|
-
expect(first?.bytes).toEqual(competing?.bytes);
|
|
365
|
-
expect(parse(first?.text ?? "{}")).toEqual(parse(competing?.text ?? "{}"));
|
|
366
|
-
expect(readFileSync(configPath)).toEqual(competing?.bytes);
|
|
367
|
-
expect(readFileSync(`${configPath}.pre-per-harness.bak`)).toEqual(original);
|
|
54
|
+
expect(loaded?.text).toBe(original);
|
|
55
|
+
expect(loaded?.migrated).toBe(false);
|
|
56
|
+
expect(readFileSync(configPath).equals(before)).toBe(true);
|
|
57
|
+
expect(readdirSync(directory)).toEqual(["mmc.jsonc"]);
|
|
368
58
|
});
|
|
369
59
|
|
|
370
|
-
it("
|
|
60
|
+
it("returns null when the file does not exist", () => {
|
|
371
61
|
const directory = temporaryDirectory();
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const before = statSync(configPath);
|
|
376
|
-
|
|
377
|
-
const loaded = loadRawConfigFile({ configPath, tier: "project" });
|
|
378
|
-
const after = statSync(configPath);
|
|
379
|
-
const config = parse(loaded?.text ?? "{}");
|
|
380
|
-
|
|
381
|
-
expect(loaded?.migrated).toBe(false);
|
|
382
|
-
expect(loaded?.warnings[0]).toContain("Adapted flat model config in memory");
|
|
383
|
-
expect(readFileSync(configPath)).toEqual(original);
|
|
384
|
-
expect(after.mtimeMs).toBe(before.mtimeMs);
|
|
385
|
-
expect((config.historian as Record<string, unknown>).opencode).toEqual({
|
|
386
|
-
model: "provider/model",
|
|
387
|
-
});
|
|
62
|
+
expect(
|
|
63
|
+
loadRawConfigFile({ configPath: join(directory, "absent.jsonc"), tier: "user" }),
|
|
64
|
+
).toBeNull();
|
|
388
65
|
});
|
|
389
66
|
});
|