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.
Files changed (46) hide show
  1. package/README.md +12 -6
  2. package/dist/agents/agent-resolver.js +33 -3
  3. package/dist/agents/canonical-agent-manifest.js +68 -21
  4. package/dist/agents/canonical-agent-projection.js +46 -4
  5. package/dist/cli/cli-help.js +14 -3
  6. package/dist/cli/commands/index.js +1 -0
  7. package/dist/cli/commands/interactive-entrypoint-dispatcher.js +8 -0
  8. package/dist/cli/commands/mcp-dispatcher.js +49 -18
  9. package/dist/cli/commands/memory-sdd-dispatcher.js +71 -5
  10. package/dist/cli/commands/setup-dispatcher.js +22 -10
  11. package/dist/cli/commands/status-dispatcher.js +130 -0
  12. package/dist/cli/commands/workflow-dispatcher.js +11 -5
  13. package/dist/cli/dispatcher.js +9 -1
  14. package/dist/cli/product-resume-renderer.js +32 -0
  15. package/dist/cli/product-status-renderer.js +81 -0
  16. package/dist/cli/sdd-renderer.js +90 -7
  17. package/dist/cli/tui/main-menu/main-menu-read-model.js +8 -8
  18. package/dist/cli/tui/setup/setup-tui-services.js +27 -10
  19. package/dist/code/cli/code-command.js +7 -4
  20. package/dist/code/reporting/summary.js +4 -1
  21. package/dist/code/runtime/code-runtime.js +27 -4
  22. package/dist/code/runtime/sdd-context.js +18 -2
  23. package/dist/governance/governance-report-builder.js +18 -7
  24. package/dist/mcp/claude-code-agent-config.js +10 -4
  25. package/dist/mcp/client-install-opencode-contract.js +2 -2
  26. package/dist/mcp/client-install-opencode.js +10 -6
  27. package/dist/mcp/control-plane.js +56 -0
  28. package/dist/mcp/opencode-default-agent-config.js +7 -4
  29. package/dist/mcp/provider-status.js +86 -81
  30. package/dist/mcp/schema.js +25 -7
  31. package/dist/mcp/stdio-server.js +4 -0
  32. package/dist/mcp/validation.js +39 -3
  33. package/dist/resume/product-resume.js +166 -0
  34. package/dist/runs/repositories/runs.js +12 -1
  35. package/dist/runs/run-service.js +62 -5
  36. package/dist/sdd/schema.js +8 -0
  37. package/dist/sdd/sdd-continuation-plan.js +81 -0
  38. package/dist/sdd/sdd-workflow-service.js +103 -16
  39. package/dist/skills/skill-resolver.js +21 -4
  40. package/dist/status/product-status.js +121 -0
  41. package/docs/architecture.md +1 -1
  42. package/docs/cli.md +40 -12
  43. package/docs/code-runtime.md +3 -0
  44. package/docs/glossary.md +1 -1
  45. package/docs/mcp.md +18 -4
  46. 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
- version: 1,
59
- kind: 'provider-status',
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
- version: 1,
122
- kind: 'provider-status',
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
- version: 1,
169
- kind: 'provider-status',
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
  }
@@ -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: z.enum(sddPhases),
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: z.enum(sddPhases),
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: z.enum(sddPhases),
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: z.enum(sddPhases),
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: z.enum(sddPhases),
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: z.enum(sddPhases),
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: z.enum(sddPhases).optional(),
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),
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  import { isRiskyPermissionCategory, permissionCategories } from '../permissions/schema.js';
2
2
  import { parseOperationRetryPolicy } from '../runs/operation-retry.js';
3
- import { isSddPhase, sddPhases } from '../sdd/schema.js';
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
- if (!isSddPhase(phase.value))
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: phase.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(parameters);
90
+ .all(queryParameters);
80
91
  return ok(rows.map(mapRun));
81
92
  }
82
93
  catch (cause) {