@cortexkit/opencode-magic-context 0.38.1 → 0.40.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/README.md +57 -4
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/profiles.d.ts +22 -0
- package/dist/config/profiles.d.ts.map +1 -0
- package/dist/config/project-security.d.ts +14 -6
- package/dist/config/project-security.d.ts.map +1 -1
- package/dist/config/raw-loader.d.ts +42 -0
- package/dist/config/raw-loader.d.ts.map +1 -0
- package/dist/config/schema/magic-context.d.ts +966 -531
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts +3 -2
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts +9 -2
- package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +4 -2
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +21 -5
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/refresh-primers.d.ts +3 -2
- package/dist/features/magic-context/dreamer/refresh-primers.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-config.d.ts +2 -1
- package/dist/features/magic-context/dreamer/task-config.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts +0 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-scheduler.d.ts +3 -2
- package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts +5 -2
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts +6 -3
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/fail-closed-block.d.ts +11 -1
- package/dist/features/magic-context/fail-closed-block.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-failure.d.ts +11 -0
- package/dist/features/magic-context/memory/embedding-failure.d.ts.map +1 -0
- package/dist/features/magic-context/memory/embedding-openai.d.ts +5 -0
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-provider.d.ts +3 -0
- package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/mural/compress-cues.d.ts +3 -2
- package/dist/features/magic-context/mural/compress-cues.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +2 -0
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/compiler.d.ts +3 -2
- package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/condition-compiler.d.ts +2 -0
- package/dist/features/magic-context/smart-notes/condition-compiler.d.ts.map +1 -1
- package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +2 -2
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +7 -2
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-shared.d.ts +1 -0
- package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.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.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/features/magic-context/tool-definition-tokens.d.ts +10 -0
- package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts +11 -0
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts +3 -2
- package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts +3 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +4 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-historian.d.ts +4 -1
- package/dist/hooks/magic-context/compartment-runner-historian.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-runner-types.d.ts +4 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +14 -2
- package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts +7 -0
- package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -1
- package/dist/hooks/magic-context/format-embed-failure.d.ts +3 -0
- package/dist/hooks/magic-context/format-embed-failure.d.ts.map +1 -0
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +13 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-persist.d.ts +14 -0
- package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -0
- package/dist/hooks/magic-context/lkg-slot.d.ts +13 -0
- package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-state-sync.d.ts +1 -0
- package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-transport.d.ts +12 -0
- package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
- package/dist/hooks/magic-context/raw-fallback-context-limit.d.ts.map +1 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts +4 -1
- package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-content.d.ts +6 -0
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +12 -0
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +5 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +9 -1
- 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-2y0ybv5x.js → index-3dkzzh0t.js} +15 -17
- package/dist/{index-5znjkjws.js → index-axpym70t.js} +879 -170
- package/dist/index-ehpzdy0v.js +126 -0
- package/dist/{index-22mw8km4.js → index-rw0qrkvq.js} +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18466 -16177
- package/dist/model-resolution-r9jbcz86.js +15 -0
- package/dist/plugin/dream-timer.d.ts +3 -0
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +2 -0
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/messages-transform.d.ts +14 -6
- package/dist/plugin/messages-transform.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/{recomp-orchestrator-p030kxtc.js → recomp-orchestrator-dmyvqekw.js} +2 -3
- package/dist/shared/jsonc-edit.d.ts +6 -1
- package/dist/shared/jsonc-edit.d.ts.map +1 -1
- package/dist/shared/model-resolution.d.ts +53 -0
- package/dist/shared/model-resolution.d.ts.map +1 -0
- package/dist/shared/model-suggestion-retry.d.ts +4 -1
- package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
- package/dist/shared/resolve-fallbacks.d.ts +5 -1
- package/dist/shared/resolve-fallbacks.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +2 -0
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/rpc-utils.d.ts +12 -1
- package/dist/shared/rpc-utils.d.ts.map +1 -1
- package/dist/shared/status-detail-text.d.ts +4 -0
- package/dist/shared/status-detail-text.d.ts.map +1 -0
- package/dist/shared/subagent-runner.d.ts +2 -1
- package/dist/shared/subagent-runner.d.ts.map +1 -1
- package/dist/{storage-meta-session-2306t3j7.js → storage-meta-session-yvtbaw7c.js} +1 -1
- package/dist/{task-config-at3adc59.js → task-config-a1qmj9bx.js} +2 -2
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/config/index.test.ts +434 -19
- package/src/config/index.ts +67 -33
- package/src/config/profiles.ts +106 -0
- package/src/config/project-security.hostile-matrix.test.ts +480 -0
- package/src/config/project-security.test.ts +43 -5
- package/src/config/project-security.ts +151 -22
- package/src/config/raw-loader.test.ts +424 -0
- package/src/config/raw-loader.ts +490 -0
- package/src/config/schema/magic-context.test.ts +272 -2
- package/src/config/schema/magic-context.ts +342 -49
- package/src/shared/jsonc-edit.ts +74 -1
- package/src/shared/model-resolution.test.ts +170 -0
- package/src/shared/model-resolution.ts +227 -0
- package/src/shared/model-suggestion-retry.test.ts +27 -0
- package/src/shared/model-suggestion-retry.ts +19 -10
- package/src/shared/resolve-fallbacks.test.ts +13 -1
- package/src/shared/resolve-fallbacks.ts +19 -4
- package/src/shared/rpc-types.ts +2 -0
- package/src/shared/rpc-utils.test.ts +32 -0
- package/src/shared/rpc-utils.ts +38 -13
- package/src/shared/status-detail-text.test.ts +82 -0
- package/src/shared/status-detail-text.ts +57 -0
- package/src/shared/subagent-runner.ts +3 -1
- package/src/tui/data/context-db.ts +1 -0
- package/src/tui-compiled/data/context-db.ts +1 -0
- package/dist/index-td9depx5.js +0 -46
- package/dist/resolve-fallbacks-qf0ekmz9.js +0 -11
package/src/config/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
|
|
3
3
|
import { detectConfigFile, isPrototypePollutionKey, parseJsonc } from "../shared/jsonc-parser";
|
|
4
4
|
import { setOutputReserveConfig } from "../shared/models-dev-cache";
|
|
@@ -14,12 +14,14 @@ import {
|
|
|
14
14
|
} from "./migrate-config-location";
|
|
15
15
|
import { migrateDreamerV2 } from "./migrate-dreamer-v2";
|
|
16
16
|
import { migrateLegacyExperimental } from "./migrate-experimental";
|
|
17
|
+
import { resolveConfigProfile } from "./profiles";
|
|
17
18
|
import {
|
|
18
19
|
constrainProjectThresholdOverrides,
|
|
19
20
|
dropInheritedEmbeddingKeyOnRedirect,
|
|
20
21
|
stripUnsafeProjectConfigFields,
|
|
21
22
|
} from "./project-security";
|
|
22
23
|
import { pruneNestedConfigLeaf } from "./prune-config-leaf";
|
|
24
|
+
import { loadRawConfigFile } from "./raw-loader";
|
|
23
25
|
import { type MagicContextConfig, MagicContextConfigSchema } from "./schema/magic-context";
|
|
24
26
|
import { resolveTransformMode } from "./transform-mode";
|
|
25
27
|
import { substituteConfigVariables } from "./variable";
|
|
@@ -109,8 +111,12 @@ function loadConfigFileDetailed(
|
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
let rawText: string;
|
|
114
|
+
let rawWarnings: string[];
|
|
112
115
|
try {
|
|
113
|
-
|
|
116
|
+
const raw = loadRawConfigFile({ configPath, tier: source });
|
|
117
|
+
if (!raw) return null;
|
|
118
|
+
rawText = raw.text;
|
|
119
|
+
rawWarnings = raw.warnings;
|
|
114
120
|
} catch (error) {
|
|
115
121
|
return {
|
|
116
122
|
config: {},
|
|
@@ -138,7 +144,7 @@ function loadConfigFileDetailed(
|
|
|
138
144
|
);
|
|
139
145
|
return {
|
|
140
146
|
config,
|
|
141
|
-
warnings: [...substituted.warnings, ...unsafeKeyWarnings].map(
|
|
147
|
+
warnings: [...rawWarnings, ...substituted.warnings, ...unsafeKeyWarnings].map(
|
|
142
148
|
(warning) => `${configPath}: ${warning}`,
|
|
143
149
|
),
|
|
144
150
|
outcome:
|
|
@@ -285,8 +291,9 @@ function parsePluginConfig(
|
|
|
285
291
|
}
|
|
286
292
|
|
|
287
293
|
// Full parse failed — recover field-by-field using defaults for invalid fields.
|
|
288
|
-
//
|
|
289
|
-
//
|
|
294
|
+
// Invalid nested leaves are pruned from agent blocks; only root-level or
|
|
295
|
+
// unreachable agent errors drop the block, because guessing a model config
|
|
296
|
+
// could run an expensive unintended model or fail silently.
|
|
290
297
|
const defaults = MagicContextConfigSchema.parse({});
|
|
291
298
|
const warnings: string[] = [];
|
|
292
299
|
|
|
@@ -309,7 +316,13 @@ function parsePluginConfig(
|
|
|
309
316
|
const key = String(topKey);
|
|
310
317
|
errorPaths.add(key);
|
|
311
318
|
const paths = issuePathsByKey.get(key) ?? [];
|
|
312
|
-
|
|
319
|
+
if (issue.code === "unrecognized_keys") {
|
|
320
|
+
for (const unrecognizedKey of issue.keys) {
|
|
321
|
+
paths.push([...issue.path, unrecognizedKey]);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
paths.push([...issue.path]);
|
|
325
|
+
}
|
|
313
326
|
issuePathsByKey.set(key, paths);
|
|
314
327
|
const msg = issue.message;
|
|
315
328
|
if (msg && !GENERIC_ZOD_PREFIXES.some((p) => msg.startsWith(p))) {
|
|
@@ -324,21 +337,12 @@ function parsePluginConfig(
|
|
|
324
337
|
for (const key of errorPaths) {
|
|
325
338
|
recoveredTopLevelKeys.push(key);
|
|
326
339
|
const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
|
|
327
|
-
if (isAgentConfig) {
|
|
328
|
-
// Drop agent configs entirely on error — don't default them
|
|
329
|
-
delete patched[key];
|
|
330
|
-
warnings.push(
|
|
331
|
-
`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`,
|
|
332
|
-
);
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
340
|
|
|
336
|
-
// For object-valued keys (
|
|
337
|
-
// leaves and keep valid siblings, so one bad
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
//
|
|
341
|
-
// isn't a prunable object.
|
|
341
|
+
// For object-valued keys (including agent harness blocks), prune only invalid
|
|
342
|
+
// nested leaves and keep valid siblings, so one bad field does not remove
|
|
343
|
+
// already-migrated keys such as memory.auto_search and
|
|
344
|
+
// memory.git_commit_indexing. Fall back to whole-key deletion when the issue
|
|
345
|
+
// is at the key itself or the value is not a prunable object.
|
|
342
346
|
const issuePaths = issuePathsByKey.get(key) ?? [];
|
|
343
347
|
const rawValue = rawConfig[key];
|
|
344
348
|
const allNested =
|
|
@@ -364,10 +368,22 @@ function parsePluginConfig(
|
|
|
364
368
|
prunedLeaves.push(result.removed);
|
|
365
369
|
}
|
|
366
370
|
}
|
|
367
|
-
|
|
368
|
-
|
|
371
|
+
if (prunedLeaves.length === issuePaths.length) {
|
|
372
|
+
patched[key] = prunedBlock;
|
|
373
|
+
const reason = customMessagesByKey.get(key);
|
|
374
|
+
warnings.push(
|
|
375
|
+
`"${key}": invalid nested field(s) ${prunedLeaves.map((leaf) => `"${key}.${leaf}"`).join(", ")}, using defaults for those.${reason ? ` ${reason}` : ""}`,
|
|
376
|
+
);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Root-level or unreachable agent errors cannot be repaired safely because
|
|
382
|
+
// guessing a model configuration could select an expensive unintended model.
|
|
383
|
+
if (isAgentConfig) {
|
|
384
|
+
delete patched[key];
|
|
369
385
|
warnings.push(
|
|
370
|
-
`"${key}": invalid
|
|
386
|
+
`"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`,
|
|
371
387
|
);
|
|
372
388
|
continue;
|
|
373
389
|
}
|
|
@@ -533,11 +549,6 @@ export function loadPluginConfigDetailed(directory: string): LoadResultDetailed
|
|
|
533
549
|
: null;
|
|
534
550
|
|
|
535
551
|
const allWarnings: string[] = [];
|
|
536
|
-
let mergedRaw: Record<string, unknown> = {};
|
|
537
|
-
// Threshold trust boundary is relative to the USER/default effective config:
|
|
538
|
-
// a cloned repo may delay compaction, but it may not lower thresholds in a
|
|
539
|
-
// way that forces extra historian work on the user's account.
|
|
540
|
-
const trustedBaseConfig = parsePluginConfig(userLoaded?.config ?? {});
|
|
541
552
|
|
|
542
553
|
if (userLegacyFallback.source) {
|
|
543
554
|
allWarnings.push(
|
|
@@ -561,26 +572,48 @@ export function loadPluginConfigDetailed(directory: string): LoadResultDetailed
|
|
|
561
572
|
|
|
562
573
|
if (userLoaded) {
|
|
563
574
|
allWarnings.push(...userLoaded.warnings.map((w) => `[user config] ${w}`));
|
|
564
|
-
mergedRaw = deepMergeRawConfig(mergedRaw, userLoaded.config);
|
|
565
575
|
}
|
|
566
576
|
|
|
577
|
+
let projectRaw: Record<string, unknown> = {};
|
|
567
578
|
if (projectLoaded) {
|
|
568
579
|
allWarnings.push(...projectLoaded.warnings.map((w) => `[project config] ${w}`));
|
|
569
|
-
|
|
580
|
+
projectRaw = { ...projectLoaded.config };
|
|
570
581
|
for (const warning of stripUnsafeProjectConfigFields(projectRaw)) {
|
|
571
582
|
allWarnings.push(`[project config] ${warning}`);
|
|
572
583
|
}
|
|
573
|
-
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Resolve profiles at the single user→project merge choke point. The profile
|
|
587
|
+
// definition is parsed from the trusted user tier, then its validated model
|
|
588
|
+
// overlay is merged before the untrusted project config. The raw selector is
|
|
589
|
+
// consumed here; only the resolved name remains as status metadata.
|
|
590
|
+
const profileResolution = resolveConfigProfile({
|
|
591
|
+
userRaw: userLoaded?.config ?? {},
|
|
592
|
+
projectRaw,
|
|
593
|
+
});
|
|
594
|
+
allWarnings.push(...profileResolution.warnings.map((warning) => `[config] ${warning}`));
|
|
595
|
+
const trustedProfiledRaw = deepMergeRawConfig(
|
|
596
|
+
profileResolution.userBase,
|
|
597
|
+
profileResolution.overlay,
|
|
598
|
+
);
|
|
599
|
+
let mergedRaw = trustedProfiledRaw;
|
|
600
|
+
// Threshold trust boundary is relative to the USER/default effective config:
|
|
601
|
+
// a cloned repo may delay compaction, but it may not lower thresholds in a
|
|
602
|
+
// way that forces extra historian work on the user's account.
|
|
603
|
+
const trustedBaseConfig = parsePluginConfig(trustedProfiledRaw);
|
|
604
|
+
|
|
605
|
+
if (projectLoaded) {
|
|
606
|
+
mergedRaw = deepMergeRawConfig(mergedRaw, profileResolution.projectBase);
|
|
574
607
|
for (const warning of dropInheritedEmbeddingKeyOnRedirect(
|
|
575
608
|
projectRaw,
|
|
576
609
|
mergedRaw,
|
|
577
|
-
|
|
610
|
+
profileResolution.userBase,
|
|
578
611
|
)) {
|
|
579
612
|
allWarnings.push(`[project config] ${warning}`);
|
|
580
613
|
}
|
|
581
614
|
for (const warning of constrainProjectThresholdOverrides({
|
|
582
615
|
mergedRaw,
|
|
583
|
-
projectRaw,
|
|
616
|
+
projectRaw: profileResolution.projectBase,
|
|
584
617
|
trustedBaseConfig,
|
|
585
618
|
})) {
|
|
586
619
|
allWarnings.push(`[project config] ${warning}`);
|
|
@@ -589,6 +622,7 @@ export function loadPluginConfigDetailed(directory: string): LoadResultDetailed
|
|
|
589
622
|
|
|
590
623
|
const recoveredTopLevelKeys: string[] = [];
|
|
591
624
|
const config = parsePluginConfig(mergedRaw, recoveredTopLevelKeys);
|
|
625
|
+
if (profileResolution.activeProfile) config.profile = profileResolution.activeProfile;
|
|
592
626
|
setOutputReserveConfig(config.output_reserve);
|
|
593
627
|
setWindowOverlayPath(config.models?.window_overlay_path);
|
|
594
628
|
if (config.configWarnings?.length) {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ConfigProfilesSchema } from "./schema/magic-context";
|
|
2
|
+
|
|
3
|
+
export interface ProfileResolution {
|
|
4
|
+
/** User config minus the profile declarations and selector. */
|
|
5
|
+
userBase: Record<string, unknown>;
|
|
6
|
+
/** Project config minus the profile declarations and selector. */
|
|
7
|
+
projectBase: Record<string, unknown>;
|
|
8
|
+
/** The validated user-owned overlay for the active profile, if any. */
|
|
9
|
+
overlay: Record<string, unknown>;
|
|
10
|
+
/** The selected profile name, absent when no valid profile is active. */
|
|
11
|
+
activeProfile?: string;
|
|
12
|
+
warnings: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ProfileSelection {
|
|
16
|
+
declared: boolean;
|
|
17
|
+
name?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function withoutProfileFields(raw: Record<string, unknown>): Record<string, unknown> {
|
|
21
|
+
const copy = { ...raw };
|
|
22
|
+
delete copy.profile;
|
|
23
|
+
delete copy.profiles;
|
|
24
|
+
return copy;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readProfileSelection(raw: Record<string, unknown>): ProfileSelection {
|
|
28
|
+
if (!Object.hasOwn(raw, "profile")) return { declared: false };
|
|
29
|
+
const value = raw.profile;
|
|
30
|
+
if (typeof value !== "string") return { declared: true };
|
|
31
|
+
const name = value.trim();
|
|
32
|
+
return name.length > 0 ? { declared: true, name } : { declared: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the user-owned profile overlay without retaining any process-global
|
|
37
|
+
* selection state. Callers merge `userBase`, `overlay`, then `projectBase` for
|
|
38
|
+
* every project load, so a Pi /cd or OpenCode multi-root session cannot borrow
|
|
39
|
+
* another repository's selected profile.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveConfigProfile(args: {
|
|
42
|
+
userRaw: Record<string, unknown>;
|
|
43
|
+
projectRaw: Record<string, unknown>;
|
|
44
|
+
}): ProfileResolution {
|
|
45
|
+
const warnings: string[] = [];
|
|
46
|
+
const userSelection = readProfileSelection(args.userRaw);
|
|
47
|
+
const projectSelection = readProfileSelection(args.projectRaw);
|
|
48
|
+
const selection = projectSelection.name
|
|
49
|
+
? { name: projectSelection.name, source: "project" }
|
|
50
|
+
: userSelection.name
|
|
51
|
+
? { name: userSelection.name, source: "user" }
|
|
52
|
+
: undefined;
|
|
53
|
+
|
|
54
|
+
if (projectSelection.declared && !projectSelection.name) {
|
|
55
|
+
warnings.push(
|
|
56
|
+
"Ignoring invalid profile selection from project config; expected a non-empty string.",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (!projectSelection.declared && userSelection.declared && !userSelection.name) {
|
|
60
|
+
warnings.push(
|
|
61
|
+
"Ignoring invalid profile selection from user config; expected a non-empty string.",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let profiles: Record<string, Record<string, unknown>> = {};
|
|
66
|
+
if (Object.hasOwn(args.userRaw, "profiles")) {
|
|
67
|
+
const parsed = ConfigProfilesSchema.safeParse(args.userRaw.profiles);
|
|
68
|
+
if (parsed.success) {
|
|
69
|
+
profiles = parsed.data as Record<string, Record<string, unknown>>;
|
|
70
|
+
} else {
|
|
71
|
+
warnings.push(
|
|
72
|
+
"Ignoring profiles from user config: invalid profile configuration; profiles may contain only historian/dreamer harness model blocks and sidekick model-selection fields.",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!selection) {
|
|
78
|
+
return {
|
|
79
|
+
userBase: withoutProfileFields(args.userRaw),
|
|
80
|
+
projectBase: withoutProfileFields(args.projectRaw),
|
|
81
|
+
overlay: {},
|
|
82
|
+
warnings,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!Object.hasOwn(profiles, selection.name)) {
|
|
87
|
+
warnings.push(
|
|
88
|
+
`Unknown profile "${selection.name}" selected by ${selection.source} config; using base config without a profile.`,
|
|
89
|
+
);
|
|
90
|
+
return {
|
|
91
|
+
userBase: withoutProfileFields(args.userRaw),
|
|
92
|
+
projectBase: withoutProfileFields(args.projectRaw),
|
|
93
|
+
overlay: {},
|
|
94
|
+
warnings,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const overlay = profiles[selection.name];
|
|
99
|
+
return {
|
|
100
|
+
userBase: withoutProfileFields(args.userRaw),
|
|
101
|
+
projectBase: withoutProfileFields(args.projectRaw),
|
|
102
|
+
overlay,
|
|
103
|
+
activeProfile: selection.name,
|
|
104
|
+
warnings,
|
|
105
|
+
};
|
|
106
|
+
}
|