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,185 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
export class AgentRepository {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
register(input) {
|
|
8
|
+
const validation = validate(input);
|
|
9
|
+
if (!validation.ok)
|
|
10
|
+
return validation;
|
|
11
|
+
const parentValidation = input.mode === 'subagent' ? this.validateParent(input) : ok(undefined);
|
|
12
|
+
if (!parentValidation.ok)
|
|
13
|
+
return parentValidation;
|
|
14
|
+
const result = this.db.transaction(() => {
|
|
15
|
+
const existing = this.findByNaturalKey(input.project, input.scope, input.name);
|
|
16
|
+
if (existing !== undefined && existing.mode !== input.mode) {
|
|
17
|
+
throw new AgentRegistryValidationError(`Agent already exists as ${existing.mode}: ${input.project}/${input.scope}/${input.name}`);
|
|
18
|
+
}
|
|
19
|
+
const id = existing?.id ?? randomUUID();
|
|
20
|
+
const now = new Date().toISOString();
|
|
21
|
+
const parameters = toRowParameters(input, id, existing?.created_at ?? now, now);
|
|
22
|
+
this.db.connection.prepare(`
|
|
23
|
+
INSERT INTO agents(id, project, scope, mode, name, description, instructions_json, capabilities_json, permissions_json, memory_json, workflows_json, skills_json, adapters_json, parent_agent_id, created_at, updated_at)
|
|
24
|
+
VALUES (@id, @project, @scope, @mode, @name, @description, @instructionsJson, @capabilitiesJson, @permissionsJson, @memoryJson, @workflowsJson, @skillsJson, @adaptersJson, @parentAgentId, @createdAt, @updatedAt)
|
|
25
|
+
ON CONFLICT(project, scope, name) DO UPDATE SET
|
|
26
|
+
mode=excluded.mode,
|
|
27
|
+
description=excluded.description,
|
|
28
|
+
instructions_json=excluded.instructions_json,
|
|
29
|
+
capabilities_json=excluded.capabilities_json,
|
|
30
|
+
permissions_json=excluded.permissions_json,
|
|
31
|
+
memory_json=excluded.memory_json,
|
|
32
|
+
workflows_json=excluded.workflows_json,
|
|
33
|
+
skills_json=excluded.skills_json,
|
|
34
|
+
adapters_json=excluded.adapters_json,
|
|
35
|
+
parent_agent_id=excluded.parent_agent_id,
|
|
36
|
+
updated_at=excluded.updated_at
|
|
37
|
+
`).run(parameters);
|
|
38
|
+
const found = this.getById(id);
|
|
39
|
+
if (!found.ok)
|
|
40
|
+
throw new Error(found.error.message);
|
|
41
|
+
return found.value;
|
|
42
|
+
});
|
|
43
|
+
if (!result.ok && result.error.cause instanceof AgentRegistryValidationError) {
|
|
44
|
+
return validationFailure(result.error.cause.message);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
getById(id) {
|
|
49
|
+
try {
|
|
50
|
+
const row = this.db.connection.prepare('SELECT * FROM agents WHERE id=?').get(id);
|
|
51
|
+
return row ? ok(map(row)) : missing(`Agent not found: ${id}`);
|
|
52
|
+
}
|
|
53
|
+
catch (cause) {
|
|
54
|
+
return fail('Failed to read agent', cause);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
getByName(project, scope, name) {
|
|
58
|
+
try {
|
|
59
|
+
const row = this.findByNaturalKey(project, scope, name);
|
|
60
|
+
return row ? ok(map(row)) : missing(`Agent not found: ${project}/${scope}/${name}`);
|
|
61
|
+
}
|
|
62
|
+
catch (cause) {
|
|
63
|
+
return fail('Failed to read agent by name', cause);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
list(filters = {}) {
|
|
67
|
+
try {
|
|
68
|
+
const where = [];
|
|
69
|
+
const parameters = {};
|
|
70
|
+
for (const [key, column] of [['project', 'project'], ['scope', 'scope'], ['mode', 'mode'], ['parentAgentId', 'parent_agent_id']]) {
|
|
71
|
+
const value = filters[key];
|
|
72
|
+
if (value !== undefined) {
|
|
73
|
+
where.push(`${column}=@${key}`);
|
|
74
|
+
parameters[key] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const rows = this.db.connection.prepare(`
|
|
78
|
+
SELECT * FROM agents
|
|
79
|
+
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
|
80
|
+
ORDER BY name ASC
|
|
81
|
+
`).all(parameters);
|
|
82
|
+
return ok(rows.map((row) => toSummary(map(row))));
|
|
83
|
+
}
|
|
84
|
+
catch (cause) {
|
|
85
|
+
return fail('Failed to list agents', cause);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
findByNaturalKey(project, scope, name) {
|
|
89
|
+
return this.db.connection.prepare('SELECT * FROM agents WHERE project=? AND scope=? AND name=?').get(project, scope, name);
|
|
90
|
+
}
|
|
91
|
+
validateParent(input) {
|
|
92
|
+
if (input.parentAgentId === undefined)
|
|
93
|
+
return validationFailure('Subagents require parentAgentId');
|
|
94
|
+
const parent = this.getById(input.parentAgentId);
|
|
95
|
+
if (!parent.ok)
|
|
96
|
+
return validationFailure(parent.error.message);
|
|
97
|
+
if (parent.value.mode !== 'agent')
|
|
98
|
+
return validationFailure('Subagent parent must be an agent');
|
|
99
|
+
if (parent.value.project !== input.project || parent.value.scope !== input.scope)
|
|
100
|
+
return validationFailure('Subagent parent must share project and scope');
|
|
101
|
+
return ok(undefined);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function validate(input) {
|
|
105
|
+
if (!input.project.trim())
|
|
106
|
+
return validationFailure('Agent project is required');
|
|
107
|
+
if (!input.name.trim())
|
|
108
|
+
return validationFailure('Agent name is required');
|
|
109
|
+
if (!input.description.trim())
|
|
110
|
+
return validationFailure('Agent description is required');
|
|
111
|
+
if (!input.instructions.value.trim())
|
|
112
|
+
return validationFailure('Agent instructions are required');
|
|
113
|
+
if (input.mode === 'agent' && input.parentAgentId !== undefined)
|
|
114
|
+
return validationFailure('Top-level agents cannot have parentAgentId');
|
|
115
|
+
return ok(undefined);
|
|
116
|
+
}
|
|
117
|
+
function toRowParameters(input, id, createdAt, updatedAt) {
|
|
118
|
+
return {
|
|
119
|
+
id,
|
|
120
|
+
project: input.project,
|
|
121
|
+
scope: input.scope,
|
|
122
|
+
mode: input.mode,
|
|
123
|
+
name: input.name,
|
|
124
|
+
description: input.description,
|
|
125
|
+
instructionsJson: JSON.stringify(input.instructions),
|
|
126
|
+
capabilitiesJson: JSON.stringify(input.capabilities ?? []),
|
|
127
|
+
permissionsJson: JSON.stringify(input.permissions ?? {}),
|
|
128
|
+
memoryJson: JSON.stringify(input.memory ?? { scopes: [] }),
|
|
129
|
+
workflowsJson: JSON.stringify(input.workflows ?? []),
|
|
130
|
+
skillsJson: JSON.stringify(input.skills ?? []),
|
|
131
|
+
adaptersJson: JSON.stringify(input.adapters ?? {}),
|
|
132
|
+
parentAgentId: input.parentAgentId ?? null,
|
|
133
|
+
createdAt,
|
|
134
|
+
updatedAt,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function map(row) {
|
|
138
|
+
const value = {
|
|
139
|
+
id: row.id,
|
|
140
|
+
project: row.project,
|
|
141
|
+
scope: row.scope,
|
|
142
|
+
mode: row.mode,
|
|
143
|
+
name: row.name,
|
|
144
|
+
description: row.description,
|
|
145
|
+
instructions: JSON.parse(row.instructions_json),
|
|
146
|
+
capabilities: JSON.parse(row.capabilities_json),
|
|
147
|
+
permissions: JSON.parse(row.permissions_json),
|
|
148
|
+
memory: JSON.parse(row.memory_json),
|
|
149
|
+
workflows: JSON.parse(row.workflows_json),
|
|
150
|
+
skills: JSON.parse(row.skills_json),
|
|
151
|
+
adapters: JSON.parse(row.adapters_json),
|
|
152
|
+
createdAt: row.created_at,
|
|
153
|
+
updatedAt: row.updated_at,
|
|
154
|
+
};
|
|
155
|
+
if (row.parent_agent_id !== null)
|
|
156
|
+
value.parentAgentId = row.parent_agent_id;
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
159
|
+
function toSummary(agent) {
|
|
160
|
+
const value = {
|
|
161
|
+
id: agent.id,
|
|
162
|
+
project: agent.project,
|
|
163
|
+
scope: agent.scope,
|
|
164
|
+
mode: agent.mode,
|
|
165
|
+
name: agent.name,
|
|
166
|
+
description: agent.description,
|
|
167
|
+
capabilities: agent.capabilities,
|
|
168
|
+
createdAt: agent.createdAt,
|
|
169
|
+
updatedAt: agent.updatedAt,
|
|
170
|
+
};
|
|
171
|
+
if (agent.parentAgentId !== undefined)
|
|
172
|
+
value.parentAgentId = agent.parentAgentId;
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
function ok(value) { return { ok: true, value }; }
|
|
176
|
+
function missing(message) { return { ok: false, error: { code: 'not_found', message } }; }
|
|
177
|
+
function validationFailure(message) { return { ok: false, error: { code: 'validation_failed', message } }; }
|
|
178
|
+
function fail(message, cause) {
|
|
179
|
+
const error = { code: 'validation_failed', message };
|
|
180
|
+
if (cause !== undefined)
|
|
181
|
+
error.cause = cause;
|
|
182
|
+
return { ok: false, error };
|
|
183
|
+
}
|
|
184
|
+
class AgentRegistryValidationError extends Error {
|
|
185
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
export class ManagerProfileOverlayRepository {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
save(input) {
|
|
8
|
+
const validation = validate(input);
|
|
9
|
+
if (!validation.ok)
|
|
10
|
+
return validation;
|
|
11
|
+
try {
|
|
12
|
+
const existing = this.find(input.project, input.scope, input.managerName);
|
|
13
|
+
const id = existing?.id ?? randomUUID();
|
|
14
|
+
const now = new Date().toISOString();
|
|
15
|
+
this.db.connection.prepare(`
|
|
16
|
+
INSERT INTO manager_profile_overlays(id, project, scope, manager_name, instructions, created_at, updated_at)
|
|
17
|
+
VALUES (@id, @project, @scope, @managerName, @instructions, @createdAt, @updatedAt)
|
|
18
|
+
ON CONFLICT(project, scope, manager_name) DO UPDATE SET
|
|
19
|
+
instructions=excluded.instructions,
|
|
20
|
+
updated_at=excluded.updated_at
|
|
21
|
+
`).run({
|
|
22
|
+
id,
|
|
23
|
+
project: input.project.trim(),
|
|
24
|
+
scope: input.scope,
|
|
25
|
+
managerName: input.managerName.trim(),
|
|
26
|
+
instructions: input.instructions.trim(),
|
|
27
|
+
createdAt: existing?.created_at ?? now,
|
|
28
|
+
updatedAt: now,
|
|
29
|
+
});
|
|
30
|
+
const saved = this.get(input.project, input.scope, input.managerName);
|
|
31
|
+
if (!saved.ok)
|
|
32
|
+
return saved;
|
|
33
|
+
return saved;
|
|
34
|
+
}
|
|
35
|
+
catch (cause) {
|
|
36
|
+
return fail('Failed to save manager profile overlay', cause);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
get(project, scope, managerName) {
|
|
40
|
+
try {
|
|
41
|
+
const row = this.find(project, scope, managerName);
|
|
42
|
+
return row === undefined ? missing(`Manager profile overlay not found: ${project}/${scope}/${managerName}`) : ok(map(row));
|
|
43
|
+
}
|
|
44
|
+
catch (cause) {
|
|
45
|
+
return fail('Failed to read manager profile overlay', cause);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
find(project, scope, managerName) {
|
|
49
|
+
return this.db.connection.prepare('SELECT * FROM manager_profile_overlays WHERE project=? AND scope=? AND manager_name=?')
|
|
50
|
+
.get(project.trim(), scope, managerName.trim());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function validate(input) {
|
|
54
|
+
if (!input.project.trim())
|
|
55
|
+
return validationFailure('Project is required');
|
|
56
|
+
if (!input.managerName.trim())
|
|
57
|
+
return validationFailure('managerName is required');
|
|
58
|
+
if (!input.instructions.trim())
|
|
59
|
+
return validationFailure('instructions are required');
|
|
60
|
+
return ok(undefined);
|
|
61
|
+
}
|
|
62
|
+
function map(row) {
|
|
63
|
+
return {
|
|
64
|
+
id: row.id,
|
|
65
|
+
project: row.project,
|
|
66
|
+
scope: row.scope,
|
|
67
|
+
managerName: row.manager_name,
|
|
68
|
+
instructions: row.instructions,
|
|
69
|
+
createdAt: row.created_at,
|
|
70
|
+
updatedAt: row.updated_at,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function ok(value) { return { ok: true, value }; }
|
|
74
|
+
function missing(message) { return { ok: false, error: { code: 'not_found', message } }; }
|
|
75
|
+
function validationFailure(message) { return { ok: false, error: { code: 'validation_failed', message } }; }
|
|
76
|
+
function fail(message, cause) {
|
|
77
|
+
const error = { code: 'validation_failed', message };
|
|
78
|
+
if (cause !== undefined)
|
|
79
|
+
error.cause = cause;
|
|
80
|
+
return { ok: false, error };
|
|
81
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { redactJsonValue, redactedPlaceholder } from '../export/redaction.js';
|
|
2
|
+
import { listWorkflows } from '../workflows/workflow-registry.js';
|
|
3
|
+
const noLink = 'sin vínculo disponible';
|
|
4
|
+
export function dashboardCopyAction(label, command, safety = 'read-only') {
|
|
5
|
+
return { label, command, safety, copyOnly: true, executesInTui: false };
|
|
6
|
+
}
|
|
7
|
+
export function dashboardSafety() {
|
|
8
|
+
return { readOnly: true, copyOnly: true, providerWrites: false, providerExecution: false, modifiesDotOpencode: false, modifiesDotClaude: false, modifiesOpenSpec: false, executesWorkflow: false, executesDoctor: false };
|
|
9
|
+
}
|
|
10
|
+
export function buildDashboardWorkflowsReadModel(input = {}) {
|
|
11
|
+
const project = input.project ?? '<project>';
|
|
12
|
+
const workflows = listWorkflows();
|
|
13
|
+
const items = workflows.map((workflow) => {
|
|
14
|
+
const lastKnownRun = (input.runs ?? []).filter((run) => run.workflow === workflow.id).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))[0];
|
|
15
|
+
return {
|
|
16
|
+
id: workflow.id,
|
|
17
|
+
label: workflow.title,
|
|
18
|
+
description: workflow.description,
|
|
19
|
+
phase: workflow.defaultPhase,
|
|
20
|
+
mutability: workflow.mutability,
|
|
21
|
+
safetyProfile: workflow.safetyProfile,
|
|
22
|
+
requiresSddArtifacts: workflow.requiresSddArtifacts,
|
|
23
|
+
availability: 'available',
|
|
24
|
+
inputsSummary: workflow.id === 'sdd' ? '--project and --change for SDD status; --intent for explicit preview outside TUI' : '--project and --intent for explicit preview outside TUI',
|
|
25
|
+
sourceRef: 'src/workflows/workflow-registry.ts',
|
|
26
|
+
...(lastKnownRun === undefined ? {} : { lastKnownRun: { id: lastKnownRun.id, status: lastKnownRun.status, phase: lastKnownRun.phase, updatedAt: lastKnownRun.updatedAt } }),
|
|
27
|
+
nextActions: [
|
|
28
|
+
dashboardCopyAction('Preview workflow metadata', `npm run cli -- ${workflow.id} preview --project ${project} --intent <intent>`),
|
|
29
|
+
dashboardCopyAction('Inspect workflow runs', `npm run cli -- runs list --project ${project}`),
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
meta: meta('workflows', 'Workflows', 'workflow registry', input.generatedAt),
|
|
35
|
+
summary: `${items.length} workflow(s) from registry; actions are copy-only and not executed by the TUI.`,
|
|
36
|
+
items,
|
|
37
|
+
...(items.length === 0 ? { emptyState: { message: 'No workflows are registered in the workflow registry.', nextActions: [dashboardCopyAction('Inspect help', 'npm run cli -- --help')] } } : {}),
|
|
38
|
+
copyActions: [dashboardCopyAction('Inspect CLI help', 'npm run cli -- --help')],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function buildDashboardRunsReadModel(input) {
|
|
42
|
+
const items = input.runs.map((run) => buildRunTraceItem(run, input.detailsByRunId?.[run.id], input.insightsByRunId?.[run.id]));
|
|
43
|
+
const project = input.project ?? '<project>';
|
|
44
|
+
return {
|
|
45
|
+
meta: meta('runs', 'Runs', 'run records, details, checkpoints, approvals, operation attempts', input.generatedAt),
|
|
46
|
+
summary: `${items.length} visible run(s); ${items.reduce((count, item) => count + item.relatedApprovals.length, 0)} approval(s); ${items.reduce((count, item) => count + item.relatedPreflights.length, 0)} preflight/attempt(s).`,
|
|
47
|
+
items,
|
|
48
|
+
...(items.length === 0 ? { emptyState: { message: 'No runs are registered or visible for this project.', nextActions: [dashboardCopyAction('Inspect runs', `npm run cli -- runs list --project ${project}`)] } } : {}),
|
|
49
|
+
copyActions: [dashboardCopyAction('Inspect runs', `npm run cli -- runs list --project ${project}`)],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function buildDashboardApprovalsReadModel(input) {
|
|
53
|
+
const items = input.runs.flatMap((run) => approvalItemsForRun(run, input.detailsByRunId?.[run.id]));
|
|
54
|
+
items.sort((a, b) => (a.status === 'pending' ? 0 : 1) - (b.status === 'pending' ? 0 : 1) || (b.createdAt ?? '').localeCompare(a.createdAt ?? ''));
|
|
55
|
+
const project = input.project ?? '<project>';
|
|
56
|
+
return {
|
|
57
|
+
meta: meta('approvals', 'Approvals', 'run approvals and operation attempts', input.generatedAt),
|
|
58
|
+
summary: `${items.filter((item) => item.status === 'pending').length} pending; ${items.length} pending/recent approval or preflight item(s).`,
|
|
59
|
+
items,
|
|
60
|
+
...(items.length === 0 ? { emptyState: { message: 'No pending or recent approvals/preflights are visible for this project.', nextActions: [dashboardCopyAction('Inspect approvals', `npm run cli -- approvals list --project ${project}`)] } } : {}),
|
|
61
|
+
copyActions: [dashboardCopyAction('Inspect approvals', `npm run cli -- approvals list --project ${project}`)],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function redactDashboardValue(value) {
|
|
65
|
+
return redactJsonValue(value);
|
|
66
|
+
}
|
|
67
|
+
function meta(screen, title, source, generatedAt = new Date().toISOString()) {
|
|
68
|
+
return { screen, title, generatedAt, source, redactionsApplied: [redactedPlaceholder], safety: dashboardSafety() };
|
|
69
|
+
}
|
|
70
|
+
function buildRunTraceItem(run, details, insights) {
|
|
71
|
+
const checkpoints = details?.checkpoints ?? [];
|
|
72
|
+
const lastCheckpoint = checkpoints.at(-1);
|
|
73
|
+
const approvals = details?.approvals ?? [];
|
|
74
|
+
const attempts = details?.operationAttempts ?? [];
|
|
75
|
+
const relatedApprovals = approvals.map((approval) => {
|
|
76
|
+
const attempt = attempts.find((candidate) => candidate.approvalId === approval.id || candidate.decisionEventId === approval.decisionEventId);
|
|
77
|
+
return { id: approval.id, status: approval.status, ...(attempt === undefined ? {} : { category: attempt.category, operation: attempt.operation }) };
|
|
78
|
+
});
|
|
79
|
+
const relatedPreflights = attempts.map((attempt) => ({ id: attempt.id, status: attempt.status, category: attempt.category, operation: attempt.operation, riskFlags: riskFlags(attempt) }));
|
|
80
|
+
return {
|
|
81
|
+
runId: run.id,
|
|
82
|
+
project: run.project,
|
|
83
|
+
workflow: run.workflow,
|
|
84
|
+
phase: run.phase,
|
|
85
|
+
status: run.status,
|
|
86
|
+
...(run.outcome === undefined ? {} : { outcome: run.outcome }),
|
|
87
|
+
...(run.outcomeReason === undefined ? {} : { outcomeReason: safeText(run.outcomeReason) }),
|
|
88
|
+
selectedAgentId: run.selectedAgentId,
|
|
89
|
+
model: run.model,
|
|
90
|
+
createdAt: run.createdAt,
|
|
91
|
+
updatedAt: run.updatedAt,
|
|
92
|
+
...(run.completedAt === undefined ? {} : { completedAt: run.completedAt }),
|
|
93
|
+
...(lastCheckpoint === undefined ? {} : { lastCheckpoint: { id: lastCheckpoint.id, label: lastCheckpoint.label, createdAt: lastCheckpoint.createdAt, stateSummary: summarizeJson(lastCheckpoint.state) } }),
|
|
94
|
+
checkpointsSummary: checkpoints.length === 0 ? noLink : `${checkpoints.length} checkpoint(s); latest ${lastCheckpoint?.label ?? 'unknown'}`,
|
|
95
|
+
relatedApprovals,
|
|
96
|
+
relatedPreflights,
|
|
97
|
+
blockers: insights?.resumePlan.blockers.map((blocker) => `${blocker.code}: ${safeText(blocker.message)}`) ?? [],
|
|
98
|
+
latestTimeline: insights?.timeline.slice(-3).map((entry) => `${entry.type}: ${safeText(entry.label)}`) ?? [],
|
|
99
|
+
nextActions: [dashboardCopyAction('Inspect run', `npm run cli -- runs get --id ${run.id}`), dashboardCopyAction('Inspect timeline', `npm run cli -- runs timeline --id ${run.id}`), dashboardCopyAction('Inspect resume plan', `npm run cli -- runs resume-inspect --id ${run.id}`)],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function approvalItemsForRun(run, details) {
|
|
103
|
+
if (details === undefined)
|
|
104
|
+
return [];
|
|
105
|
+
const approvalItems = details.approvals.map((approval) => {
|
|
106
|
+
const attempt = details.operationAttempts.find((candidate) => candidate.approvalId === approval.id || candidate.decisionEventId === approval.decisionEventId);
|
|
107
|
+
return {
|
|
108
|
+
id: approval.id,
|
|
109
|
+
kind: 'approval',
|
|
110
|
+
runId: run.id,
|
|
111
|
+
category: attempt?.category ?? 'unknown',
|
|
112
|
+
operation: attempt?.operation ?? 'unknown',
|
|
113
|
+
status: approval.status,
|
|
114
|
+
riskFlags: attempt === undefined ? [] : riskFlags(attempt),
|
|
115
|
+
reason: safeText(approval.resolutionReason ?? (approval.status === 'pending' ? 'Awaiting explicit decision outside TUI.' : 'No reason recorded.')),
|
|
116
|
+
agent: run.selectedAgentId,
|
|
117
|
+
createdAt: approval.requestedAt,
|
|
118
|
+
updatedAt: approval.resolvedAt ?? approval.requestedAt,
|
|
119
|
+
nextActions: [dashboardCopyAction('Inspect run approvals', `npm run cli -- runs approvals --run-id ${run.id}`), dashboardCopyAction('Inspect resume gate', `npm run cli -- runs resume-gate --approval ${approval.id}`)],
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
const preflightItems = details.operationAttempts.map((attempt) => ({
|
|
123
|
+
id: attempt.id,
|
|
124
|
+
kind: 'preflight',
|
|
125
|
+
runId: run.id,
|
|
126
|
+
category: attempt.category,
|
|
127
|
+
operation: attempt.operation,
|
|
128
|
+
status: attempt.status,
|
|
129
|
+
riskFlags: riskFlags(attempt),
|
|
130
|
+
reason: summarizeJson(attempt.operationMetadata),
|
|
131
|
+
agent: attempt.executorName,
|
|
132
|
+
createdAt: attempt.reservedAt,
|
|
133
|
+
updatedAt: attempt.updatedAt,
|
|
134
|
+
nextActions: [dashboardCopyAction('Inspect run', `npm run cli -- runs get --id ${run.id}`), dashboardCopyAction('Evaluate retry', `npm run cli -- runs retry-check --approval ${attempt.approvalId ?? '<approval-id>'}`)],
|
|
135
|
+
}));
|
|
136
|
+
return [...approvalItems, ...preflightItems];
|
|
137
|
+
}
|
|
138
|
+
function riskFlags(attempt) {
|
|
139
|
+
const metadata = redactDashboardValue(attempt.operationMetadata);
|
|
140
|
+
if (typeof metadata !== 'object' || metadata === null || Array.isArray(metadata))
|
|
141
|
+
return [];
|
|
142
|
+
return ['destructive', 'external', 'privileged', 'ambiguous', 'sandboxStrategy']
|
|
143
|
+
.filter((key) => Object.prototype.hasOwnProperty.call(metadata, key))
|
|
144
|
+
.map((key) => `${key}=${String(metadata[key])}`);
|
|
145
|
+
}
|
|
146
|
+
function summarizeJson(value) {
|
|
147
|
+
const redacted = redactDashboardValue(value);
|
|
148
|
+
const rendered = typeof redacted === 'string' ? redacted : JSON.stringify(redacted);
|
|
149
|
+
return safeText(rendered).slice(0, 180);
|
|
150
|
+
}
|
|
151
|
+
function safeText(value) {
|
|
152
|
+
return redactJsonValue(value).replaceAll(/\s+/g, ' ').trim();
|
|
153
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { renderSetupStatus, setupNeedsAction } from './setup-status-renderer.js';
|
|
2
|
+
export function renderDashboard(view) {
|
|
3
|
+
return [
|
|
4
|
+
'VGXNESS Dashboard',
|
|
5
|
+
'',
|
|
6
|
+
renderProject(view),
|
|
7
|
+
'',
|
|
8
|
+
renderLatestRuns(view.runs),
|
|
9
|
+
'',
|
|
10
|
+
renderSelectedRun(view.selectedRun, view.selectedRunInsights),
|
|
11
|
+
'',
|
|
12
|
+
renderSetup(view.setup),
|
|
13
|
+
'',
|
|
14
|
+
renderSdd(view.sdd, view.project),
|
|
15
|
+
'',
|
|
16
|
+
renderNextDecision(view),
|
|
17
|
+
'',
|
|
18
|
+
renderSafetyBoundary(),
|
|
19
|
+
'',
|
|
20
|
+
].join('\n');
|
|
21
|
+
}
|
|
22
|
+
function renderSetup(setup) {
|
|
23
|
+
return setup === undefined ? ['Setup Status', '- Setup lifecycle status unavailable. Run `setup status` for JSON details.'].join('\n') : renderSetupStatus(setup);
|
|
24
|
+
}
|
|
25
|
+
function renderProject(view) {
|
|
26
|
+
return ['Project', `- Name: ${view.project}`, `- Store: ${view.databasePath}`].join('\n');
|
|
27
|
+
}
|
|
28
|
+
function renderLatestRuns(runs) {
|
|
29
|
+
if (runs.length === 0)
|
|
30
|
+
return ['Latest Runs', '- No runs found for this project.'].join('\n');
|
|
31
|
+
return ['Latest Runs', ...runs.map((run) => `- ${run.id}: ${run.status} ${run.workflow}/${run.phase} — ${run.userIntent}`)].join('\n');
|
|
32
|
+
}
|
|
33
|
+
function renderSelectedRun(run, insights) {
|
|
34
|
+
if (run === undefined)
|
|
35
|
+
return ['Selected Run', '- No run selected. Pass --run-id <id> for details.'].join('\n');
|
|
36
|
+
const lines = [
|
|
37
|
+
'Selected Run',
|
|
38
|
+
`- ID: ${run.id}`,
|
|
39
|
+
`- Status: ${run.status}`,
|
|
40
|
+
`- Intent: ${run.userIntent}`,
|
|
41
|
+
`- Events: ${run.events.length}`,
|
|
42
|
+
`- Checkpoints: ${run.checkpoints.length}`,
|
|
43
|
+
`- Approvals: ${run.approvals.length}`,
|
|
44
|
+
`- Operation attempts: ${run.operationAttempts.length}`,
|
|
45
|
+
];
|
|
46
|
+
if (insights !== undefined)
|
|
47
|
+
lines.push('', renderRunInsights(insights));
|
|
48
|
+
return lines.join('\n');
|
|
49
|
+
}
|
|
50
|
+
function renderRunInsights(insights) {
|
|
51
|
+
const lines = [
|
|
52
|
+
'Run Insights',
|
|
53
|
+
`- Debug: ${insights.debug.cause} — ${insights.debug.summary}`,
|
|
54
|
+
`- Recommended action: ${insights.debug.recommendedAction}`,
|
|
55
|
+
`- Resume plan: ${renderResumePlan(insights.resumePlan)}`,
|
|
56
|
+
];
|
|
57
|
+
if (insights.resumePlan.blockers.length > 0)
|
|
58
|
+
lines.push(`- Blockers: ${insights.resumePlan.blockers.map((blocker) => `${blocker.code} — ${blocker.message}`).join('; ')}`);
|
|
59
|
+
const latestTimeline = insights.timeline.slice(-3).map((entry) => `${entry.type}: ${entry.label}`);
|
|
60
|
+
if (latestTimeline.length > 0)
|
|
61
|
+
lines.push('- Latest timeline:', ...latestTimeline.map((entry) => ` - ${entry}`));
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
function renderResumePlan(plan) {
|
|
65
|
+
if (plan.resumable && plan.latestCheckpoint !== undefined)
|
|
66
|
+
return `manual review eligible from checkpoint ${plan.latestCheckpoint.id}`;
|
|
67
|
+
return 'blocked or unavailable';
|
|
68
|
+
}
|
|
69
|
+
function renderSdd(sdd, project) {
|
|
70
|
+
if (sdd === undefined)
|
|
71
|
+
return ['SDD Change', '- No change selected. Pass --change <id> to inspect workflow state.'].join('\n');
|
|
72
|
+
const present = sdd.status.phases.filter((phase) => phase.present).map((phase) => phase.phase);
|
|
73
|
+
if (present.length === 0) {
|
|
74
|
+
return ['SDD Change', `- Change ${sdd.change} cannot be found yet. Pass --change <id> after saving SDD artifacts.`].join('\n');
|
|
75
|
+
}
|
|
76
|
+
const lines = [
|
|
77
|
+
'SDD Change',
|
|
78
|
+
`- Change: ${sdd.change}`,
|
|
79
|
+
`- Present: ${present.join(', ')}`,
|
|
80
|
+
`- Missing: ${sdd.status.phases.filter((phase) => !phase.present).map((phase) => phase.phase).join(', ') || 'none'}`,
|
|
81
|
+
];
|
|
82
|
+
if (sdd.status.nextReadyPhase !== undefined)
|
|
83
|
+
lines.push(`- Next ready phase: ${sdd.status.nextReadyPhase}`);
|
|
84
|
+
if (sdd.readiness !== undefined && !sdd.readiness.ready)
|
|
85
|
+
lines.push(`- Missing prerequisites: ${sdd.readiness.missingArtifactTopicKeys.join(', ')}`);
|
|
86
|
+
lines.push(`- Inspect with: sdd status --project ${project} --change ${sdd.change}`);
|
|
87
|
+
return lines.join('\n');
|
|
88
|
+
}
|
|
89
|
+
function renderNextDecision(view) {
|
|
90
|
+
if (view.setup !== undefined && setupNeedsAction(view.setup)) {
|
|
91
|
+
return ['Next Decision', `- Resolve setup first with \`${view.setup.nextAction.command}\`: ${view.setup.nextAction.reason}.`].join('\n');
|
|
92
|
+
}
|
|
93
|
+
const sddNext = view.sdd?.status.nextReadyPhase;
|
|
94
|
+
const hasSddArtifacts = (view.sdd?.status.phases.some((phase) => phase.present) ?? false);
|
|
95
|
+
if (view.sdd !== undefined && hasSddArtifacts && sddNext !== undefined) {
|
|
96
|
+
return ['Next Decision', `- Continue SDD workflow with \`sdd ready --project ${view.project} --change ${view.sdd.change} --phase ${sddNext}\`.`].join('\n');
|
|
97
|
+
}
|
|
98
|
+
if (view.selectedRun !== undefined)
|
|
99
|
+
return ['Next Decision', '- Inspect selected run events, checkpoints, approvals, or operation attempts.'].join('\n');
|
|
100
|
+
if (view.runs.length === 0)
|
|
101
|
+
return ['Next Decision', '- Create a run or pass --change <id> to inspect SDD state.'].join('\n');
|
|
102
|
+
return ['Next Decision', '- Select a run with --run-id <id> or pass --change <id> for workflow readiness.'].join('\n');
|
|
103
|
+
}
|
|
104
|
+
function renderSafetyBoundary() {
|
|
105
|
+
return [
|
|
106
|
+
'Safety Boundary',
|
|
107
|
+
'- Read-only dashboard: no preflight, provider execution, shell execution, config writes, workers, sandbox, worktree, or OpenSpec writes.',
|
|
108
|
+
].join('\n');
|
|
109
|
+
}
|