@aiwg/cli 2026.10.1 → 2026.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3091 -24
- package/bin/aiwg.mjs +86 -21
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +105 -15
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/channel/manager.mjs +59 -10
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/decision.js +210 -4
- package/dist/src/cli/handlers/diagnose.js +9 -2
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/init.js +245 -30
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +69 -6
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +16 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/runtime-info.js +24 -9
- package/dist/src/cli/handlers/script-runner.js +16 -6
- package/dist/src/cli/handlers/session.js +396 -62
- package/dist/src/cli/handlers/sessions.js +146 -40
- package/dist/src/cli/handlers/setup-manifest.js +439 -39
- package/dist/src/cli/handlers/setup.js +139 -39
- package/dist/src/cli/handlers/subcommands.js +3 -1
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/use.js +13 -0
- package/dist/src/cli/handlers/utilities.js +106 -23
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/config/aiwg-config.js +268 -4
- package/dist/src/config/cli.js +146 -19
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/user-config.js +36 -10
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/driver.js +3 -1
- package/dist/src/decision/entry.js +38 -6
- package/dist/src/decision/heldout/collector.js +10 -1
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/ledger.js +839 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +3 -1
- package/dist/src/installation/manager-command.mjs +1 -1
- package/dist/src/installation/manager.mjs +17 -2
- package/dist/src/lint/runner.js +2 -2
- package/dist/src/mcp/adapters/codex-runtime.js +360 -56
- package/dist/src/mcp/cli.mjs +587 -77
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.mjs +16 -26
- package/dist/src/mcp/helpers.mjs +106 -2
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +264 -57
- package/dist/src/mcp/registry.mjs +198 -45
- package/dist/src/mcp/server.mjs +403 -20
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +21 -3
- package/dist/src/mcp/tools/orchestration.mjs +38 -0
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +1539 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +258 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +639 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +70 -13
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3091 -24
- package/dist/src/models/model-discovery.js +862 -63
- package/dist/src/models/provider-policy.js +625 -11
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +259 -0
- package/dist/src/models/worker-direct-readiness.js +529 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +131 -0
- package/dist/src/models/worker-installation.mjs +879 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.mjs +387 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/grok-build-headless.js +43 -21
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +9 -0
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +538 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +45 -0
- package/dist/src/sessions/adapters/generic.js +37 -3
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +423 -24
- package/dist/src/sessions/adapters/muse-workspace-scan.js +108 -0
- package/dist/src/sessions/adapters/muse.js +68 -29
- package/dist/src/sessions/adapters/pi.js +1 -0
- package/dist/src/sessions/batch-contracts.js +13 -1
- package/dist/src/sessions/batch-import.js +23 -42
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +35 -0
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/importer.js +24 -8
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/repository.js +54 -13
- package/dist/src/sessions/workspace-discovery.js +99 -37
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/smiths/context-pipeline/parallelism-section.js +192 -3
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +10 -14
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +18 -1
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1066 -0
- package/tools/_resolve-impl.mjs +39 -0
- package/tools/agents/deploy-agents.mjs +15 -2
- package/tools/agents/providers/base.mjs +49 -32
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +6 -3
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/providers/deepseek-harness-transport.mjs +358 -0
- package/tools/providers/omp-transport.mjs +192 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +333 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +690 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/vscode-extension/schemas/aiwg.config.v1.json +1953 -0
|
@@ -1,2 +1,112 @@
|
|
|
1
1
|
export const MISSION_API_VERSION = 'mission.aiwg.io/v1';
|
|
2
|
+
// Worker dispatch payloads merged from 05 §5.1 into schemas/mission-v1.schema.json.
|
|
3
|
+
// The schema file is the executable contract; these types mirror its shapes.
|
|
4
|
+
export const WORKER_CONTRACT_REVISION = 1;
|
|
5
|
+
export const WORKER_BASELINE_FEATURES = [
|
|
6
|
+
'worker-dispatch.v1',
|
|
7
|
+
'worker-policy-origins.v1',
|
|
8
|
+
'worker-admission-fencing.v1',
|
|
9
|
+
'worker-claims.v1',
|
|
10
|
+
'worker-activation.v1',
|
|
11
|
+
];
|
|
12
|
+
export const WORKER_OPTIONAL_FEATURES = ['worker-health.v1'];
|
|
13
|
+
/** Adapter capability additionally required before Flow invokeNode. */
|
|
14
|
+
export const WORKER_FLOW_VALUE_FEATURE = 'worker-flow-values.v1';
|
|
15
|
+
export const WORKER_KNOWN_FEATURES = [
|
|
16
|
+
...WORKER_BASELINE_FEATURES,
|
|
17
|
+
...WORKER_OPTIONAL_FEATURES,
|
|
18
|
+
WORKER_FLOW_VALUE_FEATURE,
|
|
19
|
+
];
|
|
20
|
+
export const WORKER_DEFINITIONS = [
|
|
21
|
+
'workerArtifact',
|
|
22
|
+
'workerEffort',
|
|
23
|
+
'workerTuple',
|
|
24
|
+
'workerLimits',
|
|
25
|
+
'workerSelectionIntent',
|
|
26
|
+
'workerBrief',
|
|
27
|
+
'workerClaims',
|
|
28
|
+
'workerUsage',
|
|
29
|
+
'workerExecution',
|
|
30
|
+
'workerVerification',
|
|
31
|
+
'workerResult',
|
|
32
|
+
'workerDispatch',
|
|
33
|
+
'workerDirectIntent',
|
|
34
|
+
'workerDirectBrief',
|
|
35
|
+
'workerResolvedInvocation',
|
|
36
|
+
'workerDirectVerification',
|
|
37
|
+
'workerDirectResult',
|
|
38
|
+
'workerHealthSummary',
|
|
39
|
+
'workerEffectiveHealth',
|
|
40
|
+
];
|
|
41
|
+
export const WORKER_DEFINITION_PRODUCERS = {
|
|
42
|
+
workerArtifact: 'shared-component',
|
|
43
|
+
workerEffort: 'shared-component',
|
|
44
|
+
workerTuple: 'shared-component',
|
|
45
|
+
workerLimits: 'shared-component',
|
|
46
|
+
workerSelectionIntent: 'conductor',
|
|
47
|
+
workerBrief: 'conductor',
|
|
48
|
+
workerClaims: 'worker',
|
|
49
|
+
workerUsage: 'shared-component',
|
|
50
|
+
workerExecution: 'adapter',
|
|
51
|
+
workerVerification: 'verifier',
|
|
52
|
+
workerResult: 'trusted-assembly',
|
|
53
|
+
workerDispatch: 'conductor',
|
|
54
|
+
workerDirectIntent: 'conductor',
|
|
55
|
+
workerDirectBrief: 'conductor',
|
|
56
|
+
workerResolvedInvocation: 'conductor',
|
|
57
|
+
workerDirectVerification: 'verifier',
|
|
58
|
+
workerDirectResult: 'trusted-assembly',
|
|
59
|
+
workerHealthSummary: 'shared-component',
|
|
60
|
+
workerEffectiveHealth: 'shared-component',
|
|
61
|
+
};
|
|
62
|
+
/** Bounded activity-log transition summary budget (UTF-8 bytes, marker included). */
|
|
63
|
+
export const HEALTH_ACTIVITY_SUMMARY_MAX_UTF8_BYTES = 1024;
|
|
64
|
+
/**
|
|
65
|
+
* Closed observer reason-code allowlist for activity summaries. Mirrors the
|
|
66
|
+
* `reasons` set in tools/ralph-external/lib/worker-health.mjs: B-OG-50 owns
|
|
67
|
+
* the observer set, B-OG-54 owns this mapping allowlist; extend them together.
|
|
68
|
+
* Unlisted codes project as UNCLASSIFIED#digest so free text (and any secret
|
|
69
|
+
* material it carries) never crosses into the shared trace.
|
|
70
|
+
*/
|
|
71
|
+
export const HEALTH_KNOWN_REASON_CODES = [
|
|
72
|
+
'OK',
|
|
73
|
+
'AWAITING_HANDSHAKE',
|
|
74
|
+
'PROCESS_ONLY',
|
|
75
|
+
'ACCEPTED',
|
|
76
|
+
'PROGRESS',
|
|
77
|
+
'QUIET',
|
|
78
|
+
'NO_PROGRESS',
|
|
79
|
+
'STARTUP_DEADLINE',
|
|
80
|
+
'HARD_DEADLINE',
|
|
81
|
+
'STEP_LIMIT',
|
|
82
|
+
'AUTH',
|
|
83
|
+
'RATE_LIMIT',
|
|
84
|
+
'NETWORK',
|
|
85
|
+
'MODEL',
|
|
86
|
+
'TRUST',
|
|
87
|
+
'STDIN_WAIT',
|
|
88
|
+
'SANDBOX',
|
|
89
|
+
'NOT_FOUND',
|
|
90
|
+
'RESOURCE',
|
|
91
|
+
'PROVIDER_ERROR',
|
|
92
|
+
'CRASH',
|
|
93
|
+
'EXIT_ERROR',
|
|
94
|
+
'FINAL_INVALID',
|
|
95
|
+
'CANCELLED',
|
|
96
|
+
'DISCONNECTED',
|
|
97
|
+
'OBSERVER_GAP',
|
|
98
|
+
'CAPTURE_WRITE',
|
|
99
|
+
'OUTPUT_LIMIT',
|
|
100
|
+
'MALFORMED_FRAME',
|
|
101
|
+
'SOURCE_GAP',
|
|
102
|
+
'IDENTITY_CHANGED',
|
|
103
|
+
];
|
|
104
|
+
export class WorkerContractError extends Error {
|
|
105
|
+
code;
|
|
106
|
+
constructor(code, message) {
|
|
107
|
+
super(message);
|
|
108
|
+
this.name = 'WorkerContractError';
|
|
109
|
+
this.code = code;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
2
112
|
//# sourceMappingURL=types.js.map
|
|
@@ -9,15 +9,18 @@ import { readFile } from 'fs/promises';
|
|
|
9
9
|
import { resolve } from 'path';
|
|
10
10
|
import { homedir } from 'os';
|
|
11
11
|
import { existsSync } from 'fs';
|
|
12
|
-
import {
|
|
12
|
+
import { workerConfigDigest, validateWorkerGroupReferences } from '../config/aiwg-config.js';
|
|
13
|
+
import { DEFAULT_WORKER_SELECTION, mergeWorkerSelection, WorkerSelectionPolicySchema, validateUserProjectModelConfig } from './provider-policy.js';
|
|
13
14
|
/**
|
|
14
15
|
* Loads and merges model configurations
|
|
15
16
|
*/
|
|
16
17
|
export class ConfigLoader {
|
|
17
18
|
cache = null;
|
|
19
|
+
cacheDigest = null;
|
|
20
|
+
inputDigests = null;
|
|
18
21
|
locations;
|
|
19
|
-
constructor(projectPath) {
|
|
20
|
-
const aiwgHome = process.env.AIWG_HOME || resolve(homedir(), '.local/share/ai-writing-guide');
|
|
22
|
+
constructor(projectPath, options) {
|
|
23
|
+
const aiwgHome = options?.aiwgHome || process.env.AIWG_HOME || resolve(homedir(), '.local/share/ai-writing-guide');
|
|
21
24
|
const userConfigDir = process.env.AIWG_CONFIG || resolve(homedir(), '.config/aiwg');
|
|
22
25
|
this.locations = {
|
|
23
26
|
aiwgDefault: resolve(aiwgHome, 'agentic/code/frameworks/sdlc-complete/config/models-v2.json'),
|
|
@@ -29,17 +32,65 @@ export class ConfigLoader {
|
|
|
29
32
|
* Load and merge all configurations
|
|
30
33
|
* Precedence: project > user > AIWG default
|
|
31
34
|
*/
|
|
32
|
-
async load() {
|
|
33
|
-
if (this.cache) {
|
|
34
|
-
return this.cache;
|
|
35
|
-
}
|
|
35
|
+
async load(parallelism) {
|
|
36
36
|
// Load all configs
|
|
37
37
|
const aiwgDefault = await this.loadAiwgDefault();
|
|
38
38
|
const userConfig = await this.loadUserConfig();
|
|
39
39
|
const projectConfig = await this.loadProjectConfig();
|
|
40
|
+
const digest = workerConfigDigest([aiwgDefault, userConfig, projectConfig, parallelism]);
|
|
41
|
+
if (this.cache && this.cacheDigest === digest)
|
|
42
|
+
return this.cache;
|
|
40
43
|
// Merge with precedence
|
|
41
44
|
const merged = this.merge(aiwgDefault, userConfig, projectConfig);
|
|
45
|
+
// A group-bound wrapper's inherited exact pin survives a higher-precedence
|
|
46
|
+
// tier-only preference. Preserve the legacy unbound merge otherwise.
|
|
47
|
+
if (parallelism?.worker_dispatch) {
|
|
48
|
+
for (const [agent, override] of Object.entries(projectConfig?.agentOverrides ?? {})) {
|
|
49
|
+
merged.agentOverrides[agent] = { ...userConfig?.agentOverrides?.[agent], ...override };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (parallelism?.worker_dispatch || [aiwgDefault, userConfig, projectConfig].some(config => config?.defaults?.workerSelection !== undefined)) {
|
|
53
|
+
const result = mergeWorkerSelection([
|
|
54
|
+
{ source: this.locations.aiwgDefault, authority: 'builtin', digest: workerConfigDigest(aiwgDefault), policy: DEFAULT_WORKER_SELECTION },
|
|
55
|
+
{ source: this.locations.aiwgDefault, authority: 'builtin', digest: workerConfigDigest(aiwgDefault), policy: aiwgDefault.defaults.workerSelection },
|
|
56
|
+
{ source: this.locations.userConfig, authority: 'user', digest: workerConfigDigest(userConfig), policy: userConfig?.defaults?.workerSelection },
|
|
57
|
+
{ source: this.locations.projectConfig, authority: 'project', digest: workerConfigDigest(projectConfig), policy: projectConfig?.defaults?.workerSelection },
|
|
58
|
+
]);
|
|
59
|
+
if (result.diagnostics.length)
|
|
60
|
+
throw new Error(result.diagnostics.map(d => `${d.code}: ${d.pointer}: ${d.message}; origins=${JSON.stringify(d.origins ?? [])}`).join('; '));
|
|
61
|
+
merged.defaults.workerSelection = result.policy;
|
|
62
|
+
merged.workerPolicyOrigins = result.origins;
|
|
63
|
+
}
|
|
64
|
+
if (parallelism?.worker_dispatch) {
|
|
65
|
+
// Exact wrapper pins remain explainable alongside worker restrictions.
|
|
66
|
+
for (const layer of [
|
|
67
|
+
{ config: userConfig, source: this.locations.userConfig, authority: 'user' },
|
|
68
|
+
{ config: projectConfig, source: this.locations.projectConfig, authority: 'project' },
|
|
69
|
+
]) {
|
|
70
|
+
for (const [agent, override] of Object.entries(layer.config?.agentOverrides ?? {})) {
|
|
71
|
+
if (override['model-override'] === undefined)
|
|
72
|
+
continue;
|
|
73
|
+
const pointer = `/agentOverrides/${agent.replace(/~/g, '~0').replace(/\//g, '~1')}/model-override`;
|
|
74
|
+
merged.workerPolicyOrigins[pointer] ??= [];
|
|
75
|
+
merged.workerPolicyOrigins[pointer].push({
|
|
76
|
+
source: layer.source, authority: layer.authority, digest: workerConfigDigest(layer.config),
|
|
77
|
+
pointer, contribution: override['model-override'],
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const errors = validateWorkerGroupReferences(parallelism, merged.defaults.workerSelection);
|
|
82
|
+
if (errors.length)
|
|
83
|
+
throw new Error(errors.join('; '));
|
|
84
|
+
}
|
|
85
|
+
// Publish only a fully validated candidate. A failed merge must not become
|
|
86
|
+
// a cache hit on the next read, nor replace the prior inspectable value.
|
|
42
87
|
this.cache = merged;
|
|
88
|
+
this.cacheDigest = digest;
|
|
89
|
+
this.inputDigests = {
|
|
90
|
+
builtinModelsDigest: workerConfigDigest(aiwgDefault),
|
|
91
|
+
userModelsDigest: workerConfigDigest(userConfig),
|
|
92
|
+
projectModelsDigest: workerConfigDigest(projectConfig),
|
|
93
|
+
};
|
|
43
94
|
return merged;
|
|
44
95
|
}
|
|
45
96
|
/**
|
|
@@ -53,6 +104,8 @@ export class ConfigLoader {
|
|
|
53
104
|
if (!config.version || !config.tiers || !config.providers) {
|
|
54
105
|
throw new Error('Invalid AIWG default config: missing required v2 fields');
|
|
55
106
|
}
|
|
107
|
+
if (config.defaults?.workerSelection !== undefined)
|
|
108
|
+
WorkerSelectionPolicySchema.parse(config.defaults.workerSelection);
|
|
56
109
|
return config;
|
|
57
110
|
}
|
|
58
111
|
catch (error) {
|
|
@@ -63,9 +116,6 @@ export class ConfigLoader {
|
|
|
63
116
|
* Load user configuration (optional)
|
|
64
117
|
*/
|
|
65
118
|
async loadUserConfig() {
|
|
66
|
-
if (!existsSync(this.locations.userConfig)) {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
119
|
try {
|
|
70
120
|
const content = await readFile(this.locations.userConfig, 'utf-8');
|
|
71
121
|
const parsed = JSON.parse(content);
|
|
@@ -76,6 +126,8 @@ export class ConfigLoader {
|
|
|
76
126
|
return parsed;
|
|
77
127
|
}
|
|
78
128
|
catch (error) {
|
|
129
|
+
if (error.code === 'ENOENT')
|
|
130
|
+
return null;
|
|
79
131
|
throw new Error(`Invalid user model config ${this.locations.userConfig}: ${error}`);
|
|
80
132
|
}
|
|
81
133
|
}
|
|
@@ -83,9 +135,6 @@ export class ConfigLoader {
|
|
|
83
135
|
* Load project configuration (optional)
|
|
84
136
|
*/
|
|
85
137
|
async loadProjectConfig() {
|
|
86
|
-
if (!existsSync(this.locations.projectConfig)) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
138
|
try {
|
|
90
139
|
const content = await readFile(this.locations.projectConfig, 'utf-8');
|
|
91
140
|
const parsed = JSON.parse(content);
|
|
@@ -96,6 +145,8 @@ export class ConfigLoader {
|
|
|
96
145
|
return parsed;
|
|
97
146
|
}
|
|
98
147
|
catch (error) {
|
|
148
|
+
if (error.code === 'ENOENT')
|
|
149
|
+
return null;
|
|
99
150
|
throw new Error(`Invalid project model config ${this.locations.projectConfig}: ${error}`);
|
|
100
151
|
}
|
|
101
152
|
}
|
|
@@ -217,6 +268,12 @@ export class ConfigLoader {
|
|
|
217
268
|
*/
|
|
218
269
|
clearCache() {
|
|
219
270
|
this.cache = null;
|
|
271
|
+
this.cacheDigest = null;
|
|
272
|
+
this.inputDigests = null;
|
|
273
|
+
}
|
|
274
|
+
/** Digests of the last successful read; no marker or snapshot writes. */
|
|
275
|
+
getInputDigests() {
|
|
276
|
+
return this.inputDigests ? { ...this.inputDigests } : null;
|
|
220
277
|
}
|
|
221
278
|
/**
|
|
222
279
|
* Get configuration file locations
|
|
@@ -33,6 +33,8 @@ function stringArray(value) {
|
|
|
33
33
|
if (!text.startsWith('[') || !text.endsWith(']'))
|
|
34
34
|
return undefined;
|
|
35
35
|
const matches = [...text.matchAll(/"((?:\\.|[^"\\])*)"|'([^']*)'/g)];
|
|
36
|
+
if (!/^\[\s*(?:s\s*(?:,\s*s\s*)*,?\s*)?\]$/.test(text.replace(/"((?:\\.|[^"\\])*)"|'([^']*)'/g, 's')))
|
|
37
|
+
return undefined;
|
|
36
38
|
return matches.map(match => match[1] !== undefined
|
|
37
39
|
? JSON.parse(`"${match[1]}"`)
|
|
38
40
|
: match[2]);
|
|
@@ -55,17 +57,25 @@ function mapKeys(value) {
|
|
|
55
57
|
}
|
|
56
58
|
/** Parse only non-secret fields needed for model discovery. */
|
|
57
59
|
export function parseGrokBuildModelConfig(text, scope) {
|
|
58
|
-
const parsed = { models: new Map(), projectModelFields: false };
|
|
60
|
+
const parsed = { models: new Map(), projectModelFields: false, diagnostics: [] };
|
|
59
61
|
let table = '';
|
|
60
|
-
for (const raw of text.split(/\r?\n/)) {
|
|
62
|
+
for (const [index, raw] of text.split(/\r?\n/).entries()) {
|
|
63
|
+
const invalid = (field) => parsed.diagnostics.push(`line ${index + 1}: malformed ${field}`);
|
|
61
64
|
const nextTable = tableName(raw);
|
|
62
65
|
if (nextTable !== undefined) {
|
|
63
66
|
table = nextTable;
|
|
64
67
|
continue;
|
|
65
68
|
}
|
|
69
|
+
if (/^\s*\[models?(?:[.\]\s]|$)/.test(raw)) {
|
|
70
|
+
invalid('model table');
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
66
73
|
const match = raw.match(/^\s*([A-Za-z0-9_-]+)\s*=\s*(.*?)\s*(?:#.*)?$/);
|
|
67
|
-
if (!match)
|
|
74
|
+
if (!match) {
|
|
75
|
+
if (/^models?(?:\.|$)/.test(table) && raw.trim() && !raw.trim().startsWith('#'))
|
|
76
|
+
invalid('model setting');
|
|
68
77
|
continue;
|
|
78
|
+
}
|
|
69
79
|
const [, key, value] = match;
|
|
70
80
|
const top = table.split('.', 1)[0];
|
|
71
81
|
if (scope === 'project' && !PROJECT_ALLOWED.has(top)) {
|
|
@@ -80,6 +90,10 @@ export function parseGrokBuildModelConfig(text, scope) {
|
|
|
80
90
|
parsed.allowedModels = stringArray(value);
|
|
81
91
|
else if (key === 'disabled_models')
|
|
82
92
|
parsed.disabledModels = stringArray(value);
|
|
93
|
+
if (key === 'default' && !parsed.defaultModel
|
|
94
|
+
|| key === 'allowed_models' && !parsed.allowedModels
|
|
95
|
+
|| key === 'disabled_models' && !parsed.disabledModels)
|
|
96
|
+
invalid(key);
|
|
83
97
|
continue;
|
|
84
98
|
}
|
|
85
99
|
const alias = modelAlias(table);
|
|
@@ -108,49 +122,76 @@ export function parseGrokBuildModelConfig(text, scope) {
|
|
|
108
122
|
model.supportsBackendSearch = value.trim() === 'true';
|
|
109
123
|
else if (key === 'reasoning_efforts')
|
|
110
124
|
model.reasoningEfforts = stringArray(value);
|
|
125
|
+
if (['model', 'name', 'base_url', 'api_base_url'].includes(key) && unquote(value) === undefined
|
|
126
|
+
|| key === 'env_key' && unquote(value) === undefined && stringArray(value) === undefined
|
|
127
|
+
|| ['hidden', 'supports_backend_search'].includes(key) && !/^(true|false)$/.test(value.trim())
|
|
128
|
+
|| key === 'context_window' && (!Number.isSafeInteger(Number(value)) || Number(value) < 1)
|
|
129
|
+
|| key === 'reasoning_efforts' && !model.reasoningEfforts)
|
|
130
|
+
invalid(key);
|
|
111
131
|
parsed.models.set(alias, model);
|
|
112
132
|
}
|
|
113
133
|
return parsed;
|
|
114
134
|
}
|
|
115
|
-
function inspectLayers(report) {
|
|
135
|
+
function inspectLayers(report, diagnostics) {
|
|
116
136
|
const sources = (report.configSources ?? report.config_sources);
|
|
117
|
-
if (!sources || !Array.isArray(sources.layers))
|
|
137
|
+
if (!sources || !Array.isArray(sources.layers)) {
|
|
138
|
+
if (sources !== undefined)
|
|
139
|
+
diagnostics.push('Grok Build /configSources/layers: expected array');
|
|
118
140
|
return [];
|
|
141
|
+
}
|
|
119
142
|
return sources.layers.flatMap((entry, index) => {
|
|
120
|
-
if (!entry || typeof entry !== 'object')
|
|
143
|
+
if (!entry || typeof entry !== 'object') {
|
|
144
|
+
diagnostics.push(`Grok Build /configSources/layers/${index}: malformed config layer`);
|
|
121
145
|
return [];
|
|
146
|
+
}
|
|
122
147
|
const item = entry;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
: [];
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
function inspectModels(report) {
|
|
129
|
-
const candidates = [report.models, report.modelCatalog, report.model_catalog]
|
|
130
|
-
.find(Array.isArray);
|
|
131
|
-
if (!candidates)
|
|
132
|
-
return [];
|
|
133
|
-
return candidates.flatMap(entry => {
|
|
134
|
-
if (typeof entry === 'string')
|
|
135
|
-
return [{ id: entry, alias: entry }];
|
|
136
|
-
if (!entry || typeof entry !== 'object')
|
|
148
|
+
if (typeof item.role !== 'string' || !item.role || typeof item.path !== 'string' || !item.path) {
|
|
149
|
+
diagnostics.push(`Grok Build /configSources/layers/${index}: missing role or path`);
|
|
137
150
|
return [];
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (!alias)
|
|
141
|
-
return [];
|
|
142
|
-
return [{
|
|
143
|
-
id: alias,
|
|
144
|
-
alias,
|
|
145
|
-
...(typeof model.name === 'string' ? { displayName: model.name } : {}),
|
|
146
|
-
...(model.default === true || model.isDefault === true ? { isDefault: true } : {}),
|
|
147
|
-
...(model.hidden === true ? { hidden: true } : {}),
|
|
148
|
-
...(Array.isArray(model.reasoningEfforts)
|
|
149
|
-
? { reasoningEfforts: model.reasoningEfforts.filter((item) => typeof item === 'string') }
|
|
150
|
-
: {}),
|
|
151
|
-
}];
|
|
151
|
+
}
|
|
152
|
+
return [{ role: item.role, path: item.path, ...(typeof item.note === 'string' ? { note: item.note } : {}), precedence: index + 1 }];
|
|
152
153
|
});
|
|
153
154
|
}
|
|
155
|
+
function inspectModels(report, diagnostics) {
|
|
156
|
+
for (const key of ['models', 'modelCatalog', 'model_catalog']) {
|
|
157
|
+
if (!(key in report))
|
|
158
|
+
continue;
|
|
159
|
+
const candidates = report[key];
|
|
160
|
+
if (!Array.isArray(candidates)) {
|
|
161
|
+
diagnostics.push(`Grok Build /${key}: expected model array`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
return candidates.flatMap((entry, index) => {
|
|
165
|
+
const invalid = () => {
|
|
166
|
+
diagnostics.push(`Grok Build /${key}/${index}: malformed model row`);
|
|
167
|
+
return [];
|
|
168
|
+
};
|
|
169
|
+
if (typeof entry === 'string')
|
|
170
|
+
return entry.trim() ? [{ id: entry, alias: entry }] : invalid();
|
|
171
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
172
|
+
return invalid();
|
|
173
|
+
const model = entry;
|
|
174
|
+
const alias = [model.alias, model.key, model.id].find(value => typeof value === 'string');
|
|
175
|
+
if (!alias?.trim()
|
|
176
|
+
|| (model.name !== undefined && typeof model.name !== 'string')
|
|
177
|
+
|| ['default', 'isDefault', 'hidden'].some(field => model[field] !== undefined && typeof model[field] !== 'boolean')
|
|
178
|
+
|| (model.reasoningEfforts !== undefined && (!Array.isArray(model.reasoningEfforts)
|
|
179
|
+
|| model.reasoningEfforts.some(item => typeof item !== 'string' || !item.trim()))))
|
|
180
|
+
return invalid();
|
|
181
|
+
return [{
|
|
182
|
+
id: alias,
|
|
183
|
+
alias,
|
|
184
|
+
...(typeof model.name === 'string' ? { displayName: model.name } : {}),
|
|
185
|
+
...(model.default === true || model.isDefault === true ? { isDefault: true } : {}),
|
|
186
|
+
...(model.hidden === true ? { hidden: true } : {}),
|
|
187
|
+
...(Array.isArray(model.reasoningEfforts)
|
|
188
|
+
? { reasoningEfforts: model.reasoningEfforts.filter((item) => typeof item === 'string') }
|
|
189
|
+
: {}),
|
|
190
|
+
}];
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
154
195
|
function setSelected(selected, key, value, layer, constraint) {
|
|
155
196
|
if (value === undefined)
|
|
156
197
|
return;
|
|
@@ -174,7 +215,7 @@ function parseEnvOverlay(env) {
|
|
|
174
215
|
if (!content || typeof content !== 'object' || Array.isArray(content))
|
|
175
216
|
return null;
|
|
176
217
|
const value = content;
|
|
177
|
-
const parsed = { models: new Map(), projectModelFields: false };
|
|
218
|
+
const parsed = { models: new Map(), projectModelFields: false, diagnostics: [] };
|
|
178
219
|
const settings = value.models;
|
|
179
220
|
if (settings && typeof settings === 'object' && !Array.isArray(settings)) {
|
|
180
221
|
const table = settings;
|
|
@@ -184,6 +225,13 @@ function parseEnvOverlay(env) {
|
|
|
184
225
|
parsed.allowedModels = table.allowed_models;
|
|
185
226
|
if (Array.isArray(table.disabled_models) && table.disabled_models.every(entry => typeof entry === 'string'))
|
|
186
227
|
parsed.disabledModels = table.disabled_models;
|
|
228
|
+
if (table.default !== undefined && (typeof table.default !== 'string' || !table.default)
|
|
229
|
+
|| table.allowed_models !== undefined && !parsed.allowedModels
|
|
230
|
+
|| table.disabled_models !== undefined && !parsed.disabledModels)
|
|
231
|
+
parsed.diagnostics.push('/models: malformed model settings');
|
|
232
|
+
}
|
|
233
|
+
else if (settings !== undefined) {
|
|
234
|
+
parsed.diagnostics.push('/models: expected object');
|
|
187
235
|
}
|
|
188
236
|
// The host allowlists env-overlay sections; per-model secrets and model definitions
|
|
189
237
|
// are intentionally not imported from arbitrary JSON here.
|
|
@@ -239,7 +287,8 @@ export async function discoverGrokBuildModels(options = {}) {
|
|
|
239
287
|
error: 'Grok Build inspect returned an unsupported JSON shape; update Grok Build or AIWG.' };
|
|
240
288
|
}
|
|
241
289
|
}
|
|
242
|
-
const
|
|
290
|
+
const validationDiagnostics = [];
|
|
291
|
+
const layers = inspectLayers(report, validationDiagnostics);
|
|
243
292
|
if (layers.length === 0 && !Object.keys(report).some(key => ['models', 'modelCatalog', 'model_catalog'].includes(key))) {
|
|
244
293
|
return { ...base, ...(runtimeVersion ? { runtimeVersion } : {}), errorKind: 'invalid-output',
|
|
245
294
|
error: 'Grok Build inspect JSON omitted configSources; update Grok Build or AIWG.' };
|
|
@@ -247,7 +296,7 @@ export async function discoverGrokBuildModels(options = {}) {
|
|
|
247
296
|
const diagnostics = [];
|
|
248
297
|
const selected = {};
|
|
249
298
|
const constraints = [];
|
|
250
|
-
const discovered = inspectModels(report);
|
|
299
|
+
const discovered = inspectModels(report, validationDiagnostics);
|
|
251
300
|
const parsedByLayer = await Promise.all(layers.map(layer => layer.role === 'env_overlay'
|
|
252
301
|
&& layer.path.includes('(inline)') ? parseEnvOverlay(env) : readLayer(layer)));
|
|
253
302
|
let requirementsDefault;
|
|
@@ -255,8 +304,11 @@ export async function discoverGrokBuildModels(options = {}) {
|
|
|
255
304
|
for (let i = 0; i < layers.length; i++) {
|
|
256
305
|
const layer = layers[i];
|
|
257
306
|
const parsed = parsedByLayer[i];
|
|
258
|
-
if (!parsed)
|
|
307
|
+
if (!parsed) {
|
|
308
|
+
validationDiagnostics.push(`${layer.path}: config layer could not be read or parsed within bounds`);
|
|
259
309
|
continue;
|
|
310
|
+
}
|
|
311
|
+
validationDiagnostics.push(...parsed.diagnostics.map(diagnostic => `${layer.path}: ${diagnostic}`));
|
|
260
312
|
if (parsed.projectModelFields)
|
|
261
313
|
diagnostics.push(`${layer.path}: ignored model settings because Grok Build project scope does not allow them`);
|
|
262
314
|
const requirement = /requirements|mdm/i.test(layer.role);
|
|
@@ -334,10 +386,10 @@ export async function discoverGrokBuildModels(options = {}) {
|
|
|
334
386
|
policyConstraint: /requirements|mdm/i.test(layer.role) })),
|
|
335
387
|
selected,
|
|
336
388
|
constraints,
|
|
337
|
-
diagnostics,
|
|
389
|
+
diagnostics: [...validationDiagnostics, ...diagnostics],
|
|
338
390
|
},
|
|
339
|
-
...(models.length === 0 ? { errorKind: 'invalid-output',
|
|
340
|
-
error: 'Grok Build exposed no model aliases. Configure [models].default or [model.<alias>] in $GROK_HOME/config.toml; AIWG will not fabricate an xAI model ID.' } : {}),
|
|
391
|
+
...(validationDiagnostics.length || models.length === 0 ? { errorKind: 'invalid-output',
|
|
392
|
+
error: validationDiagnostics.join('; ') || 'Grok Build exposed no model aliases. Configure [models].default or [model.<alias>] in $GROK_HOME/config.toml; AIWG will not fabricate an xAI model ID.' } : {}),
|
|
341
393
|
};
|
|
342
394
|
}
|
|
343
395
|
/** Resolve aliases only from the observed catalog. Exact API model ids are never synthesized. */
|