vgxness 1.20.0 → 1.20.2
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/agents/canonical-agent-manifest.js +1 -1
- package/dist/agents/canonical-agent-projection.js +4 -4
- package/dist/agents/renderers/opencode-renderer.js +2 -1
- package/dist/mcp/client-install-opencode-contract.js +5 -1
- package/dist/mcp/client-install-opencode.js +4 -1
- package/dist/mcp/control-plane.js +5 -8
- package/dist/mcp/opencode-handoff-preview.js +72 -12
- package/dist/mcp/provider-doctor.js +30 -0
- package/dist/mcp/provider-status.js +63 -7
- package/dist/sdd/sdd-workflow-service.js +67 -58
- package/package.json +1 -1
|
@@ -168,7 +168,7 @@ Coordinate SDD; keep chat thin, use VGXNESS MCP state, delegate to exact SDD sub
|
|
|
168
168
|
- Before phase advancement, call \`vgxness_sdd_status\`/\`vgxness_sdd_next\` plus \`vgxness_sdd_ready\` or \`vgxness_sdd_get_readiness\`.
|
|
169
169
|
- Before risky VGX-managed side effects (edit, shell/tests, git, network, provider-tool, secrets, external-directory, destructive, privileged, ambiguous), call \`vgxness_run_preflight\` with runId/workflow/phase/agent when available. Stop on approval/block; never invent approval.
|
|
170
170
|
- Direct human acceptance via \`vgxness_sdd_accept_artifact\` is not a generic SDD write: do not preflight solely for it; require exact project/change/phase, \`acceptedBy.type\` \`"human"\`, non-empty \`acceptedBy.id\`, and eligible status/readiness. Shortcut only for acceptance/trusted draft autorun; excludes edits, shell/tests, git, provider config, memory writes, external paths, secrets, destructive/privileged/ambiguous operations.
|
|
171
|
-
-
|
|
171
|
+
- bounded read/search repo tools may be enabled for inspection; bash, edit, and write remain denied. Diagnostics are config-level evidence, not host runtime proof.
|
|
172
172
|
- Do not mutate provider/global OpenCode config. Do not publish packages unless explicitly requested. Never revert/overwrite unrelated user work. Preserve \`permission.task\` deny-by-default with only exact known SDD subagents.
|
|
173
173
|
- Do not change provider model/reasoning config unless explicitly requested.
|
|
174
174
|
|
|
@@ -137,7 +137,7 @@ function openCodeManagerPermissionsFor(agent) {
|
|
|
137
137
|
...openCodePermissionsFor(agent, { task: createCanonicalOpenCodeSddTaskPermissions() }),
|
|
138
138
|
bash: 'deny',
|
|
139
139
|
edit: 'deny',
|
|
140
|
-
read: '
|
|
140
|
+
read: 'allow',
|
|
141
141
|
write: 'deny',
|
|
142
142
|
};
|
|
143
143
|
}
|
|
@@ -148,9 +148,9 @@ function openCodeManagerTools() {
|
|
|
148
148
|
delegation_list: true,
|
|
149
149
|
delegation_read: true,
|
|
150
150
|
edit: false,
|
|
151
|
-
glob:
|
|
152
|
-
grep:
|
|
153
|
-
read:
|
|
151
|
+
glob: true,
|
|
152
|
+
grep: true,
|
|
153
|
+
read: true,
|
|
154
154
|
write: false,
|
|
155
155
|
};
|
|
156
156
|
}
|
|
@@ -15,7 +15,7 @@ const openCodeCapabilities = {
|
|
|
15
15
|
'instructions',
|
|
16
16
|
'mode',
|
|
17
17
|
'adapter model',
|
|
18
|
-
'adapter config keys: variant, temperature, top_p, hidden, options, color, steps, permission',
|
|
18
|
+
'adapter config keys: variant, temperature, top_p, hidden, options, color, steps, permission, tools',
|
|
19
19
|
],
|
|
20
20
|
skillInjectionModes: ['external preview services may inject skill payloads before rendering; renderer does not resolve skills'],
|
|
21
21
|
permissionCategories: ['registry permission metadata mapped from adapter permission config'],
|
|
@@ -78,6 +78,7 @@ function openCodeAgentConfig(agent) {
|
|
|
78
78
|
copyString(config, rendered, 'color');
|
|
79
79
|
copyNumber(config, rendered, 'steps');
|
|
80
80
|
copyJson(config, rendered, 'permission');
|
|
81
|
+
copyJson(config, rendered, 'tools');
|
|
81
82
|
return rendered;
|
|
82
83
|
}
|
|
83
84
|
function copyString(source, target, key) {
|
|
@@ -197,7 +197,11 @@ function warningsForScope(scope, overwriteVgxness, agentPlan, bashPermissionPoli
|
|
|
197
197
|
`Reinstall/overwrite is enabled: existing VGXNESS-managed OpenCode entries will be replaced (${agentPlan.installsAgents ? 'mcp.vgxness, default_agent, instructions AGENTS.md, and known VGXNESS agents' : 'mcp.vgxness only'}); unrelated OpenCode config is preserved.`,
|
|
198
198
|
]
|
|
199
199
|
: [];
|
|
200
|
-
const bashWarnings = bashPermissionPolicy.manager === 'deny'
|
|
200
|
+
const bashWarnings = bashPermissionPolicy.manager === 'deny'
|
|
201
|
+
? [
|
|
202
|
+
'OpenCode config sets top-level permission.bash = ask; manager read/search discovery tools (read/glob/grep) are allowed while manager bash/edit/write are denied; SDD subagents keep explicit phase permissions. This is config-level evidence only and does not verify provider runtime enforcement.',
|
|
203
|
+
]
|
|
204
|
+
: ['OpenCode top-level permission.bash is set to ask.'];
|
|
201
205
|
return [
|
|
202
206
|
'Restart OpenCode after installation so it reloads the user MCP config.',
|
|
203
207
|
'OpenCode project config may override user config for a workspace; check project-level config if vgxness is not visible.',
|
|
@@ -214,7 +214,10 @@ function confirmationRequiredMessage(scope) {
|
|
|
214
214
|
return `\`mcp install opencode\` requires explicit --yes before any ${scope} config write.`;
|
|
215
215
|
}
|
|
216
216
|
function warnings() {
|
|
217
|
-
return [
|
|
217
|
+
return [
|
|
218
|
+
'Restart OpenCode after installation so it reloads the project MCP config.',
|
|
219
|
+
'OpenCode config sets top-level permission.bash = ask; manager read/search discovery tools (read/glob/grep) are allowed while manager bash/edit/write are denied; SDD subagents keep explicit phase permissions. This is config-level evidence only and does not verify provider runtime enforcement.',
|
|
220
|
+
];
|
|
218
221
|
}
|
|
219
222
|
function manualTest(databasePath, source) {
|
|
220
223
|
return {
|
|
@@ -308,19 +308,16 @@ function governanceSddServices(services) {
|
|
|
308
308
|
};
|
|
309
309
|
}
|
|
310
310
|
function sddContinueEnvelope(input, services) {
|
|
311
|
-
const
|
|
312
|
-
if (!
|
|
313
|
-
return errorEnvelope(
|
|
314
|
-
const cockpit = services.sdd.getCockpit({ project: input.project, change: input.change });
|
|
315
|
-
if (!cockpit.ok)
|
|
316
|
-
return errorEnvelope(cockpit.error.code, cockpit.error.message, 'vgxness_sdd_continue');
|
|
311
|
+
const context = services.sdd.getContinuationContext({ project: input.project, change: input.change });
|
|
312
|
+
if (!context.ok)
|
|
313
|
+
return errorEnvelope(context.error.code, context.error.message, 'vgxness_sdd_continue');
|
|
317
314
|
const relatedRun = services.runs.findRelatedInterruptedSddRun({ project: input.project, change: input.change });
|
|
318
315
|
if (!relatedRun.ok)
|
|
319
316
|
return errorEnvelope(relatedRun.error.code, relatedRun.error.message, 'vgxness_sdd_continue');
|
|
320
317
|
return successEnvelope('vgxness_sdd_continue', sddContinuationPlanFrom({
|
|
321
318
|
project: input.project,
|
|
322
|
-
next:
|
|
323
|
-
cockpit:
|
|
319
|
+
next: context.value.next,
|
|
320
|
+
cockpit: context.value.cockpit,
|
|
324
321
|
...(relatedRun.value === undefined ? {} : { relatedRunContext: relatedRun.value }),
|
|
325
322
|
}));
|
|
326
323
|
}
|
|
@@ -70,23 +70,42 @@ export class OpenCodeHandoffPreviewService {
|
|
|
70
70
|
return { summary: { ...summary, unavailableReason: reason }, inspection: notInspectedSdd(reason) };
|
|
71
71
|
}
|
|
72
72
|
const status = this.deps.sdd.getStatus({ project: input.project, change: input.change });
|
|
73
|
-
const
|
|
74
|
-
const inspection = { inspected: true, statusQueried: true, nextQueried: true, readinessQueried: false };
|
|
73
|
+
const inspection = { inspected: true, statusQueried: true, nextQueried: true, readinessQueried: input.phase !== undefined };
|
|
75
74
|
if (status.ok)
|
|
76
75
|
summary.status = status.value;
|
|
77
76
|
else
|
|
78
77
|
warnings.push(`SDD status unavailable: ${status.error.message}`);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
const continuation = this.deps.sdd.getContinuationContext?.({ project: input.project, change: input.change });
|
|
79
|
+
if (continuation !== undefined) {
|
|
80
|
+
if (continuation.ok) {
|
|
81
|
+
const continuationNext = continuationNextValue(continuation.value);
|
|
82
|
+
if (continuationNext !== undefined)
|
|
83
|
+
summary.next = continuationNext;
|
|
84
|
+
if (input.phase !== undefined) {
|
|
85
|
+
const continuationReadiness = continuationReadinessValue(continuation.value, input.phase);
|
|
86
|
+
if (continuationReadiness !== undefined)
|
|
87
|
+
summary.readiness = continuationReadiness;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
warnings.push(`SDD continuation context unavailable: ${continuation.error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (summary.next === undefined) {
|
|
95
|
+
const next = this.deps.sdd.getNext({ project: input.project, change: input.change });
|
|
96
|
+
if (next.ok)
|
|
97
|
+
summary.next = next.value;
|
|
88
98
|
else
|
|
89
|
-
warnings.push(`SDD
|
|
99
|
+
warnings.push(`SDD next phase unavailable: ${next.error.message}`);
|
|
100
|
+
}
|
|
101
|
+
if (input.phase !== undefined) {
|
|
102
|
+
if (summary.readiness === undefined) {
|
|
103
|
+
const ready = this.deps.sdd.getReady({ project: input.project, change: input.change, phase: input.phase });
|
|
104
|
+
if (ready.ok)
|
|
105
|
+
summary.readiness = ready.value;
|
|
106
|
+
else
|
|
107
|
+
warnings.push(`SDD readiness unavailable: ${ready.error.message}`);
|
|
108
|
+
}
|
|
90
109
|
}
|
|
91
110
|
return { summary, inspection };
|
|
92
111
|
}
|
|
@@ -108,6 +127,47 @@ export class OpenCodeHandoffPreviewService {
|
|
|
108
127
|
function notInspectedSdd(reason) {
|
|
109
128
|
return { inspected: false, statusQueried: false, nextQueried: false, readinessQueried: false, notInspectedReason: reason };
|
|
110
129
|
}
|
|
130
|
+
function continuationNextValue(value) {
|
|
131
|
+
const record = asRecord(value);
|
|
132
|
+
if (record === undefined)
|
|
133
|
+
return undefined;
|
|
134
|
+
const next = record.next;
|
|
135
|
+
return next === null ? undefined : next;
|
|
136
|
+
}
|
|
137
|
+
function continuationReadinessValue(value, phase) {
|
|
138
|
+
const record = asRecord(value);
|
|
139
|
+
if (record === undefined)
|
|
140
|
+
return undefined;
|
|
141
|
+
return readinessFromPhaseEntries(asRecord(record.cockpit)?.phases, phase) ?? readinessFromPhaseEntries(record.phases, phase) ?? readinessFromPhaseEntries(asRecord(record.readModel)?.phases, phase) ?? readinessFromReadinessMap(record.readinessByPhase, phase) ?? readinessFromReadinessMap(record.phaseReadiness, phase) ?? matchingReadiness(record.readiness, phase);
|
|
142
|
+
}
|
|
143
|
+
function readinessFromPhaseEntries(value, phase) {
|
|
144
|
+
if (!Array.isArray(value))
|
|
145
|
+
return undefined;
|
|
146
|
+
for (const entry of value) {
|
|
147
|
+
const record = asRecord(entry);
|
|
148
|
+
if (record?.phase === phase)
|
|
149
|
+
return record.readiness === null ? undefined : record.readiness;
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
function readinessFromReadinessMap(value, phase) {
|
|
154
|
+
const record = asRecord(value);
|
|
155
|
+
if (record === undefined)
|
|
156
|
+
return undefined;
|
|
157
|
+
const readiness = record[phase];
|
|
158
|
+
return readiness === null ? undefined : readiness;
|
|
159
|
+
}
|
|
160
|
+
function matchingReadiness(value, phase) {
|
|
161
|
+
const record = asRecord(value);
|
|
162
|
+
if (record === undefined)
|
|
163
|
+
return undefined;
|
|
164
|
+
if (record.phase !== undefined && record.phase !== phase)
|
|
165
|
+
return undefined;
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
function asRecord(value) {
|
|
169
|
+
return typeof value === 'object' && value !== null ? value : undefined;
|
|
170
|
+
}
|
|
111
171
|
function managerInput(input, scope) {
|
|
112
172
|
return { project: input.project, scope, agentName: input.agentName ?? 'vgxness-manager', ...(input.agentId === undefined ? {} : { agentId: input.agentId }), ...(input.workspaceRoot === undefined ? {} : { workspaceRoot: input.workspaceRoot }), ...(input.maxSourceBytes === undefined ? {} : { maxSourceBytes: input.maxSourceBytes }), payloadMode: 'compact' };
|
|
113
173
|
}
|
|
@@ -40,6 +40,7 @@ export class ProviderDoctorService {
|
|
|
40
40
|
configReadableCheck(readableJson.length, paths.filter((path) => path.exists).length),
|
|
41
41
|
mcpEntryCheck(config),
|
|
42
42
|
defaultAgentCheck(config),
|
|
43
|
+
managerNativeCapabilityCheck(config),
|
|
43
44
|
subagentsCheck(config),
|
|
44
45
|
delegationCheck(config),
|
|
45
46
|
{ id: 'mcp-current-call', status: 'pass', detail: 'Current MCP call reached the VGXNESS control-plane.' },
|
|
@@ -255,6 +256,35 @@ function defaultAgentCheck(config) {
|
|
|
255
256
|
return { id: 'opencode-default-agent', status: 'fail', detail: 'OpenCode default_agent is not vgxness-manager or the manager config is missing.' };
|
|
256
257
|
return { id: 'opencode-default-agent', status: 'pass', detail: 'OpenCode default agent points to vgxness-manager.' };
|
|
257
258
|
}
|
|
259
|
+
function managerNativeCapabilityCheck(config) {
|
|
260
|
+
const manager = isRecord(config?.agent) && isRecord(config.agent[vgxnessOpenCodeDefaultAgent]) ? config.agent[vgxnessOpenCodeDefaultAgent] : undefined;
|
|
261
|
+
const permission = isRecord(manager?.permission) ? manager.permission : undefined;
|
|
262
|
+
const tools = isRecord(manager?.tools) ? manager.tools : undefined;
|
|
263
|
+
const problems = [];
|
|
264
|
+
if (permission?.read !== 'allow')
|
|
265
|
+
problems.push('permission.read is not allow');
|
|
266
|
+
for (const tool of ['read', 'glob', 'grep']) {
|
|
267
|
+
if (tools?.[tool] !== true)
|
|
268
|
+
problems.push(`tools.${tool} is not enabled`);
|
|
269
|
+
}
|
|
270
|
+
for (const tool of ['bash', 'edit', 'write']) {
|
|
271
|
+
if (tools?.[tool] !== false)
|
|
272
|
+
problems.push(`tools.${tool} is not disabled`);
|
|
273
|
+
}
|
|
274
|
+
const evidenceSuffix = 'This is config-level evidence only; host runtime/tool presence is not verified.';
|
|
275
|
+
if (problems.length > 0)
|
|
276
|
+
return {
|
|
277
|
+
id: 'opencode-manager-native-capability',
|
|
278
|
+
status: 'fail',
|
|
279
|
+
detail: `Manager native capability drift detected: ${problems.join('; ')}. ${evidenceSuffix}`,
|
|
280
|
+
remediation: 'Re-run explicit OpenCode setup/install only after reviewing provider config drift; doctor does not auto-repair or write config.',
|
|
281
|
+
};
|
|
282
|
+
return {
|
|
283
|
+
id: 'opencode-manager-native-capability',
|
|
284
|
+
status: 'pass',
|
|
285
|
+
detail: `Manager config allows read/glob/grep inspection and denies bash/edit/write native tools. ${evidenceSuffix}`,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
258
288
|
function subagentsCheck(config) {
|
|
259
289
|
const agents = isRecord(config?.agent) ? config.agent : undefined;
|
|
260
290
|
const missing = vgxnessOpenCodeSddSubagents.filter((name) => !isRecord(agents?.[name]) || agents[name]?.hidden !== true);
|
|
@@ -27,10 +27,11 @@ export class ProviderStatusService {
|
|
|
27
27
|
const mcpEntry = inspectOpenCodeMcpEntry(paths);
|
|
28
28
|
const providerConfig = readFirstParsedConfig(paths);
|
|
29
29
|
const managerConfigured = hasConfiguredManager(providerConfig);
|
|
30
|
+
const managerNativeCapabilityStatus = inspectManagerNativeCapability(providerConfig);
|
|
30
31
|
const subagentsConfigured = hasConfiguredSubagents(providerConfig);
|
|
31
32
|
const tools = requiredToolPresence();
|
|
32
33
|
const evidence = providerEvidence({ staticManifestKnown: true, renderedConfigExpected: true, configPathInspected: paths.length > 0 });
|
|
33
|
-
const configStatus = resolveConfigStatus(paths, mcpEntry, managerConfigured, subagentsConfigured);
|
|
34
|
+
const configStatus = resolveConfigStatus(paths, mcpEntry, managerConfigured, managerNativeCapabilityStatus, subagentsConfigured);
|
|
34
35
|
const status = rollupProviderHealth([canonicalAgentManifest.status, configStatus, ...tools.map((tool) => (tool.present ? 'pass' : 'fail'))]);
|
|
35
36
|
const sdd = normalized.change.length > 0 ? this.readSdd(normalized.project, normalized.change) : undefined;
|
|
36
37
|
const generatedAt = 'read-only-snapshot';
|
|
@@ -38,14 +39,14 @@ export class ProviderStatusService {
|
|
|
38
39
|
? paths.map((path) => path.path)
|
|
39
40
|
: paths.filter((path) => path.exists || path.status !== 'not-configured').map((path) => path.path);
|
|
40
41
|
const verboseShape = {
|
|
41
|
-
config: { status: configStatus, paths, mcpEntry },
|
|
42
|
+
config: { status: configStatus, paths, mcpEntry, managerNativeCapability: managerNativeCapabilityStatus },
|
|
42
43
|
canonicalAgentManifest,
|
|
43
44
|
providerEvidence: evidence,
|
|
44
45
|
sdd,
|
|
45
46
|
mcpRequiredTools: tools,
|
|
46
47
|
};
|
|
47
48
|
const compactShape = {
|
|
48
|
-
config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') },
|
|
49
|
+
config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact'), managerNativeCapability: managerNativeCapabilityStatus },
|
|
49
50
|
canonicalAgentManifest,
|
|
50
51
|
providerEvidence: evidence,
|
|
51
52
|
sdd: sdd === undefined ? undefined : compactSdd(sdd, 'compact'),
|
|
@@ -86,13 +87,32 @@ export class ProviderStatusService {
|
|
|
86
87
|
return {
|
|
87
88
|
ok: true,
|
|
88
89
|
value: normalized.payloadMode === 'verbose'
|
|
89
|
-
? { ...reportBase, payloadMode: 'verbose', config: { status: configStatus, paths: compactPaths(paths, 'verbose'), mcpEntry: compactMcpEntry(mcpEntry, 'verbose') } }
|
|
90
|
-
: { ...reportBase, payloadMode: 'compact', config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') } },
|
|
90
|
+
? { ...reportBase, payloadMode: 'verbose', config: { status: configStatus, paths: compactPaths(paths, 'verbose'), mcpEntry: compactMcpEntry(mcpEntry, 'verbose'), managerNativeCapability: managerNativeCapabilityStatus } }
|
|
91
|
+
: { ...reportBase, payloadMode: 'compact', config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact'), managerNativeCapability: managerNativeCapabilityStatus } },
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
readSdd(project, change) {
|
|
94
95
|
if (this.deps.sdd === undefined)
|
|
95
96
|
return { change };
|
|
97
|
+
const continuation = this.deps.sdd.getContinuationContext?.({ project, change });
|
|
98
|
+
if (continuation?.ok === true && isRecord(continuation.value)) {
|
|
99
|
+
const continuationNext = isRecord(continuation.value.next) ? continuation.value.next : undefined;
|
|
100
|
+
const continuationStatus = projectSddCockpitStatus(continuation.value.cockpit, change, continuationNext);
|
|
101
|
+
if (continuationNext !== undefined) {
|
|
102
|
+
if (continuationStatus !== undefined)
|
|
103
|
+
return { change, status: continuationStatus, next: continuationNext };
|
|
104
|
+
const status = this.deps.sdd.getStatus({ project, change });
|
|
105
|
+
return { change, ...(status.ok ? { status: status.value } : {}), next: continuationNext };
|
|
106
|
+
}
|
|
107
|
+
if (continuationStatus !== undefined) {
|
|
108
|
+
const next = this.deps.sdd.getNext({ project, change });
|
|
109
|
+
return {
|
|
110
|
+
change,
|
|
111
|
+
status: projectSddCockpitStatus(continuation.value.cockpit, change, next.ok && isRecord(next.value) ? next.value : undefined),
|
|
112
|
+
...(next.ok && isRecord(next.value) ? { next: next.value } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
96
116
|
const status = this.deps.sdd.getStatus({ project, change });
|
|
97
117
|
const next = this.deps.sdd.getNext({ project, change });
|
|
98
118
|
return { change, ...(status.ok ? { status: status.value } : {}), ...(next.ok ? { next: next.value } : {}) };
|
|
@@ -257,6 +277,31 @@ function compactSdd(sdd, mode) {
|
|
|
257
277
|
...(sdd.next === undefined ? {} : { next: compactUnknown(sdd.next) }),
|
|
258
278
|
};
|
|
259
279
|
}
|
|
280
|
+
function projectSddCockpitStatus(cockpit, change, next) {
|
|
281
|
+
if (!isRecord(cockpit) || !Array.isArray(cockpit.phases))
|
|
282
|
+
return undefined;
|
|
283
|
+
const phases = cockpit.phases.map((phase) => projectSddPhaseStatus(phase));
|
|
284
|
+
if (phases.some((phase) => phase === undefined))
|
|
285
|
+
return undefined;
|
|
286
|
+
return {
|
|
287
|
+
change,
|
|
288
|
+
phases,
|
|
289
|
+
...(next?.status === 'runnable' && typeof next.nextPhase === 'string' ? { nextReadyPhase: next.nextPhase } : {}),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function projectSddPhaseStatus(phase) {
|
|
293
|
+
if (!isRecord(phase) || typeof phase.phase !== 'string' || typeof phase.topicKey !== 'string' || typeof phase.present !== 'boolean')
|
|
294
|
+
return undefined;
|
|
295
|
+
return {
|
|
296
|
+
phase: phase.phase,
|
|
297
|
+
topicKey: phase.topicKey,
|
|
298
|
+
present: phase.present,
|
|
299
|
+
...(typeof phase.state === 'string' ? { state: phase.state } : {}),
|
|
300
|
+
...(typeof phase.accepted === 'boolean' ? { accepted: phase.accepted } : {}),
|
|
301
|
+
...(typeof phase.legacy === 'boolean' ? { legacy: phase.legacy } : {}),
|
|
302
|
+
...(typeof phase.artifactId === 'string' ? { artifactId: phase.artifactId } : {}),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
260
305
|
function compactUnknown(value) {
|
|
261
306
|
if (!isRecord(value))
|
|
262
307
|
return value;
|
|
@@ -349,12 +394,12 @@ function requiredToolPresence() {
|
|
|
349
394
|
diagnostic: 'Static VGXNESS manifest expects this MCP tool; provider host tool presence was not verified.',
|
|
350
395
|
}));
|
|
351
396
|
}
|
|
352
|
-
function resolveConfigStatus(paths, mcpEntry, managerConfigured, subagentsConfigured) {
|
|
397
|
+
function resolveConfigStatus(paths, mcpEntry, managerConfigured, managerNativeCapabilityStatus, subagentsConfigured) {
|
|
353
398
|
if (paths.some((path) => path.status === 'fail') || mcpEntry.status === 'fail')
|
|
354
399
|
return 'fail';
|
|
355
400
|
if (mcpEntry.status === 'not-configured')
|
|
356
401
|
return 'not-configured';
|
|
357
|
-
if (!managerConfigured || !subagentsConfigured)
|
|
402
|
+
if (!managerConfigured || managerNativeCapabilityStatus === 'warn' || !subagentsConfigured)
|
|
358
403
|
return 'warn';
|
|
359
404
|
if (hasProjectConfigAlongsideUser(paths))
|
|
360
405
|
return 'warn';
|
|
@@ -405,6 +450,17 @@ function hasConfiguredManager(config) {
|
|
|
405
450
|
isRecord(agent?.[vgxnessOpenCodeDefaultAgent]) &&
|
|
406
451
|
promptContractVersion(agent[vgxnessOpenCodeDefaultAgent]) === vgxnessOpenCodePromptContractVersion);
|
|
407
452
|
}
|
|
453
|
+
function inspectManagerNativeCapability(config) {
|
|
454
|
+
if (config === undefined)
|
|
455
|
+
return 'not-configured';
|
|
456
|
+
const agent = isRecord(config.agent) ? config.agent : undefined;
|
|
457
|
+
const manager = isRecord(agent?.[vgxnessOpenCodeDefaultAgent]) ? agent[vgxnessOpenCodeDefaultAgent] : undefined;
|
|
458
|
+
const permission = isRecord(manager?.permission) ? manager.permission : undefined;
|
|
459
|
+
const tools = isRecord(manager?.tools) ? manager.tools : undefined;
|
|
460
|
+
const readSearchEnabled = permission?.read === 'allow' && tools?.read === true && tools.glob === true && tools.grep === true;
|
|
461
|
+
const writeShellDenied = tools?.bash === false && tools.edit === false && tools.write === false;
|
|
462
|
+
return readSearchEnabled && writeShellDenied ? 'pass' : 'warn';
|
|
463
|
+
}
|
|
408
464
|
function hasConfiguredSubagents(config) {
|
|
409
465
|
if (config === undefined)
|
|
410
466
|
return false;
|
|
@@ -80,57 +80,17 @@ export class SddWorkflowService {
|
|
|
80
80
|
const snapshot = this.loadPhaseSnapshot(validated.value.project, validated.value.change);
|
|
81
81
|
if (!snapshot.ok)
|
|
82
82
|
return snapshot;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
phase: phaseStatus.phase,
|
|
96
|
-
topicKey: phaseStatus.topicKey,
|
|
97
|
-
present: phaseStatus.present,
|
|
98
|
-
accepted: phaseStatus.accepted === true,
|
|
99
|
-
legacy: phaseStatus.legacy === true,
|
|
100
|
-
state: phaseStatus.state ?? 'missing',
|
|
101
|
-
readiness,
|
|
102
|
-
...(artifact === undefined ? {} : { artifact }),
|
|
103
|
-
blockers,
|
|
104
|
-
gates,
|
|
105
|
-
};
|
|
106
|
-
});
|
|
107
|
-
const artifacts = cockpitPhases.map((phase) => phase.artifact).filter((artifact) => artifact !== undefined);
|
|
108
|
-
const aggregateBlockers = dedupeCockpitBlockers([
|
|
109
|
-
...cockpitPhases.flatMap((phase) => phase.blockers),
|
|
110
|
-
...(next.missingPrerequisiteTopicKeys ?? []).map((topicKey) => blockerFromTopicKey(validated.value.change, topicKey)),
|
|
111
|
-
...(next.blockedPrerequisites ?? []).map((blocker) => ({
|
|
112
|
-
kind: 'readiness',
|
|
113
|
-
phase: blocker.phase,
|
|
114
|
-
topicKey: blocker.topicKey,
|
|
115
|
-
reason: blocker.reason,
|
|
116
|
-
...(blocker.artifactId === undefined ? {} : { artifactId: blocker.artifactId }),
|
|
117
|
-
})),
|
|
118
|
-
]);
|
|
119
|
-
const cockpit = {
|
|
120
|
-
project: validated.value.project,
|
|
121
|
-
change: validated.value.change,
|
|
122
|
-
...(next.nextPhase === undefined ? {} : { actionablePhase: next.nextPhase }),
|
|
123
|
-
next,
|
|
124
|
-
recommendedAction: cockpitRecommendedAction(next, aggregateBlockers),
|
|
125
|
-
phases: cockpitPhases,
|
|
126
|
-
artifacts,
|
|
127
|
-
acceptedCount: cockpitPhases.filter((phase) => phase.accepted).length,
|
|
128
|
-
legacyCount: cockpitPhases.filter((phase) => phase.legacy).length,
|
|
129
|
-
aggregateBlockers,
|
|
130
|
-
gates: cockpitGatesFromPhases(cockpitPhases, next),
|
|
131
|
-
inspectCommand: `vgxness sdd cockpit --project ${validated.value.project} --change ${validated.value.change} --json`,
|
|
132
|
-
};
|
|
133
|
-
return ok(cockpit);
|
|
83
|
+
return ok(this.cockpitFromSnapshot(snapshot.value));
|
|
84
|
+
}
|
|
85
|
+
getContinuationContext(input) {
|
|
86
|
+
const validated = validateProjectAndChange(input.project, input.change);
|
|
87
|
+
if (!validated.ok)
|
|
88
|
+
return validated;
|
|
89
|
+
const snapshot = this.loadPhaseSnapshot(validated.value.project, validated.value.change);
|
|
90
|
+
if (!snapshot.ok)
|
|
91
|
+
return snapshot;
|
|
92
|
+
const cockpit = this.cockpitFromSnapshot(snapshot.value);
|
|
93
|
+
return ok({ next: cockpit.next, cockpit });
|
|
134
94
|
}
|
|
135
95
|
getGovernanceSnapshot(input) {
|
|
136
96
|
const validated = validateProjectAndChange(input.project, input.change);
|
|
@@ -306,13 +266,10 @@ export class SddWorkflowService {
|
|
|
306
266
|
},
|
|
307
267
|
});
|
|
308
268
|
}
|
|
309
|
-
const
|
|
310
|
-
if (!
|
|
311
|
-
return
|
|
312
|
-
const
|
|
313
|
-
const artifacts = sddPhases
|
|
314
|
-
.map((phase) => artifactsByTopicKey.get(sddTopicKey(validated.value.change, phase)))
|
|
315
|
-
.filter((artifact) => artifact !== undefined);
|
|
269
|
+
const snapshot = this.loadPhaseSnapshot(validated.value.project, validated.value.change);
|
|
270
|
+
if (!snapshot.ok)
|
|
271
|
+
return snapshot;
|
|
272
|
+
const artifacts = snapshot.value.phases.flatMap((phase) => (phase.artifact === undefined ? [] : [phase.artifact]));
|
|
316
273
|
return ok({ project: validated.value.project, change: validated.value.change, artifacts });
|
|
317
274
|
}
|
|
318
275
|
validatePhaseInput(input) {
|
|
@@ -358,6 +315,58 @@ export class SddWorkflowService {
|
|
|
358
315
|
});
|
|
359
316
|
return ok({ project, change, phases });
|
|
360
317
|
}
|
|
318
|
+
cockpitFromSnapshot(snapshot) {
|
|
319
|
+
const phases = snapshot.phases;
|
|
320
|
+
const next = nextDecisionFromStatuses(snapshot.change, phases, this.options);
|
|
321
|
+
const cockpitPhases = phases.map((phaseStatus) => {
|
|
322
|
+
const readiness = {
|
|
323
|
+
change: snapshot.change,
|
|
324
|
+
phase: phaseStatus.phase,
|
|
325
|
+
...getReadinessFromStatuses(snapshot.change, phaseStatus.phase, phases, this.options),
|
|
326
|
+
};
|
|
327
|
+
const artifact = phaseStatus.present ? cockpitArtifactSummaryFromSnapshotItem(phaseStatus) : undefined;
|
|
328
|
+
const blockers = cockpitBlockersForPhase(phaseStatus, readiness);
|
|
329
|
+
const gates = phaseGateFromStatus(phaseStatus, readiness);
|
|
330
|
+
return {
|
|
331
|
+
phase: phaseStatus.phase,
|
|
332
|
+
topicKey: phaseStatus.topicKey,
|
|
333
|
+
present: phaseStatus.present,
|
|
334
|
+
accepted: phaseStatus.accepted === true,
|
|
335
|
+
legacy: phaseStatus.legacy === true,
|
|
336
|
+
state: phaseStatus.state ?? 'missing',
|
|
337
|
+
readiness,
|
|
338
|
+
...(artifact === undefined ? {} : { artifact }),
|
|
339
|
+
blockers,
|
|
340
|
+
gates,
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
const artifacts = cockpitPhases.map((phase) => phase.artifact).filter((artifact) => artifact !== undefined);
|
|
344
|
+
const aggregateBlockers = dedupeCockpitBlockers([
|
|
345
|
+
...cockpitPhases.flatMap((phase) => phase.blockers),
|
|
346
|
+
...(next.missingPrerequisiteTopicKeys ?? []).map((topicKey) => blockerFromTopicKey(snapshot.change, topicKey)),
|
|
347
|
+
...(next.blockedPrerequisites ?? []).map((blocker) => ({
|
|
348
|
+
kind: 'readiness',
|
|
349
|
+
phase: blocker.phase,
|
|
350
|
+
topicKey: blocker.topicKey,
|
|
351
|
+
reason: blocker.reason,
|
|
352
|
+
...(blocker.artifactId === undefined ? {} : { artifactId: blocker.artifactId }),
|
|
353
|
+
})),
|
|
354
|
+
]);
|
|
355
|
+
return {
|
|
356
|
+
project: snapshot.project,
|
|
357
|
+
change: snapshot.change,
|
|
358
|
+
...(next.nextPhase === undefined ? {} : { actionablePhase: next.nextPhase }),
|
|
359
|
+
next,
|
|
360
|
+
recommendedAction: cockpitRecommendedAction(next, aggregateBlockers),
|
|
361
|
+
phases: cockpitPhases,
|
|
362
|
+
artifacts,
|
|
363
|
+
acceptedCount: cockpitPhases.filter((phase) => phase.accepted).length,
|
|
364
|
+
legacyCount: cockpitPhases.filter((phase) => phase.legacy).length,
|
|
365
|
+
aggregateBlockers,
|
|
366
|
+
gates: cockpitGatesFromPhases(cockpitPhases, next),
|
|
367
|
+
inspectCommand: `vgxness sdd cockpit --project ${snapshot.project} --change ${snapshot.change} --json`,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
361
370
|
}
|
|
362
371
|
export function nextDecisionFromStatuses(change, phases, options = {}) {
|
|
363
372
|
const blockedPresentPhase = phases.find((status) => {
|