vgxness 1.9.2 → 1.9.4
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 +12 -6
- package/dist/agents/agent-resolver.js +33 -3
- package/dist/agents/canonical-agent-manifest.js +68 -21
- package/dist/agents/canonical-agent-projection.js +46 -4
- package/dist/cli/cli-help.js +14 -3
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/interactive-entrypoint-dispatcher.js +8 -0
- package/dist/cli/commands/mcp-dispatcher.js +49 -18
- package/dist/cli/commands/memory-sdd-dispatcher.js +71 -5
- package/dist/cli/commands/setup-dispatcher.js +22 -10
- package/dist/cli/commands/status-dispatcher.js +130 -0
- package/dist/cli/commands/workflow-dispatcher.js +11 -5
- package/dist/cli/dispatcher.js +9 -1
- package/dist/cli/product-resume-renderer.js +32 -0
- package/dist/cli/product-status-renderer.js +81 -0
- package/dist/cli/sdd-renderer.js +90 -7
- package/dist/cli/tui/main-menu/main-menu-read-model.js +8 -8
- package/dist/cli/tui/setup/setup-tui-services.js +27 -10
- package/dist/code/cli/code-command.js +7 -4
- package/dist/code/reporting/summary.js +4 -1
- package/dist/code/runtime/code-runtime.js +27 -4
- package/dist/code/runtime/sdd-context.js +18 -2
- package/dist/governance/governance-report-builder.js +18 -7
- package/dist/mcp/claude-code-agent-config.js +10 -4
- package/dist/mcp/client-install-opencode-contract.js +2 -2
- package/dist/mcp/client-install-opencode.js +10 -6
- package/dist/mcp/control-plane.js +56 -0
- package/dist/mcp/opencode-default-agent-config.js +7 -4
- package/dist/mcp/provider-status.js +86 -81
- package/dist/mcp/schema.js +25 -7
- package/dist/mcp/stdio-server.js +4 -0
- package/dist/mcp/validation.js +39 -3
- package/dist/resume/product-resume.js +166 -0
- package/dist/runs/repositories/runs.js +12 -1
- package/dist/runs/run-service.js +62 -5
- package/dist/sdd/schema.js +8 -0
- package/dist/sdd/sdd-continuation-plan.js +81 -0
- package/dist/sdd/sdd-workflow-service.js +103 -16
- package/dist/skills/skill-resolver.js +21 -4
- package/dist/status/product-status.js +121 -0
- package/docs/architecture.md +1 -1
- package/docs/cli.md +40 -12
- package/docs/code-runtime.md +3 -0
- package/docs/glossary.md +1 -1
- package/docs/mcp.md +18 -4
- package/package.json +1 -1
|
@@ -52,35 +52,36 @@ export class ProviderStatusService {
|
|
|
52
52
|
const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
|
|
53
53
|
const issueCount = [canonicalAgentManifest.status, configStatus, ...tools.map((tool) => (tool.present ? 'pass' : 'fail'))].filter((item) => item === 'fail' || item === 'not-configured').length;
|
|
54
54
|
const warningCount = [configStatus, mcpEntry.status, ...paths.map((path) => path.status)].filter((item) => item === 'warn').length;
|
|
55
|
+
const reportBase = {
|
|
56
|
+
version: 1,
|
|
57
|
+
kind: 'provider-status',
|
|
58
|
+
project: normalized.project,
|
|
59
|
+
providerAdapter: normalized.providerAdapter,
|
|
60
|
+
scope: normalized.scope,
|
|
61
|
+
workspaceRoot: normalized.workspaceRoot,
|
|
62
|
+
status,
|
|
63
|
+
overallStatus: status,
|
|
64
|
+
inspectedPaths: checkedPaths,
|
|
65
|
+
issueCount,
|
|
66
|
+
warningCount,
|
|
67
|
+
summary: summarizeStatus(status, mcpEntry),
|
|
68
|
+
nextAction: nextActionFor(status, mcpEntry, sdd?.next),
|
|
69
|
+
checkedPaths,
|
|
70
|
+
canonicalAgentManifest,
|
|
71
|
+
...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
|
|
72
|
+
mcpRequiredTools: tools,
|
|
73
|
+
originalBytes,
|
|
74
|
+
compactBytes,
|
|
75
|
+
verboseAvailable: normalized.payloadMode === 'compact',
|
|
76
|
+
fullContentRef: `provider-status:${normalized.providerAdapter}:${normalized.workspaceRoot}`,
|
|
77
|
+
generatedAt,
|
|
78
|
+
safety: PROVIDER_HEALTH_SAFETY,
|
|
79
|
+
};
|
|
55
80
|
return {
|
|
56
81
|
ok: true,
|
|
57
|
-
value:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
project: normalized.project,
|
|
61
|
-
providerAdapter: normalized.providerAdapter,
|
|
62
|
-
scope: normalized.scope,
|
|
63
|
-
workspaceRoot: normalized.workspaceRoot,
|
|
64
|
-
status,
|
|
65
|
-
payloadMode: normalized.payloadMode,
|
|
66
|
-
overallStatus: status,
|
|
67
|
-
inspectedPaths: checkedPaths,
|
|
68
|
-
issueCount,
|
|
69
|
-
warningCount,
|
|
70
|
-
summary: summarizeStatus(status, mcpEntry),
|
|
71
|
-
nextAction: nextActionFor(status, mcpEntry, sdd?.next),
|
|
72
|
-
checkedPaths,
|
|
73
|
-
canonicalAgentManifest,
|
|
74
|
-
config: { status: configStatus, paths: compactPaths(paths, normalized.payloadMode), mcpEntry: compactMcpEntry(mcpEntry, normalized.payloadMode) },
|
|
75
|
-
...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
|
|
76
|
-
mcpRequiredTools: tools,
|
|
77
|
-
originalBytes,
|
|
78
|
-
compactBytes,
|
|
79
|
-
verboseAvailable: normalized.payloadMode === 'compact',
|
|
80
|
-
fullContentRef: `provider-status:${normalized.providerAdapter}:${normalized.workspaceRoot}`,
|
|
81
|
-
generatedAt,
|
|
82
|
-
safety: PROVIDER_HEALTH_SAFETY,
|
|
83
|
-
},
|
|
82
|
+
value: normalized.payloadMode === 'verbose'
|
|
83
|
+
? { ...reportBase, payloadMode: 'verbose', config: { status: configStatus, paths: compactPaths(paths, 'verbose'), mcpEntry: compactMcpEntry(mcpEntry, 'verbose') } }
|
|
84
|
+
: { ...reportBase, payloadMode: 'compact', config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') } },
|
|
84
85
|
};
|
|
85
86
|
}
|
|
86
87
|
readSdd(project, change) {
|
|
@@ -115,35 +116,36 @@ export class ProviderStatusService {
|
|
|
115
116
|
const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
|
|
116
117
|
const issueCount = [canonicalAgentManifest.status, configStatus, ...agentStatuses].filter((item) => item === 'fail' || item === 'not-configured').length;
|
|
117
118
|
const warningCount = advisory.length + resolvedScope.value.warnings.length + paths.filter((path) => path.status === 'warn').length;
|
|
119
|
+
const reportBase = {
|
|
120
|
+
version: 1,
|
|
121
|
+
kind: 'provider-status',
|
|
122
|
+
project: normalized.project,
|
|
123
|
+
providerAdapter: 'claude',
|
|
124
|
+
scope: normalized.scope,
|
|
125
|
+
workspaceRoot: normalized.workspaceRoot,
|
|
126
|
+
status,
|
|
127
|
+
overallStatus: status,
|
|
128
|
+
inspectedPaths: checkedPaths,
|
|
129
|
+
issueCount,
|
|
130
|
+
warningCount,
|
|
131
|
+
summary: summarizeClaudeStatus(status, mcpEntry),
|
|
132
|
+
nextAction: nextActionFor(status, mcpEntry, sdd?.next),
|
|
133
|
+
checkedPaths,
|
|
134
|
+
canonicalAgentManifest,
|
|
135
|
+
...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
|
|
136
|
+
mcpRequiredTools: tools,
|
|
137
|
+
originalBytes,
|
|
138
|
+
compactBytes,
|
|
139
|
+
verboseAvailable: normalized.payloadMode === 'compact',
|
|
140
|
+
fullContentRef: `provider-status:claude:${normalized.workspaceRoot}`,
|
|
141
|
+
generatedAt: 'read-only-snapshot',
|
|
142
|
+
safety: PROVIDER_HEALTH_SAFETY,
|
|
143
|
+
};
|
|
118
144
|
return {
|
|
119
145
|
ok: true,
|
|
120
|
-
value:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
project: normalized.project,
|
|
124
|
-
providerAdapter: 'claude',
|
|
125
|
-
scope: normalized.scope,
|
|
126
|
-
workspaceRoot: normalized.workspaceRoot,
|
|
127
|
-
status,
|
|
128
|
-
payloadMode: normalized.payloadMode,
|
|
129
|
-
overallStatus: status,
|
|
130
|
-
inspectedPaths: checkedPaths,
|
|
131
|
-
issueCount,
|
|
132
|
-
warningCount,
|
|
133
|
-
summary: summarizeClaudeStatus(status, mcpEntry),
|
|
134
|
-
nextAction: nextActionFor(status, mcpEntry, sdd?.next),
|
|
135
|
-
checkedPaths,
|
|
136
|
-
canonicalAgentManifest,
|
|
137
|
-
config: { status: configStatus, paths: compactPaths(paths, normalized.payloadMode), mcpEntry: compactMcpEntry(mcpEntry, normalized.payloadMode) },
|
|
138
|
-
...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
|
|
139
|
-
mcpRequiredTools: tools,
|
|
140
|
-
originalBytes,
|
|
141
|
-
compactBytes,
|
|
142
|
-
verboseAvailable: normalized.payloadMode === 'compact',
|
|
143
|
-
fullContentRef: `provider-status:claude:${normalized.workspaceRoot}`,
|
|
144
|
-
generatedAt: 'read-only-snapshot',
|
|
145
|
-
safety: PROVIDER_HEALTH_SAFETY,
|
|
146
|
-
},
|
|
146
|
+
value: normalized.payloadMode === 'verbose'
|
|
147
|
+
? { ...reportBase, payloadMode: 'verbose', config: { status: configStatus, paths: compactPaths(paths, 'verbose'), mcpEntry: compactMcpEntry(mcpEntry, 'verbose') } }
|
|
148
|
+
: { ...reportBase, payloadMode: 'compact', config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') } },
|
|
147
149
|
};
|
|
148
150
|
}
|
|
149
151
|
getClaudeUserGlobalStatus(normalized, canonicalScope = 'user', scopeWarnings = []) {
|
|
@@ -162,35 +164,38 @@ export class ProviderStatusService {
|
|
|
162
164
|
const checkedPaths = normalized.payloadMode === 'verbose' ? [mcpState.path, agents.directoryPath, ...agents.agents.map((agent) => agent.path), userMemory.path] : [mcpState.path, userMemory.path, ...agents.agents.filter((agent) => agent.exists || agent.status !== 'missing').map((agent) => agent.path)];
|
|
163
165
|
const verboseShape = { config: { status: configStatus, paths, mcpEntry }, canonicalAgentManifest, agents, userMemory: { status: userMemory.status, action: userMemory.action }, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, scopeWarnings, sdd, mcpRequiredTools: tools };
|
|
164
166
|
const compactShape = { config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') }, canonicalAgentManifest, agentSummary: summarizeClaudeAgents(agents), userMemory: { status: userMemory.status, action: userMemory.action }, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, sdd: sdd === undefined ? undefined : compactSdd(sdd, 'compact'), mcpRequiredTools: tools };
|
|
167
|
+
const originalBytes = Buffer.byteLength(JSON.stringify(verboseShape), 'utf8');
|
|
168
|
+
const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
|
|
169
|
+
const reportBase = {
|
|
170
|
+
version: 1,
|
|
171
|
+
kind: 'provider-status',
|
|
172
|
+
project: normalized.project,
|
|
173
|
+
providerAdapter: 'claude',
|
|
174
|
+
scope: normalized.scope,
|
|
175
|
+
workspaceRoot: normalized.workspaceRoot,
|
|
176
|
+
status,
|
|
177
|
+
overallStatus: status,
|
|
178
|
+
inspectedPaths: checkedPaths,
|
|
179
|
+
issueCount: [canonicalAgentManifest.status, configStatus, ...agentStatuses].filter((item) => item === 'fail' || item === 'not-configured').length,
|
|
180
|
+
warningCount: scopeWarnings.length + paths.filter((path) => path.status === 'warn').length + 1,
|
|
181
|
+
summary: summarizeClaudeStatus(status, mcpEntry),
|
|
182
|
+
nextAction: nextActionFor(status, mcpEntry, sdd?.next),
|
|
183
|
+
checkedPaths,
|
|
184
|
+
canonicalAgentManifest,
|
|
185
|
+
...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
|
|
186
|
+
mcpRequiredTools: tools,
|
|
187
|
+
originalBytes,
|
|
188
|
+
compactBytes,
|
|
189
|
+
verboseAvailable: normalized.payloadMode === 'compact',
|
|
190
|
+
fullContentRef: `provider-status:claude:user-global:${normalized.workspaceRoot}`,
|
|
191
|
+
generatedAt: 'read-only-snapshot',
|
|
192
|
+
safety: { ...PROVIDER_HEALTH_SAFETY, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES },
|
|
193
|
+
};
|
|
165
194
|
return {
|
|
166
195
|
ok: true,
|
|
167
|
-
value:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
project: normalized.project,
|
|
171
|
-
providerAdapter: 'claude',
|
|
172
|
-
scope: normalized.scope,
|
|
173
|
-
workspaceRoot: normalized.workspaceRoot,
|
|
174
|
-
status,
|
|
175
|
-
payloadMode: normalized.payloadMode,
|
|
176
|
-
overallStatus: status,
|
|
177
|
-
inspectedPaths: checkedPaths,
|
|
178
|
-
issueCount: [canonicalAgentManifest.status, configStatus, ...agentStatuses].filter((item) => item === 'fail' || item === 'not-configured').length,
|
|
179
|
-
warningCount: scopeWarnings.length + paths.filter((path) => path.status === 'warn').length + 1,
|
|
180
|
-
summary: summarizeClaudeStatus(status, mcpEntry),
|
|
181
|
-
nextAction: nextActionFor(status, mcpEntry, sdd?.next),
|
|
182
|
-
checkedPaths,
|
|
183
|
-
canonicalAgentManifest,
|
|
184
|
-
config: { status: configStatus, paths: compactPaths(paths, normalized.payloadMode), mcpEntry: compactMcpEntry(mcpEntry, normalized.payloadMode) },
|
|
185
|
-
...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
|
|
186
|
-
mcpRequiredTools: tools,
|
|
187
|
-
originalBytes: Buffer.byteLength(JSON.stringify(verboseShape), 'utf8'),
|
|
188
|
-
compactBytes: Buffer.byteLength(JSON.stringify(compactShape), 'utf8'),
|
|
189
|
-
verboseAvailable: normalized.payloadMode === 'compact',
|
|
190
|
-
fullContentRef: `provider-status:claude:user-global:${normalized.workspaceRoot}`,
|
|
191
|
-
generatedAt: 'read-only-snapshot',
|
|
192
|
-
safety: { ...PROVIDER_HEALTH_SAFETY, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES },
|
|
193
|
-
},
|
|
196
|
+
value: normalized.payloadMode === 'verbose'
|
|
197
|
+
? { ...reportBase, payloadMode: 'verbose', config: { status: configStatus, paths: compactPaths(paths, 'verbose'), mcpEntry: compactMcpEntry(mcpEntry, 'verbose') } }
|
|
198
|
+
: { ...reportBase, payloadMode: 'compact', config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') } },
|
|
194
199
|
};
|
|
195
200
|
}
|
|
196
201
|
}
|
package/dist/mcp/schema.js
CHANGED
|
@@ -14,6 +14,7 @@ export const SUPPORTED_VGX_MCP_TOOL_NAMES = [
|
|
|
14
14
|
'vgxness_sdd_list_artifacts',
|
|
15
15
|
'vgxness_sdd_next',
|
|
16
16
|
'vgxness_sdd_cockpit',
|
|
17
|
+
'vgxness_sdd_continue',
|
|
17
18
|
'vgxness_governance_report',
|
|
18
19
|
'vgxness_memory_save',
|
|
19
20
|
'vgxness_memory_search',
|
|
@@ -37,6 +38,7 @@ export const SUPPORTED_VGX_MCP_TOOL_NAMES = [
|
|
|
37
38
|
'vgxness_run_start',
|
|
38
39
|
'vgxness_run_checkpoint',
|
|
39
40
|
'vgxness_run_finalize',
|
|
41
|
+
'vgxness_run_resume_candidates',
|
|
40
42
|
'vgxness_run_resume_inspect',
|
|
41
43
|
'vgxness_run_resume_gate',
|
|
42
44
|
'vgxness_provider_status',
|
|
@@ -55,6 +57,7 @@ export const EXPOSED_VGX_MCP_TOOL_NAMES = [
|
|
|
55
57
|
'sdd_list_artifacts',
|
|
56
58
|
'sdd_next',
|
|
57
59
|
'sdd_cockpit',
|
|
60
|
+
'sdd_continue',
|
|
58
61
|
'governance_report',
|
|
59
62
|
'memory_save',
|
|
60
63
|
'memory_search',
|
|
@@ -78,6 +81,7 @@ export const EXPOSED_VGX_MCP_TOOL_NAMES = [
|
|
|
78
81
|
'run_start',
|
|
79
82
|
'run_checkpoint',
|
|
80
83
|
'run_finalize',
|
|
84
|
+
'run_resume_candidates',
|
|
81
85
|
'run_resume_inspect',
|
|
82
86
|
'run_resume_gate',
|
|
83
87
|
'provider_status',
|
|
@@ -104,6 +108,7 @@ const payloadModes = ['compact', 'verbose'];
|
|
|
104
108
|
const contextCockpitLevels = ['compact', 'expanded', 'verbose'];
|
|
105
109
|
const providerChangePlanProviders = ['opencode', 'claude', 'antigravity', 'custom'];
|
|
106
110
|
const providerChangePlanTypes = ['opencode-mcp-install', 'claude-mcp-install', 'setup', 'install', 'config-preparation'];
|
|
111
|
+
const sddPhaseInputSchema = z.union([z.enum(sddPhases), z.literal('apply')]);
|
|
107
112
|
const jsonValueSchema = z.lazy(() => z.union([z.string(), z.number().finite(), z.boolean(), z.null(), z.array(jsonValueSchema), z.record(z.string(), jsonValueSchema)]));
|
|
108
113
|
export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
109
114
|
vgxness_sdd_status: z
|
|
@@ -116,7 +121,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
116
121
|
.object({
|
|
117
122
|
project: z.string().min(1),
|
|
118
123
|
change: z.string().min(1),
|
|
119
|
-
phase:
|
|
124
|
+
phase: sddPhaseInputSchema,
|
|
120
125
|
runId: z.string().min(1).optional(),
|
|
121
126
|
agentId: z.string().min(1).optional(),
|
|
122
127
|
})
|
|
@@ -125,7 +130,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
125
130
|
.object({
|
|
126
131
|
project: z.string().min(1),
|
|
127
132
|
change: z.string().min(1),
|
|
128
|
-
phase:
|
|
133
|
+
phase: sddPhaseInputSchema,
|
|
129
134
|
runId: z.string().min(1).optional(),
|
|
130
135
|
agentId: z.string().min(1).optional(),
|
|
131
136
|
})
|
|
@@ -134,7 +139,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
134
139
|
.object({
|
|
135
140
|
project: z.string().min(1),
|
|
136
141
|
change: z.string().min(1),
|
|
137
|
-
phase:
|
|
142
|
+
phase: sddPhaseInputSchema,
|
|
138
143
|
content: z.string().min(1),
|
|
139
144
|
})
|
|
140
145
|
.passthrough(),
|
|
@@ -142,7 +147,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
142
147
|
.object({
|
|
143
148
|
project: z.string().min(1),
|
|
144
149
|
change: z.string().min(1),
|
|
145
|
-
phase:
|
|
150
|
+
phase: sddPhaseInputSchema,
|
|
146
151
|
acceptedBy: z.object({
|
|
147
152
|
type: z.literal('human'),
|
|
148
153
|
id: z.string().min(1),
|
|
@@ -158,7 +163,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
158
163
|
.object({
|
|
159
164
|
project: z.string().min(1),
|
|
160
165
|
change: z.string().min(1),
|
|
161
|
-
phase:
|
|
166
|
+
phase: sddPhaseInputSchema,
|
|
162
167
|
reopenedBy: z.object({
|
|
163
168
|
type: z.literal('human'),
|
|
164
169
|
id: z.string().min(1),
|
|
@@ -174,7 +179,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
174
179
|
.object({
|
|
175
180
|
project: z.string().min(1),
|
|
176
181
|
change: z.string().min(1),
|
|
177
|
-
phase:
|
|
182
|
+
phase: sddPhaseInputSchema,
|
|
178
183
|
payloadMode: z.enum(payloadModes).optional(),
|
|
179
184
|
})
|
|
180
185
|
.passthrough(),
|
|
@@ -197,6 +202,13 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
197
202
|
change: z.string().min(1),
|
|
198
203
|
})
|
|
199
204
|
.passthrough(),
|
|
205
|
+
vgxness_sdd_continue: z
|
|
206
|
+
.object({
|
|
207
|
+
project: z.string().min(1),
|
|
208
|
+
change: z.string().min(1),
|
|
209
|
+
payloadMode: z.enum(payloadModes).optional(),
|
|
210
|
+
})
|
|
211
|
+
.passthrough(),
|
|
200
212
|
vgxness_governance_report: z
|
|
201
213
|
.object({
|
|
202
214
|
project: z.string().min(1),
|
|
@@ -364,7 +376,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
364
376
|
workspaceRoot: z.string().min(1).optional(),
|
|
365
377
|
maxSourceBytes: z.number().int().positive().optional(),
|
|
366
378
|
change: z.string().min(1).optional(),
|
|
367
|
-
phase:
|
|
379
|
+
phase: sddPhaseInputSchema.optional(),
|
|
368
380
|
})
|
|
369
381
|
.passthrough(),
|
|
370
382
|
vgxness_run_list: z
|
|
@@ -431,6 +443,12 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
431
443
|
outcomeReason: z.string().min(1).optional(),
|
|
432
444
|
})
|
|
433
445
|
.passthrough(),
|
|
446
|
+
vgxness_run_resume_candidates: z
|
|
447
|
+
.object({
|
|
448
|
+
project: z.string().min(1),
|
|
449
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
450
|
+
})
|
|
451
|
+
.passthrough(),
|
|
434
452
|
vgxness_run_resume_inspect: z
|
|
435
453
|
.object({
|
|
436
454
|
runId: z.string().min(1),
|
package/dist/mcp/stdio-server.js
CHANGED
|
@@ -57,6 +57,8 @@ function descriptionForTool(publicToolName) {
|
|
|
57
57
|
return 'Read-only OpenCode handoff preview; returns context for manual continuation only, does not execute/control OpenCode, write .opencode/provider config, or create runs, checkpoints, events, sessions, or skill-usage records.';
|
|
58
58
|
if (publicToolName === 'verification_plan')
|
|
59
59
|
return 'Read-only verification plan recommendations only; does not execute commands, write provider config, persist results, create checkpoints, or infer SDD acceptance.';
|
|
60
|
+
if (publicToolName === 'run_resume_candidates')
|
|
61
|
+
return 'Read-only interrupted run resume candidate discovery by project; lists bounded failed, blocked, and needs-human runs without mutation, retry admission, provider execution, artifact/config/openspec writes, sandboxes, worktrees, or sessions.';
|
|
60
62
|
if (publicToolName === 'run_resume_inspect')
|
|
61
63
|
return 'Read-only run resume advisory inspect; plan-only and does not execute resume logic, invoke providers, write provider config, mutate retry/abandon/attempt state, or reconstruct sandboxes, worktrees, sessions, or transcripts.';
|
|
62
64
|
if (publicToolName === 'run_resume_gate')
|
|
@@ -65,6 +67,8 @@ function descriptionForTool(publicToolName) {
|
|
|
65
67
|
return 'Read-only context cockpit for start/resume/recovery; returns latest restorable session plus bounded memory previews without traces, provider config writes, repository writes, runs, artifacts, or session mutations.';
|
|
66
68
|
if (publicToolName === 'sdd_cockpit')
|
|
67
69
|
return 'Read-only SDD cockpit summary with next decision, explicit acceptance state, metadata-only artifact summaries, and aggregate blockers.';
|
|
70
|
+
if (publicToolName === 'sdd_continue')
|
|
71
|
+
return 'Read-only SDD continuation planner; returns blocker actions, safe suggested commands, related interrupted run context, and safety notes without provider execution, run creation, artifact mutation, provider config writes, or openspec writes.';
|
|
68
72
|
const toolName = toInternalVgxMcpToolName(publicToolName);
|
|
69
73
|
return `VGX control-plane tool ${toolName}`;
|
|
70
74
|
}
|
package/dist/mcp/validation.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isRiskyPermissionCategory, permissionCategories } from '../permissions/schema.js';
|
|
2
2
|
import { parseOperationRetryPolicy } from '../runs/operation-retry.js';
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeSddPhaseInput, sddPhases } from '../sdd/schema.js';
|
|
4
4
|
import { workflowIds } from '../workflows/schema.js';
|
|
5
5
|
import { supportedTypeMessage, verificationChangeTypes } from '../verification/index.js';
|
|
6
6
|
import { errorEnvelope, isVgxMcpToolName, } from './schema.js';
|
|
@@ -52,6 +52,8 @@ export function validateVgxMcpToolCall(call) {
|
|
|
52
52
|
return validationSuccess(tool.value, validateSddNextInput(input, tool.value));
|
|
53
53
|
case 'vgxness_sdd_cockpit':
|
|
54
54
|
return validationSuccess(tool.value, validateSddCockpitInput(input, tool.value));
|
|
55
|
+
case 'vgxness_sdd_continue':
|
|
56
|
+
return validationSuccess(tool.value, validateSddContinueInput(input, tool.value));
|
|
55
57
|
case 'vgxness_governance_report':
|
|
56
58
|
return validationSuccess(tool.value, validateGovernanceReportInput(input, tool.value));
|
|
57
59
|
case 'vgxness_memory_save':
|
|
@@ -98,6 +100,8 @@ export function validateVgxMcpToolCall(call) {
|
|
|
98
100
|
return validationSuccess(tool.value, validateRunCheckpointInput(input, tool.value));
|
|
99
101
|
case 'vgxness_run_finalize':
|
|
100
102
|
return validationSuccess(tool.value, validateRunFinalizeInput(input, tool.value));
|
|
103
|
+
case 'vgxness_run_resume_candidates':
|
|
104
|
+
return validationSuccess(tool.value, validateRunResumeCandidatesInput(input, tool.value));
|
|
101
105
|
case 'vgxness_run_resume_inspect':
|
|
102
106
|
return validationSuccess(tool.value, validateRunResumeInspectInput(input, tool.value));
|
|
103
107
|
case 'vgxness_run_resume_gate':
|
|
@@ -240,6 +244,21 @@ function validateSddCockpitInput(input, tool) {
|
|
|
240
244
|
return record;
|
|
241
245
|
return readProjectAndChange(record.value, tool);
|
|
242
246
|
}
|
|
247
|
+
function validateSddContinueInput(input, tool) {
|
|
248
|
+
const record = inputRecord(input, tool, ['project', 'change', 'payloadMode']);
|
|
249
|
+
if (!record.ok)
|
|
250
|
+
return record;
|
|
251
|
+
const base = readProjectAndChange(record.value, tool);
|
|
252
|
+
if (!base.ok)
|
|
253
|
+
return base;
|
|
254
|
+
const payloadMode = readOptionalOneOf(record.value, 'payloadMode', payloadModes, tool);
|
|
255
|
+
if (!payloadMode.ok)
|
|
256
|
+
return payloadMode;
|
|
257
|
+
const result = { ...base.value };
|
|
258
|
+
if (payloadMode.value !== undefined)
|
|
259
|
+
result.payloadMode = payloadMode.value;
|
|
260
|
+
return { ok: true, value: result };
|
|
261
|
+
}
|
|
243
262
|
function validateContextCockpitInput(input, tool) {
|
|
244
263
|
const record = inputRecord(input, tool, ['project', 'change', 'directory', 'limit', 'level']);
|
|
245
264
|
if (!record.ok)
|
|
@@ -971,6 +990,22 @@ function validateRunFinalizeInput(input, tool) {
|
|
|
971
990
|
return copied;
|
|
972
991
|
return { ok: true, value: result };
|
|
973
992
|
}
|
|
993
|
+
function validateRunResumeCandidatesInput(input, tool) {
|
|
994
|
+
const record = inputRecord(input, tool, ['project', 'limit']);
|
|
995
|
+
if (!record.ok)
|
|
996
|
+
return record;
|
|
997
|
+
const project = readNonEmptyString(record.value, 'project', tool);
|
|
998
|
+
if (!project.ok)
|
|
999
|
+
return project;
|
|
1000
|
+
const result = { project: project.value };
|
|
1001
|
+
if (record.value.limit !== undefined) {
|
|
1002
|
+
const limit = readBoundedLimit(record.value, tool);
|
|
1003
|
+
if (!limit.ok)
|
|
1004
|
+
return limit;
|
|
1005
|
+
result.limit = limit.value;
|
|
1006
|
+
}
|
|
1007
|
+
return { ok: true, value: result };
|
|
1008
|
+
}
|
|
974
1009
|
function validateRunResumeInspectInput(input, tool) {
|
|
975
1010
|
const record = inputRecord(input, tool, ['runId']);
|
|
976
1011
|
if (!record.ok)
|
|
@@ -1053,9 +1088,10 @@ function readPhase(record, tool) {
|
|
|
1053
1088
|
const phase = readNonEmptyString(record, 'phase', tool);
|
|
1054
1089
|
if (!phase.ok)
|
|
1055
1090
|
return phase;
|
|
1056
|
-
|
|
1091
|
+
const normalized = normalizeSddPhaseInput(phase.value);
|
|
1092
|
+
if (normalized === undefined)
|
|
1057
1093
|
return validationFailure(`phase must be one of: ${sddPhases.join(', ')}`, tool);
|
|
1058
|
-
return { ok: true, value:
|
|
1094
|
+
return { ok: true, value: normalized };
|
|
1059
1095
|
}
|
|
1060
1096
|
function copyOptionalStrings(target, record, tool, fields) {
|
|
1061
1097
|
for (const field of fields) {
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { basename, resolve } from 'node:path';
|
|
2
|
+
const candidateLimit = 5;
|
|
3
|
+
const baseSafety = [
|
|
4
|
+
'Read-only resume cockpit: does not execute providers, retry operations, edit files, write provider config, create sandboxes, or create worktrees.',
|
|
5
|
+
'This command only helps inspect interrupted work; any continuation remains an explicit human decision.',
|
|
6
|
+
];
|
|
7
|
+
export function buildProductResume(input) {
|
|
8
|
+
const initialProject = resolveProject(input);
|
|
9
|
+
if (input.runId === undefined) {
|
|
10
|
+
return buildOrientationResume(input, initialProject);
|
|
11
|
+
}
|
|
12
|
+
if (input.databaseError !== undefined) {
|
|
13
|
+
return blockedResume({
|
|
14
|
+
project: initialProject,
|
|
15
|
+
runId: input.runId,
|
|
16
|
+
blocker: `Unable to read local memory store${input.databasePath === undefined ? '' : ` at ${input.databasePath}`}: ${input.databaseError}`,
|
|
17
|
+
why: 'Run inspection requires a readable local memory store.',
|
|
18
|
+
command: `vgxness resume --project ${initialProject.value} --run-id ${input.runId} --db <path>`,
|
|
19
|
+
safety: baseSafety,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
if (input.runs?.getRunOperatorResumePlan === undefined) {
|
|
23
|
+
return blockedResume({
|
|
24
|
+
project: initialProject,
|
|
25
|
+
runId: input.runId,
|
|
26
|
+
blocker: 'Run resume inspection service is not available for this request.',
|
|
27
|
+
why: 'Use the installed CLI or provide a readable local memory store.',
|
|
28
|
+
command: `vgxness resume --project ${initialProject.value} --run-id ${input.runId}`,
|
|
29
|
+
safety: baseSafety,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const inspected = input.runs.getRunOperatorResumePlan(input.runId);
|
|
33
|
+
if (!inspected.ok) {
|
|
34
|
+
return blockedResume({
|
|
35
|
+
project: initialProject,
|
|
36
|
+
runId: input.runId,
|
|
37
|
+
blocker: inspected.error.message,
|
|
38
|
+
why: 'VGXNESS could not find or inspect the requested run.',
|
|
39
|
+
command: `vgxness runs list --project ${initialProject.value}`,
|
|
40
|
+
safety: baseSafety,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return fromRunOperatorResumePlan(inspected.value, initialProject, input.databasePath !== undefined);
|
|
44
|
+
}
|
|
45
|
+
function buildOrientationResume(input, project) {
|
|
46
|
+
const dbHint = input.databasePath === undefined ? '' : ` --db ${input.databasePath}`;
|
|
47
|
+
const command = `vgxness runs list --project ${project.value} --status failed${dbHint}`;
|
|
48
|
+
const manualNextCommands = [
|
|
49
|
+
`vgxness runs list --project ${project.value} --status failed${dbHint}`,
|
|
50
|
+
`vgxness runs list --project ${project.value} --status blocked${dbHint}`,
|
|
51
|
+
`vgxness runs list --project ${project.value} --status needs-human${dbHint}`,
|
|
52
|
+
`vgxness resume --project ${project.value} --run-id <id>${dbHint}`,
|
|
53
|
+
];
|
|
54
|
+
if (input.databaseError !== undefined) {
|
|
55
|
+
return {
|
|
56
|
+
version: 1,
|
|
57
|
+
kind: 'product-resume',
|
|
58
|
+
mode: 'orientation',
|
|
59
|
+
project,
|
|
60
|
+
resumable: false,
|
|
61
|
+
resume: ['Find an interrupted run, then inspect it before deciding whether to continue manually.'],
|
|
62
|
+
why: ['No --run-id was provided, so VGXNESS cannot inspect checkpoint, approval, or blocker state for a selected run yet.'],
|
|
63
|
+
blockers: [`Unable to read local memory store${input.databasePath === undefined ? '' : ` at ${input.databasePath}`}: ${input.databaseError}`],
|
|
64
|
+
command,
|
|
65
|
+
manualNextCommands,
|
|
66
|
+
safety: baseSafety,
|
|
67
|
+
candidateRuns: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const listed = input.runs?.listRecentInterruptedRuns?.({ project: project.value, limit: candidateLimit });
|
|
71
|
+
if (listed !== undefined && !listed.ok) {
|
|
72
|
+
return {
|
|
73
|
+
version: 1,
|
|
74
|
+
kind: 'product-resume',
|
|
75
|
+
mode: 'orientation',
|
|
76
|
+
project,
|
|
77
|
+
resumable: false,
|
|
78
|
+
resume: ['Find an interrupted run, then inspect it before deciding whether to continue manually.'],
|
|
79
|
+
why: ['No --run-id was provided, so VGXNESS cannot inspect checkpoint, approval, or blocker state for a selected run yet.'],
|
|
80
|
+
blockers: [listed.error.message],
|
|
81
|
+
command,
|
|
82
|
+
manualNextCommands,
|
|
83
|
+
safety: baseSafety,
|
|
84
|
+
candidateRuns: [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const candidateRuns = (listed?.value ?? []).map((candidate) => ({
|
|
88
|
+
...candidate,
|
|
89
|
+
...(candidate.userIntent === undefined ? {} : { userIntent: shorten(candidate.userIntent, 72) }),
|
|
90
|
+
command: `vgxness resume --project ${project.value} --run-id ${candidate.runId}${dbHint}`,
|
|
91
|
+
}));
|
|
92
|
+
const why = listed === undefined
|
|
93
|
+
? ['No --run-id was provided, so VGXNESS cannot inspect checkpoint, approval, or blocker state for a selected run yet.']
|
|
94
|
+
: ['No --run-id was provided, so VGXNESS is showing recent failed, blocked, or needs-human runs for this project.'];
|
|
95
|
+
return {
|
|
96
|
+
version: 1,
|
|
97
|
+
kind: 'product-resume',
|
|
98
|
+
mode: 'orientation',
|
|
99
|
+
project,
|
|
100
|
+
resumable: false,
|
|
101
|
+
resume: candidateRuns.length === 0
|
|
102
|
+
? ['Find an interrupted run, then inspect it before deciding whether to continue manually.']
|
|
103
|
+
: ['Recent interrupted runs were found. Inspect one before deciding whether to continue manually.'],
|
|
104
|
+
why,
|
|
105
|
+
blockers: [],
|
|
106
|
+
command,
|
|
107
|
+
manualNextCommands,
|
|
108
|
+
safety: [listed === undefined ? 'Did not open the local memory store because no project-specific run lookup was requested.' : 'Opened the local memory store read-only to list recent interrupted runs.', ...baseSafety],
|
|
109
|
+
candidateRuns,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function resolveProject(input) {
|
|
113
|
+
if (input.project !== undefined && input.project.trim().length > 0)
|
|
114
|
+
return { value: input.project.trim(), source: 'flag' };
|
|
115
|
+
const directoryName = basename(resolve(input.cwd));
|
|
116
|
+
return { value: directoryName.length > 0 ? directoryName : 'unknown-project', source: 'cwd' };
|
|
117
|
+
}
|
|
118
|
+
function fromRunOperatorResumePlan(inspect, fallbackProject, includeDbHint) {
|
|
119
|
+
const project = { value: inspect.run.project || fallbackProject.value, source: 'run' };
|
|
120
|
+
const blockerLines = inspect.blockers.map((blocker) => (blocker.relatedId === undefined ? blocker.message : `${blocker.message} (${blocker.relatedId})`));
|
|
121
|
+
const latestCheckpoint = inspect.latestCheckpoint === null ? 'none' : `${inspect.latestCheckpoint.label} (${inspect.latestCheckpoint.id})`;
|
|
122
|
+
const dbHint = includeDbHint ? ' --db <path>' : '';
|
|
123
|
+
return {
|
|
124
|
+
version: 1,
|
|
125
|
+
kind: 'product-resume',
|
|
126
|
+
mode: 'inspect',
|
|
127
|
+
project,
|
|
128
|
+
runId: inspect.run.id,
|
|
129
|
+
resumable: inspect.resumePlan.resumable,
|
|
130
|
+
resume: [inspect.resumePlan.recommendedAction],
|
|
131
|
+
why: [
|
|
132
|
+
`Run: ${inspect.run.id}`,
|
|
133
|
+
`Status: ${inspect.run.status}`,
|
|
134
|
+
`Workflow: ${inspect.run.workflow} / ${inspect.run.phase}`,
|
|
135
|
+
`Latest checkpoint: ${latestCheckpoint}`,
|
|
136
|
+
inspect.debug.summary,
|
|
137
|
+
],
|
|
138
|
+
blockers: blockerLines,
|
|
139
|
+
command: `vgxness resume --project ${project.value} --run-id ${inspect.run.id}${dbHint}`,
|
|
140
|
+
manualNextCommands: inspect.manualNextCommands,
|
|
141
|
+
safety: baseSafety,
|
|
142
|
+
candidateRuns: [],
|
|
143
|
+
inspect,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function blockedResume(input) {
|
|
147
|
+
return {
|
|
148
|
+
version: 1,
|
|
149
|
+
kind: 'product-resume',
|
|
150
|
+
mode: 'inspect',
|
|
151
|
+
project: input.project,
|
|
152
|
+
runId: input.runId,
|
|
153
|
+
resumable: false,
|
|
154
|
+
resume: ['Resolve the blocker, then inspect the run again before deciding whether to continue manually.'],
|
|
155
|
+
why: [`Run: ${input.runId}`, input.why],
|
|
156
|
+
blockers: [input.blocker],
|
|
157
|
+
command: input.command,
|
|
158
|
+
manualNextCommands: [input.command],
|
|
159
|
+
safety: input.safety,
|
|
160
|
+
candidateRuns: [],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function shorten(value, maxLength) {
|
|
164
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
165
|
+
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3)}...`;
|
|
166
|
+
}
|
|
@@ -70,13 +70,24 @@ export class RunRepository {
|
|
|
70
70
|
where.push('status=@status');
|
|
71
71
|
parameters.status = filters.status;
|
|
72
72
|
}
|
|
73
|
+
if (filters.statuses !== undefined && filters.statuses.length > 0) {
|
|
74
|
+
const statusPlaceholders = filters.statuses.map((_, index) => `@status${index}`);
|
|
75
|
+
where.push(`status IN (${statusPlaceholders.join(', ')})`);
|
|
76
|
+
for (const [index, status] of filters.statuses.entries())
|
|
77
|
+
parameters[`status${index}`] = status;
|
|
78
|
+
}
|
|
79
|
+
const limitClause = filters.limit === undefined ? '' : 'LIMIT @limit';
|
|
80
|
+
const queryParameters = { ...parameters };
|
|
81
|
+
if (filters.limit !== undefined)
|
|
82
|
+
queryParameters.limit = filters.limit;
|
|
73
83
|
const rows = this.db.connection
|
|
74
84
|
.prepare(`
|
|
75
85
|
SELECT * FROM runs
|
|
76
86
|
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
|
77
87
|
ORDER BY created_at DESC
|
|
88
|
+
${limitClause}
|
|
78
89
|
`)
|
|
79
|
-
.all(
|
|
90
|
+
.all(queryParameters);
|
|
80
91
|
return ok(rows.map(mapRun));
|
|
81
92
|
}
|
|
82
93
|
catch (cause) {
|