vgxness 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +110 -0
- package/dist/agents/agent-activation-service.js +144 -0
- package/dist/agents/agent-registry-service.js +46 -0
- package/dist/agents/agent-resolver.js +249 -0
- package/dist/agents/agent-seed-service.js +146 -0
- package/dist/agents/manager-profile-overlay-service.js +34 -0
- package/dist/agents/profile-model-routing.js +26 -0
- package/dist/agents/renderers/claude-renderer.js +98 -0
- package/dist/agents/renderers/index.js +16 -0
- package/dist/agents/renderers/json-renderer.js +87 -0
- package/dist/agents/renderers/opencode-renderer.js +100 -0
- package/dist/agents/renderers/provider-adapter.js +6 -0
- package/dist/agents/repositories/agents.js +185 -0
- package/dist/agents/repositories/manager-profile-overlays.js +81 -0
- package/dist/agents/schema.js +1 -0
- package/dist/cli/dashboard-operational-read-models.js +153 -0
- package/dist/cli/dashboard-renderer.js +109 -0
- package/dist/cli/dashboard-screen-renderers.js +332 -0
- package/dist/cli/dashboard-tui-read-model.js +71 -0
- package/dist/cli/dashboard-tui-state.js +218 -0
- package/dist/cli/dispatcher.js +2880 -0
- package/dist/cli/index.js +27 -0
- package/dist/cli/interactive-dashboard.js +29 -0
- package/dist/cli/mcp-start-path.js +21 -0
- package/dist/cli/setup-status-renderer.js +29 -0
- package/dist/cli/setup-wizard-read-model.js +56 -0
- package/dist/cli/setup-wizard-renderer.js +148 -0
- package/dist/cli/setup-wizard-state.js +82 -0
- package/dist/cli/tui-render-helpers.js +192 -0
- package/dist/export/redaction.js +71 -0
- package/dist/harness/tools/agents.js +245 -0
- package/dist/harness/tools/memory.js +29 -0
- package/dist/mcp/client-install-opencode-contract.js +227 -0
- package/dist/mcp/client-install-opencode.js +194 -0
- package/dist/mcp/client-setup-preview.js +38 -0
- package/dist/mcp/control-plane.js +175 -0
- package/dist/mcp/doctor.js +193 -0
- package/dist/mcp/index.js +10 -0
- package/dist/mcp/opencode-default-agent-config.js +156 -0
- package/dist/mcp/opencode-visibility.js +102 -0
- package/dist/mcp/schema.js +234 -0
- package/dist/mcp/stdio-server.js +56 -0
- package/dist/mcp/validation.js +761 -0
- package/dist/memory/import/dry-run-planner.js +58 -0
- package/dist/memory/import/index.js +3 -0
- package/dist/memory/import/observation-writer.js +220 -0
- package/dist/memory/import/package.js +178 -0
- package/dist/memory/memory-service.js +126 -0
- package/dist/memory/repositories/artifacts.js +41 -0
- package/dist/memory/repositories/observations.js +133 -0
- package/dist/memory/repositories/sessions.js +105 -0
- package/dist/memory/repositories/traces.js +58 -0
- package/dist/memory/schema.js +1 -0
- package/dist/memory/search.js +11 -0
- package/dist/memory/sqlite/database.js +97 -0
- package/dist/memory/sqlite/migrations/001_initial.sql +128 -0
- package/dist/memory/sqlite/migrations/002_observation_revisions.sql +14 -0
- package/dist/memory/sqlite/migrations/003_agent_registry.sql +26 -0
- package/dist/memory/sqlite/migrations/004_run_runtime.sql +62 -0
- package/dist/memory/sqlite/migrations/005_run_approvals.sql +20 -0
- package/dist/memory/sqlite/migrations/006_run_operation_attempts.sql +32 -0
- package/dist/memory/sqlite/migrations/007_abandoned_operation_attempts.sql +46 -0
- package/dist/memory/sqlite/migrations/008_run_execution_plan_events.sql +105 -0
- package/dist/memory/sqlite/migrations/009_multiple_operation_attempts.sql +73 -0
- package/dist/memory/sqlite/migrations/010_skill_registry.sql +66 -0
- package/dist/memory/sqlite/migrations/011_skill_usage_resolution_outcomes.sql +21 -0
- package/dist/memory/sqlite/migrations/012_skill_improvement_proposals.sql +37 -0
- package/dist/memory/sqlite/migrations/013_skill_evaluation_scenarios.sql +43 -0
- package/dist/memory/sqlite/migrations/014_manager_profile_overlays.sql +14 -0
- package/dist/memory/storage-paths.js +72 -0
- package/dist/orchestrator/natural-language-planner.js +191 -0
- package/dist/orchestrator/schema.js +1 -0
- package/dist/permissions/index.js +2 -0
- package/dist/permissions/policy-evaluator.js +109 -0
- package/dist/permissions/schema.js +1 -0
- package/dist/providers/opencode/injection-preview.js +134 -0
- package/dist/providers/opencode/manager-payload.js +129 -0
- package/dist/runs/execution-planning.js +117 -0
- package/dist/runs/operation-execution.js +1 -0
- package/dist/runs/operation-retry.js +124 -0
- package/dist/runs/repositories/runs.js +611 -0
- package/dist/runs/run-insights.js +145 -0
- package/dist/runs/run-service.js +713 -0
- package/dist/runs/run-snapshot-export-service.js +31 -0
- package/dist/runs/sandbox-process-execution.js +218 -0
- package/dist/runs/sandbox-worktree-planning.js +59 -0
- package/dist/runs/schema.js +1 -0
- package/dist/sdd/artifact-portability-service.js +118 -0
- package/dist/sdd/schema.js +17 -0
- package/dist/sdd/sdd-workflow-service.js +217 -0
- package/dist/setup/backup-rollback-service.js +76 -0
- package/dist/setup/index.js +3 -0
- package/dist/setup/providers/antigravity-setup-adapter.js +18 -0
- package/dist/setup/providers/claude-setup-adapter.js +30 -0
- package/dist/setup/providers/custom-setup-adapter.js +18 -0
- package/dist/setup/providers/index.js +6 -0
- package/dist/setup/providers/opencode-setup-adapter.js +104 -0
- package/dist/setup/providers/provider-setup-adapter.js +15 -0
- package/dist/setup/providers/provider-setup-registry.js +11 -0
- package/dist/setup/schema.js +1 -0
- package/dist/setup/setup-defaults.js +11 -0
- package/dist/setup/setup-lifecycle-service.js +175 -0
- package/dist/setup/setup-plan.js +105 -0
- package/dist/skills/repositories/skill-evaluation-scenarios.js +289 -0
- package/dist/skills/repositories/skill-improvement-proposals.js +288 -0
- package/dist/skills/repositories/skills.js +430 -0
- package/dist/skills/schema.js +1 -0
- package/dist/skills/skill-payload.js +94 -0
- package/dist/skills/skill-registry-service.js +92 -0
- package/dist/skills/skill-resolver.js +191 -0
- package/dist/workflows/command-allowlist-adapter.js +70 -0
- package/dist/workflows/schema.js +4 -0
- package/dist/workflows/workflow-executor.js +345 -0
- package/dist/workflows/workflow-registry.js +66 -0
- package/docs/architecture.md +698 -0
- package/docs/cli.md +741 -0
- package/docs/funcionamiento-del-sistema.md +868 -0
- package/docs/harness-gap-analysis.md +229 -0
- package/docs/prd.md +372 -0
- package/package.json +57 -0
|
@@ -0,0 +1,2880 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { AgentRegistryService } from '../agents/agent-registry-service.js';
|
|
4
|
+
import { AgentSeedService } from '../agents/agent-seed-service.js';
|
|
5
|
+
import { ManagerProfileOverlayService } from '../agents/manager-profile-overlay-service.js';
|
|
6
|
+
import { resolveAgentProfileModel } from '../agents/profile-model-routing.js';
|
|
7
|
+
import { ManagerProfileOverlayRepository } from '../agents/repositories/manager-profile-overlays.js';
|
|
8
|
+
import { getProviderRenderer } from '../agents/renderers/index.js';
|
|
9
|
+
import { renderDashboard as renderStatusDashboard } from './dashboard-renderer.js';
|
|
10
|
+
import { buildDashboardApprovalsReadModel, buildDashboardRunsReadModel, buildDashboardWorkflowsReadModel } from './dashboard-operational-read-models.js';
|
|
11
|
+
import { dashboardKeyFromInput, loadInitialDashboardState, reduceDashboardKey, refreshDashboard, renderDashboard as renderInteractiveDashboard, resolveDashboardRenderStyle } from './interactive-dashboard.js';
|
|
12
|
+
import { sanitizeDashboardError } from './dashboard-tui-read-model.js';
|
|
13
|
+
import { createAgentRegistryToolHandlers } from '../harness/tools/agents.js';
|
|
14
|
+
import { createMemoryToolHandlers } from '../harness/tools/memory.js';
|
|
15
|
+
import { MemoryService } from '../memory/memory-service.js';
|
|
16
|
+
import { planMemoryImportDryRun, validateMemoryImportPackage, writeMemoryImportObservations } from '../memory/import/index.js';
|
|
17
|
+
import { openMemoryDatabase } from '../memory/sqlite/database.js';
|
|
18
|
+
import { prepareMemoryDatabasePath, resolveMemoryDatabasePath } from '../memory/storage-paths.js';
|
|
19
|
+
import { planOpenCodeMcpInstall } from '../mcp/client-install-opencode-contract.js';
|
|
20
|
+
import { installOpenCodeMcpClient } from '../mcp/client-install-opencode.js';
|
|
21
|
+
import { createMcpClientSetupPreview, isMcpClientSetupProvider } from '../mcp/client-setup-preview.js';
|
|
22
|
+
import { createMcpDoctorReport } from '../mcp/doctor.js';
|
|
23
|
+
import { createOpenCodeMcpVisibilityReport } from '../mcp/opencode-visibility.js';
|
|
24
|
+
import { createNaturalLanguagePlan } from '../orchestrator/natural-language-planner.js';
|
|
25
|
+
import { evaluatePermission, permissionCategories } from '../permissions/policy-evaluator.js';
|
|
26
|
+
import { OpenCodeInjectionPreviewService } from '../providers/opencode/injection-preview.js';
|
|
27
|
+
import { parseOperationRetryPolicy } from '../runs/operation-retry.js';
|
|
28
|
+
import { RunSnapshotExportService } from '../runs/run-snapshot-export-service.js';
|
|
29
|
+
import { RunService } from '../runs/run-service.js';
|
|
30
|
+
import { ArtifactPortabilityService } from '../sdd/artifact-portability-service.js';
|
|
31
|
+
import { isSddPhase, sddPhases } from '../sdd/schema.js';
|
|
32
|
+
import { SddWorkflowService } from '../sdd/sdd-workflow-service.js';
|
|
33
|
+
import { SetupLifecycleService } from '../setup/setup-lifecycle-service.js';
|
|
34
|
+
import { rollbackConfigBackup } from '../setup/backup-rollback-service.js';
|
|
35
|
+
import { createSetupPlan } from '../setup/setup-plan.js';
|
|
36
|
+
import { vgxnessSetupDefaults } from '../setup/setup-defaults.js';
|
|
37
|
+
import { SkillRegistryService } from '../skills/skill-registry-service.js';
|
|
38
|
+
import { findWorkflowDefinition, getWorkflowDefinition, listWorkflows } from '../workflows/workflow-registry.js';
|
|
39
|
+
import { CommandAllowlistAdapter, commandAllowlistIds } from '../workflows/command-allowlist-adapter.js';
|
|
40
|
+
import { GuardedProviderWorkflowExecutor, operationMetadataForWorkflowExecution, SafeNonDispatchingWorkflowExecutor, workflowExecutorSafety } from '../workflows/workflow-executor.js';
|
|
41
|
+
import { isWorkflowId } from '../workflows/schema.js';
|
|
42
|
+
const promptBuffers = new WeakMap();
|
|
43
|
+
export function dispatchCli(argv, environment) {
|
|
44
|
+
const parsed = parseArgs(argv);
|
|
45
|
+
const [area, command] = parsed.positionals;
|
|
46
|
+
if (!area || area === 'help' || area === '--help' || area === '-h')
|
|
47
|
+
return okText(visibleHelpText());
|
|
48
|
+
if (area === 'init')
|
|
49
|
+
return runSetupPlanCommand(parsed, environment);
|
|
50
|
+
if (area === 'doctor')
|
|
51
|
+
return usageFailure('doctor is available through the async CLI entrypoint; run vgxness doctor from the installed CLI.');
|
|
52
|
+
if (!command)
|
|
53
|
+
return usageFailure(`Missing command for ${area}`);
|
|
54
|
+
const commandValidation = validateCommand(area, command);
|
|
55
|
+
if (!commandValidation.ok)
|
|
56
|
+
return usageFailure(commandValidation.message);
|
|
57
|
+
if (area === 'setup')
|
|
58
|
+
return runSetupLifecycleCommand(parsed, environment);
|
|
59
|
+
if (isWorkflowId(area) && command === 'preview')
|
|
60
|
+
return runWorkflowPreviewCommand(area, parsed);
|
|
61
|
+
if (area === 'memory' && command === 'import')
|
|
62
|
+
return runMemoryImportCommand(parsed, environment);
|
|
63
|
+
const selectedDatabasePath = databasePathFor(parsed.flags, environment);
|
|
64
|
+
if (!selectedDatabasePath.ok)
|
|
65
|
+
return resultFailure(selectedDatabasePath);
|
|
66
|
+
const databasePath = selectedDatabasePath.value;
|
|
67
|
+
const opened = openCliDatabase(databasePath);
|
|
68
|
+
if (!opened.ok)
|
|
69
|
+
return resultFailure(opened);
|
|
70
|
+
try {
|
|
71
|
+
if (isWorkflowId(area) && command === 'run')
|
|
72
|
+
return runWorkflowRunCommand(area, parsed, opened.value);
|
|
73
|
+
if (isWorkflowId(area) && command === 'execute')
|
|
74
|
+
return runWorkflowExecuteCommand(area, parsed, opened.value, environment);
|
|
75
|
+
if (area === 'agents')
|
|
76
|
+
return runAgentCommand(command, parsed, opened.value, environment);
|
|
77
|
+
if (area === 'subagents')
|
|
78
|
+
return runSubagentCommand(command, parsed, opened.value, environment);
|
|
79
|
+
if (area === 'memory')
|
|
80
|
+
return runMemoryCommand(command, parsed, opened.value);
|
|
81
|
+
if (area === 'approvals')
|
|
82
|
+
return runApprovalsCommand(command, parsed, opened.value);
|
|
83
|
+
if (area === 'runs')
|
|
84
|
+
return runRunsCommand(command, parsed, opened.value, environment);
|
|
85
|
+
if (area === 'permissions')
|
|
86
|
+
return runPermissionsCommand(command, parsed, opened.value, environment);
|
|
87
|
+
if (area === 'skills')
|
|
88
|
+
return runSkillCommand(command, parsed, opened.value, environment);
|
|
89
|
+
if (area === 'sdd')
|
|
90
|
+
return runSddCommand(command, parsed, opened.value, environment);
|
|
91
|
+
if (area === 'orchestrator')
|
|
92
|
+
return runOrchestratorCommand(command, parsed, opened.value);
|
|
93
|
+
if (area === 'opencode')
|
|
94
|
+
return runOpenCodeCommand(command, parsed, opened.value, environment);
|
|
95
|
+
if (area === 'dashboard')
|
|
96
|
+
return runDashboardCommand(command, parsed, opened.value, environment, databasePath);
|
|
97
|
+
return usageFailure(`Unknown command area: ${area}`);
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
opened.value.close();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function runWorkflowPreviewCommand(workflow, parsed) {
|
|
104
|
+
const project = optionalStringFlag(parsed.flags, 'project') ?? 'vgxness';
|
|
105
|
+
const intent = optionalStringFlag(parsed.flags, 'intent') ?? parsed.positionals.slice(2).join(' ').trim();
|
|
106
|
+
const selected = getWorkflowDefinition(workflow);
|
|
107
|
+
return jsonResult({
|
|
108
|
+
ok: true,
|
|
109
|
+
value: {
|
|
110
|
+
workflow: selected,
|
|
111
|
+
registry: listWorkflows(),
|
|
112
|
+
...(intent.length > 0 ? { planner: createNaturalLanguagePlan({ project, intent }) } : {}),
|
|
113
|
+
safety: {
|
|
114
|
+
executed: false,
|
|
115
|
+
callsProvider: false,
|
|
116
|
+
editsFiles: false,
|
|
117
|
+
writesProviderConfig: false,
|
|
118
|
+
recordsRuns: false,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function runWorkflowRunCommand(workflow, parsed, database) {
|
|
124
|
+
const project = optionalStringFlag(parsed.flags, 'project') ?? 'vgxness';
|
|
125
|
+
const intent = requiredFlag(parsed.flags, 'intent');
|
|
126
|
+
if (!intent.ok)
|
|
127
|
+
return resultFailure(intent);
|
|
128
|
+
const selected = getWorkflowDefinition(workflow);
|
|
129
|
+
const planner = createNaturalLanguagePlan({ project, intent: intent.value });
|
|
130
|
+
const recommended = getWorkflowDefinition(planner.workflow);
|
|
131
|
+
const phase = optionalStringFlag(parsed.flags, 'phase') ?? selected.defaultPhase;
|
|
132
|
+
if (selected.id === 'sdd' && !isSddPhase(phase))
|
|
133
|
+
return resultFailure(validationFailure(`Unknown SDD phase: ${phase}. Expected one of: ${sddPhases.join(', ')}`));
|
|
134
|
+
const providerAdapter = optionalStringFlag(parsed.flags, 'provider-adapter') ?? 'opencode';
|
|
135
|
+
const model = optionalStringFlag(parsed.flags, 'model') ?? 'openai/gpt-5.5';
|
|
136
|
+
const registry = new AgentRegistryService(database);
|
|
137
|
+
const explicitAgentId = optionalStringFlag(parsed.flags, 'agent-id');
|
|
138
|
+
const selectedAgent = resolveWorkflowRunAgent({
|
|
139
|
+
registry,
|
|
140
|
+
project,
|
|
141
|
+
intent: intent.value,
|
|
142
|
+
workflow: selected.id,
|
|
143
|
+
phase,
|
|
144
|
+
providerAdapter,
|
|
145
|
+
...(explicitAgentId !== undefined ? { explicitAgentId } : {}),
|
|
146
|
+
});
|
|
147
|
+
if (!selectedAgent.ok)
|
|
148
|
+
return jsonResult({ ok: true, value: selectedAgent.value });
|
|
149
|
+
const service = new RunService(database);
|
|
150
|
+
const created = service.createRun({
|
|
151
|
+
project,
|
|
152
|
+
userIntent: intent.value,
|
|
153
|
+
workflow: selected.id,
|
|
154
|
+
phase,
|
|
155
|
+
selectedAgentId: selectedAgent.value.agent.id,
|
|
156
|
+
providerAdapter,
|
|
157
|
+
model,
|
|
158
|
+
});
|
|
159
|
+
if (!created.ok)
|
|
160
|
+
return resultFailure(created);
|
|
161
|
+
const executionSafety = workflowRunSafety(selected);
|
|
162
|
+
const recommendation = workflowRunRecommendation(selected, recommended, planner);
|
|
163
|
+
const checkpointState = toJsonValue({
|
|
164
|
+
kind: 'workflow-run-plan',
|
|
165
|
+
version: 1,
|
|
166
|
+
workflow: selected,
|
|
167
|
+
planner,
|
|
168
|
+
safety: executionSafety,
|
|
169
|
+
recommendation,
|
|
170
|
+
selectedAgent: selectedAgent.value.summary,
|
|
171
|
+
nextAction: workflowRunNextAction(selected, recommended, planner),
|
|
172
|
+
});
|
|
173
|
+
const checkpoint = service.appendCheckpoint({
|
|
174
|
+
runId: created.value.id,
|
|
175
|
+
label: 'workflow-run-planned',
|
|
176
|
+
state: checkpointState,
|
|
177
|
+
});
|
|
178
|
+
if (!checkpoint.ok)
|
|
179
|
+
return resultFailure(checkpoint);
|
|
180
|
+
return jsonResult({
|
|
181
|
+
ok: true,
|
|
182
|
+
value: {
|
|
183
|
+
ok: true,
|
|
184
|
+
runId: created.value.id,
|
|
185
|
+
workflow: selected,
|
|
186
|
+
phase,
|
|
187
|
+
selectedAgent: selectedAgent.value.summary,
|
|
188
|
+
planner: {
|
|
189
|
+
workflow: planner.workflow,
|
|
190
|
+
flow: planner.flow,
|
|
191
|
+
confidence: planner.confidence,
|
|
192
|
+
reason: planner.reason,
|
|
193
|
+
signals: planner.signals,
|
|
194
|
+
needsClarification: planner.needsClarification,
|
|
195
|
+
},
|
|
196
|
+
safety: executionSafety,
|
|
197
|
+
checkpointId: checkpoint.value.id,
|
|
198
|
+
recommendation,
|
|
199
|
+
nextAction: workflowRunNextAction(selected, recommended, planner),
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function runWorkflowExecuteCommand(workflow, parsed, database, environment) {
|
|
204
|
+
const runId = requiredFlag(parsed.flags, 'run-id');
|
|
205
|
+
if (!runId.ok)
|
|
206
|
+
return resultFailure(runId);
|
|
207
|
+
const service = new RunService(database);
|
|
208
|
+
const details = service.getRun(runId.value);
|
|
209
|
+
if (!details.ok)
|
|
210
|
+
return resultFailure(details);
|
|
211
|
+
const selected = getWorkflowDefinition(workflow);
|
|
212
|
+
if (details.value.workflow !== selected.id) {
|
|
213
|
+
const checkpoint = appendWorkflowExecutionCheckpoint(service, details.value.id, 'workflow-execution-blocked', {
|
|
214
|
+
kind: 'workflow-execution-blocked',
|
|
215
|
+
version: 1,
|
|
216
|
+
runId: details.value.id,
|
|
217
|
+
requestedWorkflow: selected.id,
|
|
218
|
+
runWorkflow: details.value.workflow,
|
|
219
|
+
status: 'blocked',
|
|
220
|
+
dispatched: false,
|
|
221
|
+
reason: 'workflow-mismatch',
|
|
222
|
+
message: `Run ${details.value.id} belongs to workflow ${details.value.workflow}, not ${selected.id}.`,
|
|
223
|
+
safety: workflowExecutionSafety(false),
|
|
224
|
+
});
|
|
225
|
+
if (!checkpoint.ok)
|
|
226
|
+
return resultFailure(checkpoint);
|
|
227
|
+
return jsonResult({ ok: true, value: workflowExecutionBlockedValue(details.value.id, selected.id, 'workflow-mismatch', 'Run workflow does not match the execute command.', checkpoint) });
|
|
228
|
+
}
|
|
229
|
+
const agent = new AgentRegistryService(database).getAgent(details.value.selectedAgentId);
|
|
230
|
+
if (!agent.ok) {
|
|
231
|
+
const checkpoint = appendWorkflowExecutionCheckpoint(service, details.value.id, 'workflow-execution-blocked', {
|
|
232
|
+
kind: 'workflow-execution-blocked',
|
|
233
|
+
version: 1,
|
|
234
|
+
runId: details.value.id,
|
|
235
|
+
workflow: selected.id,
|
|
236
|
+
status: 'blocked',
|
|
237
|
+
dispatched: false,
|
|
238
|
+
reason: 'selected-agent-unresolved',
|
|
239
|
+
message: agent.error.message,
|
|
240
|
+
selectedAgentId: details.value.selectedAgentId,
|
|
241
|
+
safety: workflowExecutionSafety(false),
|
|
242
|
+
});
|
|
243
|
+
if (!checkpoint.ok)
|
|
244
|
+
return resultFailure(checkpoint);
|
|
245
|
+
return jsonResult({ ok: true, value: workflowExecutionBlockedValue(details.value.id, selected.id, 'selected-agent-unresolved', agent.error.message, checkpoint) });
|
|
246
|
+
}
|
|
247
|
+
const agentGate = validateWorkflowExecutionAgent(agent.value, details.value, selected);
|
|
248
|
+
if (!agentGate.ok) {
|
|
249
|
+
const checkpoint = appendWorkflowExecutionCheckpoint(service, details.value.id, 'workflow-execution-blocked', {
|
|
250
|
+
kind: 'workflow-execution-blocked',
|
|
251
|
+
version: 1,
|
|
252
|
+
runId: details.value.id,
|
|
253
|
+
workflow: selected.id,
|
|
254
|
+
status: 'blocked',
|
|
255
|
+
dispatched: false,
|
|
256
|
+
reason: agentGate.reason,
|
|
257
|
+
message: agentGate.message,
|
|
258
|
+
selectedAgent: workflowRunAgentSummary(agent.value, { policy: 'run-selected-agent', reasons: ['run.selectedAgentId resolved to a real registry agent'] }),
|
|
259
|
+
safety: workflowExecutionSafety(false),
|
|
260
|
+
});
|
|
261
|
+
if (!checkpoint.ok)
|
|
262
|
+
return resultFailure(checkpoint);
|
|
263
|
+
return jsonResult({ ok: true, value: workflowExecutionBlockedValue(details.value.id, selected.id, agentGate.reason, agentGate.message, checkpoint) });
|
|
264
|
+
}
|
|
265
|
+
if (parsed.flags.confirm !== true) {
|
|
266
|
+
const checkpoint = appendWorkflowExecutionCheckpoint(service, details.value.id, 'workflow-execution-blocked', {
|
|
267
|
+
kind: 'workflow-execution-blocked',
|
|
268
|
+
version: 1,
|
|
269
|
+
runId: details.value.id,
|
|
270
|
+
workflow: selected.id,
|
|
271
|
+
status: 'blocked',
|
|
272
|
+
dispatched: false,
|
|
273
|
+
reason: 'missing-confirmation',
|
|
274
|
+
message: 'Explicit --confirm is required before delegated workflow execution can be requested.',
|
|
275
|
+
selectedAgent: workflowRunAgentSummary(agent.value, { policy: 'run-selected-agent', reasons: ['run.selectedAgentId resolved to a real registry agent'] }),
|
|
276
|
+
safety: workflowExecutionSafety(false),
|
|
277
|
+
nextAction: `Re-run: vgxness ${selected.id} execute --run-id ${details.value.id} --confirm`,
|
|
278
|
+
});
|
|
279
|
+
if (!checkpoint.ok)
|
|
280
|
+
return resultFailure(checkpoint);
|
|
281
|
+
return jsonResult({ ok: true, value: workflowExecutionBlockedValue(details.value.id, selected.id, 'missing-confirmation', 'Explicit --confirm is required.', checkpoint) });
|
|
282
|
+
}
|
|
283
|
+
const planner = createNaturalLanguagePlan({ project: details.value.project, intent: details.value.userIntent });
|
|
284
|
+
const recommended = getWorkflowDefinition(planner.workflow);
|
|
285
|
+
const recommendation = workflowRunRecommendation(selected, recommended, planner);
|
|
286
|
+
if (recommended.id === 'sdd' && selected.id !== 'sdd' && parsed.flags['override-escalation'] !== true) {
|
|
287
|
+
const checkpoint = appendWorkflowExecutionCheckpoint(service, details.value.id, 'workflow-execution-blocked', {
|
|
288
|
+
kind: 'workflow-execution-blocked',
|
|
289
|
+
version: 1,
|
|
290
|
+
runId: details.value.id,
|
|
291
|
+
workflow: selected.id,
|
|
292
|
+
status: 'blocked',
|
|
293
|
+
dispatched: false,
|
|
294
|
+
reason: 'escalation-recommended',
|
|
295
|
+
message: 'Planner recommends formal SDD; delegated lightweight execution is blocked unless --override-escalation is supplied.',
|
|
296
|
+
recommendation,
|
|
297
|
+
planner,
|
|
298
|
+
selectedAgent: workflowRunAgentSummary(agent.value, { policy: 'run-selected-agent', reasons: ['run.selectedAgentId resolved to a real registry agent'] }),
|
|
299
|
+
safety: workflowExecutionSafety(false),
|
|
300
|
+
nextAction: workflowRunNextAction(selected, recommended, planner),
|
|
301
|
+
});
|
|
302
|
+
if (!checkpoint.ok)
|
|
303
|
+
return resultFailure(checkpoint);
|
|
304
|
+
return jsonResult({ ok: true, value: workflowExecutionBlockedValue(details.value.id, selected.id, 'escalation-recommended', 'Planner recommends formal SDD; supply --override-escalation only after human review.', checkpoint, recommendation) });
|
|
305
|
+
}
|
|
306
|
+
const workspaceRoot = optionalStringFlag(parsed.flags, 'workspace') ?? environment.cwd;
|
|
307
|
+
const executorMode = optionalStringFlag(parsed.flags, 'executor') ?? 'safe-non-dispatching';
|
|
308
|
+
if (executorMode !== 'safe-non-dispatching' && executorMode !== 'provider' && executorMode !== 'command')
|
|
309
|
+
return resultFailure(validationFailure(`Unknown workflow executor: ${executorMode}. Expected safe-non-dispatching, provider, or command.`));
|
|
310
|
+
const commandId = optionalStringFlag(parsed.flags, 'command');
|
|
311
|
+
if (executorMode === 'command' && commandId === undefined)
|
|
312
|
+
return resultFailure(validationFailure(`--executor command requires --command <name>. Allowlisted commands: ${commandAllowlistIds().join(', ')}`));
|
|
313
|
+
if (executorMode !== 'command' && commandId !== undefined)
|
|
314
|
+
return resultFailure(validationFailure('--command can only be used with --executor command'));
|
|
315
|
+
const commandTargets = optionalStringFlag(parsed.flags, 'command-targets')?.split(',').map((item) => item.trim()).filter((item) => item.length > 0) ?? [];
|
|
316
|
+
let commandAdapter;
|
|
317
|
+
if (executorMode === 'command') {
|
|
318
|
+
try {
|
|
319
|
+
const commandCwd = optionalStringFlag(parsed.flags, 'command-cwd');
|
|
320
|
+
commandAdapter = new CommandAllowlistAdapter({
|
|
321
|
+
commandId: commandId ?? '',
|
|
322
|
+
workspaceRoot,
|
|
323
|
+
targetPaths: commandTargets,
|
|
324
|
+
...(commandCwd === undefined ? {} : { cwd: commandCwd }),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
return resultFailure(validationFailure(error instanceof Error ? error.message : 'Command allowlist adapter rejected the request.'));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const operation = {
|
|
332
|
+
category: executorMode === 'command' ? 'shell' : 'provider-tool',
|
|
333
|
+
operation: executorMode === 'command' ? `command-allowlist:${commandId}` : 'workflow-delegated-dispatch',
|
|
334
|
+
...(executorMode === 'command' ? {} : { providerToolName: `${details.value.providerAdapter}:workflow-dispatch` }),
|
|
335
|
+
workspaceRoot,
|
|
336
|
+
agent: agent.value,
|
|
337
|
+
ambiguous: executorMode !== 'command',
|
|
338
|
+
input: toJsonValue({
|
|
339
|
+
runId: details.value.id,
|
|
340
|
+
project: details.value.project,
|
|
341
|
+
workflow: selected.id,
|
|
342
|
+
phase: details.value.phase,
|
|
343
|
+
userIntent: details.value.userIntent,
|
|
344
|
+
providerAdapter: details.value.providerAdapter,
|
|
345
|
+
model: details.value.model,
|
|
346
|
+
selectedAgentId: agent.value.id,
|
|
347
|
+
requestedBy: 'cli-workflow-execute',
|
|
348
|
+
...(executorMode === 'command' ? { command: commandId, allowlistedCommand: commandAdapter?.plan.request.argv ?? [] } : {}),
|
|
349
|
+
}),
|
|
350
|
+
};
|
|
351
|
+
const preflight = service.preflightOperation({ runId: details.value.id, ...operation });
|
|
352
|
+
if (!preflight.ok)
|
|
353
|
+
return resultFailure(preflight);
|
|
354
|
+
if (preflight.value.outcome === 'approval-needed') {
|
|
355
|
+
const pending = recordWorkflowPendingApproval(service, details.value.id, operation, executorMode, preflight.value);
|
|
356
|
+
if (!pending.ok)
|
|
357
|
+
return resultFailure(pending);
|
|
358
|
+
}
|
|
359
|
+
const selectedAgentSummary = workflowRunAgentSummary(agent.value, { policy: 'run-selected-agent', reasons: ['run.selectedAgentId resolved to a real registry agent'] });
|
|
360
|
+
const executor = executorMode === 'provider' || executorMode === 'command'
|
|
361
|
+
? new GuardedProviderWorkflowExecutor({
|
|
362
|
+
...(commandAdapter === undefined ? {} : { adapter: commandAdapter }),
|
|
363
|
+
capabilities: {
|
|
364
|
+
sandboxEnforceable: commandAdapter !== undefined,
|
|
365
|
+
processExecutionEnforceable: commandAdapter !== undefined,
|
|
366
|
+
workspaceBoundaryEnforced: commandAdapter !== undefined,
|
|
367
|
+
providerConfigWritesBlocked: commandAdapter !== undefined,
|
|
368
|
+
},
|
|
369
|
+
})
|
|
370
|
+
: new SafeNonDispatchingWorkflowExecutor();
|
|
371
|
+
const executorResult = executor.execute({
|
|
372
|
+
run: details.value,
|
|
373
|
+
workflow: selected,
|
|
374
|
+
selectedAgent: agent.value,
|
|
375
|
+
selectedAgentSummary,
|
|
376
|
+
workspaceRoot,
|
|
377
|
+
requestedOperation: operation,
|
|
378
|
+
preflight: preflight.value,
|
|
379
|
+
...(commandAdapter === undefined ? {} : { sandboxExecutionEvidence: commandAdapter.plan.evidence }),
|
|
380
|
+
gates: {
|
|
381
|
+
validRunId: true,
|
|
382
|
+
workflowMatchesRun: true,
|
|
383
|
+
selectedAgentReal: true,
|
|
384
|
+
selectedAgentMatchesRun: true,
|
|
385
|
+
workspaceRoot,
|
|
386
|
+
confirmed: true,
|
|
387
|
+
escalationOverride: parsed.flags['override-escalation'] === true,
|
|
388
|
+
},
|
|
389
|
+
plannerRecommendation: { planner, recommendation },
|
|
390
|
+
});
|
|
391
|
+
const checkpoint = appendWorkflowExecutionCheckpoint(service, details.value.id, executorResult.checkpointLabel, executorResult.checkpointPayload);
|
|
392
|
+
if (!checkpoint.ok)
|
|
393
|
+
return resultFailure(checkpoint);
|
|
394
|
+
return jsonResult({ ok: true, value: {
|
|
395
|
+
ok: true,
|
|
396
|
+
runId: details.value.id,
|
|
397
|
+
workflow: selected.id,
|
|
398
|
+
phase: details.value.phase,
|
|
399
|
+
status: executorResult.status,
|
|
400
|
+
dispatched: executorResult.dispatched,
|
|
401
|
+
dispatchMode: executorResult.dispatchMode,
|
|
402
|
+
executorId: executorResult.executorId,
|
|
403
|
+
executorKind: executorResult.executorKind,
|
|
404
|
+
reason: executorResult.reason,
|
|
405
|
+
message: executorResult.message,
|
|
406
|
+
selectedAgent: selectedAgentSummary,
|
|
407
|
+
preflight: {
|
|
408
|
+
outcome: preflight.value.outcome,
|
|
409
|
+
decision: preflight.value.decision,
|
|
410
|
+
audit: preflight.value.audit,
|
|
411
|
+
eligibleForFutureExecution: preflight.value.eligibleForFutureExecution,
|
|
412
|
+
safety: preflight.value.safety,
|
|
413
|
+
},
|
|
414
|
+
executorAudit: executorResult.audit,
|
|
415
|
+
checkpointId: checkpoint.value.id,
|
|
416
|
+
safety: workflowExecutionSafety(true),
|
|
417
|
+
nextAction: executorResult.nextAction,
|
|
418
|
+
} });
|
|
419
|
+
}
|
|
420
|
+
function recordWorkflowPendingApproval(service, runId, operation, executorMode, preflight) {
|
|
421
|
+
const approvalId = preflight.approval?.id ?? preflight.audit.approvalId;
|
|
422
|
+
return service.appendEvent({
|
|
423
|
+
runId,
|
|
424
|
+
kind: 'operation-execution',
|
|
425
|
+
title: `Workflow operation pending approval: ${operation.category} ${operation.operation}`,
|
|
426
|
+
payload: {
|
|
427
|
+
status: 'pending-approval',
|
|
428
|
+
executorName: `workflow-${executorMode}`,
|
|
429
|
+
requestedOperation: operationMetadataForWorkflowExecution(operation),
|
|
430
|
+
input: operation.input ?? null,
|
|
431
|
+
decisionEventId: preflight.permissionEvent.id,
|
|
432
|
+
approvalId: approvalId ?? null,
|
|
433
|
+
decision: preflight.decision.decision,
|
|
434
|
+
reason: preflight.decision.reason,
|
|
435
|
+
message: preflight.decision.message,
|
|
436
|
+
executorInvoked: false,
|
|
437
|
+
operationExecuted: false,
|
|
438
|
+
timestamp: new Date().toISOString(),
|
|
439
|
+
},
|
|
440
|
+
relatedType: 'approval',
|
|
441
|
+
...(approvalId === undefined ? {} : { relatedId: approvalId }),
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function resolveWorkflowRunAgent(input) {
|
|
445
|
+
if (input.explicitAgentId !== undefined) {
|
|
446
|
+
const agent = input.registry.getAgent(input.explicitAgentId);
|
|
447
|
+
if (!agent.ok) {
|
|
448
|
+
return { ok: false, value: workflowRunBlockedAgentResolution(input, `Explicit --agent-id could not be resolved: ${agent.error.message}`) };
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
ok: true,
|
|
452
|
+
value: {
|
|
453
|
+
agent: agent.value,
|
|
454
|
+
summary: workflowRunAgentSummary(agent.value, {
|
|
455
|
+
policy: 'explicit-agent-id',
|
|
456
|
+
reasons: ['explicit --agent-id selected a registry agent'],
|
|
457
|
+
fallbackReason: null,
|
|
458
|
+
}),
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
const resolution = input.registry.resolveAgents({
|
|
463
|
+
project: input.project,
|
|
464
|
+
scope: 'project',
|
|
465
|
+
mode: 'agent',
|
|
466
|
+
workflow: input.workflow,
|
|
467
|
+
phase: input.phase,
|
|
468
|
+
providerAdapter: input.providerAdapter,
|
|
469
|
+
intent: input.intent,
|
|
470
|
+
taskDescription: input.intent,
|
|
471
|
+
desiredCapabilities: [],
|
|
472
|
+
});
|
|
473
|
+
if (!resolution.ok)
|
|
474
|
+
return { ok: false, value: workflowRunBlockedAgentResolution(input, resolution.error.message) };
|
|
475
|
+
const manager = resolution.value.candidates.find((candidate) => candidate.agent.name === 'vgxness-manager');
|
|
476
|
+
const selected = manager ?? resolution.value.candidates[0];
|
|
477
|
+
if (selected === undefined) {
|
|
478
|
+
return {
|
|
479
|
+
ok: false,
|
|
480
|
+
value: workflowRunBlockedAgentResolution(input, 'No project agent matched the requested workflow, phase, provider adapter, and project scope. Seed or register vgxness-manager before running workflows.', resolution.value.skipped.map((skipped) => ({ agent: skipped.agent, reasons: skipped.reasons }))),
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
ok: true,
|
|
485
|
+
value: {
|
|
486
|
+
agent: selected.agent,
|
|
487
|
+
summary: workflowRunAgentSummary(selected.agent, {
|
|
488
|
+
policy: 'project-manager-preferred',
|
|
489
|
+
reasons: selected.reasons,
|
|
490
|
+
fallbackReason: manager === undefined && selected.agent.name !== 'vgxness-manager'
|
|
491
|
+
? 'vgxness-manager was not resolvable for this workflow/phase/provider; selected the highest-scoring project agent instead.'
|
|
492
|
+
: null,
|
|
493
|
+
score: selected.score,
|
|
494
|
+
}),
|
|
495
|
+
},
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function workflowRunAgentSummary(agent, details) {
|
|
499
|
+
return {
|
|
500
|
+
id: agent.id,
|
|
501
|
+
name: agent.name,
|
|
502
|
+
mode: agent.mode,
|
|
503
|
+
project: agent.project,
|
|
504
|
+
scope: agent.scope,
|
|
505
|
+
workflows: agent.workflows,
|
|
506
|
+
policy: details.policy,
|
|
507
|
+
reasons: details.reasons,
|
|
508
|
+
fallbackReason: details.fallbackReason ?? null,
|
|
509
|
+
...(details.score !== undefined ? { score: details.score } : {}),
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
function workflowRunBlockedAgentResolution(input, reason, skipped) {
|
|
513
|
+
return {
|
|
514
|
+
ok: false,
|
|
515
|
+
status: 'blocked',
|
|
516
|
+
reason,
|
|
517
|
+
workflow: input.workflow,
|
|
518
|
+
phase: input.phase,
|
|
519
|
+
providerAdapter: input.providerAdapter,
|
|
520
|
+
selectedAgent: null,
|
|
521
|
+
safety: {
|
|
522
|
+
executed: false,
|
|
523
|
+
recordsRuns: false,
|
|
524
|
+
callsProvider: false,
|
|
525
|
+
editsFiles: false,
|
|
526
|
+
writesProviderConfig: false,
|
|
527
|
+
mutatesSddArtifacts: false,
|
|
528
|
+
},
|
|
529
|
+
nextAction: 'Register or seed a real project agent such as vgxness-manager, then retry the workflow run.',
|
|
530
|
+
...(skipped !== undefined ? { skipped } : {}),
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
function workflowRunSafety(workflow) {
|
|
534
|
+
return {
|
|
535
|
+
executed: true,
|
|
536
|
+
recordsRuns: true,
|
|
537
|
+
callsProvider: false,
|
|
538
|
+
editsFiles: false,
|
|
539
|
+
writesProviderConfig: false,
|
|
540
|
+
mutability: workflow.mutability,
|
|
541
|
+
safetyProfile: workflow.safetyProfile,
|
|
542
|
+
requiresSddArtifacts: workflow.requiresSddArtifacts,
|
|
543
|
+
notes: [
|
|
544
|
+
'Safe run slice: recorded run intent and checkpoint only.',
|
|
545
|
+
'No provider tools were called, no user files were edited, no provider config was written, and no SDD artifacts were mutated.',
|
|
546
|
+
],
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function workflowRunRecommendation(selected, recommended, planner) {
|
|
550
|
+
if (selected.id === recommended.id) {
|
|
551
|
+
return {
|
|
552
|
+
status: 'aligned',
|
|
553
|
+
recommendedWorkflow: recommended.id,
|
|
554
|
+
message: `Planner agrees with explicit ${selected.id} workflow.`,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
return {
|
|
558
|
+
status: recommended.id === 'sdd' ? 'escalation-recommended' : 'workflow-mismatch',
|
|
559
|
+
explicitWorkflow: selected.id,
|
|
560
|
+
recommendedWorkflow: recommended.id,
|
|
561
|
+
reason: planner.reason,
|
|
562
|
+
message: recommended.id === 'sdd'
|
|
563
|
+
? `Planner recommends formal SDD instead of ${selected.id}; do not continue with mutable lightweight work until reviewed.`
|
|
564
|
+
: `Planner recommends ${recommended.id} instead of explicit ${selected.id}; review before continuing.`,
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
function workflowRunNextAction(selected, recommended, planner) {
|
|
568
|
+
if (recommended.id === 'sdd' && selected.id !== 'sdd') {
|
|
569
|
+
return {
|
|
570
|
+
type: 'review-escalation',
|
|
571
|
+
command: planner.suggestedChangeId === undefined ? null : `vgxness sdd next --project ${planner.project} --change ${planner.suggestedChangeId}`,
|
|
572
|
+
reason: 'Planner detected broad or risky work and recommends the formal SDD workflow before any edits.',
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
return {
|
|
576
|
+
type: selected.id === 'sdd' ? 'continue-sdd' : 'manual-continuation',
|
|
577
|
+
command: selected.id === 'sdd' && planner.suggestedChangeId !== undefined ? `vgxness sdd next --project ${planner.project} --change ${planner.suggestedChangeId}` : null,
|
|
578
|
+
reason: selected.id === 'sdd'
|
|
579
|
+
? 'Use existing SDD commands to inspect readiness; this run command did not mutate artifacts.'
|
|
580
|
+
: 'Inspect the recorded checkpoint, then continue manually or through a future approved executor slice.',
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function appendWorkflowExecutionCheckpoint(service, runId, label, state) {
|
|
584
|
+
return service.appendCheckpoint({ runId, label, state: toJsonValue(state) });
|
|
585
|
+
}
|
|
586
|
+
function workflowExecutionSafety(preflightRecorded) {
|
|
587
|
+
return workflowExecutorSafety(preflightRecorded);
|
|
588
|
+
}
|
|
589
|
+
function workflowExecutionBlockedValue(runId, workflow, reason, message, checkpoint, recommendation) {
|
|
590
|
+
return {
|
|
591
|
+
ok: false,
|
|
592
|
+
runId,
|
|
593
|
+
workflow,
|
|
594
|
+
status: 'blocked',
|
|
595
|
+
dispatched: false,
|
|
596
|
+
reason,
|
|
597
|
+
message,
|
|
598
|
+
...(checkpoint.ok ? { checkpointId: checkpoint.value.id } : {}),
|
|
599
|
+
...(recommendation === undefined ? {} : { recommendation }),
|
|
600
|
+
safety: workflowExecutionSafety(false),
|
|
601
|
+
nextAction: 'Resolve the blocking safety gate, then re-run the explicit execute command.',
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function validateWorkflowExecutionAgent(agent, run, workflow) {
|
|
605
|
+
if (agent.mode !== 'agent')
|
|
606
|
+
return { ok: false, reason: 'selected-agent-not-primary-agent', message: `Selected registry entry ${agent.id} is ${agent.mode}; delegated workflow execution requires a primary agent.` };
|
|
607
|
+
if (agent.id !== run.selectedAgentId)
|
|
608
|
+
return { ok: false, reason: 'selected-agent-mismatch', message: 'Resolved selected agent does not match run.selectedAgentId.' };
|
|
609
|
+
if (agent.project !== run.project)
|
|
610
|
+
return { ok: false, reason: 'selected-agent-project-mismatch', message: `Selected agent belongs to project ${agent.project}, not run project ${run.project}.` };
|
|
611
|
+
if (agent.adapters[run.providerAdapter] === undefined)
|
|
612
|
+
return { ok: false, reason: 'selected-agent-provider-mismatch', message: `Selected agent has no ${run.providerAdapter} adapter.` };
|
|
613
|
+
if (!agent.workflows.includes(workflow.id) && !agent.workflows.includes(`${workflow.id}:${run.phase}`)) {
|
|
614
|
+
return { ok: false, reason: 'selected-agent-workflow-mismatch', message: `Selected agent is not registered for workflow ${workflow.id} or phase ${workflow.id}:${run.phase}.` };
|
|
615
|
+
}
|
|
616
|
+
return { ok: true };
|
|
617
|
+
}
|
|
618
|
+
function toJsonValue(value) {
|
|
619
|
+
return JSON.parse(JSON.stringify(value));
|
|
620
|
+
}
|
|
621
|
+
function runSetupLifecycleCommand(parsed, environment) {
|
|
622
|
+
const [area, command] = parsed.positionals;
|
|
623
|
+
if (area === 'setup' && command === 'plan')
|
|
624
|
+
return runSetupPlanCommand(parsed, environment);
|
|
625
|
+
if (area === 'setup' && command === 'apply')
|
|
626
|
+
return usageFailure('setup apply is available through the async CLI entrypoint; run vgxness setup apply --yes from the installed CLI.');
|
|
627
|
+
if (area === 'setup' && command === 'rollback')
|
|
628
|
+
return runSetupRollbackCommand(parsed);
|
|
629
|
+
if (area === 'setup' && command !== 'status')
|
|
630
|
+
return usageFailure(command === undefined ? 'Missing command for setup' : `Unknown setup command: ${command}`);
|
|
631
|
+
const scope = scopeFlag(parsed.flags, 'scope', 'project');
|
|
632
|
+
if (!scope.ok)
|
|
633
|
+
return resultFailure(scope);
|
|
634
|
+
const project = optionalStringFlag(parsed.flags, 'project') ?? 'vgxness';
|
|
635
|
+
const selectedDatabasePath = databasePathFor(parsed.flags, environment);
|
|
636
|
+
if (!selectedDatabasePath.ok)
|
|
637
|
+
return resultFailure(selectedDatabasePath);
|
|
638
|
+
const databasePath = selectedDatabasePath.value;
|
|
639
|
+
const opened = openCliDatabase(databasePath);
|
|
640
|
+
const service = createSetupLifecycleService({ opened, databasePath, environment });
|
|
641
|
+
try {
|
|
642
|
+
return jsonResult(service.getStatus({ project, projectRoot: environment.cwd, scope: scope.value }));
|
|
643
|
+
}
|
|
644
|
+
finally {
|
|
645
|
+
if (opened.ok)
|
|
646
|
+
opened.value.close();
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function runSetupPlanCommand(parsed, environment) {
|
|
650
|
+
const input = setupPlanInputFromFlags(parsed, environment);
|
|
651
|
+
if (!input.ok)
|
|
652
|
+
return resultFailure(input);
|
|
653
|
+
const plan = createSetupPlan(input.value);
|
|
654
|
+
return jsonResult(plan);
|
|
655
|
+
}
|
|
656
|
+
function runSetupRollbackCommand(parsed) {
|
|
657
|
+
const backupPath = optionalStringFlag(parsed.flags, 'backup');
|
|
658
|
+
if (backupPath === undefined)
|
|
659
|
+
return usageFailure('setup rollback requires --backup <path>');
|
|
660
|
+
const result = rollbackConfigBackup({ backupPath: resolve(backupPath) });
|
|
661
|
+
if (parsed.flags.json === true)
|
|
662
|
+
return jsonResult(result);
|
|
663
|
+
if (!result.ok)
|
|
664
|
+
return resultFailure(result);
|
|
665
|
+
const lines = [
|
|
666
|
+
'Rollback restored OpenCode config.',
|
|
667
|
+
`Target: ${result.value.targetPath}`,
|
|
668
|
+
`Restored from: ${result.value.backupPath}`,
|
|
669
|
+
...(result.value.preRollbackBackupPath === undefined ? [] : [`Pre-rollback backup: ${result.value.preRollbackBackupPath}`]),
|
|
670
|
+
'Next: vgx doctor; restart OpenCode and verify the vgxness MCP server is visible.',
|
|
671
|
+
];
|
|
672
|
+
return okText(`${lines.join('\n')}\n`);
|
|
673
|
+
}
|
|
674
|
+
async function runSetupApplyCommand(parsed, environment) {
|
|
675
|
+
if (parsed.flags.yes !== true)
|
|
676
|
+
return usageFailure('setup apply writes provider config and requires --yes');
|
|
677
|
+
const input = setupPlanInputFromFlags(parsed, environment);
|
|
678
|
+
if (!input.ok)
|
|
679
|
+
return resultFailure(input);
|
|
680
|
+
const plan = createSetupPlan(input.value);
|
|
681
|
+
if (!plan.ok)
|
|
682
|
+
return resultFailure(plan);
|
|
683
|
+
if (plan.value.status !== 'ready')
|
|
684
|
+
return resultFailure(validationFailure(`Setup plan is ${plan.value.status}; resolve conflicts before applying.`));
|
|
685
|
+
if (input.value.provider === 'none')
|
|
686
|
+
return jsonResult({ ok: true, value: { status: 'manual-required', message: 'Provider is none; there is no config to apply.', plan: plan.value } });
|
|
687
|
+
const result = await installOpenCodeMcpClient({
|
|
688
|
+
cwd: environment.cwd,
|
|
689
|
+
databasePath: plan.value.db.path,
|
|
690
|
+
databasePathSource: plan.value.db.source === 'global-default' || plan.value.db.source === 'environment' ? plan.value.db.source : 'flag',
|
|
691
|
+
scope: input.value.scope ?? vgxnessSetupDefaults.defaultOpenCodeScope,
|
|
692
|
+
env: environment.env,
|
|
693
|
+
confirmed: true,
|
|
694
|
+
mcpOnly: input.value.installMode === 'mcp-only',
|
|
695
|
+
});
|
|
696
|
+
return result.status === 'installed'
|
|
697
|
+
? jsonResult({ ok: true, value: { kind: 'setup-apply-result', version: 1, status: 'installed', plan: plan.value, opencode: result, nextCommands: ['vgx doctor', 'Restart OpenCode and verify the vgxness MCP server is visible.'] } })
|
|
698
|
+
: resultFailure(validationFailure(`${result.reason}: ${result.message}`));
|
|
699
|
+
}
|
|
700
|
+
function setupPlanInputFromFlags(parsed, environment) {
|
|
701
|
+
const project = optionalStringFlag(parsed.flags, 'project');
|
|
702
|
+
const database = setupDatabaseFlags(parsed.flags);
|
|
703
|
+
if (!database.ok)
|
|
704
|
+
return database;
|
|
705
|
+
const provider = setupProviderFlag(parsed.flags);
|
|
706
|
+
if (!provider.ok)
|
|
707
|
+
return provider;
|
|
708
|
+
const scope = opencodeInstallScopeFlag(parsed.flags, 'scope');
|
|
709
|
+
if (!scope.ok)
|
|
710
|
+
return scope;
|
|
711
|
+
const installMode = setupInstallModeFlag(parsed.flags);
|
|
712
|
+
if (!installMode.ok)
|
|
713
|
+
return installMode;
|
|
714
|
+
return {
|
|
715
|
+
ok: true,
|
|
716
|
+
value: {
|
|
717
|
+
...(project === undefined ? {} : { project }),
|
|
718
|
+
workspaceRoot: environment.cwd,
|
|
719
|
+
env: environment.env,
|
|
720
|
+
databaseMode: database.value.mode,
|
|
721
|
+
...(database.value.path === undefined ? {} : { databasePath: database.value.path }),
|
|
722
|
+
provider: provider.value,
|
|
723
|
+
scope: scope.value,
|
|
724
|
+
installMode: installMode.value,
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
function setupDatabaseFlags(flags) {
|
|
729
|
+
const dbValue = optionalStringFlag(flags, 'db');
|
|
730
|
+
const dbPath = optionalStringFlag(flags, 'db-path');
|
|
731
|
+
if (dbValue === undefined)
|
|
732
|
+
return { ok: true, value: { mode: vgxnessSetupDefaults.defaultDatabaseMode } };
|
|
733
|
+
if (dbValue === 'global' || dbValue === 'project-local')
|
|
734
|
+
return { ok: true, value: { mode: dbValue } };
|
|
735
|
+
if (dbValue === 'custom')
|
|
736
|
+
return dbPath === undefined ? validationFailure('--db custom requires --db-path <path>') : { ok: true, value: { mode: 'custom', path: dbPath } };
|
|
737
|
+
return { ok: true, value: { mode: 'custom', path: dbValue } };
|
|
738
|
+
}
|
|
739
|
+
function setupProviderFlag(flags) {
|
|
740
|
+
const value = optionalStringFlag(flags, 'provider') ?? vgxnessSetupDefaults.defaultProvider;
|
|
741
|
+
return value === 'opencode' || value === 'none' ? { ok: true, value } : validationFailure('--provider must be opencode or none');
|
|
742
|
+
}
|
|
743
|
+
function setupInstallModeFlag(flags) {
|
|
744
|
+
const value = optionalStringFlag(flags, 'mode');
|
|
745
|
+
if (value !== undefined)
|
|
746
|
+
return value === 'mcp-only' || value === 'mcp-plus-agents' ? { ok: true, value } : validationFailure('--mode must be mcp-only or mcp-plus-agents');
|
|
747
|
+
if (flags['mcp-only'] === true || flags['no-agents'] === true)
|
|
748
|
+
return { ok: true, value: 'mcp-only' };
|
|
749
|
+
return { ok: true, value: vgxnessSetupDefaults.defaultInstallMode };
|
|
750
|
+
}
|
|
751
|
+
function runDoctorAliasCommand(parsed, environment) {
|
|
752
|
+
return runMcpDoctorCommand({ ...parsed, positionals: ['mcp', 'doctor', ...parsed.positionals.slice(1)] }, environment);
|
|
753
|
+
}
|
|
754
|
+
function createSetupLifecycleService(input) {
|
|
755
|
+
return new SetupLifecycleService({
|
|
756
|
+
store: () => input.opened.ok ? { ok: true, value: { path: input.databasePath } } : input.opened,
|
|
757
|
+
mcp: (setupInput) => {
|
|
758
|
+
const visibility = createOpenCodeMcpVisibilityReport({ cwd: input.environment.cwd, projectRoot: setupInput.projectRoot });
|
|
759
|
+
const preview = createMcpClientSetupPreview({ provider: 'opencode', databasePath: setupInput.databasePath ?? input.databasePath });
|
|
760
|
+
return {
|
|
761
|
+
ok: true,
|
|
762
|
+
value: {
|
|
763
|
+
ready: visibility.ready,
|
|
764
|
+
evidence: setupMcpEvidence(visibility, preview),
|
|
765
|
+
nextAction: visibility.ready ? 'Run `opencode mcp list` from inside the project to verify visibility.' : 'Preview OpenCode MCP setup, inspect `mcp install opencode --plan`, then apply with `--yes` only after review.',
|
|
766
|
+
},
|
|
767
|
+
};
|
|
768
|
+
},
|
|
769
|
+
defaultContext: (setupInput) => {
|
|
770
|
+
if (!input.opened.ok)
|
|
771
|
+
return validationFailure('Default context cannot be read until the local store is available.');
|
|
772
|
+
if (setupInput.project === undefined)
|
|
773
|
+
return validationFailure('Default context cannot be read until a project is selected.');
|
|
774
|
+
const registry = new AgentRegistryService(input.opened.value);
|
|
775
|
+
const agent = registry.getAgentByName(setupInput.project, setupInput.scope ?? 'project', setupInput.agentName);
|
|
776
|
+
if (!agent.ok)
|
|
777
|
+
return agent;
|
|
778
|
+
const selection = resolveAgentProfileModel({ agent: agent.value, providerAdapter: 'opencode' });
|
|
779
|
+
if (!selection.ok)
|
|
780
|
+
return selection;
|
|
781
|
+
return {
|
|
782
|
+
ok: true,
|
|
783
|
+
value: {
|
|
784
|
+
agentId: agent.value.id,
|
|
785
|
+
providerAdapter: selection.value.providerAdapter,
|
|
786
|
+
model: selection.value.model,
|
|
787
|
+
...(selection.value.profile === undefined ? {} : { profile: selection.value.profile }),
|
|
788
|
+
},
|
|
789
|
+
};
|
|
790
|
+
},
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
function readSetupStatus(service, input) {
|
|
794
|
+
return service.getStatus(input);
|
|
795
|
+
}
|
|
796
|
+
function collectRunInsights(service, runs) {
|
|
797
|
+
const insightsByRunId = {};
|
|
798
|
+
for (const run of runs) {
|
|
799
|
+
const insights = service.getRunInsights(run.id);
|
|
800
|
+
if (!insights.ok)
|
|
801
|
+
return insights;
|
|
802
|
+
insightsByRunId[run.id] = insights.value;
|
|
803
|
+
}
|
|
804
|
+
return { ok: true, value: insightsByRunId };
|
|
805
|
+
}
|
|
806
|
+
function collectRunDetails(service, runs) {
|
|
807
|
+
const detailsByRunId = {};
|
|
808
|
+
for (const run of runs) {
|
|
809
|
+
const details = service.getRun(run.id);
|
|
810
|
+
if (!details.ok)
|
|
811
|
+
return details;
|
|
812
|
+
detailsByRunId[run.id] = details.value;
|
|
813
|
+
}
|
|
814
|
+
return { ok: true, value: detailsByRunId };
|
|
815
|
+
}
|
|
816
|
+
function setupMcpEvidence(visibility, preview) {
|
|
817
|
+
return [
|
|
818
|
+
visibility.ready ? 'OpenCode project config defines mcp.vgxness and is visible from the current working directory.' : `OpenCode MCP visibility is not ready at ${visibility.targetPath}.`,
|
|
819
|
+
`Preview command targets ${preview.server.args.join(' ')}.`,
|
|
820
|
+
...visibility.guidance,
|
|
821
|
+
];
|
|
822
|
+
}
|
|
823
|
+
export async function dispatchCliAsync(argv, environment) {
|
|
824
|
+
const parsed = parseArgs(argv);
|
|
825
|
+
const [area, command] = parsed.positionals;
|
|
826
|
+
if (argv.length === 0)
|
|
827
|
+
return runDashboardInteractiveCommand({ positionals: ['dashboard', 'interactive'], flags: parsed.flags }, environment);
|
|
828
|
+
if (area === 'dashboard' && command === 'interactive')
|
|
829
|
+
return runDashboardInteractiveCommand(parsed, environment);
|
|
830
|
+
if (area === 'mcp') {
|
|
831
|
+
if (!command)
|
|
832
|
+
return usageFailure('Missing command for mcp');
|
|
833
|
+
if (command === 'install')
|
|
834
|
+
return runMcpInstallCommand(parsed, environment);
|
|
835
|
+
if (command === 'setup')
|
|
836
|
+
return runMcpSetupCommand(parsed, environment);
|
|
837
|
+
if (command === 'doctor')
|
|
838
|
+
return runMcpDoctorCommand(parsed, environment);
|
|
839
|
+
return usageFailure(`Unknown mcp command: ${command}`);
|
|
840
|
+
}
|
|
841
|
+
if (area === 'doctor')
|
|
842
|
+
return runDoctorAliasCommand(parsed, environment);
|
|
843
|
+
if (area === 'init')
|
|
844
|
+
return runInitCommand(parsed, environment);
|
|
845
|
+
if (area === 'setup' && command === 'apply')
|
|
846
|
+
return runSetupApplyCommand(parsed, environment);
|
|
847
|
+
if (area === 'setup' && command === 'rollback')
|
|
848
|
+
return runSetupRollbackCommand(parsed);
|
|
849
|
+
return dispatchCli(argv, environment);
|
|
850
|
+
}
|
|
851
|
+
async function runInitCommand(parsed, environment) {
|
|
852
|
+
if (parsed.flags.plan === true || parsed.flags.json === true)
|
|
853
|
+
return runSetupPlanCommand(parsed, environment);
|
|
854
|
+
if (parsed.flags.yes === true)
|
|
855
|
+
return runSetupApplyCommand({ ...parsed, flags: { ...parsed.flags, yes: true } }, environment);
|
|
856
|
+
const terminal = environment.stdin?.isTTY === true && environment.stdout?.isTTY === true;
|
|
857
|
+
if (!terminal)
|
|
858
|
+
return runSetupPlanCommand(parsed, environment);
|
|
859
|
+
const defaults = defaultWizardInput(environment);
|
|
860
|
+
const answers = await promptSetupWizard(environment, defaults);
|
|
861
|
+
if (!answers.ok)
|
|
862
|
+
return resultFailure(answers);
|
|
863
|
+
const plan = createSetupPlan(answers.value.input);
|
|
864
|
+
if (!plan.ok)
|
|
865
|
+
return resultFailure(plan);
|
|
866
|
+
environment.stdout?.write(renderSetupPlanForHumans(plan.value));
|
|
867
|
+
const confirmation = (await promptLine(environment, 'Apply this setup? Type "yes" to continue: ')).trim();
|
|
868
|
+
if (confirmation !== 'yes') {
|
|
869
|
+
return okText('No changes were made. Next: run `vgx init --plan` to review the plan or `vgx setup apply --yes` when ready.\n');
|
|
870
|
+
}
|
|
871
|
+
return runSetupApplyCommand({ ...parsed, flags: answers.value.flags }, environment);
|
|
872
|
+
}
|
|
873
|
+
function defaultWizardInput(environment) {
|
|
874
|
+
return {
|
|
875
|
+
project: environment.cwd.replace(/[\\/]+$/, '').split(/[\\/]/).pop() || 'vgxness',
|
|
876
|
+
databaseMode: vgxnessSetupDefaults.defaultDatabaseMode,
|
|
877
|
+
provider: vgxnessSetupDefaults.defaultProvider,
|
|
878
|
+
scope: vgxnessSetupDefaults.defaultOpenCodeScope,
|
|
879
|
+
installMode: vgxnessSetupDefaults.defaultInstallMode,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
async function promptSetupWizard(environment, defaults) {
|
|
883
|
+
const project = defaulted(await promptLine(environment, `Project name [${defaults.project}]: `), defaults.project);
|
|
884
|
+
const db = defaulted(await promptLine(environment, 'DB location: global default, project-local, custom path [global]: '), defaults.databaseMode);
|
|
885
|
+
const databaseMode = db === 'global default' || db === 'global' ? 'global' : db === 'project-local' ? 'project-local' : db === 'custom path' || db === 'custom' ? 'custom' : undefined;
|
|
886
|
+
if (databaseMode === undefined)
|
|
887
|
+
return validationFailure('DB location must be global, project-local, or custom path.');
|
|
888
|
+
const databasePath = databaseMode === 'custom' ? defaulted(await promptLine(environment, 'Custom DB path: '), '') : undefined;
|
|
889
|
+
if (databaseMode === 'custom' && (databasePath ?? '').length === 0)
|
|
890
|
+
return validationFailure('Custom DB path is required.');
|
|
891
|
+
const providerText = defaulted(await promptLine(environment, 'Provider: OpenCode, none/manual [OpenCode]: '), defaults.provider);
|
|
892
|
+
const provider = providerText.toLowerCase() === 'opencode' ? 'opencode' : providerText.toLowerCase() === 'none/manual' || providerText.toLowerCase() === 'none' || providerText.toLowerCase() === 'manual' ? 'none' : undefined;
|
|
893
|
+
if (provider === undefined)
|
|
894
|
+
return validationFailure('Provider must be OpenCode or none/manual.');
|
|
895
|
+
const scopeText = defaulted(await promptLine(environment, 'OpenCode scope: project, user [project]: '), defaults.scope);
|
|
896
|
+
const scope = scopeText === 'project' || scopeText === 'user' ? scopeText : undefined;
|
|
897
|
+
if (scope === undefined)
|
|
898
|
+
return validationFailure('OpenCode scope must be project or user.');
|
|
899
|
+
const modeText = defaulted(await promptLine(environment, 'Install mode: MCP + manager/SDD agents, MCP only [MCP + manager/SDD agents]: '), defaults.installMode);
|
|
900
|
+
const installMode = modeText === 'mcp-plus-agents' || modeText.toLowerCase() === 'mcp + manager/sdd agents' ? 'mcp-plus-agents' : modeText === 'mcp-only' || modeText.toLowerCase() === 'mcp only' ? 'mcp-only' : undefined;
|
|
901
|
+
if (installMode === undefined)
|
|
902
|
+
return validationFailure('Install mode must be MCP + manager/SDD agents or MCP only.');
|
|
903
|
+
const input = { project, workspaceRoot: environment.cwd, env: environment.env, databaseMode, ...(databasePath === undefined ? {} : { databasePath }), provider, scope, installMode };
|
|
904
|
+
const flags = { project, db: databaseMode, provider, scope, mode: installMode, yes: true, ...(databasePath === undefined ? {} : { 'db-path': databasePath }) };
|
|
905
|
+
return { ok: true, value: { input, flags } };
|
|
906
|
+
}
|
|
907
|
+
function defaulted(value, fallback) {
|
|
908
|
+
const trimmed = value.trim();
|
|
909
|
+
return trimmed.length === 0 ? fallback : trimmed;
|
|
910
|
+
}
|
|
911
|
+
function promptLine(environment, prompt) {
|
|
912
|
+
environment.stdout?.write(prompt);
|
|
913
|
+
const stdin = environment.stdin;
|
|
914
|
+
if (stdin === undefined)
|
|
915
|
+
return Promise.resolve('');
|
|
916
|
+
const buffered = promptBuffers.get(stdin) ?? '';
|
|
917
|
+
const immediateNewline = buffered.search(/\r?\n/);
|
|
918
|
+
if (immediateNewline >= 0) {
|
|
919
|
+
const line = buffered.slice(0, immediateNewline);
|
|
920
|
+
promptBuffers.set(stdin, buffered.slice(immediateNewline + (buffered[immediateNewline] === '\r' && buffered[immediateNewline + 1] === '\n' ? 2 : 1)));
|
|
921
|
+
return Promise.resolve(line);
|
|
922
|
+
}
|
|
923
|
+
return new Promise((resolveLine) => {
|
|
924
|
+
const listener = (chunk) => {
|
|
925
|
+
const nextBuffered = (promptBuffers.get(stdin) ?? '') + chunk.toString();
|
|
926
|
+
const newline = nextBuffered.search(/\r?\n/);
|
|
927
|
+
if (newline >= 0) {
|
|
928
|
+
cleanup();
|
|
929
|
+
const line = nextBuffered.slice(0, newline);
|
|
930
|
+
promptBuffers.set(stdin, nextBuffered.slice(newline + (nextBuffered[newline] === '\r' && nextBuffered[newline + 1] === '\n' ? 2 : 1)));
|
|
931
|
+
resolveLine(line);
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
promptBuffers.set(stdin, nextBuffered);
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
const cleanup = () => {
|
|
938
|
+
if (stdin.off !== undefined)
|
|
939
|
+
stdin.off('data', listener);
|
|
940
|
+
else
|
|
941
|
+
stdin.removeListener?.('data', listener);
|
|
942
|
+
};
|
|
943
|
+
stdin.on('data', listener);
|
|
944
|
+
stdin.resume?.();
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
function renderSetupPlanForHumans(plan) {
|
|
948
|
+
const actions = plan.actions.map((action) => `- ${action.description}${action.targetPath === undefined ? '' : ` (${action.targetPath})`}`).join('\n') || '- No provider config writes planned.';
|
|
949
|
+
const backups = plan.backupsPlanned.map((backup) => `- ${backup.targetPath}: ${backup.reason}`).join('\n') || '- None.';
|
|
950
|
+
return `\nSetup plan\nProject: ${plan.project}\nDatabase: ${plan.db.mode} (${plan.db.path})\nProvider: ${plan.provider}\nStatus: ${plan.status}\nActions:\n${actions}\nBackups:\n${backups}\n\n`;
|
|
951
|
+
}
|
|
952
|
+
async function runDashboardInteractiveCommand(parsed, environment) {
|
|
953
|
+
const project = optionalStringFlag(parsed.flags, 'project');
|
|
954
|
+
const terminal = resolveInteractiveTerminal(environment);
|
|
955
|
+
if (!terminal.ok)
|
|
956
|
+
return resultFailure(terminal);
|
|
957
|
+
const limit = optionalNumberFlag(parsed.flags, 'limit');
|
|
958
|
+
if (!limit.ok)
|
|
959
|
+
return resultFailure(limit);
|
|
960
|
+
const selectedDatabasePath = databasePathFor(parsed.flags, environment);
|
|
961
|
+
if (!selectedDatabasePath.ok)
|
|
962
|
+
return resultFailure(selectedDatabasePath);
|
|
963
|
+
const databasePath = selectedDatabasePath.value;
|
|
964
|
+
const opened = openCliDatabase(databasePath);
|
|
965
|
+
if (!opened.ok)
|
|
966
|
+
return resultFailure(opened);
|
|
967
|
+
try {
|
|
968
|
+
const runs = new RunService(opened.value);
|
|
969
|
+
const sdd = new SddWorkflowService(new MemoryService(opened.value));
|
|
970
|
+
const setup = createSetupLifecycleService({ opened, databasePath, environment });
|
|
971
|
+
const agents = new AgentRegistryService(opened.value);
|
|
972
|
+
const skills = new SkillRegistryService(opened.value);
|
|
973
|
+
const managerProfiles = new ManagerProfileOverlayService({ agents, overlays: new ManagerProfileOverlayRepository(opened.value) });
|
|
974
|
+
const change = optionalStringFlag(parsed.flags, 'change');
|
|
975
|
+
const loader = () => {
|
|
976
|
+
if (project === undefined) {
|
|
977
|
+
const setupStatus = readSetupStatus(setup, { projectRoot: environment.cwd, scope: 'project' });
|
|
978
|
+
const sectionErrors = {};
|
|
979
|
+
if (!setupStatus.ok)
|
|
980
|
+
sectionErrors.setup = sanitizeDashboardError(setupStatus.error.message);
|
|
981
|
+
return {
|
|
982
|
+
ok: true,
|
|
983
|
+
value: {
|
|
984
|
+
runs: [],
|
|
985
|
+
workflows: buildDashboardWorkflowsReadModel({ generatedAt: new Date().toISOString() }),
|
|
986
|
+
operationalRuns: buildDashboardRunsReadModel({ runs: [], generatedAt: new Date().toISOString() }),
|
|
987
|
+
approvals: buildDashboardApprovalsReadModel({ runs: [], generatedAt: new Date().toISOString() }),
|
|
988
|
+
doctor: { status: 'ready-without-project', summary: 'No project selected; environment-only dashboard checks are available.', warnings: ['Project-scoped run, SDD, approval, and agent checks are deferred.'], commands: ['npm run cli -- setup status', 'npm run cli -- mcp setup --provider opencode --preview'] },
|
|
989
|
+
...(setupStatus.ok ? { setup: setupStatus.value } : {}),
|
|
990
|
+
sectionErrors,
|
|
991
|
+
},
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
const listed = runs.listRuns({ project });
|
|
995
|
+
if (!listed.ok)
|
|
996
|
+
return listed;
|
|
997
|
+
const visibleRuns = listed.value.slice(0, limit.value ?? 10);
|
|
998
|
+
const details = collectRunDetails(runs, visibleRuns);
|
|
999
|
+
if (!details.ok)
|
|
1000
|
+
return details;
|
|
1001
|
+
const insights = collectRunInsights(runs, visibleRuns);
|
|
1002
|
+
if (!insights.ok)
|
|
1003
|
+
return insights;
|
|
1004
|
+
const generatedAt = new Date().toISOString();
|
|
1005
|
+
const workflowsReadModel = buildDashboardWorkflowsReadModel({ project, runs: visibleRuns, generatedAt });
|
|
1006
|
+
const operationalRuns = buildDashboardRunsReadModel({ project, runs: visibleRuns, detailsByRunId: details.value, insightsByRunId: insights.value, generatedAt });
|
|
1007
|
+
const approvalsReadModel = buildDashboardApprovalsReadModel({ project, runs: visibleRuns, detailsByRunId: details.value, generatedAt });
|
|
1008
|
+
const setupStatus = readSetupStatus(setup, { project, projectRoot: environment.cwd, scope: 'project' });
|
|
1009
|
+
const sectionErrors = {};
|
|
1010
|
+
if (!setupStatus.ok)
|
|
1011
|
+
sectionErrors.setup = sanitizeDashboardError(setupStatus.error.message);
|
|
1012
|
+
const profiles = buildDashboardProfiles(managerProfiles, project, setupStatus.ok ? setupStatus.value : undefined, sectionErrors);
|
|
1013
|
+
const agentsSkills = buildDashboardAgentsSkills(agents, skills, project, limit.value ?? 10, sectionErrors);
|
|
1014
|
+
const doctor = buildDashboardDoctor(project, setupStatus.ok ? setupStatus.value : undefined, sectionErrors);
|
|
1015
|
+
if (change === undefined)
|
|
1016
|
+
return { ok: true, value: { runs: visibleRuns, runInsights: insights.value, workflows: workflowsReadModel, operationalRuns, approvals: approvalsReadModel, ...(setupStatus.ok ? { setup: setupStatus.value } : {}), profiles, agentsSkills, doctor, sectionErrors } };
|
|
1017
|
+
const status = sdd.getDashboardStatus({ project, change });
|
|
1018
|
+
if (!status.ok)
|
|
1019
|
+
sectionErrors.sdd = sanitizeDashboardError(status.error.message);
|
|
1020
|
+
const readiness = status.ok && status.value.nextReadyPhase !== undefined ? sdd.getReady({ project, change, phase: status.value.nextReadyPhase }) : undefined;
|
|
1021
|
+
if (readiness !== undefined && !readiness.ok)
|
|
1022
|
+
sectionErrors.sdd = sanitizeDashboardError(readiness.error.message);
|
|
1023
|
+
return {
|
|
1024
|
+
ok: true,
|
|
1025
|
+
value: {
|
|
1026
|
+
runs: visibleRuns,
|
|
1027
|
+
runInsights: insights.value,
|
|
1028
|
+
workflows: workflowsReadModel,
|
|
1029
|
+
operationalRuns,
|
|
1030
|
+
approvals: approvalsReadModel,
|
|
1031
|
+
...(setupStatus.ok ? { setup: setupStatus.value } : {}),
|
|
1032
|
+
...(status.ok ? { sdd: { change, status: status.value, ...(readiness?.ok ? { readiness: readiness.value } : {}) } } : {}),
|
|
1033
|
+
profiles,
|
|
1034
|
+
agentsSkills,
|
|
1035
|
+
doctor,
|
|
1036
|
+
sectionErrors,
|
|
1037
|
+
},
|
|
1038
|
+
};
|
|
1039
|
+
};
|
|
1040
|
+
await runInteractiveDashboardLoop({ ...(project === undefined ? {} : { project }), loader, input: terminal.value.input, output: terminal.value.output, env: environment.env });
|
|
1041
|
+
return { exitCode: 0, stdout: '', stderr: '' };
|
|
1042
|
+
}
|
|
1043
|
+
finally {
|
|
1044
|
+
opened.value.close();
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
function buildDashboardProfiles(managerProfiles, project, setupStatus, sectionErrors) {
|
|
1048
|
+
const resolved = managerProfiles.resolveEffectiveManager({ project, scope: 'project', managerName: 'vgxness-manager' });
|
|
1049
|
+
if (!resolved.ok) {
|
|
1050
|
+
sectionErrors.agents = sanitizeDashboardError(resolved.error.message);
|
|
1051
|
+
return { status: setupStatus === undefined ? 'error' : 'blocked', managerName: 'vgxness-manager', scope: 'project', error: sectionErrors.agents };
|
|
1052
|
+
}
|
|
1053
|
+
const providerAdapter = setupStatus?.defaults.providerAdapter ?? Object.keys(resolved.value.manager.adapters)[0];
|
|
1054
|
+
const adapter = providerAdapter === undefined ? undefined : resolved.value.manager.adapters[providerAdapter];
|
|
1055
|
+
return {
|
|
1056
|
+
status: providerAdapter === undefined && adapter?.model === undefined ? 'empty' : 'ready',
|
|
1057
|
+
managerName: 'vgxness-manager',
|
|
1058
|
+
scope: 'project',
|
|
1059
|
+
agentId: resolved.value.manager.id,
|
|
1060
|
+
...(providerAdapter === undefined ? {} : { providerAdapter }),
|
|
1061
|
+
...(adapter?.model === undefined ? setupStatus?.defaults.model === undefined ? {} : { model: setupStatus.defaults.model } : { model: adapter.model }),
|
|
1062
|
+
...(setupStatus?.defaults.profile === undefined ? {} : { profile: setupStatus.defaults.profile }),
|
|
1063
|
+
overlayPresent: resolved.value.overlay !== undefined,
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
function buildDashboardAgentsSkills(agents, skills, project, limit, sectionErrors) {
|
|
1067
|
+
const agentList = agents.listAgents({ project, scope: 'project' });
|
|
1068
|
+
const skillList = skills.listSkills({ project, scope: 'project' });
|
|
1069
|
+
if (!agentList.ok)
|
|
1070
|
+
sectionErrors.agents = sanitizeDashboardError(agentList.error.message);
|
|
1071
|
+
if (!skillList.ok)
|
|
1072
|
+
sectionErrors.agents = sanitizeDashboardError(skillList.error.message);
|
|
1073
|
+
const agentValues = agentList.ok ? agentList.value : [];
|
|
1074
|
+
const skillValues = skillList.ok ? skillList.value : [];
|
|
1075
|
+
const visibleAgents = agentValues.slice(0, limit).map((summary) => {
|
|
1076
|
+
const details = agents.getAgent(summary.id);
|
|
1077
|
+
const providerAdapter = details.ok ? Object.keys(details.value.adapters)[0] : undefined;
|
|
1078
|
+
const model = details.ok && providerAdapter !== undefined ? details.value.adapters[providerAdapter]?.model : undefined;
|
|
1079
|
+
return {
|
|
1080
|
+
id: summary.id,
|
|
1081
|
+
name: summary.name,
|
|
1082
|
+
mode: summary.mode,
|
|
1083
|
+
scope: summary.scope,
|
|
1084
|
+
description: summary.description,
|
|
1085
|
+
...(providerAdapter === undefined ? {} : { providerAdapter }),
|
|
1086
|
+
...(model === undefined ? {} : { model }),
|
|
1087
|
+
...(summary.parentAgentId === undefined ? {} : { parentAgentId: summary.parentAgentId }),
|
|
1088
|
+
};
|
|
1089
|
+
});
|
|
1090
|
+
const visibleSkills = skillValues.slice(0, limit).map((skill) => ({ id: skill.id, name: skill.name, scope: skill.scope, description: skill.description }));
|
|
1091
|
+
const status = sectionErrors.agents !== undefined ? 'error' : visibleAgents.length === 0 && visibleSkills.length === 0 ? 'empty' : 'ready';
|
|
1092
|
+
return { status, agents: visibleAgents, skills: visibleSkills, truncated: agentValues.length > limit || skillValues.length > limit, ...(sectionErrors.agents === undefined ? {} : { error: sectionErrors.agents }) };
|
|
1093
|
+
}
|
|
1094
|
+
function buildDashboardDoctor(project, setupStatus, sectionErrors) {
|
|
1095
|
+
void sectionErrors;
|
|
1096
|
+
if (setupStatus === undefined) {
|
|
1097
|
+
return { status: 'empty', summary: 'No setup evidence is available; the TUI did not run doctor checks.', warnings: ['Doctor execution is explicit because checks may prepare local state.'], commands: ['npm run cli -- mcp doctor', 'npm run cli -- mcp doctor opencode'] };
|
|
1098
|
+
}
|
|
1099
|
+
const blocked = setupStatus.store.status !== 'ready' || setupStatus.defaults.status !== 'ready' || setupStatus.mcp.status !== 'ready';
|
|
1100
|
+
return {
|
|
1101
|
+
status: blocked ? 'blocked' : 'ready',
|
|
1102
|
+
summary: blocked ? `Setup diagnostics need attention for ${project}: ${setupStatus.nextAction.reason}` : `Setup evidence is ready for ${project}; run doctor outside TUI for fresh checks.`,
|
|
1103
|
+
warnings: [...setupStatus.mcp.evidence, 'The TUI never executes doctor checks implicitly.'].slice(0, 4),
|
|
1104
|
+
commands: ['npm run cli -- mcp doctor', 'npm run cli -- mcp doctor opencode', 'npm run cli -- setup status'],
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
async function runInteractiveDashboardLoop(input) {
|
|
1108
|
+
let state = await loadInitialDashboardState(input.project, input.loader);
|
|
1109
|
+
const renderStyle = resolveDashboardRenderStyle({ isTTY: input.output.isTTY, env: input.env });
|
|
1110
|
+
return new Promise((resolve) => {
|
|
1111
|
+
const render = () => input.output.write(`\u001Bc${renderInteractiveDashboard(state, { style: renderStyle })}`);
|
|
1112
|
+
const finish = () => {
|
|
1113
|
+
input.input.setRawMode?.(false);
|
|
1114
|
+
input.input.pause?.();
|
|
1115
|
+
if (input.input.off !== undefined)
|
|
1116
|
+
input.input.off('data', onData);
|
|
1117
|
+
else
|
|
1118
|
+
input.input.removeListener?.('data', onData);
|
|
1119
|
+
resolve();
|
|
1120
|
+
};
|
|
1121
|
+
const onData = (chunk) => {
|
|
1122
|
+
void (async () => {
|
|
1123
|
+
const key = dashboardKeyFromInput(chunk.toString());
|
|
1124
|
+
if (key === undefined)
|
|
1125
|
+
return;
|
|
1126
|
+
if (key === 'refresh')
|
|
1127
|
+
state = await refreshDashboard(state, input.loader);
|
|
1128
|
+
else
|
|
1129
|
+
state = reduceDashboardKey(state, key);
|
|
1130
|
+
render();
|
|
1131
|
+
if (state.shouldExit)
|
|
1132
|
+
finish();
|
|
1133
|
+
})();
|
|
1134
|
+
};
|
|
1135
|
+
input.input.setRawMode?.(true);
|
|
1136
|
+
input.input.resume?.();
|
|
1137
|
+
input.input.on('data', onData);
|
|
1138
|
+
render();
|
|
1139
|
+
if (state.shouldExit)
|
|
1140
|
+
finish();
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
function resolveInteractiveTerminal(environment) {
|
|
1144
|
+
const input = environment.stdin;
|
|
1145
|
+
const output = environment.stdout;
|
|
1146
|
+
if (input === undefined || output === undefined || input.isTTY !== true || output.isTTY !== true || input.setRawMode === undefined) {
|
|
1147
|
+
return validationFailure('Interactive dashboard mode is unavailable because stdin/stdout are not an interactive terminal');
|
|
1148
|
+
}
|
|
1149
|
+
return { ok: true, value: { input, output } };
|
|
1150
|
+
}
|
|
1151
|
+
async function runMcpInstallCommand(parsed, environment) {
|
|
1152
|
+
const client = parsed.positionals[2];
|
|
1153
|
+
if (client !== 'opencode')
|
|
1154
|
+
return usageFailure('mcp install requires client opencode');
|
|
1155
|
+
const databasePath = databasePathSelectionFor(parsed.flags, environment);
|
|
1156
|
+
if (!databasePath.ok)
|
|
1157
|
+
return resultFailure(databasePath);
|
|
1158
|
+
const scope = opencodeInstallScopeFlag(parsed.flags, 'scope');
|
|
1159
|
+
if (!scope.ok)
|
|
1160
|
+
return resultFailure(scope);
|
|
1161
|
+
const mcpOnly = parsed.flags['mcp-only'] === true || parsed.flags['no-agents'] === true;
|
|
1162
|
+
if (parsed.flags.plan === true) {
|
|
1163
|
+
return jsonResult({
|
|
1164
|
+
ok: true,
|
|
1165
|
+
value: planOpenCodeMcpInstall({ cwd: environment.cwd, databasePath: databasePath.value.path, databasePathSource: databasePath.value.source, scope: scope.value, env: environment.env, mcpOnly }),
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
const result = await installOpenCodeMcpClient({
|
|
1169
|
+
cwd: environment.cwd,
|
|
1170
|
+
databasePath: databasePath.value.path,
|
|
1171
|
+
databasePathSource: databasePath.value.source,
|
|
1172
|
+
scope: scope.value,
|
|
1173
|
+
env: environment.env,
|
|
1174
|
+
confirmed: parsed.flags.yes === true,
|
|
1175
|
+
mcpOnly,
|
|
1176
|
+
});
|
|
1177
|
+
return result.status === 'installed'
|
|
1178
|
+
? jsonResult({ ok: true, value: result })
|
|
1179
|
+
: resultFailure({ ok: false, error: { code: 'validation_failed', message: `${result.reason}: ${result.message} Manual doctor command: ${result.manualTest.doctorCommand.join(' ')}` } });
|
|
1180
|
+
}
|
|
1181
|
+
function runMcpSetupCommand(parsed, environment) {
|
|
1182
|
+
if (parsed.flags.preview !== true)
|
|
1183
|
+
return usageFailure('mcp setup requires --preview; provider config installation is not supported');
|
|
1184
|
+
const provider = optionalStringFlag(parsed.flags, 'provider') ?? 'opencode';
|
|
1185
|
+
if (!isMcpClientSetupProvider(provider))
|
|
1186
|
+
return resultFailure(validationFailure('--provider must be opencode or claude'));
|
|
1187
|
+
const databasePath = databasePathSelectionFor(parsed.flags, environment);
|
|
1188
|
+
if (!databasePath.ok)
|
|
1189
|
+
return resultFailure(databasePath);
|
|
1190
|
+
return jsonResult({
|
|
1191
|
+
ok: true,
|
|
1192
|
+
value: createMcpClientSetupPreview({ provider, databasePath: databasePath.value.path, databasePathSource: databasePath.value.source }),
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
async function runMcpDoctorCommand(parsed, environment) {
|
|
1196
|
+
if (parsed.positionals[2] === 'opencode')
|
|
1197
|
+
return runMcpDoctorOpenCodeCommand(parsed, environment);
|
|
1198
|
+
const timeoutMs = optionalNumberFlag(parsed.flags, 'timeout-ms');
|
|
1199
|
+
if (!timeoutMs.ok)
|
|
1200
|
+
return resultFailure(timeoutMs);
|
|
1201
|
+
const databasePath = databasePathFor(parsed.flags, environment);
|
|
1202
|
+
if (!databasePath.ok)
|
|
1203
|
+
return resultFailure(databasePath);
|
|
1204
|
+
const report = await createMcpDoctorReport({
|
|
1205
|
+
cwd: environment.cwd,
|
|
1206
|
+
databasePath: databasePath.value,
|
|
1207
|
+
project: optionalStringFlag(parsed.flags, 'project') ?? 'vgxness',
|
|
1208
|
+
change: optionalStringFlag(parsed.flags, 'change') ?? 'doctor-smoke',
|
|
1209
|
+
timeoutMs: timeoutMs.value ?? 5_000,
|
|
1210
|
+
env: environment.env,
|
|
1211
|
+
});
|
|
1212
|
+
return { exitCode: report.ready ? 0 : 1, stdout: `${JSON.stringify(report, null, 2)}\n`, stderr: '' };
|
|
1213
|
+
}
|
|
1214
|
+
function runMcpDoctorOpenCodeCommand(parsed, environment) {
|
|
1215
|
+
const scope = opencodeInstallScopeFlag(parsed.flags, 'scope');
|
|
1216
|
+
if (!scope.ok)
|
|
1217
|
+
return resultFailure(scope);
|
|
1218
|
+
const projectRoot = optionalStringFlag(parsed.flags, 'project-root');
|
|
1219
|
+
const report = createOpenCodeMcpVisibilityReport({
|
|
1220
|
+
cwd: environment.cwd,
|
|
1221
|
+
env: environment.env,
|
|
1222
|
+
scope: scope.value,
|
|
1223
|
+
...(projectRoot !== undefined ? { projectRoot } : {}),
|
|
1224
|
+
});
|
|
1225
|
+
return { exitCode: report.ready ? 0 : 1, stdout: `${JSON.stringify(report, null, 2)}\n`, stderr: '' };
|
|
1226
|
+
}
|
|
1227
|
+
function runOpenCodeCommand(command, parsed, database, environment) {
|
|
1228
|
+
if (command !== 'preview')
|
|
1229
|
+
return usageFailure(`Unknown opencode command: ${command}`);
|
|
1230
|
+
const scope = optionalScopeFlag(parsed.flags, 'scope');
|
|
1231
|
+
if (!scope.ok)
|
|
1232
|
+
return resultFailure(scope);
|
|
1233
|
+
const agents = new AgentRegistryService(database);
|
|
1234
|
+
const managerProfiles = new ManagerProfileOverlayService({ agents, overlays: new ManagerProfileOverlayRepository(database) });
|
|
1235
|
+
const service = new OpenCodeInjectionPreviewService({
|
|
1236
|
+
agents,
|
|
1237
|
+
managerProfiles,
|
|
1238
|
+
skills: new SkillRegistryService(database),
|
|
1239
|
+
sdd: new SddWorkflowService(new MemoryService(database)),
|
|
1240
|
+
});
|
|
1241
|
+
const provider = optionalStringFlag(parsed.flags, 'provider');
|
|
1242
|
+
const project = optionalStringFlag(parsed.flags, 'project');
|
|
1243
|
+
const change = optionalStringFlag(parsed.flags, 'change');
|
|
1244
|
+
const phase = optionalStringFlag(parsed.flags, 'phase');
|
|
1245
|
+
const agentId = optionalStringFlag(parsed.flags, 'agent-id');
|
|
1246
|
+
const agentName = optionalStringFlag(parsed.flags, 'agent');
|
|
1247
|
+
return jsonResult(service.preview({
|
|
1248
|
+
...(provider !== undefined ? { provider } : {}),
|
|
1249
|
+
...(project !== undefined ? { project } : {}),
|
|
1250
|
+
...(change !== undefined ? { change } : {}),
|
|
1251
|
+
...(phase !== undefined ? { phase } : {}),
|
|
1252
|
+
...(agentId !== undefined ? { agentId } : {}),
|
|
1253
|
+
...(agentName !== undefined ? { agentName } : {}),
|
|
1254
|
+
...(scope.value !== undefined ? { scope: scope.value } : {}),
|
|
1255
|
+
workspaceRoot: environment.cwd,
|
|
1256
|
+
}));
|
|
1257
|
+
}
|
|
1258
|
+
function runSddCommand(command, parsed, database, environment) {
|
|
1259
|
+
const memory = new MemoryService(database);
|
|
1260
|
+
const service = new SddWorkflowService(memory);
|
|
1261
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
1262
|
+
const change = requiredFlag(parsed.flags, 'change');
|
|
1263
|
+
if (!project.ok)
|
|
1264
|
+
return resultFailure(project);
|
|
1265
|
+
if (!change.ok)
|
|
1266
|
+
return resultFailure(change);
|
|
1267
|
+
if (command === 'status') {
|
|
1268
|
+
return jsonResult(service.getStatus({ project: project.value, change: change.value }));
|
|
1269
|
+
}
|
|
1270
|
+
if (command === 'next') {
|
|
1271
|
+
return jsonResult(service.getNext({ project: project.value, change: change.value }));
|
|
1272
|
+
}
|
|
1273
|
+
if (command === 'ready') {
|
|
1274
|
+
const phase = requiredFlag(parsed.flags, 'phase');
|
|
1275
|
+
if (!phase.ok)
|
|
1276
|
+
return resultFailure(phase);
|
|
1277
|
+
return jsonResult(service.getReady({ project: project.value, change: change.value, phase: phase.value }));
|
|
1278
|
+
}
|
|
1279
|
+
if (command === 'save-artifact') {
|
|
1280
|
+
const phase = requiredFlag(parsed.flags, 'phase');
|
|
1281
|
+
const content = requiredFlag(parsed.flags, 'content');
|
|
1282
|
+
if (!phase.ok)
|
|
1283
|
+
return resultFailure(phase);
|
|
1284
|
+
if (!content.ok)
|
|
1285
|
+
return resultFailure(content);
|
|
1286
|
+
return jsonResult(service.saveArtifact({ project: project.value, change: change.value, phase: phase.value, content: content.value }));
|
|
1287
|
+
}
|
|
1288
|
+
if (command === 'get-artifact') {
|
|
1289
|
+
const phase = requiredFlag(parsed.flags, 'phase');
|
|
1290
|
+
if (!phase.ok)
|
|
1291
|
+
return resultFailure(phase);
|
|
1292
|
+
return jsonResult(service.getArtifact({ project: project.value, change: change.value, phase: phase.value }));
|
|
1293
|
+
}
|
|
1294
|
+
if (command === 'list-artifacts') {
|
|
1295
|
+
return jsonResult(service.listArtifacts({ project: project.value, change: change.value }));
|
|
1296
|
+
}
|
|
1297
|
+
if (command === 'export') {
|
|
1298
|
+
const portability = new ArtifactPortabilityService(memory, service);
|
|
1299
|
+
const exported = portability.exportPackage({ project: project.value, change: change.value });
|
|
1300
|
+
if (!exported.ok)
|
|
1301
|
+
return resultFailure(exported);
|
|
1302
|
+
const file = optionalStringFlag(parsed.flags, 'file');
|
|
1303
|
+
if (file !== undefined) {
|
|
1304
|
+
const written = writeJsonFile(file, exported.value, environment);
|
|
1305
|
+
if (!written.ok)
|
|
1306
|
+
return resultFailure(written);
|
|
1307
|
+
}
|
|
1308
|
+
return jsonResult(exported);
|
|
1309
|
+
}
|
|
1310
|
+
if (command === 'import') {
|
|
1311
|
+
const file = requiredFlag(parsed.flags, 'file');
|
|
1312
|
+
if (!file.ok)
|
|
1313
|
+
return resultFailure(file);
|
|
1314
|
+
const artifactPackage = readJsonFile(file.value, environment);
|
|
1315
|
+
if (!artifactPackage.ok)
|
|
1316
|
+
return resultFailure(artifactPackage);
|
|
1317
|
+
const portability = new ArtifactPortabilityService(memory, service);
|
|
1318
|
+
const input = { project: project.value, change: change.value, package: artifactPackage.value, overwrite: parsed.flags.overwrite === true };
|
|
1319
|
+
return jsonResult(parsed.flags.write === true ? portability.importPackage(input) : portability.planImport(input));
|
|
1320
|
+
}
|
|
1321
|
+
return usageFailure(`Unknown sdd command: ${command}`);
|
|
1322
|
+
}
|
|
1323
|
+
function runOrchestratorCommand(command, parsed, database) {
|
|
1324
|
+
if (command !== 'preview')
|
|
1325
|
+
return usageFailure(`Unknown orchestrator command: ${command}`);
|
|
1326
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
1327
|
+
const intent = requiredFlag(parsed.flags, 'intent');
|
|
1328
|
+
if (!project.ok)
|
|
1329
|
+
return resultFailure(project);
|
|
1330
|
+
if (!intent.ok)
|
|
1331
|
+
return resultFailure(intent);
|
|
1332
|
+
const change = optionalStringFlag(parsed.flags, 'change');
|
|
1333
|
+
const sdd = new SddWorkflowService(new MemoryService(database));
|
|
1334
|
+
const sddContext = {};
|
|
1335
|
+
if (change !== undefined) {
|
|
1336
|
+
const status = sdd.getStatus({ project: project.value, change });
|
|
1337
|
+
if (!status.ok)
|
|
1338
|
+
return resultFailure(status);
|
|
1339
|
+
const next = sdd.getNext({ project: project.value, change });
|
|
1340
|
+
if (!next.ok)
|
|
1341
|
+
return resultFailure(next);
|
|
1342
|
+
sddContext.status = status.value;
|
|
1343
|
+
sddContext.next = next.value;
|
|
1344
|
+
}
|
|
1345
|
+
return jsonResult({
|
|
1346
|
+
ok: true,
|
|
1347
|
+
value: createNaturalLanguagePlan({
|
|
1348
|
+
project: project.value,
|
|
1349
|
+
intent: intent.value,
|
|
1350
|
+
...(change !== undefined ? { change } : {}),
|
|
1351
|
+
...(change !== undefined ? { sdd: sddContext } : {}),
|
|
1352
|
+
}),
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
function runDashboardCommand(command, parsed, database, environment, databasePath) {
|
|
1356
|
+
if (command !== 'status')
|
|
1357
|
+
return usageFailure(`Unknown dashboard command: ${command}`);
|
|
1358
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
1359
|
+
if (!project.ok)
|
|
1360
|
+
return resultFailure(project);
|
|
1361
|
+
const limit = optionalNumberFlag(parsed.flags, 'limit');
|
|
1362
|
+
if (!limit.ok)
|
|
1363
|
+
return resultFailure(limit);
|
|
1364
|
+
const runs = new RunService(database);
|
|
1365
|
+
const listed = runs.listRuns({ project: project.value });
|
|
1366
|
+
if (!listed.ok)
|
|
1367
|
+
return resultFailure(listed);
|
|
1368
|
+
const runId = optionalStringFlag(parsed.flags, 'run-id');
|
|
1369
|
+
const selectedRun = runId === undefined ? undefined : runs.getRun(runId);
|
|
1370
|
+
if (selectedRun !== undefined && !selectedRun.ok)
|
|
1371
|
+
return resultFailure(validationFailure(`Unknown run: ${runId}`));
|
|
1372
|
+
const selectedRunInsights = runId === undefined ? undefined : runs.getRunInsights(runId);
|
|
1373
|
+
if (selectedRunInsights !== undefined && !selectedRunInsights.ok)
|
|
1374
|
+
return resultFailure(selectedRunInsights);
|
|
1375
|
+
const change = optionalStringFlag(parsed.flags, 'change');
|
|
1376
|
+
const memory = new MemoryService(database);
|
|
1377
|
+
const sddService = new SddWorkflowService(memory);
|
|
1378
|
+
const setupService = createSetupLifecycleService({ opened: { ok: true, value: database }, databasePath, environment });
|
|
1379
|
+
const setupStatus = readSetupStatus(setupService, { project: project.value, projectRoot: environment.cwd, scope: 'project' });
|
|
1380
|
+
if (!setupStatus.ok)
|
|
1381
|
+
return resultFailure(setupStatus);
|
|
1382
|
+
const sddStatus = change === undefined ? undefined : sddService.getStatus({ project: project.value, change });
|
|
1383
|
+
if (sddStatus !== undefined && !sddStatus.ok)
|
|
1384
|
+
return resultFailure(sddStatus);
|
|
1385
|
+
const nextReadyPhase = sddStatus?.value.nextReadyPhase;
|
|
1386
|
+
const readiness = change !== undefined && nextReadyPhase !== undefined ? sddService.getReady({ project: project.value, change, phase: nextReadyPhase }) : undefined;
|
|
1387
|
+
if (readiness !== undefined && !readiness.ok)
|
|
1388
|
+
return resultFailure(readiness);
|
|
1389
|
+
return okText(renderStatusDashboard({
|
|
1390
|
+
project: project.value,
|
|
1391
|
+
databasePath,
|
|
1392
|
+
runs: listed.value.slice(0, limit.value ?? 5),
|
|
1393
|
+
setup: setupStatus.value,
|
|
1394
|
+
...(selectedRun?.ok ? { selectedRun: selectedRun.value } : {}),
|
|
1395
|
+
...(selectedRunInsights?.ok ? { selectedRunInsights: selectedRunInsights.value } : {}),
|
|
1396
|
+
...(change !== undefined && sddStatus?.ok ? { sdd: { change, status: sddStatus.value, ...(readiness?.ok ? { readiness: readiness.value } : {}) } } : {}),
|
|
1397
|
+
}));
|
|
1398
|
+
}
|
|
1399
|
+
function runSkillCommand(command, parsed, database, environment) {
|
|
1400
|
+
const registry = new SkillRegistryService(database);
|
|
1401
|
+
if (command === 'register') {
|
|
1402
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
1403
|
+
const scope = scopeFlag(parsed.flags, 'scope', 'project');
|
|
1404
|
+
const name = requiredFlag(parsed.flags, 'name');
|
|
1405
|
+
const description = requiredFlag(parsed.flags, 'description');
|
|
1406
|
+
if (!project.ok)
|
|
1407
|
+
return resultFailure(project);
|
|
1408
|
+
if (!scope.ok)
|
|
1409
|
+
return resultFailure(scope);
|
|
1410
|
+
if (!name.ok)
|
|
1411
|
+
return resultFailure(name);
|
|
1412
|
+
if (!description.ok)
|
|
1413
|
+
return resultFailure(description);
|
|
1414
|
+
return jsonResult(registry.registerSkill({ project: project.value, scope: scope.value, name: name.value, description: description.value }));
|
|
1415
|
+
}
|
|
1416
|
+
if (command === 'list') {
|
|
1417
|
+
const filters = {};
|
|
1418
|
+
const project = optionalStringFlag(parsed.flags, 'project');
|
|
1419
|
+
if (project !== undefined)
|
|
1420
|
+
filters.project = project;
|
|
1421
|
+
const scope = optionalScopeFlag(parsed.flags, 'scope');
|
|
1422
|
+
if (!scope.ok)
|
|
1423
|
+
return resultFailure(scope);
|
|
1424
|
+
if (scope.value !== undefined)
|
|
1425
|
+
filters.scope = scope.value;
|
|
1426
|
+
return jsonResult(registry.listSkills(filters));
|
|
1427
|
+
}
|
|
1428
|
+
if (command === 'get') {
|
|
1429
|
+
const skill = resolveSkill(parsed, registry);
|
|
1430
|
+
return skill.ok ? jsonResult(registry.getSkillDetails(skill.value.id)) : resultFailure(skill);
|
|
1431
|
+
}
|
|
1432
|
+
if (command === 'add-version') {
|
|
1433
|
+
const skill = resolveSkill(parsed, registry);
|
|
1434
|
+
if (!skill.ok)
|
|
1435
|
+
return resultFailure(skill);
|
|
1436
|
+
const version = requiredFlag(parsed.flags, 'version');
|
|
1437
|
+
const source = skillSourceFromFlags(parsed.flags);
|
|
1438
|
+
const status = optionalSkillVersionStatusFlag(parsed.flags, 'status');
|
|
1439
|
+
if (!version.ok)
|
|
1440
|
+
return resultFailure(version);
|
|
1441
|
+
if (!source.ok)
|
|
1442
|
+
return resultFailure(source);
|
|
1443
|
+
if (!status.ok)
|
|
1444
|
+
return resultFailure(status);
|
|
1445
|
+
const compatibility = optionalJsonFlag(parsed.flags, 'compatibility');
|
|
1446
|
+
if (!compatibility.ok)
|
|
1447
|
+
return resultFailure(compatibility);
|
|
1448
|
+
return jsonResult(registry.addSkillVersion({
|
|
1449
|
+
skillId: skill.value.id,
|
|
1450
|
+
version: version.value,
|
|
1451
|
+
source: source.value,
|
|
1452
|
+
...(compatibility.value !== undefined ? { compatibility: compatibility.value } : {}),
|
|
1453
|
+
...(status.value !== undefined ? { status: status.value } : {}),
|
|
1454
|
+
...(parsed.flags.activate === true ? { activate: true } : {}),
|
|
1455
|
+
}));
|
|
1456
|
+
}
|
|
1457
|
+
if (command === 'attach') {
|
|
1458
|
+
const skill = resolveSkill(parsed, registry);
|
|
1459
|
+
const targetType = skillTargetTypeFlag(parsed.flags, 'target-type');
|
|
1460
|
+
const targetKey = requiredFlag(parsed.flags, 'target-key');
|
|
1461
|
+
if (!skill.ok)
|
|
1462
|
+
return resultFailure(skill);
|
|
1463
|
+
if (!targetType.ok)
|
|
1464
|
+
return resultFailure(targetType);
|
|
1465
|
+
if (!targetKey.ok)
|
|
1466
|
+
return resultFailure(targetKey);
|
|
1467
|
+
const versionId = optionalStringFlag(parsed.flags, 'version-id') ?? skill.value.currentVersionId;
|
|
1468
|
+
const metadata = jsonFlag(parsed.flags, 'metadata', {});
|
|
1469
|
+
if (!metadata.ok)
|
|
1470
|
+
return resultFailure(metadata);
|
|
1471
|
+
return jsonResult(registry.attachSkill({ skillId: skill.value.id, targetType: targetType.value, targetKey: targetKey.value, ...(versionId !== undefined ? { versionId } : {}), metadata: metadata.value }));
|
|
1472
|
+
}
|
|
1473
|
+
if (command === 'detach') {
|
|
1474
|
+
const skill = resolveSkill(parsed, registry);
|
|
1475
|
+
const targetType = skillTargetTypeFlag(parsed.flags, 'target-type');
|
|
1476
|
+
const targetKey = requiredFlag(parsed.flags, 'target-key');
|
|
1477
|
+
if (!skill.ok)
|
|
1478
|
+
return resultFailure(skill);
|
|
1479
|
+
if (!targetType.ok)
|
|
1480
|
+
return resultFailure(targetType);
|
|
1481
|
+
if (!targetKey.ok)
|
|
1482
|
+
return resultFailure(targetKey);
|
|
1483
|
+
return jsonResult(registry.detachSkill(skill.value.id, targetType.value, targetKey.value));
|
|
1484
|
+
}
|
|
1485
|
+
if (command === 'record-usage') {
|
|
1486
|
+
const skill = resolveSkill(parsed, registry);
|
|
1487
|
+
const outcome = skillUsageOutcomeFlag(parsed.flags, 'outcome');
|
|
1488
|
+
if (!skill.ok)
|
|
1489
|
+
return resultFailure(skill);
|
|
1490
|
+
if (!outcome.ok)
|
|
1491
|
+
return resultFailure(outcome);
|
|
1492
|
+
const targetType = optionalSkillTargetTypeFlag(parsed.flags, 'target-type');
|
|
1493
|
+
if (!targetType.ok)
|
|
1494
|
+
return resultFailure(targetType);
|
|
1495
|
+
const targetKey = optionalStringFlag(parsed.flags, 'target-key');
|
|
1496
|
+
const versionId = optionalStringFlag(parsed.flags, 'version-id') ?? skill.value.currentVersionId;
|
|
1497
|
+
const runId = optionalStringFlag(parsed.flags, 'run-id');
|
|
1498
|
+
const notes = optionalStringFlag(parsed.flags, 'notes');
|
|
1499
|
+
return jsonResult(registry.recordSkillUsage({
|
|
1500
|
+
skillId: skill.value.id,
|
|
1501
|
+
...(versionId !== undefined ? { versionId } : {}),
|
|
1502
|
+
...(runId !== undefined ? { runId } : {}),
|
|
1503
|
+
...(targetType.value !== undefined ? { targetType: targetType.value } : {}),
|
|
1504
|
+
...(targetKey !== undefined ? { targetKey } : {}),
|
|
1505
|
+
outcome: outcome.value,
|
|
1506
|
+
...(notes !== undefined ? { notes } : {}),
|
|
1507
|
+
}));
|
|
1508
|
+
}
|
|
1509
|
+
if (command === 'resolve') {
|
|
1510
|
+
const input = resolveSkillsInput(parsed.flags);
|
|
1511
|
+
return input.ok ? jsonResult(registry.resolveSkills(input.value)) : resultFailure(input);
|
|
1512
|
+
}
|
|
1513
|
+
if (command === 'propose') {
|
|
1514
|
+
const skill = resolveSkill(parsed, registry);
|
|
1515
|
+
if (!skill.ok)
|
|
1516
|
+
return resultFailure(skill);
|
|
1517
|
+
const baseVersionId = optionalStringFlag(parsed.flags, 'base-version-id') ?? skill.value.currentVersionId;
|
|
1518
|
+
if (baseVersionId === undefined)
|
|
1519
|
+
return resultFailure(validationFailure('Missing required --base-version-id because the skill has no current version'));
|
|
1520
|
+
const proposedVersion = requiredFlag(parsed.flags, 'proposed-version');
|
|
1521
|
+
const proposedSource = skillSourceFromFlags(parsed.flags);
|
|
1522
|
+
const rationale = requiredFlag(parsed.flags, 'rationale');
|
|
1523
|
+
const compatibility = optionalJsonFlag(parsed.flags, 'compatibility');
|
|
1524
|
+
const sourceSignal = jsonFlag(parsed.flags, 'source-signal', {});
|
|
1525
|
+
if (!proposedVersion.ok)
|
|
1526
|
+
return resultFailure(proposedVersion);
|
|
1527
|
+
if (!proposedSource.ok)
|
|
1528
|
+
return resultFailure(proposedSource);
|
|
1529
|
+
if (!rationale.ok)
|
|
1530
|
+
return resultFailure(rationale);
|
|
1531
|
+
if (!compatibility.ok)
|
|
1532
|
+
return resultFailure(compatibility);
|
|
1533
|
+
if (!sourceSignal.ok)
|
|
1534
|
+
return resultFailure(sourceSignal);
|
|
1535
|
+
return jsonResult(registry.createSkillImprovementProposal({
|
|
1536
|
+
skillId: skill.value.id,
|
|
1537
|
+
baseVersionId,
|
|
1538
|
+
proposedVersion: proposedVersion.value,
|
|
1539
|
+
proposedSource: proposedSource.value,
|
|
1540
|
+
...(compatibility.value !== undefined ? { proposedCompatibility: compatibility.value } : {}),
|
|
1541
|
+
rationale: rationale.value,
|
|
1542
|
+
sourceSignal: sourceSignal.value,
|
|
1543
|
+
}));
|
|
1544
|
+
}
|
|
1545
|
+
if (command === 'proposals') {
|
|
1546
|
+
const filters = {};
|
|
1547
|
+
const skillId = optionalStringFlag(parsed.flags, 'skill-id');
|
|
1548
|
+
if (skillId !== undefined)
|
|
1549
|
+
filters.skillId = skillId;
|
|
1550
|
+
const status = optionalSkillImprovementProposalStatusFlag(parsed.flags, 'status');
|
|
1551
|
+
if (!status.ok)
|
|
1552
|
+
return resultFailure(status);
|
|
1553
|
+
if (status.value !== undefined)
|
|
1554
|
+
filters.status = status.value;
|
|
1555
|
+
return jsonResult(registry.listSkillImprovementProposals(filters));
|
|
1556
|
+
}
|
|
1557
|
+
if (command === 'create-scenario') {
|
|
1558
|
+
const skill = resolveSkill(parsed, registry);
|
|
1559
|
+
if (!skill.ok)
|
|
1560
|
+
return resultFailure(skill);
|
|
1561
|
+
const name = scenarioNameFlag(parsed.flags);
|
|
1562
|
+
const criteria = jsonFlag(parsed.flags, 'criteria', {});
|
|
1563
|
+
const createdBy = requiredFlag(parsed.flags, 'created-by');
|
|
1564
|
+
if (!name.ok)
|
|
1565
|
+
return resultFailure(name);
|
|
1566
|
+
if (!criteria.ok)
|
|
1567
|
+
return resultFailure(criteria);
|
|
1568
|
+
if (!createdBy.ok)
|
|
1569
|
+
return resultFailure(createdBy);
|
|
1570
|
+
const proposalId = optionalStringFlag(parsed.flags, 'proposal');
|
|
1571
|
+
const versionId = optionalStringFlag(parsed.flags, 'version-id');
|
|
1572
|
+
return jsonResult(registry.createSkillEvaluationScenario({
|
|
1573
|
+
skillId: skill.value.id,
|
|
1574
|
+
name: name.value,
|
|
1575
|
+
criteria: criteria.value,
|
|
1576
|
+
createdBy: createdBy.value,
|
|
1577
|
+
...(proposalId !== undefined ? { proposalId } : {}),
|
|
1578
|
+
...(versionId !== undefined ? { versionId } : {}),
|
|
1579
|
+
}));
|
|
1580
|
+
}
|
|
1581
|
+
if (command === 'list-scenarios') {
|
|
1582
|
+
const skill = resolveSkill(parsed, registry);
|
|
1583
|
+
if (!skill.ok)
|
|
1584
|
+
return resultFailure(skill);
|
|
1585
|
+
const proposalId = optionalStringFlag(parsed.flags, 'proposal');
|
|
1586
|
+
const versionId = optionalStringFlag(parsed.flags, 'version-id');
|
|
1587
|
+
return jsonResult(registry.listSkillEvaluationScenarios({
|
|
1588
|
+
skillId: skill.value.id,
|
|
1589
|
+
...(proposalId !== undefined ? { proposalId } : {}),
|
|
1590
|
+
...(versionId !== undefined ? { versionId } : {}),
|
|
1591
|
+
}));
|
|
1592
|
+
}
|
|
1593
|
+
if (command === 'record-evaluation-result') {
|
|
1594
|
+
const scenarioId = requiredFlag(parsed.flags, 'scenario');
|
|
1595
|
+
const status = skillEvaluationResultStatusFlag(parsed.flags, 'status');
|
|
1596
|
+
const result = jsonFlag(parsed.flags, 'result', {});
|
|
1597
|
+
const observedBehavior = requiredFlag(parsed.flags, 'observed-behavior');
|
|
1598
|
+
const evaluator = requiredFlag(parsed.flags, 'evaluator');
|
|
1599
|
+
if (!scenarioId.ok)
|
|
1600
|
+
return resultFailure(scenarioId);
|
|
1601
|
+
if (!status.ok)
|
|
1602
|
+
return resultFailure(status);
|
|
1603
|
+
if (!result.ok)
|
|
1604
|
+
return resultFailure(result);
|
|
1605
|
+
if (!observedBehavior.ok)
|
|
1606
|
+
return resultFailure(observedBehavior);
|
|
1607
|
+
if (!evaluator.ok)
|
|
1608
|
+
return resultFailure(evaluator);
|
|
1609
|
+
const proposalId = optionalStringFlag(parsed.flags, 'proposal');
|
|
1610
|
+
const versionId = optionalStringFlag(parsed.flags, 'version-id');
|
|
1611
|
+
const notes = optionalStringFlag(parsed.flags, 'notes');
|
|
1612
|
+
return jsonResult(registry.recordSkillEvaluationResult({
|
|
1613
|
+
scenarioId: scenarioId.value,
|
|
1614
|
+
status: status.value,
|
|
1615
|
+
result: result.value,
|
|
1616
|
+
observedBehavior: observedBehavior.value,
|
|
1617
|
+
evaluator: evaluator.value,
|
|
1618
|
+
...(proposalId !== undefined ? { proposalId } : {}),
|
|
1619
|
+
...(versionId !== undefined ? { versionId } : {}),
|
|
1620
|
+
...(notes !== undefined ? { notes } : {}),
|
|
1621
|
+
}));
|
|
1622
|
+
}
|
|
1623
|
+
if (command === 'list-evaluation-results') {
|
|
1624
|
+
const status = optionalSkillEvaluationResultStatusFlag(parsed.flags, 'status');
|
|
1625
|
+
if (!status.ok)
|
|
1626
|
+
return resultFailure(status);
|
|
1627
|
+
const scenarioId = optionalStringFlag(parsed.flags, 'scenario');
|
|
1628
|
+
const skillId = optionalStringFlag(parsed.flags, 'skill-id');
|
|
1629
|
+
const proposalId = optionalStringFlag(parsed.flags, 'proposal');
|
|
1630
|
+
const versionId = optionalStringFlag(parsed.flags, 'version-id');
|
|
1631
|
+
const evaluator = optionalStringFlag(parsed.flags, 'evaluator');
|
|
1632
|
+
return jsonResult(registry.listSkillEvaluationResults({
|
|
1633
|
+
...(scenarioId !== undefined ? { scenarioId } : {}),
|
|
1634
|
+
...(skillId !== undefined ? { skillId } : {}),
|
|
1635
|
+
...(proposalId !== undefined ? { proposalId } : {}),
|
|
1636
|
+
...(versionId !== undefined ? { versionId } : {}),
|
|
1637
|
+
...(evaluator !== undefined ? { evaluator } : {}),
|
|
1638
|
+
...(status.value !== undefined ? { status: status.value } : {}),
|
|
1639
|
+
}));
|
|
1640
|
+
}
|
|
1641
|
+
if (command === 'get-proposal') {
|
|
1642
|
+
const proposalId = requiredFlag(parsed.flags, 'proposal');
|
|
1643
|
+
return proposalId.ok ? jsonResult(registry.getSkillImprovementProposal(proposalId.value)) : resultFailure(proposalId);
|
|
1644
|
+
}
|
|
1645
|
+
if (command === 'submit-proposal' || command === 'approve-proposal' || command === 'reject-proposal' || command === 'cancel-proposal' || command === 'apply-proposal') {
|
|
1646
|
+
const input = proposalActorInput(parsed.flags);
|
|
1647
|
+
if (!input.ok)
|
|
1648
|
+
return resultFailure(input);
|
|
1649
|
+
if (command === 'submit-proposal')
|
|
1650
|
+
return jsonResult(registry.submitSkillImprovementProposal(input.value));
|
|
1651
|
+
if (command === 'approve-proposal')
|
|
1652
|
+
return jsonResult(registry.approveSkillImprovementProposal(input.value));
|
|
1653
|
+
if (command === 'reject-proposal')
|
|
1654
|
+
return jsonResult(registry.rejectSkillImprovementProposal(input.value));
|
|
1655
|
+
if (command === 'cancel-proposal')
|
|
1656
|
+
return jsonResult(registry.cancelSkillImprovementProposal(input.value));
|
|
1657
|
+
return jsonResult(registry.applySkillImprovementProposal(input.value));
|
|
1658
|
+
}
|
|
1659
|
+
if (command === 'payload') {
|
|
1660
|
+
const input = skillPayloadInput(parsed.flags);
|
|
1661
|
+
return input.ok ? jsonResult(registry.buildSkillPayload(input.value, { workspaceRoot: environment.cwd })) : resultFailure(input);
|
|
1662
|
+
}
|
|
1663
|
+
return usageFailure(`Unknown skills command: ${command}`);
|
|
1664
|
+
}
|
|
1665
|
+
function proposalActorInput(flags) {
|
|
1666
|
+
const proposalId = requiredFlag(flags, 'proposal');
|
|
1667
|
+
const actor = requiredFlag(flags, 'actor');
|
|
1668
|
+
if (!proposalId.ok)
|
|
1669
|
+
return proposalId;
|
|
1670
|
+
if (!actor.ok)
|
|
1671
|
+
return actor;
|
|
1672
|
+
const reason = optionalStringFlag(flags, 'reason');
|
|
1673
|
+
return { ok: true, value: { proposalId: proposalId.value, actor: actor.value, ...(reason !== undefined ? { reason } : {}) } };
|
|
1674
|
+
}
|
|
1675
|
+
function skillPayloadInput(flags) {
|
|
1676
|
+
if (flags['record-usage'] !== undefined)
|
|
1677
|
+
return validationFailure('skills payload is read-only; use skills resolve with --record-usage for explicit usage writes');
|
|
1678
|
+
const input = resolveSkillsInput(flags);
|
|
1679
|
+
if (!input.ok)
|
|
1680
|
+
return input;
|
|
1681
|
+
return { ok: true, value: input.value };
|
|
1682
|
+
}
|
|
1683
|
+
function resolveSkillsInput(flags) {
|
|
1684
|
+
const scope = optionalScopeFlag(flags, 'scope');
|
|
1685
|
+
if (!scope.ok)
|
|
1686
|
+
return scope;
|
|
1687
|
+
const recordUsage = optionalSkillResolutionUsageFlag(flags, 'record-usage');
|
|
1688
|
+
if (!recordUsage.ok)
|
|
1689
|
+
return recordUsage;
|
|
1690
|
+
const input = {};
|
|
1691
|
+
const project = optionalStringFlag(flags, 'project');
|
|
1692
|
+
const agentId = optionalStringFlag(flags, 'agent-id');
|
|
1693
|
+
const agentName = optionalStringFlag(flags, 'agent');
|
|
1694
|
+
const workflow = optionalStringFlag(flags, 'workflow');
|
|
1695
|
+
const phase = optionalStringFlag(flags, 'phase');
|
|
1696
|
+
const providerAdapter = optionalStringFlag(flags, 'provider');
|
|
1697
|
+
const runId = optionalStringFlag(flags, 'run');
|
|
1698
|
+
if (project !== undefined)
|
|
1699
|
+
input.project = project;
|
|
1700
|
+
if (scope.value !== undefined)
|
|
1701
|
+
input.scope = scope.value;
|
|
1702
|
+
if (agentId !== undefined)
|
|
1703
|
+
input.agentId = agentId;
|
|
1704
|
+
if (agentName !== undefined)
|
|
1705
|
+
input.agentName = agentName;
|
|
1706
|
+
if (workflow !== undefined)
|
|
1707
|
+
input.workflow = workflow;
|
|
1708
|
+
if (phase !== undefined)
|
|
1709
|
+
input.phase = phase;
|
|
1710
|
+
if (providerAdapter !== undefined)
|
|
1711
|
+
input.providerAdapter = providerAdapter;
|
|
1712
|
+
if (runId !== undefined)
|
|
1713
|
+
input.runId = runId;
|
|
1714
|
+
if (recordUsage.value !== undefined)
|
|
1715
|
+
input.recordUsage = recordUsage.value;
|
|
1716
|
+
if (input.recordUsage !== undefined && input.runId === undefined)
|
|
1717
|
+
return validationFailure('--run is required when --record-usage is set');
|
|
1718
|
+
return { ok: true, value: input };
|
|
1719
|
+
}
|
|
1720
|
+
function resolveSkill(parsed, registry) {
|
|
1721
|
+
const id = optionalStringFlag(parsed.flags, 'id');
|
|
1722
|
+
if (id !== undefined)
|
|
1723
|
+
return registry.getSkill(id);
|
|
1724
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
1725
|
+
const scope = scopeFlag(parsed.flags, 'scope', 'project');
|
|
1726
|
+
const name = requiredFlag(parsed.flags, 'name');
|
|
1727
|
+
if (!project.ok)
|
|
1728
|
+
return project;
|
|
1729
|
+
if (!scope.ok)
|
|
1730
|
+
return scope;
|
|
1731
|
+
if (!name.ok)
|
|
1732
|
+
return name;
|
|
1733
|
+
return registry.getSkillByName(project.value, scope.value, name.value);
|
|
1734
|
+
}
|
|
1735
|
+
function scenarioNameFlag(flags) {
|
|
1736
|
+
const scenarioName = optionalStringFlag(flags, 'scenario-name');
|
|
1737
|
+
if (scenarioName !== undefined)
|
|
1738
|
+
return { ok: true, value: scenarioName };
|
|
1739
|
+
const id = optionalStringFlag(flags, 'id');
|
|
1740
|
+
if (id !== undefined)
|
|
1741
|
+
return requiredFlag(flags, 'name');
|
|
1742
|
+
return validationFailure('Missing required --scenario-name');
|
|
1743
|
+
}
|
|
1744
|
+
function runAgentCommand(command, parsed, database, environment) {
|
|
1745
|
+
const handlers = createAgentRegistryToolHandlers({ service: new AgentRegistryService(database) });
|
|
1746
|
+
const registry = new AgentRegistryService(database);
|
|
1747
|
+
const managerProfiles = new ManagerProfileOverlayService({ agents: registry, overlays: new ManagerProfileOverlayRepository(database) });
|
|
1748
|
+
if (command === 'seed') {
|
|
1749
|
+
if (parsed.positionals[2] !== 'load')
|
|
1750
|
+
return usageFailure('agents seed requires load');
|
|
1751
|
+
const file = requiredFlag(parsed.flags, 'file');
|
|
1752
|
+
if (!file.ok)
|
|
1753
|
+
return resultFailure(file);
|
|
1754
|
+
const manifest = readJsonFile(file.value, environment);
|
|
1755
|
+
if (!manifest.ok)
|
|
1756
|
+
return resultFailure(manifest);
|
|
1757
|
+
return jsonResult(new AgentSeedService(database).loadManifest(manifest.value));
|
|
1758
|
+
}
|
|
1759
|
+
if (command === 'register') {
|
|
1760
|
+
const payload = registerPayload(parsed, environment, false);
|
|
1761
|
+
return payload.ok ? jsonResult(handlers.registerAgent(payload.value)) : resultFailure(payload);
|
|
1762
|
+
}
|
|
1763
|
+
if (command === 'list') {
|
|
1764
|
+
const filters = {};
|
|
1765
|
+
const project = optionalStringFlag(parsed.flags, 'project');
|
|
1766
|
+
if (project !== undefined)
|
|
1767
|
+
filters.project = project;
|
|
1768
|
+
const scope = optionalScopeFlag(parsed.flags, 'scope');
|
|
1769
|
+
if (!scope.ok)
|
|
1770
|
+
return resultFailure(scope);
|
|
1771
|
+
if (scope.value !== undefined)
|
|
1772
|
+
filters.scope = scope.value;
|
|
1773
|
+
const mode = optionalModeFlag(parsed.flags, 'mode');
|
|
1774
|
+
if (!mode.ok)
|
|
1775
|
+
return resultFailure(mode);
|
|
1776
|
+
filters.mode = mode.value ?? 'agent';
|
|
1777
|
+
return jsonResult(handlers.listAgents(filters));
|
|
1778
|
+
}
|
|
1779
|
+
if (command === 'get') {
|
|
1780
|
+
const id = optionalStringFlag(parsed.flags, 'id');
|
|
1781
|
+
if (id !== undefined)
|
|
1782
|
+
return jsonResult(expectAgentMode(handlers.getAgent({ id }), 'agent'));
|
|
1783
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
1784
|
+
const scope = scopeFlag(parsed.flags, 'scope', 'project');
|
|
1785
|
+
const name = requiredFlag(parsed.flags, 'name');
|
|
1786
|
+
if (!project.ok)
|
|
1787
|
+
return resultFailure(project);
|
|
1788
|
+
if (!scope.ok)
|
|
1789
|
+
return resultFailure(scope);
|
|
1790
|
+
if (!name.ok)
|
|
1791
|
+
return resultFailure(name);
|
|
1792
|
+
return jsonResult(expectAgentMode(handlers.getAgentByName({ project: project.value, scope: scope.value, name: name.value }), 'agent'));
|
|
1793
|
+
}
|
|
1794
|
+
if (command === 'resolve') {
|
|
1795
|
+
const input = resolveAgentsInput(parsed.flags);
|
|
1796
|
+
return input.ok ? jsonResult(registry.resolveAgents(input.value)) : resultFailure(input);
|
|
1797
|
+
}
|
|
1798
|
+
if (command === 'manager-profile') {
|
|
1799
|
+
const action = parsed.positionals[2];
|
|
1800
|
+
if (action !== 'get' && action !== 'set')
|
|
1801
|
+
return usageFailure('agents manager-profile requires get or set');
|
|
1802
|
+
const input = managerProfileInput(parsed.flags);
|
|
1803
|
+
if (!input.ok)
|
|
1804
|
+
return resultFailure(input);
|
|
1805
|
+
if (action === 'get')
|
|
1806
|
+
return jsonResult(managerProfiles.resolveEffectiveManager(input.value));
|
|
1807
|
+
const baseline = managerProfiles.resolveEffectiveManager(input.value);
|
|
1808
|
+
if (!baseline.ok)
|
|
1809
|
+
return resultFailure(baseline);
|
|
1810
|
+
const instructions = requiredFlag(parsed.flags, 'instructions');
|
|
1811
|
+
if (!instructions.ok)
|
|
1812
|
+
return resultFailure(instructions);
|
|
1813
|
+
const saved = managerProfiles.save({ ...input.value, instructions: instructions.value });
|
|
1814
|
+
if (!saved.ok)
|
|
1815
|
+
return resultFailure(saved);
|
|
1816
|
+
return jsonResult(managerProfiles.resolveEffectiveManager(input.value));
|
|
1817
|
+
}
|
|
1818
|
+
if (command === 'render') {
|
|
1819
|
+
const provider = requiredFlag(parsed.flags, 'provider');
|
|
1820
|
+
if (!provider.ok)
|
|
1821
|
+
return resultFailure(provider);
|
|
1822
|
+
const renderer = getProviderRenderer(provider.value);
|
|
1823
|
+
if (!renderer.ok)
|
|
1824
|
+
return resultFailure(renderer);
|
|
1825
|
+
const agent = resolveAgentForRender(parsed, registry, managerProfiles);
|
|
1826
|
+
if (!agent.ok)
|
|
1827
|
+
return resultFailure(agent);
|
|
1828
|
+
const subagents = resolveSubagentsForRender(agent.value, registry);
|
|
1829
|
+
if (!subagents.ok)
|
|
1830
|
+
return resultFailure(subagents);
|
|
1831
|
+
return jsonResult(renderer.value.render({ agent: agent.value, subagents: subagents.value }));
|
|
1832
|
+
}
|
|
1833
|
+
return usageFailure(`Unknown agents command: ${command}`);
|
|
1834
|
+
}
|
|
1835
|
+
function resolveAgentsInput(flags) {
|
|
1836
|
+
const scope = optionalScopeFlag(flags, 'scope');
|
|
1837
|
+
if (!scope.ok)
|
|
1838
|
+
return scope;
|
|
1839
|
+
const mode = optionalModeFlag(flags, 'mode');
|
|
1840
|
+
if (!mode.ok)
|
|
1841
|
+
return mode;
|
|
1842
|
+
const input = {};
|
|
1843
|
+
const project = optionalStringFlag(flags, 'project');
|
|
1844
|
+
const taskDescription = optionalStringFlag(flags, 'task') ?? optionalStringFlag(flags, 'task-description');
|
|
1845
|
+
const intent = optionalStringFlag(flags, 'intent');
|
|
1846
|
+
const desiredCapabilities = csvFlag(flags, 'capabilities');
|
|
1847
|
+
const workflow = optionalStringFlag(flags, 'workflow');
|
|
1848
|
+
const phase = optionalStringFlag(flags, 'phase');
|
|
1849
|
+
const providerAdapter = optionalStringFlag(flags, 'provider') ?? optionalStringFlag(flags, 'provider-adapter');
|
|
1850
|
+
if (project !== undefined)
|
|
1851
|
+
input.project = project;
|
|
1852
|
+
if (scope.value !== undefined)
|
|
1853
|
+
input.scope = scope.value;
|
|
1854
|
+
if (taskDescription !== undefined)
|
|
1855
|
+
input.taskDescription = taskDescription;
|
|
1856
|
+
if (intent !== undefined)
|
|
1857
|
+
input.intent = intent;
|
|
1858
|
+
if (desiredCapabilities.length > 0)
|
|
1859
|
+
input.desiredCapabilities = desiredCapabilities;
|
|
1860
|
+
if (workflow !== undefined)
|
|
1861
|
+
input.workflow = workflow;
|
|
1862
|
+
if (phase !== undefined)
|
|
1863
|
+
input.phase = phase;
|
|
1864
|
+
if (providerAdapter !== undefined)
|
|
1865
|
+
input.providerAdapter = providerAdapter;
|
|
1866
|
+
if (mode.value !== undefined)
|
|
1867
|
+
input.mode = mode.value;
|
|
1868
|
+
return { ok: true, value: input };
|
|
1869
|
+
}
|
|
1870
|
+
function managerProfileInput(flags) {
|
|
1871
|
+
const scope = scopeFlag(flags, 'scope', 'project');
|
|
1872
|
+
if (!scope.ok)
|
|
1873
|
+
return scope;
|
|
1874
|
+
return { ok: true, value: { project: optionalStringFlag(flags, 'project') ?? 'vgxness', scope: scope.value, managerName: optionalStringFlag(flags, 'manager') ?? 'vgxness-manager' } };
|
|
1875
|
+
}
|
|
1876
|
+
function resolveAgentForRender(parsed, registry, managerProfiles) {
|
|
1877
|
+
const id = optionalStringFlag(parsed.flags, 'id');
|
|
1878
|
+
if (id !== undefined)
|
|
1879
|
+
return registry.getAgent(id);
|
|
1880
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
1881
|
+
const scope = scopeFlag(parsed.flags, 'scope', 'project');
|
|
1882
|
+
const name = requiredFlag(parsed.flags, 'name');
|
|
1883
|
+
if (!project.ok)
|
|
1884
|
+
return project;
|
|
1885
|
+
if (!scope.ok)
|
|
1886
|
+
return scope;
|
|
1887
|
+
if (!name.ok)
|
|
1888
|
+
return name;
|
|
1889
|
+
if (name.value === 'vgxness-manager' && managerProfiles !== undefined) {
|
|
1890
|
+
const effective = managerProfiles.resolveEffectiveManager({ project: project.value, scope: scope.value, managerName: name.value });
|
|
1891
|
+
return effective.ok ? { ok: true, value: effective.value.manager } : effective;
|
|
1892
|
+
}
|
|
1893
|
+
return registry.getAgentByName(project.value, scope.value, name.value);
|
|
1894
|
+
}
|
|
1895
|
+
function resolveSubagentsForRender(agent, registry) {
|
|
1896
|
+
if (agent.mode !== 'agent')
|
|
1897
|
+
return { ok: true, value: [] };
|
|
1898
|
+
const summaries = registry.listSubagents(agent.id);
|
|
1899
|
+
if (!summaries.ok)
|
|
1900
|
+
return summaries;
|
|
1901
|
+
const subagents = [];
|
|
1902
|
+
for (const summary of summaries.value) {
|
|
1903
|
+
const subagent = registry.getAgent(summary.id);
|
|
1904
|
+
if (!subagent.ok)
|
|
1905
|
+
return subagent;
|
|
1906
|
+
subagents.push(subagent.value);
|
|
1907
|
+
}
|
|
1908
|
+
return { ok: true, value: subagents };
|
|
1909
|
+
}
|
|
1910
|
+
function runSubagentCommand(command, parsed, database, environment) {
|
|
1911
|
+
const handlers = createAgentRegistryToolHandlers({ service: new AgentRegistryService(database) });
|
|
1912
|
+
if (command === 'register') {
|
|
1913
|
+
const payload = registerPayload(parsed, environment, true);
|
|
1914
|
+
return payload.ok ? jsonResult(handlers.registerSubagent(payload.value)) : resultFailure(payload);
|
|
1915
|
+
}
|
|
1916
|
+
if (command === 'list') {
|
|
1917
|
+
const parentAgentId = requiredFlag(parsed.flags, 'parent-agent-id');
|
|
1918
|
+
if (!parentAgentId.ok)
|
|
1919
|
+
return resultFailure(parentAgentId);
|
|
1920
|
+
return jsonResult(handlers.listSubagents({ parentAgentId: parentAgentId.value }));
|
|
1921
|
+
}
|
|
1922
|
+
if (command === 'get') {
|
|
1923
|
+
const id = optionalStringFlag(parsed.flags, 'id');
|
|
1924
|
+
if (id !== undefined)
|
|
1925
|
+
return jsonResult(expectAgentMode(handlers.getAgent({ id }), 'subagent'));
|
|
1926
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
1927
|
+
const scope = scopeFlag(parsed.flags, 'scope', 'project');
|
|
1928
|
+
const name = requiredFlag(parsed.flags, 'name');
|
|
1929
|
+
if (!project.ok)
|
|
1930
|
+
return resultFailure(project);
|
|
1931
|
+
if (!scope.ok)
|
|
1932
|
+
return resultFailure(scope);
|
|
1933
|
+
if (!name.ok)
|
|
1934
|
+
return resultFailure(name);
|
|
1935
|
+
return jsonResult(expectAgentMode(handlers.getAgentByName({ project: project.value, scope: scope.value, name: name.value }), 'subagent'));
|
|
1936
|
+
}
|
|
1937
|
+
return usageFailure(`Unknown subagents command: ${command}`);
|
|
1938
|
+
}
|
|
1939
|
+
function registerPayload(parsed, environment, subagent) {
|
|
1940
|
+
const file = optionalStringFlag(parsed.flags, 'file');
|
|
1941
|
+
if (file !== undefined) {
|
|
1942
|
+
const mergeValidation = validateFileInputIsComplete(parsed.flags, subagent);
|
|
1943
|
+
if (!mergeValidation.ok)
|
|
1944
|
+
return mergeValidation;
|
|
1945
|
+
return readJsonFile(file, environment);
|
|
1946
|
+
}
|
|
1947
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
1948
|
+
const scope = scopeFlag(parsed.flags, 'scope', 'project');
|
|
1949
|
+
const name = requiredFlag(parsed.flags, 'name');
|
|
1950
|
+
const description = requiredFlag(parsed.flags, 'description');
|
|
1951
|
+
const instructionKind = instructionKindFlag(parsed.flags);
|
|
1952
|
+
const instructions = requiredFlag(parsed.flags, 'instructions');
|
|
1953
|
+
if (!project.ok)
|
|
1954
|
+
return project;
|
|
1955
|
+
if (!scope.ok)
|
|
1956
|
+
return scope;
|
|
1957
|
+
if (!name.ok)
|
|
1958
|
+
return name;
|
|
1959
|
+
if (!description.ok)
|
|
1960
|
+
return description;
|
|
1961
|
+
if (!instructionKind.ok)
|
|
1962
|
+
return instructionKind;
|
|
1963
|
+
if (!instructions.ok)
|
|
1964
|
+
return instructions;
|
|
1965
|
+
const payload = {
|
|
1966
|
+
project: project.value,
|
|
1967
|
+
scope: scope.value,
|
|
1968
|
+
name: name.value,
|
|
1969
|
+
description: description.value,
|
|
1970
|
+
instructions: { kind: instructionKind.value, value: instructions.value },
|
|
1971
|
+
};
|
|
1972
|
+
const capabilities = csvFlag(parsed.flags, 'capabilities');
|
|
1973
|
+
if (capabilities.length)
|
|
1974
|
+
payload.capabilities = capabilities;
|
|
1975
|
+
const skills = csvFlag(parsed.flags, 'skills');
|
|
1976
|
+
if (skills.length)
|
|
1977
|
+
payload.skills = skills;
|
|
1978
|
+
if (subagent) {
|
|
1979
|
+
const parentAgentId = requiredFlag(parsed.flags, 'parent-agent-id');
|
|
1980
|
+
if (!parentAgentId.ok)
|
|
1981
|
+
return parentAgentId;
|
|
1982
|
+
payload.parentAgentId = parentAgentId.value;
|
|
1983
|
+
}
|
|
1984
|
+
return { ok: true, value: payload };
|
|
1985
|
+
}
|
|
1986
|
+
function expectAgentMode(result, mode) {
|
|
1987
|
+
if (!result.ok)
|
|
1988
|
+
return result;
|
|
1989
|
+
if (result.value.mode === mode)
|
|
1990
|
+
return result;
|
|
1991
|
+
return validationFailure(`Expected ${mode}, found ${result.value.mode}`);
|
|
1992
|
+
}
|
|
1993
|
+
function validateFileInputIsComplete(flags, subagent) {
|
|
1994
|
+
const definitionFlags = ['project', 'scope', 'name', 'description', 'instructions', 'instructions-kind', 'capabilities'];
|
|
1995
|
+
if (subagent)
|
|
1996
|
+
definitionFlags.push('parent-agent-id');
|
|
1997
|
+
const mixed = definitionFlags.filter((name) => flags[name] !== undefined);
|
|
1998
|
+
return mixed.length
|
|
1999
|
+
? validationFailure(`--file is a complete definition; do not combine it with ${mixed.map((name) => `--${name}`).join(', ')}`)
|
|
2000
|
+
: { ok: true, value: undefined };
|
|
2001
|
+
}
|
|
2002
|
+
function runMemoryCommand(command, parsed, database) {
|
|
2003
|
+
const handlers = createMemoryToolHandlers({ service: new MemoryService(database), config: { localMemoryEnabled: true } });
|
|
2004
|
+
const context = { actor: 'cli' };
|
|
2005
|
+
if (command === 'save') {
|
|
2006
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
2007
|
+
const scope = scopeFlag(parsed.flags, 'scope', 'project');
|
|
2008
|
+
const type = requiredFlag(parsed.flags, 'type');
|
|
2009
|
+
const title = requiredFlag(parsed.flags, 'title');
|
|
2010
|
+
const content = requiredFlag(parsed.flags, 'content');
|
|
2011
|
+
if (!project.ok)
|
|
2012
|
+
return resultFailure(project);
|
|
2013
|
+
if (!scope.ok)
|
|
2014
|
+
return resultFailure(scope);
|
|
2015
|
+
if (!type.ok)
|
|
2016
|
+
return resultFailure(type);
|
|
2017
|
+
if (!title.ok)
|
|
2018
|
+
return resultFailure(title);
|
|
2019
|
+
if (!content.ok)
|
|
2020
|
+
return resultFailure(content);
|
|
2021
|
+
const topicKey = optionalStringFlag(parsed.flags, 'topic-key');
|
|
2022
|
+
return jsonResult(handlers.saveObservation({
|
|
2023
|
+
project: project.value,
|
|
2024
|
+
scope: scope.value,
|
|
2025
|
+
type: type.value,
|
|
2026
|
+
title: title.value,
|
|
2027
|
+
content: content.value,
|
|
2028
|
+
...(topicKey !== undefined ? { topicKey } : {}),
|
|
2029
|
+
}, context));
|
|
2030
|
+
}
|
|
2031
|
+
if (command === 'search') {
|
|
2032
|
+
const scope = optionalScopeFlag(parsed.flags, 'scope');
|
|
2033
|
+
if (!scope.ok)
|
|
2034
|
+
return resultFailure(scope);
|
|
2035
|
+
const limit = optionalNumberFlag(parsed.flags, 'limit');
|
|
2036
|
+
if (!limit.ok)
|
|
2037
|
+
return resultFailure(limit);
|
|
2038
|
+
const filters = {};
|
|
2039
|
+
const query = optionalStringFlag(parsed.flags, 'query');
|
|
2040
|
+
if (query !== undefined)
|
|
2041
|
+
filters.query = query;
|
|
2042
|
+
const project = optionalStringFlag(parsed.flags, 'project');
|
|
2043
|
+
if (project !== undefined)
|
|
2044
|
+
filters.project = project;
|
|
2045
|
+
if (scope.value !== undefined)
|
|
2046
|
+
filters.scope = scope.value;
|
|
2047
|
+
const type = optionalStringFlag(parsed.flags, 'type');
|
|
2048
|
+
if (type !== undefined)
|
|
2049
|
+
filters.type = type;
|
|
2050
|
+
if (limit.value !== undefined)
|
|
2051
|
+
filters.limit = limit.value;
|
|
2052
|
+
return jsonResult(handlers.searchObservations(filters, context));
|
|
2053
|
+
}
|
|
2054
|
+
return usageFailure(`Unknown memory command: ${command}`);
|
|
2055
|
+
}
|
|
2056
|
+
function runMemoryImportCommand(parsed, environment) {
|
|
2057
|
+
const mode = validateMemoryImportMode(parsed.flags);
|
|
2058
|
+
if (!mode.ok)
|
|
2059
|
+
return resultFailure(mode);
|
|
2060
|
+
const file = requiredFlag(parsed.flags, 'file');
|
|
2061
|
+
if (!file.ok)
|
|
2062
|
+
return resultFailure(file);
|
|
2063
|
+
const importPackage = readJsonFile(file.value, environment);
|
|
2064
|
+
if (!importPackage.ok)
|
|
2065
|
+
return resultFailure(importPackage);
|
|
2066
|
+
const databasePath = databasePathSelectionFor(parsed.flags, environment);
|
|
2067
|
+
if (!databasePath.ok)
|
|
2068
|
+
return resultFailure(databasePath);
|
|
2069
|
+
if (mode.value === 'dry-run')
|
|
2070
|
+
return jsonResult(planMemoryImportDryRun({ packageJson: importPackage.value, targetDatabase: databasePath.value }));
|
|
2071
|
+
const packageValidation = validateMemoryImportPackage(importPackage.value);
|
|
2072
|
+
if (!packageValidation.ok)
|
|
2073
|
+
return resultFailure(validationFailure(`Invalid memory import package: ${formatMemoryImportValidationErrors(packageValidation.errors)}`));
|
|
2074
|
+
const opened = openCliDatabase(databasePath.value.path);
|
|
2075
|
+
if (!opened.ok)
|
|
2076
|
+
return resultFailure(opened);
|
|
2077
|
+
try {
|
|
2078
|
+
return jsonResult(writeMemoryImportObservations({ packageJson: importPackage.value, targetDatabase: databasePath.value, service: new MemoryService(opened.value), duplicatePolicy: mode.value }));
|
|
2079
|
+
}
|
|
2080
|
+
finally {
|
|
2081
|
+
opened.value.close();
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
function formatMemoryImportValidationErrors(errors) {
|
|
2085
|
+
return errors.map((error) => `${error.path}: ${error.message}`).join('; ');
|
|
2086
|
+
}
|
|
2087
|
+
function validateMemoryImportMode(flags) {
|
|
2088
|
+
if (flags.database !== undefined)
|
|
2089
|
+
return validationFailure('Use --db <path>; --database is not supported');
|
|
2090
|
+
const dryRun = flags['dry-run'] === true;
|
|
2091
|
+
const write = flags.write === true;
|
|
2092
|
+
const skipExisting = flags['skip-existing'] === true;
|
|
2093
|
+
const overwrite = flags.overwrite === true;
|
|
2094
|
+
if (dryRun && write)
|
|
2095
|
+
return validationFailure('Choose exactly one mode: --dry-run or --write');
|
|
2096
|
+
if (!dryRun && !write)
|
|
2097
|
+
return validationFailure('Missing import mode: pass --dry-run or --write');
|
|
2098
|
+
if (skipExisting && overwrite)
|
|
2099
|
+
return validationFailure('Choose exactly one duplicate policy: --skip-existing or --overwrite');
|
|
2100
|
+
if (dryRun && (skipExisting || overwrite))
|
|
2101
|
+
return validationFailure('--skip-existing and --overwrite are write-mode duplicate policies and cannot be used with --dry-run');
|
|
2102
|
+
if (write) {
|
|
2103
|
+
if (skipExisting === overwrite)
|
|
2104
|
+
return validationFailure('--write requires exactly one duplicate policy: --skip-existing or --overwrite');
|
|
2105
|
+
return { ok: true, value: skipExisting ? 'skip-existing' : 'overwrite' };
|
|
2106
|
+
}
|
|
2107
|
+
return { ok: true, value: 'dry-run' };
|
|
2108
|
+
}
|
|
2109
|
+
function runRunsCommand(command, parsed, database, environment) {
|
|
2110
|
+
const service = new RunService(database);
|
|
2111
|
+
if (command === 'timeline' || command === 'debug' || command === 'resume-plan' || command === 'resume-inspect')
|
|
2112
|
+
return runRunInsightCommand(command, parsed, service);
|
|
2113
|
+
if (command === 'create') {
|
|
2114
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
2115
|
+
const userIntent = requiredFlag(parsed.flags, 'intent');
|
|
2116
|
+
const workflow = requiredFlag(parsed.flags, 'workflow');
|
|
2117
|
+
const phase = requiredFlag(parsed.flags, 'phase');
|
|
2118
|
+
const selectedAgentId = requiredFlag(parsed.flags, 'agent-id');
|
|
2119
|
+
const providerAdapter = requiredFlag(parsed.flags, 'provider-adapter');
|
|
2120
|
+
const model = requiredFlag(parsed.flags, 'model');
|
|
2121
|
+
if (!project.ok)
|
|
2122
|
+
return resultFailure(project);
|
|
2123
|
+
if (!userIntent.ok)
|
|
2124
|
+
return resultFailure(userIntent);
|
|
2125
|
+
if (!workflow.ok)
|
|
2126
|
+
return resultFailure(workflow);
|
|
2127
|
+
if (!phase.ok)
|
|
2128
|
+
return resultFailure(phase);
|
|
2129
|
+
if (!selectedAgentId.ok)
|
|
2130
|
+
return resultFailure(selectedAgentId);
|
|
2131
|
+
if (!providerAdapter.ok)
|
|
2132
|
+
return resultFailure(providerAdapter);
|
|
2133
|
+
if (!model.ok)
|
|
2134
|
+
return resultFailure(model);
|
|
2135
|
+
return jsonResult(service.createRun({ project: project.value, userIntent: userIntent.value, workflow: workflow.value, phase: phase.value, selectedAgentId: selectedAgentId.value, providerAdapter: providerAdapter.value, model: model.value }));
|
|
2136
|
+
}
|
|
2137
|
+
if (command === 'list') {
|
|
2138
|
+
const status = optionalRunStatusFlag(parsed.flags, 'status');
|
|
2139
|
+
if (!status.ok)
|
|
2140
|
+
return resultFailure(status);
|
|
2141
|
+
const filters = {};
|
|
2142
|
+
const project = optionalStringFlag(parsed.flags, 'project');
|
|
2143
|
+
if (project !== undefined)
|
|
2144
|
+
filters.project = project;
|
|
2145
|
+
if (status.value !== undefined)
|
|
2146
|
+
filters.status = status.value;
|
|
2147
|
+
return jsonResult(service.listRuns(filters));
|
|
2148
|
+
}
|
|
2149
|
+
if (command === 'get') {
|
|
2150
|
+
const id = requiredFlag(parsed.flags, 'id');
|
|
2151
|
+
return id.ok ? jsonResult(service.getRun(id.value)) : resultFailure(id);
|
|
2152
|
+
}
|
|
2153
|
+
if (command === 'export-snapshot') {
|
|
2154
|
+
const project = requiredFlag(parsed.flags, 'project');
|
|
2155
|
+
const runId = requiredFlag(parsed.flags, 'run-id');
|
|
2156
|
+
if (!project.ok)
|
|
2157
|
+
return resultFailure(project);
|
|
2158
|
+
if (!runId.ok)
|
|
2159
|
+
return resultFailure(runId);
|
|
2160
|
+
const exported = new RunSnapshotExportService(service).exportSnapshot({ project: project.value, runId: runId.value });
|
|
2161
|
+
if (!exported.ok)
|
|
2162
|
+
return resultFailure(exported);
|
|
2163
|
+
const file = optionalStringFlag(parsed.flags, 'file');
|
|
2164
|
+
if (file !== undefined) {
|
|
2165
|
+
const written = writeJsonFile(file, exported.value, environment);
|
|
2166
|
+
if (!written.ok)
|
|
2167
|
+
return resultFailure(written);
|
|
2168
|
+
}
|
|
2169
|
+
return jsonResult(exported);
|
|
2170
|
+
}
|
|
2171
|
+
if (command === 'checkpoint') {
|
|
2172
|
+
const runId = requiredFlag(parsed.flags, 'run-id');
|
|
2173
|
+
const label = requiredFlag(parsed.flags, 'label');
|
|
2174
|
+
const state = jsonFlag(parsed.flags, 'state', {});
|
|
2175
|
+
if (!runId.ok)
|
|
2176
|
+
return resultFailure(runId);
|
|
2177
|
+
if (!label.ok)
|
|
2178
|
+
return resultFailure(label);
|
|
2179
|
+
if (!state.ok)
|
|
2180
|
+
return resultFailure(state);
|
|
2181
|
+
return jsonResult(service.appendCheckpoint({ runId: runId.value, label: label.value, state: state.value }));
|
|
2182
|
+
}
|
|
2183
|
+
if (command === 'finish') {
|
|
2184
|
+
const runId = requiredFlag(parsed.flags, 'run-id');
|
|
2185
|
+
const status = finalRunStatusFlag(parsed.flags, 'status');
|
|
2186
|
+
const outcome = runOutcomeFlag(parsed.flags, 'outcome');
|
|
2187
|
+
if (!runId.ok)
|
|
2188
|
+
return resultFailure(runId);
|
|
2189
|
+
if (!status.ok)
|
|
2190
|
+
return resultFailure(status);
|
|
2191
|
+
if (!outcome.ok)
|
|
2192
|
+
return resultFailure(outcome);
|
|
2193
|
+
const reason = optionalStringFlag(parsed.flags, 'reason');
|
|
2194
|
+
return jsonResult(service.updateFinalStatus({ runId: runId.value, status: status.value, outcome: outcome.value, ...(reason !== undefined ? { outcomeReason: reason } : {}) }));
|
|
2195
|
+
}
|
|
2196
|
+
if (command === 'permission-check') {
|
|
2197
|
+
const request = permissionRequestFromFlags(parsed, database, environment);
|
|
2198
|
+
if (!request.ok)
|
|
2199
|
+
return resultFailure(request);
|
|
2200
|
+
const runId = requiredFlag(parsed.flags, 'run-id');
|
|
2201
|
+
if (!runId.ok)
|
|
2202
|
+
return resultFailure(runId);
|
|
2203
|
+
return jsonResult(service.evaluatePermissionForRun({ ...request.value, runId: runId.value }));
|
|
2204
|
+
}
|
|
2205
|
+
if (command === 'preflight') {
|
|
2206
|
+
const request = permissionRequestFromFlags(parsed, database, environment);
|
|
2207
|
+
if (!request.ok)
|
|
2208
|
+
return resultFailure(request);
|
|
2209
|
+
const runId = requiredFlag(parsed.flags, 'run-id');
|
|
2210
|
+
if (!runId.ok)
|
|
2211
|
+
return resultFailure(runId);
|
|
2212
|
+
return jsonResult(service.preflightOperation({ ...request.value, runId: runId.value }));
|
|
2213
|
+
}
|
|
2214
|
+
if (command === 'approvals') {
|
|
2215
|
+
const runId = requiredFlag(parsed.flags, 'run');
|
|
2216
|
+
return runId.ok ? jsonResult(service.listApprovals(runId.value)) : resultFailure(runId);
|
|
2217
|
+
}
|
|
2218
|
+
if (command === 'retry-check') {
|
|
2219
|
+
const approvalId = requiredFlag(parsed.flags, 'approval');
|
|
2220
|
+
if (!approvalId.ok)
|
|
2221
|
+
return resultFailure(approvalId);
|
|
2222
|
+
const policy = retryPolicyFlag(parsed.flags);
|
|
2223
|
+
if (!policy.ok)
|
|
2224
|
+
return resultFailure(policy);
|
|
2225
|
+
const approval = service.getApproval(approvalId.value);
|
|
2226
|
+
if (!approval.ok)
|
|
2227
|
+
return resultFailure(approval);
|
|
2228
|
+
const retry = service.evaluateOperationRetry(policy.value === undefined
|
|
2229
|
+
? { approvalId: approvalId.value }
|
|
2230
|
+
: { approvalId: approvalId.value, policy: policy.value });
|
|
2231
|
+
if (!retry.ok)
|
|
2232
|
+
return resultFailure(retry);
|
|
2233
|
+
return jsonResult({ ok: true, value: {
|
|
2234
|
+
approvalId: approval.value.id,
|
|
2235
|
+
runId: approval.value.runId,
|
|
2236
|
+
decision: retry.value.allowed ? 'allowed' : 'blocked',
|
|
2237
|
+
allowed: retry.value.allowed,
|
|
2238
|
+
reasonCode: retry.value.reasonCode,
|
|
2239
|
+
reason: retry.value.reason,
|
|
2240
|
+
policy: retry.value.policy,
|
|
2241
|
+
retryableStatuses: retry.value.retryableStatuses,
|
|
2242
|
+
evaluatedAttemptCount: retry.value.evaluatedAttemptCount,
|
|
2243
|
+
...(retry.value.latestAttempt === undefined ? {} : { latestAttempt: retry.value.latestAttempt }),
|
|
2244
|
+
...(retry.value.activeAttempt === undefined ? {} : { activeAttempt: retry.value.activeAttempt }),
|
|
2245
|
+
executorInvoked: false,
|
|
2246
|
+
operationExecuted: false,
|
|
2247
|
+
} });
|
|
2248
|
+
}
|
|
2249
|
+
if (command === 'resume-gate') {
|
|
2250
|
+
const approvalId = requiredFlag(parsed.flags, 'approval');
|
|
2251
|
+
if (!approvalId.ok)
|
|
2252
|
+
return resultFailure(approvalId);
|
|
2253
|
+
const policy = retryPolicyFlag(parsed.flags);
|
|
2254
|
+
if (!policy.ok)
|
|
2255
|
+
return resultFailure(policy);
|
|
2256
|
+
return jsonResult(service.getRunResumeOrchestrationPlan(policy.value === undefined
|
|
2257
|
+
? { approvalId: approvalId.value }
|
|
2258
|
+
: { approvalId: approvalId.value, policy: policy.value }));
|
|
2259
|
+
}
|
|
2260
|
+
if (command === 'approve' || command === 'reject' || command === 'cancel-approval') {
|
|
2261
|
+
const approvalId = requiredFlag(parsed.flags, 'approval');
|
|
2262
|
+
const actor = requiredFlag(parsed.flags, 'actor');
|
|
2263
|
+
if (!approvalId.ok)
|
|
2264
|
+
return resultFailure(approvalId);
|
|
2265
|
+
if (!actor.ok)
|
|
2266
|
+
return resultFailure(actor);
|
|
2267
|
+
const reason = optionalStringFlag(parsed.flags, 'reason');
|
|
2268
|
+
const status = command === 'approve' ? 'approved' : command === 'reject' ? 'rejected' : 'cancelled';
|
|
2269
|
+
return jsonResult(service.resolveApproval({ approvalId: approvalId.value, status, actor: actor.value, ...(reason !== undefined ? { reason } : {}) }));
|
|
2270
|
+
}
|
|
2271
|
+
return usageFailure(`Unknown runs command: ${command}`);
|
|
2272
|
+
}
|
|
2273
|
+
function runApprovalsCommand(command, parsed, database) {
|
|
2274
|
+
const service = new RunService(database);
|
|
2275
|
+
if (command === 'list') {
|
|
2276
|
+
const runId = optionalStringFlag(parsed.flags, 'run-id');
|
|
2277
|
+
const approvals = listCliApprovals(service, runId);
|
|
2278
|
+
return approvals.ok ? jsonResult({ ok: true, value: approvals.value }) : resultFailure(approvals);
|
|
2279
|
+
}
|
|
2280
|
+
if (command === 'approve' || command === 'reject') {
|
|
2281
|
+
const id = requiredFlag(parsed.flags, 'id');
|
|
2282
|
+
if (!id.ok)
|
|
2283
|
+
return resultFailure(id);
|
|
2284
|
+
const reason = optionalStringFlag(parsed.flags, 'reason');
|
|
2285
|
+
const resolved = service.resolveApproval({
|
|
2286
|
+
approvalId: id.value,
|
|
2287
|
+
status: command === 'approve' ? 'approved' : 'rejected',
|
|
2288
|
+
actor: 'cli',
|
|
2289
|
+
...(reason === undefined ? {} : { reason }),
|
|
2290
|
+
});
|
|
2291
|
+
return jsonResult(resolved);
|
|
2292
|
+
}
|
|
2293
|
+
return usageFailure(`Unknown approvals command: ${command}`);
|
|
2294
|
+
}
|
|
2295
|
+
function listCliApprovals(service, runId) {
|
|
2296
|
+
const details = [];
|
|
2297
|
+
if (runId === undefined) {
|
|
2298
|
+
const runs = service.listRuns({});
|
|
2299
|
+
if (!runs.ok)
|
|
2300
|
+
return runs;
|
|
2301
|
+
details.push(...runs.value.map((run) => service.getRun(run.id)));
|
|
2302
|
+
}
|
|
2303
|
+
else {
|
|
2304
|
+
details.push(service.getRun(runId));
|
|
2305
|
+
}
|
|
2306
|
+
const values = [];
|
|
2307
|
+
for (const detail of details) {
|
|
2308
|
+
if (!detail.ok)
|
|
2309
|
+
return detail;
|
|
2310
|
+
for (const approval of detail.value.approvals) {
|
|
2311
|
+
const decisionEvent = detail.value.events.find((event) => event.id === approval.decisionEventId);
|
|
2312
|
+
values.push({
|
|
2313
|
+
id: approval.id,
|
|
2314
|
+
runId: approval.runId,
|
|
2315
|
+
status: approval.status,
|
|
2316
|
+
requestedAt: approval.requestedAt,
|
|
2317
|
+
resolvedAt: approval.resolvedAt ?? null,
|
|
2318
|
+
resolvedBy: approval.resolvedBy ?? null,
|
|
2319
|
+
resolutionReason: approval.resolutionReason ?? null,
|
|
2320
|
+
decisionEventId: approval.decisionEventId,
|
|
2321
|
+
operation: decisionEvent === undefined || !isJsonObject(decisionEvent.payload)
|
|
2322
|
+
? null
|
|
2323
|
+
: {
|
|
2324
|
+
category: decisionEvent.payload.category ?? null,
|
|
2325
|
+
name: decisionEvent.payload.operation ?? null,
|
|
2326
|
+
requestedOperation: decisionEvent.payload.requestedOperation ?? null,
|
|
2327
|
+
agent: decisionEvent.payload.agent ?? null,
|
|
2328
|
+
},
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
return { ok: true, value: values };
|
|
2333
|
+
}
|
|
2334
|
+
function isJsonObject(value) {
|
|
2335
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2336
|
+
}
|
|
2337
|
+
function runRunInsightCommand(command, parsed, service) {
|
|
2338
|
+
const id = requiredFlag(parsed.flags, 'id');
|
|
2339
|
+
if (!id.ok)
|
|
2340
|
+
return resultFailure(id);
|
|
2341
|
+
if (command === 'resume-inspect')
|
|
2342
|
+
return jsonResult(service.getRunOperatorResumePlan(id.value));
|
|
2343
|
+
const insights = service.getRunInsights(id.value);
|
|
2344
|
+
if (!insights.ok)
|
|
2345
|
+
return resultFailure(insights);
|
|
2346
|
+
if (command === 'timeline')
|
|
2347
|
+
return jsonResult({ ok: true, value: insights.value.timeline });
|
|
2348
|
+
if (command === 'debug')
|
|
2349
|
+
return jsonResult({ ok: true, value: insights.value.debug });
|
|
2350
|
+
return jsonResult({ ok: true, value: insights.value.resumePlan });
|
|
2351
|
+
}
|
|
2352
|
+
function runPermissionsCommand(command, parsed, database, environment) {
|
|
2353
|
+
if (command !== 'check')
|
|
2354
|
+
return usageFailure(`Unknown permissions command: ${command}`);
|
|
2355
|
+
const request = permissionRequestFromFlags(parsed, database, environment);
|
|
2356
|
+
return request.ok ? jsonResult({ ok: true, value: evaluatePermission(request.value) }) : resultFailure(request);
|
|
2357
|
+
}
|
|
2358
|
+
function permissionRequestFromFlags(parsed, database, environment) {
|
|
2359
|
+
const category = permissionCategoryFlag(parsed.flags, 'category');
|
|
2360
|
+
const operation = requiredFlag(parsed.flags, 'operation');
|
|
2361
|
+
if (!category.ok)
|
|
2362
|
+
return category;
|
|
2363
|
+
if (!operation.ok)
|
|
2364
|
+
return operation;
|
|
2365
|
+
const request = {
|
|
2366
|
+
category: category.value,
|
|
2367
|
+
operation: operation.value,
|
|
2368
|
+
workspaceRoot: optionalStringFlag(parsed.flags, 'workspace') ?? environment.cwd,
|
|
2369
|
+
};
|
|
2370
|
+
const targetPath = optionalStringFlag(parsed.flags, 'path');
|
|
2371
|
+
if (targetPath !== undefined)
|
|
2372
|
+
request.targetPath = targetPath;
|
|
2373
|
+
const providerToolName = optionalStringFlag(parsed.flags, 'provider-tool');
|
|
2374
|
+
if (providerToolName !== undefined)
|
|
2375
|
+
request.providerToolName = providerToolName;
|
|
2376
|
+
if (parsed.flags.destructive === true)
|
|
2377
|
+
request.destructive = true;
|
|
2378
|
+
if (parsed.flags.external === true)
|
|
2379
|
+
request.external = true;
|
|
2380
|
+
if (parsed.flags.privileged === true)
|
|
2381
|
+
request.privileged = true;
|
|
2382
|
+
if (parsed.flags.ambiguous === true)
|
|
2383
|
+
request.ambiguous = true;
|
|
2384
|
+
const agentId = optionalStringFlag(parsed.flags, 'agent-id');
|
|
2385
|
+
if (agentId !== undefined) {
|
|
2386
|
+
const agent = new AgentRegistryService(database).getAgent(agentId);
|
|
2387
|
+
if (!agent.ok)
|
|
2388
|
+
return agent;
|
|
2389
|
+
request.agent = agent.value;
|
|
2390
|
+
}
|
|
2391
|
+
return { ok: true, value: request };
|
|
2392
|
+
}
|
|
2393
|
+
function parseArgs(argv) {
|
|
2394
|
+
const positionals = [];
|
|
2395
|
+
const flags = {};
|
|
2396
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
2397
|
+
const current = argv[index];
|
|
2398
|
+
if (current === undefined)
|
|
2399
|
+
continue;
|
|
2400
|
+
if (!current.startsWith('--')) {
|
|
2401
|
+
positionals.push(current);
|
|
2402
|
+
continue;
|
|
2403
|
+
}
|
|
2404
|
+
const [rawKey, inlineValue] = current.slice(2).split('=', 2);
|
|
2405
|
+
if (!rawKey)
|
|
2406
|
+
continue;
|
|
2407
|
+
if (inlineValue !== undefined) {
|
|
2408
|
+
flags[rawKey] = inlineValue;
|
|
2409
|
+
continue;
|
|
2410
|
+
}
|
|
2411
|
+
const next = argv[index + 1];
|
|
2412
|
+
if (next !== undefined && !next.startsWith('--')) {
|
|
2413
|
+
flags[rawKey] = next;
|
|
2414
|
+
index += 1;
|
|
2415
|
+
}
|
|
2416
|
+
else {
|
|
2417
|
+
flags[rawKey] = true;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
return { positionals, flags };
|
|
2421
|
+
}
|
|
2422
|
+
function databasePathFor(flags, environment) {
|
|
2423
|
+
const resolved = databasePathSelectionFor(flags, environment);
|
|
2424
|
+
return resolved.ok ? { ok: true, value: resolved.value.path } : resolved;
|
|
2425
|
+
}
|
|
2426
|
+
function databasePathSelectionFor(flags, environment) {
|
|
2427
|
+
const flagPath = flags.db !== undefined ? requiredFlag(flags, 'db') : { ok: true, value: undefined };
|
|
2428
|
+
if (!flagPath.ok)
|
|
2429
|
+
return flagPath;
|
|
2430
|
+
return resolveMemoryDatabasePath({ cwd: environment.cwd, env: environment.env, explicitPath: flagPath.value });
|
|
2431
|
+
}
|
|
2432
|
+
function openCliDatabase(path) {
|
|
2433
|
+
const prepared = prepareMemoryDatabasePath(path);
|
|
2434
|
+
if (!prepared.ok)
|
|
2435
|
+
return prepared;
|
|
2436
|
+
return openMemoryDatabase({ path });
|
|
2437
|
+
}
|
|
2438
|
+
function validateCommand(area, command) {
|
|
2439
|
+
if (area === 'agents') {
|
|
2440
|
+
return command === 'register' || command === 'list' || command === 'get' || command === 'render' || command === 'resolve' || command === 'seed' || command === 'manager-profile'
|
|
2441
|
+
? { ok: true }
|
|
2442
|
+
: { ok: false, message: `Unknown agents command: ${command}` };
|
|
2443
|
+
}
|
|
2444
|
+
if (area === 'subagents') {
|
|
2445
|
+
return command === 'register' || command === 'list' || command === 'get'
|
|
2446
|
+
? { ok: true }
|
|
2447
|
+
: { ok: false, message: `Unknown subagents command: ${command}` };
|
|
2448
|
+
}
|
|
2449
|
+
if (area === 'memory') {
|
|
2450
|
+
return command === 'save' || command === 'search' || command === 'import'
|
|
2451
|
+
? { ok: true }
|
|
2452
|
+
: { ok: false, message: `Unknown memory command: ${command}` };
|
|
2453
|
+
}
|
|
2454
|
+
if (area === 'runs') {
|
|
2455
|
+
return command === 'create' || command === 'list' || command === 'get' || command === 'timeline' || command === 'debug' || command === 'resume-plan' || command === 'resume-inspect' || command === 'export-snapshot' || command === 'checkpoint' || command === 'finish' || command === 'permission-check' || command === 'preflight' || command === 'approvals' || command === 'retry-check' || command === 'resume-gate' || command === 'approve' || command === 'reject' || command === 'cancel-approval'
|
|
2456
|
+
? { ok: true }
|
|
2457
|
+
: { ok: false, message: `Unknown runs command: ${command}` };
|
|
2458
|
+
}
|
|
2459
|
+
if (area === 'approvals') {
|
|
2460
|
+
return command === 'list' || command === 'approve' || command === 'reject'
|
|
2461
|
+
? { ok: true }
|
|
2462
|
+
: { ok: false, message: `Unknown approvals command: ${command}` };
|
|
2463
|
+
}
|
|
2464
|
+
if (area === 'permissions') {
|
|
2465
|
+
return command === 'check'
|
|
2466
|
+
? { ok: true }
|
|
2467
|
+
: { ok: false, message: `Unknown permissions command: ${command}` };
|
|
2468
|
+
}
|
|
2469
|
+
if (area === 'skills') {
|
|
2470
|
+
return command === 'register' || command === 'list' || command === 'get' || command === 'add-version' || command === 'attach' || command === 'detach' || command === 'record-usage' || command === 'resolve' || command === 'payload' || command === 'propose' || command === 'proposals' || command === 'create-scenario' || command === 'list-scenarios' || command === 'record-evaluation-result' || command === 'list-evaluation-results' || command === 'get-proposal' || command === 'submit-proposal' || command === 'approve-proposal' || command === 'reject-proposal' || command === 'cancel-proposal' || command === 'apply-proposal'
|
|
2471
|
+
? { ok: true }
|
|
2472
|
+
: { ok: false, message: `Unknown skills command: ${command}` };
|
|
2473
|
+
}
|
|
2474
|
+
if (area === 'sdd') {
|
|
2475
|
+
return command === 'preview' || command === 'run' || command === 'execute' || command === 'status' || command === 'next' || command === 'ready' || command === 'save-artifact' || command === 'get-artifact' || command === 'list-artifacts' || command === 'export' || command === 'import'
|
|
2476
|
+
? { ok: true }
|
|
2477
|
+
: { ok: false, message: `Unknown sdd command: ${command}` };
|
|
2478
|
+
}
|
|
2479
|
+
if (findWorkflowDefinition(area) !== undefined) {
|
|
2480
|
+
return command === 'preview' || command === 'run' || command === 'execute'
|
|
2481
|
+
? { ok: true }
|
|
2482
|
+
: { ok: false, message: `Unknown ${area} command: ${command}` };
|
|
2483
|
+
}
|
|
2484
|
+
if (area === 'orchestrator') {
|
|
2485
|
+
return command === 'preview'
|
|
2486
|
+
? { ok: true }
|
|
2487
|
+
: { ok: false, message: `Unknown orchestrator command: ${command}` };
|
|
2488
|
+
}
|
|
2489
|
+
if (area === 'opencode') {
|
|
2490
|
+
return command === 'preview'
|
|
2491
|
+
? { ok: true }
|
|
2492
|
+
: { ok: false, message: `Unknown opencode command: ${command}` };
|
|
2493
|
+
}
|
|
2494
|
+
if (area === 'setup') {
|
|
2495
|
+
return command === 'status' || command === 'plan' || command === 'apply' || command === 'rollback'
|
|
2496
|
+
? { ok: true }
|
|
2497
|
+
: { ok: false, message: `Unknown setup command: ${command}` };
|
|
2498
|
+
}
|
|
2499
|
+
if (area === 'dashboard') {
|
|
2500
|
+
return command === 'status'
|
|
2501
|
+
? { ok: true }
|
|
2502
|
+
: { ok: false, message: `Unknown dashboard command: ${command}` };
|
|
2503
|
+
}
|
|
2504
|
+
return { ok: false, message: `Unknown command area: ${area}` };
|
|
2505
|
+
}
|
|
2506
|
+
function requiredFlag(flags, name) {
|
|
2507
|
+
const value = flags[name];
|
|
2508
|
+
return typeof value === 'string' && value.trim() ? { ok: true, value } : validationFailure(`Missing required --${name}`);
|
|
2509
|
+
}
|
|
2510
|
+
function optionalStringFlag(flags, name) {
|
|
2511
|
+
const value = flags[name];
|
|
2512
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
2513
|
+
}
|
|
2514
|
+
function scopeFlag(flags, name, fallback) {
|
|
2515
|
+
const value = optionalStringFlag(flags, name) ?? fallback;
|
|
2516
|
+
return value === 'project' || value === 'personal' ? { ok: true, value } : validationFailure(`--${name} must be project or personal`);
|
|
2517
|
+
}
|
|
2518
|
+
function optionalScopeFlag(flags, name) {
|
|
2519
|
+
const value = optionalStringFlag(flags, name);
|
|
2520
|
+
if (value === undefined)
|
|
2521
|
+
return { ok: true, value: undefined };
|
|
2522
|
+
return value === 'project' || value === 'personal' ? { ok: true, value } : validationFailure(`--${name} must be project or personal`);
|
|
2523
|
+
}
|
|
2524
|
+
function opencodeInstallScopeFlag(flags, name) {
|
|
2525
|
+
const value = optionalStringFlag(flags, name) ?? 'project';
|
|
2526
|
+
return value === 'project' || value === 'user' ? { ok: true, value } : validationFailure(`--${name} must be project or user`);
|
|
2527
|
+
}
|
|
2528
|
+
function optionalModeFlag(flags, name) {
|
|
2529
|
+
const value = optionalStringFlag(flags, name);
|
|
2530
|
+
if (value === undefined)
|
|
2531
|
+
return { ok: true, value: undefined };
|
|
2532
|
+
return value === 'agent' || value === 'subagent' ? { ok: true, value } : validationFailure(`--${name} must be agent or subagent`);
|
|
2533
|
+
}
|
|
2534
|
+
function permissionCategoryFlag(flags, name) {
|
|
2535
|
+
const value = requiredFlag(flags, name);
|
|
2536
|
+
if (!value.ok)
|
|
2537
|
+
return value;
|
|
2538
|
+
return permissionCategories.includes(value.value)
|
|
2539
|
+
? { ok: true, value: value.value }
|
|
2540
|
+
: validationFailure(`--${name} must be one of ${permissionCategories.join(', ')}`);
|
|
2541
|
+
}
|
|
2542
|
+
function skillSourceFromFlags(flags) {
|
|
2543
|
+
const kind = skillSourceKindFlag(flags, 'source-kind');
|
|
2544
|
+
if (!kind.ok)
|
|
2545
|
+
return kind;
|
|
2546
|
+
if (kind.value === 'path') {
|
|
2547
|
+
const path = requiredFlag(flags, 'source-path');
|
|
2548
|
+
return path.ok ? { ok: true, value: { kind: 'path', path: path.value } } : path;
|
|
2549
|
+
}
|
|
2550
|
+
if (kind.value === 'url') {
|
|
2551
|
+
const url = requiredFlag(flags, 'source-url');
|
|
2552
|
+
return url.ok ? { ok: true, value: { kind: 'url', url: url.value } } : url;
|
|
2553
|
+
}
|
|
2554
|
+
const inlineMetadata = jsonFlag(flags, 'inline-metadata', {});
|
|
2555
|
+
return inlineMetadata.ok
|
|
2556
|
+
? { ok: true, value: { kind: 'inline', inlineMetadata: inlineMetadata.value } }
|
|
2557
|
+
: inlineMetadata;
|
|
2558
|
+
}
|
|
2559
|
+
function skillSourceKindFlag(flags, name) {
|
|
2560
|
+
const value = requiredFlag(flags, name);
|
|
2561
|
+
if (!value.ok)
|
|
2562
|
+
return value;
|
|
2563
|
+
return value.value === 'path' || value.value === 'url' || value.value === 'inline'
|
|
2564
|
+
? { ok: true, value: value.value }
|
|
2565
|
+
: validationFailure(`--${name} must be path, url, or inline`);
|
|
2566
|
+
}
|
|
2567
|
+
function optionalSkillVersionStatusFlag(flags, name) {
|
|
2568
|
+
const value = optionalStringFlag(flags, name);
|
|
2569
|
+
if (value === undefined)
|
|
2570
|
+
return { ok: true, value: undefined };
|
|
2571
|
+
return value === 'draft' || value === 'active' || value === 'deprecated' || value === 'archived'
|
|
2572
|
+
? { ok: true, value }
|
|
2573
|
+
: validationFailure(`--${name} must be draft, active, deprecated, or archived`);
|
|
2574
|
+
}
|
|
2575
|
+
function skillTargetTypeFlag(flags, name) {
|
|
2576
|
+
const value = requiredFlag(flags, name);
|
|
2577
|
+
if (!value.ok)
|
|
2578
|
+
return value;
|
|
2579
|
+
return isSkillTargetType(value.value)
|
|
2580
|
+
? { ok: true, value: value.value }
|
|
2581
|
+
: validationFailure(`--${name} must be agent, subagent, workflow-phase, or provider-adapter`);
|
|
2582
|
+
}
|
|
2583
|
+
function optionalSkillTargetTypeFlag(flags, name) {
|
|
2584
|
+
const value = optionalStringFlag(flags, name);
|
|
2585
|
+
if (value === undefined)
|
|
2586
|
+
return { ok: true, value: undefined };
|
|
2587
|
+
return isSkillTargetType(value)
|
|
2588
|
+
? { ok: true, value }
|
|
2589
|
+
: validationFailure(`--${name} must be agent, subagent, workflow-phase, or provider-adapter`);
|
|
2590
|
+
}
|
|
2591
|
+
function skillUsageOutcomeFlag(flags, name) {
|
|
2592
|
+
const value = requiredFlag(flags, name);
|
|
2593
|
+
if (!value.ok)
|
|
2594
|
+
return value;
|
|
2595
|
+
return value.value === 'selected' || value.value === 'injected' || value.value === 'helped' || value.value === 'failed' || value.value === 'neutral'
|
|
2596
|
+
? { ok: true, value: value.value }
|
|
2597
|
+
: validationFailure(`--${name} must be selected, injected, helped, failed, or neutral`);
|
|
2598
|
+
}
|
|
2599
|
+
function optionalSkillResolutionUsageFlag(flags, name) {
|
|
2600
|
+
const value = optionalStringFlag(flags, name);
|
|
2601
|
+
if (value === undefined)
|
|
2602
|
+
return { ok: true, value: undefined };
|
|
2603
|
+
return value === 'selected' || value === 'injected'
|
|
2604
|
+
? { ok: true, value }
|
|
2605
|
+
: validationFailure(`--${name} must be selected or injected`);
|
|
2606
|
+
}
|
|
2607
|
+
function optionalSkillImprovementProposalStatusFlag(flags, name) {
|
|
2608
|
+
const value = optionalStringFlag(flags, name);
|
|
2609
|
+
if (value === undefined)
|
|
2610
|
+
return { ok: true, value: undefined };
|
|
2611
|
+
return value === 'draft' || value === 'pending-approval' || value === 'approved' || value === 'rejected' || value === 'cancelled' || value === 'applied'
|
|
2612
|
+
? { ok: true, value }
|
|
2613
|
+
: validationFailure(`--${name} must be draft, pending-approval, approved, rejected, cancelled, or applied`);
|
|
2614
|
+
}
|
|
2615
|
+
function skillEvaluationResultStatusFlag(flags, name) {
|
|
2616
|
+
const value = requiredFlag(flags, name);
|
|
2617
|
+
if (!value.ok)
|
|
2618
|
+
return value;
|
|
2619
|
+
return isSkillEvaluationResultStatus(value.value)
|
|
2620
|
+
? { ok: true, value: value.value }
|
|
2621
|
+
: validationFailure(`--${name} must be passed, failed, needs-review, or not-applicable`);
|
|
2622
|
+
}
|
|
2623
|
+
function optionalSkillEvaluationResultStatusFlag(flags, name) {
|
|
2624
|
+
const value = optionalStringFlag(flags, name);
|
|
2625
|
+
if (value === undefined)
|
|
2626
|
+
return { ok: true, value: undefined };
|
|
2627
|
+
return isSkillEvaluationResultStatus(value)
|
|
2628
|
+
? { ok: true, value }
|
|
2629
|
+
: validationFailure(`--${name} must be passed, failed, needs-review, or not-applicable`);
|
|
2630
|
+
}
|
|
2631
|
+
function isSkillEvaluationResultStatus(value) {
|
|
2632
|
+
return value === 'passed' || value === 'failed' || value === 'needs-review' || value === 'not-applicable';
|
|
2633
|
+
}
|
|
2634
|
+
function isSkillTargetType(value) {
|
|
2635
|
+
return value === 'agent' || value === 'subagent' || value === 'workflow-phase' || value === 'provider-adapter';
|
|
2636
|
+
}
|
|
2637
|
+
function instructionKindFlag(flags) {
|
|
2638
|
+
const value = optionalStringFlag(flags, 'instructions-kind') ?? 'inline';
|
|
2639
|
+
return value === 'inline' || value === 'path' ? { ok: true, value } : validationFailure('--instructions-kind must be inline or path');
|
|
2640
|
+
}
|
|
2641
|
+
function optionalNumberFlag(flags, name) {
|
|
2642
|
+
const value = optionalStringFlag(flags, name);
|
|
2643
|
+
if (value === undefined)
|
|
2644
|
+
return { ok: true, value: undefined };
|
|
2645
|
+
const parsed = Number(value);
|
|
2646
|
+
return Number.isInteger(parsed) && parsed > 0 ? { ok: true, value: parsed } : validationFailure(`--${name} must be a positive integer`);
|
|
2647
|
+
}
|
|
2648
|
+
function optionalRunStatusFlag(flags, name) {
|
|
2649
|
+
const value = optionalStringFlag(flags, name);
|
|
2650
|
+
if (value === undefined)
|
|
2651
|
+
return { ok: true, value: undefined };
|
|
2652
|
+
return isRunStatus(value) ? { ok: true, value } : validationFailure(`--${name} must be a valid run status`);
|
|
2653
|
+
}
|
|
2654
|
+
function finalRunStatusFlag(flags, name) {
|
|
2655
|
+
const value = requiredFlag(flags, name);
|
|
2656
|
+
if (!value.ok)
|
|
2657
|
+
return value;
|
|
2658
|
+
return value.value === 'completed' || value.value === 'failed' || value.value === 'blocked' || value.value === 'cancelled'
|
|
2659
|
+
? { ok: true, value: value.value }
|
|
2660
|
+
: validationFailure(`--${name} must be completed, failed, blocked, or cancelled`);
|
|
2661
|
+
}
|
|
2662
|
+
function runOutcomeFlag(flags, name) {
|
|
2663
|
+
const value = requiredFlag(flags, name);
|
|
2664
|
+
if (!value.ok)
|
|
2665
|
+
return value;
|
|
2666
|
+
return value.value === 'success' || value.value === 'partial' || value.value === 'failure' || value.value === 'blocked' || value.value === 'cancelled'
|
|
2667
|
+
? { ok: true, value: value.value }
|
|
2668
|
+
: validationFailure(`--${name} must be success, partial, failure, blocked, or cancelled`);
|
|
2669
|
+
}
|
|
2670
|
+
function jsonFlag(flags, name, fallback) {
|
|
2671
|
+
const value = optionalStringFlag(flags, name);
|
|
2672
|
+
if (value === undefined)
|
|
2673
|
+
return { ok: true, value: fallback };
|
|
2674
|
+
try {
|
|
2675
|
+
return { ok: true, value: JSON.parse(value) };
|
|
2676
|
+
}
|
|
2677
|
+
catch (cause) {
|
|
2678
|
+
return { ok: false, error: { code: 'validation_failed', message: `--${name} must be valid JSON`, cause } };
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
function optionalJsonFlag(flags, name) {
|
|
2682
|
+
const value = optionalStringFlag(flags, name);
|
|
2683
|
+
if (value === undefined)
|
|
2684
|
+
return { ok: true, value: undefined };
|
|
2685
|
+
try {
|
|
2686
|
+
return { ok: true, value: JSON.parse(value) };
|
|
2687
|
+
}
|
|
2688
|
+
catch (cause) {
|
|
2689
|
+
return { ok: false, error: { code: 'validation_failed', message: `--${name} must be valid JSON`, cause } };
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
function retryPolicyFlag(flags) {
|
|
2693
|
+
const value = optionalStringFlag(flags, 'policy');
|
|
2694
|
+
if (value === undefined)
|
|
2695
|
+
return { ok: true, value: undefined };
|
|
2696
|
+
let parsed;
|
|
2697
|
+
try {
|
|
2698
|
+
parsed = JSON.parse(value);
|
|
2699
|
+
}
|
|
2700
|
+
catch (cause) {
|
|
2701
|
+
return { ok: false, error: { code: 'validation_failed', message: '--policy must be valid JSON', cause } };
|
|
2702
|
+
}
|
|
2703
|
+
const policy = parseOperationRetryPolicy(parsed);
|
|
2704
|
+
return policy.ok ? { ok: true, value: policy.policy } : validationFailure(`Invalid --policy: ${policy.errors.join('; ')}`);
|
|
2705
|
+
}
|
|
2706
|
+
function isRunStatus(value) {
|
|
2707
|
+
return value === 'created' || value === 'planned' || value === 'running' || value === 'needs-human' || value === 'completed' || value === 'failed' || value === 'blocked' || value === 'cancelled';
|
|
2708
|
+
}
|
|
2709
|
+
function csvFlag(flags, name) {
|
|
2710
|
+
return optionalStringFlag(flags, name)?.split(',').map((value) => value.trim()).filter(Boolean) ?? [];
|
|
2711
|
+
}
|
|
2712
|
+
function readJsonFile(path, environment) {
|
|
2713
|
+
try {
|
|
2714
|
+
const parsed = JSON.parse(readFileSync(resolve(environment.cwd, path), 'utf8'));
|
|
2715
|
+
return isRecord(parsed) ? { ok: true, value: parsed } : validationFailure(`Invalid JSON package shape in --file ${path}: expected a JSON object`);
|
|
2716
|
+
}
|
|
2717
|
+
catch (cause) {
|
|
2718
|
+
const message = cause instanceof SyntaxError ? `Invalid JSON in --file ${path}` : `Unable to read --file ${path}`;
|
|
2719
|
+
return { ok: false, error: { code: 'validation_failed', message, cause } };
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
function writeJsonFile(path, value, environment) {
|
|
2723
|
+
try {
|
|
2724
|
+
const outputPath = resolve(environment.cwd, path);
|
|
2725
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
2726
|
+
writeFileSync(outputPath, `${JSON.stringify(value, null, 2)}\n`);
|
|
2727
|
+
return { ok: true, value: undefined };
|
|
2728
|
+
}
|
|
2729
|
+
catch (cause) {
|
|
2730
|
+
return { ok: false, error: { code: 'validation_failed', message: `Unable to write --file ${path}`, cause } };
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
function isRecord(value) {
|
|
2734
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2735
|
+
}
|
|
2736
|
+
function jsonResult(operation) {
|
|
2737
|
+
if (!operation.ok)
|
|
2738
|
+
return resultFailure(operation);
|
|
2739
|
+
return { exitCode: 0, stdout: `${JSON.stringify(operation.value, null, 2)}\n`, stderr: '' };
|
|
2740
|
+
}
|
|
2741
|
+
function resultFailure(result) {
|
|
2742
|
+
if (result.ok)
|
|
2743
|
+
return { exitCode: 0, stdout: '', stderr: '' };
|
|
2744
|
+
return { exitCode: 1, stdout: '', stderr: `${result.error.code}: ${result.error.message}\n` };
|
|
2745
|
+
}
|
|
2746
|
+
function usageFailure(message) {
|
|
2747
|
+
return { exitCode: 1, stdout: '', stderr: `${message}\n\n${visibleHelpText()}` };
|
|
2748
|
+
}
|
|
2749
|
+
function visibleHelpText() {
|
|
2750
|
+
return helpText().replace(' skills register --project <name> --name <name> --description <text>', ' agents resolve [--project <name>] [--scope project|personal] [--workflow <name>] [--phase <name>] [--provider <name>] [--capabilities <csv>] [--task <text>]\n skills register --project <name> --name <name> --description <text>');
|
|
2751
|
+
}
|
|
2752
|
+
function okText(stdout) {
|
|
2753
|
+
return { exitCode: 0, stdout, stderr: '' };
|
|
2754
|
+
}
|
|
2755
|
+
function validationFailure(message) {
|
|
2756
|
+
return { ok: false, error: { code: 'validation_failed', message } };
|
|
2757
|
+
}
|
|
2758
|
+
function helpText() {
|
|
2759
|
+
return `Usage: vgxness <area> <command> [flags]
|
|
2760
|
+
|
|
2761
|
+
Areas:
|
|
2762
|
+
init [--project <name>] [--provider opencode|none] [--scope project|user] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents]
|
|
2763
|
+
setup plan [--project <name>] [--provider opencode|none] [--scope project|user] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents]
|
|
2764
|
+
setup apply --yes [--project <name>] [--provider opencode|none] [--scope project|user] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents]
|
|
2765
|
+
setup rollback --backup <path> [--json]
|
|
2766
|
+
setup status [--project <name>] [--scope project|personal] [--db <path>]
|
|
2767
|
+
doctor [--db <path>] [--project <name>] [--change <id>] [--timeout-ms <ms>]
|
|
2768
|
+
Setup plan/init are read-only by default. Setup apply writes OpenCode config only with --yes and uses the global user DB plus mcp-plus-agents by default.
|
|
2769
|
+
Setup status is local-first and never writes provider config or executes providers.
|
|
2770
|
+
|
|
2771
|
+
agents register --project <name> --name <name> --description <text> --instructions <text>
|
|
2772
|
+
agents register --file <agent.json>
|
|
2773
|
+
agents seed load --file <seed.json>
|
|
2774
|
+
agents list [--project <name>] [--scope project|personal]
|
|
2775
|
+
agents get --id <id> | --project <name> --name <name> [--scope project|personal]
|
|
2776
|
+
agents render --provider json|opencode|claude (--id <id> | --project <name> --name <name> [--scope project|personal])
|
|
2777
|
+
agents manager-profile get [--project <name>] [--scope project|personal] [--manager <name>]
|
|
2778
|
+
agents manager-profile set --instructions <text> [--project <name>] [--scope project|personal] [--manager <name>]
|
|
2779
|
+
Seed loading writes only to the local registry; it does not install or write .opencode/, .claude/, or provider config.
|
|
2780
|
+
|
|
2781
|
+
opencode preview --provider opencode (--agent <name> | --agent-id <id>) --project <name> --change <id> --phase <phase> [--scope project|personal]
|
|
2782
|
+
|
|
2783
|
+
orchestrator preview --project <name> --intent <text> [--change <id>] [--db <path>]
|
|
2784
|
+
Orchestrator preview classifies natural-language requests only; it never executes providers, edits files, records runs, or writes provider config.
|
|
2785
|
+
|
|
2786
|
+
explore|quickfix|plan|build|debug|sdd preview [--project <name>] [--intent <text>]
|
|
2787
|
+
Workflow previews return registry and planner JSON only; they never execute code, edit files, record runs, or write provider config.
|
|
2788
|
+
explore|quickfix|plan|build|debug|sdd run [--project <name>] --intent <text> [--phase <name>] [--agent-id <id>] [--db <path>]
|
|
2789
|
+
Workflow runs record intent plus a safety checkpoint only; they do not call providers, edit files, write provider config, create subagents, or mutate SDD artifacts.
|
|
2790
|
+
explore|quickfix|plan|build|debug|sdd execute --run-id <id> --confirm [--override-escalation] [--workspace <path>] [--executor safe-non-dispatching|provider|command] [--command typecheck|node-version] [--command-cwd <path>] [--command-targets <paths>] [--db <path>]
|
|
2791
|
+
Workflow execute is an explicit safety-gated request; default executor records preflight/ready-to-dispatch state. --executor provider remains guarded and refuses dispatch unless enforceable sandbox/preflight/adapter gates are present. --executor command runs only hardcoded allowlisted argv through bounded-process; no shell strings or provider config targets are allowed.
|
|
2792
|
+
|
|
2793
|
+
approvals list [--run-id <id>] [--db <path>]
|
|
2794
|
+
approvals approve --id <approval-id> [--db <path>]
|
|
2795
|
+
approvals reject --id <approval-id> [--reason <text>] [--db <path>]
|
|
2796
|
+
Approval commands resolve explicit run/preflight approval records; they do not change agent seed permissions or globally allow shell/provider access.
|
|
2797
|
+
|
|
2798
|
+
mcp setup --preview --provider opencode|claude [--db <path>]
|
|
2799
|
+
mcp install opencode --plan [--scope project|user] [--db <path>] [--mcp-only|--no-agents]
|
|
2800
|
+
mcp install opencode --yes [--scope project|user] [--db <path>] [--mcp-only|--no-agents]
|
|
2801
|
+
mcp doctor opencode [--scope project|user] [--project-root <path>]
|
|
2802
|
+
mcp doctor [--db <path>] [--project <name>] [--change <id>] [--timeout-ms <ms>]
|
|
2803
|
+
MCP setup preview is read-only; it does not install or write .opencode/, .claude/, or provider config.
|
|
2804
|
+
Without --db, MCP install and setup commands use the vgxness global default database; pass --db .vgx/memory.sqlite for project-local compatibility.
|
|
2805
|
+
OpenCode install defaults to project scope and installs mcp.vgxness plus vgxness-manager and hidden vgxness-sdd-* agents; use --mcp-only for legacy MCP-only config.
|
|
2806
|
+
It writes only after --yes; use --scope user for $HOME/.config/opencode/opencode.json.
|
|
2807
|
+
Project OpenCode config can override user config. Plans are read-only; applies refuse unsafe existing config and create backups before merge.
|
|
2808
|
+
|
|
2809
|
+
skills register --project <name> --name <name> --description <text>
|
|
2810
|
+
skills list [--project <name>] [--scope project|personal]
|
|
2811
|
+
skills get --id <id> | --project <name> --name <name> [--scope project|personal]
|
|
2812
|
+
skills add-version (--id <id> | --project <name> --name <name>) --version <version> --source-kind path|url|inline [--source-path <path>] [--source-url <url>] [--inline-metadata <json>] [--activate]
|
|
2813
|
+
skills attach (--id <id> | --project <name> --name <name>) --target-type agent|subagent|workflow-phase|provider-adapter --target-key <key>
|
|
2814
|
+
skills detach (--id <id> | --project <name> --name <name>) --target-type agent|subagent|workflow-phase|provider-adapter --target-key <key>
|
|
2815
|
+
skills record-usage (--id <id> | --project <name> --name <name>) --outcome selected|injected|helped|failed|neutral [--run-id <id>]
|
|
2816
|
+
skills resolve [--agent <name> | --agent-id <id>] [--project <name>] [--workflow <name>] [--phase <name>] [--provider <name>] [--run <id>] [--record-usage selected|injected]
|
|
2817
|
+
skills payload [--agent <name> | --agent-id <id>] [--project <name>] [--workflow <name>] [--phase <name>] [--provider <name>]
|
|
2818
|
+
skills propose (--id <id> | --project <name> --name <name>) --proposed-version <version> --source-kind path|url|inline --rationale <text>
|
|
2819
|
+
skills proposals [--skill-id <id>] [--status draft|pending-approval|approved|rejected|cancelled|applied]
|
|
2820
|
+
skills create-scenario (--id <id> --name <scenario> | --project <name> --name <skill> --scenario-name <scenario>) --criteria <json> --created-by <actor> [--proposal <id>] [--version-id <id>]
|
|
2821
|
+
skills list-scenarios (--id <id> | --project <name> --name <name>) [--proposal <id>] [--version-id <id>]
|
|
2822
|
+
skills record-evaluation-result --scenario <id> --status passed|failed|needs-review|not-applicable --result <json> --observed-behavior <text> --evaluator <actor> [--proposal <id>] [--version-id <id>] [--notes <text>]
|
|
2823
|
+
skills list-evaluation-results [--scenario <id>] [--skill-id <id>] [--proposal <id>] [--version-id <id>] [--evaluator <actor>] [--status passed|failed|needs-review|not-applicable]
|
|
2824
|
+
skills get-proposal --proposal <id>
|
|
2825
|
+
skills submit-proposal|approve-proposal|reject-proposal|cancel-proposal|apply-proposal --proposal <id> --actor <name> [--reason <text>]
|
|
2826
|
+
|
|
2827
|
+
subagents register --parent-agent-id <id> --project <name> --name <name> --description <text> --instructions <text>
|
|
2828
|
+
subagents register --file <subagent.json>
|
|
2829
|
+
subagents list --parent-agent-id <id>
|
|
2830
|
+
subagents get --id <id> | --project <name> --name <name> [--scope project|personal]
|
|
2831
|
+
|
|
2832
|
+
dashboard status --project <name> [--change <id>] [--run-id <id>] [--limit <n>]
|
|
2833
|
+
dashboard interactive [--project <name>] [--change <id>] [--limit <n>]
|
|
2834
|
+
Dashboard menu: Setup, Workflows, Runs, Approvals, Agents, SDD, Doctor, Settings. Menu: ↑/↓ or j/k, Enter, 1-8. Section: b/Esc back, 1-8, r, ?, q.
|
|
2835
|
+
Dashboard interactive may launch without --project; Setup remains available and project-scoped checks are deferred while project screens render project-required recovery states.
|
|
2836
|
+
Provider setup support: OpenCode supported primary; Claude preview-only; Antigravity placeholder; Custom/future extension point.
|
|
2837
|
+
Dashboard preview/status is read-only. Provider config writes/install/apply are external-only, require explicit confirmation outside the dashboard, and are not run by dashboard flows.
|
|
2838
|
+
|
|
2839
|
+
sdd status --project <name> --change <id>
|
|
2840
|
+
sdd next --project <name> --change <id>
|
|
2841
|
+
sdd ready --project <name> --change <id> --phase <phase>
|
|
2842
|
+
sdd save-artifact --project <name> --change <id> --phase <phase> --content <text>
|
|
2843
|
+
sdd get-artifact --project <name> --change <id> --phase <phase> [--db <path>]
|
|
2844
|
+
sdd list-artifacts --project <name> --change <id> [--db <path>]
|
|
2845
|
+
SDD artifact reads use the selected local SQLite memory store via --db, VGXNESS_DB_PATH, or the global default.
|
|
2846
|
+
sdd export --project <name> --change <id> [--file <package.json>]
|
|
2847
|
+
sdd import --project <name> --change <id> --file <package.json> [--write] [--overwrite]
|
|
2848
|
+
|
|
2849
|
+
memory save --project <name> --type <type> --title <title> --content <text>
|
|
2850
|
+
memory search [--query <text>] [--project <name>] [--type <type>]
|
|
2851
|
+
memory import --file <package.json> --dry-run [--db <path>]
|
|
2852
|
+
memory import --file <package.json> --write (--skip-existing | --overwrite) [--db <path>]
|
|
2853
|
+
|
|
2854
|
+
runs create --project <name> --intent <text> --workflow <name> --phase <name> --agent-id <id> --provider-adapter <name> --model <name>
|
|
2855
|
+
runs list [--project <name>] [--status <status>]
|
|
2856
|
+
runs get --id <id>
|
|
2857
|
+
runs timeline --id <id>
|
|
2858
|
+
runs debug --id <id>
|
|
2859
|
+
runs resume-plan --id <id>
|
|
2860
|
+
runs resume-inspect --id <id>
|
|
2861
|
+
resume-inspect is read-only: it prints checkpoint context, blockers, safety flags, and manual next commands without executing providers, preflight, retry, sandbox, or worktree steps.
|
|
2862
|
+
runs export-snapshot --run-id <id> --project <name> [--file <snapshot.json>]
|
|
2863
|
+
runs checkpoint --run-id <id> --label <text> [--state <json>]
|
|
2864
|
+
runs finish --run-id <id> --status completed|failed|blocked|cancelled --outcome <outcome> [--reason <text>]
|
|
2865
|
+
runs permission-check --run-id <id> --category <category> --operation <name> [--path <path>] [--agent-id <id>] [--destructive] [--external] [--privileged] [--ambiguous]
|
|
2866
|
+
runs preflight --run-id <id> --category <category> --operation <name> [--workspace <path>] [--path <path>] [--provider-tool <name>] [--agent-id <id>] [--destructive] [--external] [--privileged] [--ambiguous]
|
|
2867
|
+
runs approvals --run <run-id>
|
|
2868
|
+
runs approve|reject|cancel-approval --approval <id> --actor <name> [--reason <text>]
|
|
2869
|
+
runs retry-check --approval <id> [--policy <json>]
|
|
2870
|
+
runs resume-gate --approval <id> [--policy <json>]
|
|
2871
|
+
resume-gate is read-only and plan-only: it prints service planner JSON without preflight, retry admission, executors, provider config writes, sandbox creation, or worktree creation.
|
|
2872
|
+
|
|
2873
|
+
permissions check --category <category> --operation <name> [--path <path>] [--agent-id <id>] [--external] [--privileged]
|
|
2874
|
+
|
|
2875
|
+
Storage:
|
|
2876
|
+
Default: global user data vgxness/memory.sqlite (macOS ~/Library/Application Support/vgxness/memory.sqlite; Linux \${XDG_DATA_HOME:-~/.local/share}/vgxness/memory.sqlite; Windows %APPDATA%\\vgxness\\memory.sqlite).
|
|
2877
|
+
Precedence: --db <path> (source: flag) > VGXNESS_DB_PATH (source: environment) > global default (source: global-default).
|
|
2878
|
+
Compatibility: pass --db .vgx/memory.sqlite to use the old project-local database explicitly.
|
|
2879
|
+
`;
|
|
2880
|
+
}
|