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,191 @@
|
|
|
1
|
+
export class SkillResolver {
|
|
2
|
+
skills;
|
|
3
|
+
agents;
|
|
4
|
+
constructor(skills, agents) {
|
|
5
|
+
this.skills = skills;
|
|
6
|
+
this.agents = agents;
|
|
7
|
+
}
|
|
8
|
+
resolve(input) {
|
|
9
|
+
const agent = this.resolveAgent(input);
|
|
10
|
+
if (!agent.ok)
|
|
11
|
+
return agent;
|
|
12
|
+
const contextProject = input.project ?? agent.value?.project;
|
|
13
|
+
const contextScope = input.scope ?? agent.value?.scope ?? 'project';
|
|
14
|
+
const targetCandidates = this.targetCandidates(input, agent.value);
|
|
15
|
+
const attachmentCandidates = this.attachmentCandidates(targetCandidates);
|
|
16
|
+
if (!attachmentCandidates.ok)
|
|
17
|
+
return attachmentCandidates;
|
|
18
|
+
const candidates = [
|
|
19
|
+
...this.agentSkillCandidates(agent.value),
|
|
20
|
+
...attachmentCandidates.value,
|
|
21
|
+
];
|
|
22
|
+
const bySkill = new Map();
|
|
23
|
+
const skipped = [];
|
|
24
|
+
for (const candidate of candidates) {
|
|
25
|
+
const skillResult = this.resolveSkill(candidate, contextProject, contextScope);
|
|
26
|
+
if (!skillResult.ok) {
|
|
27
|
+
skipped.push(skippedResolution(candidate.source, skillResult.error.message, candidate.skillId, candidate.name));
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const versionResult = this.resolveVersion(skillResult.value, candidate.versionId);
|
|
31
|
+
if (!versionResult.ok) {
|
|
32
|
+
skipped.push({ source: candidate.source, skillId: skillResult.value.id, name: skillResult.value.name, reason: versionResult.error.message });
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const existing = bySkill.get(skillResult.value.id);
|
|
36
|
+
if (existing !== undefined) {
|
|
37
|
+
existing.sources.push(candidate.source);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
bySkill.set(skillResult.value.id, { skill: skillResult.value, version: versionResult.value, sources: [candidate.source] });
|
|
41
|
+
}
|
|
42
|
+
let usageRecorded = 0;
|
|
43
|
+
if (input.runId !== undefined && input.recordUsage !== undefined) {
|
|
44
|
+
for (const resolved of bySkill.values()) {
|
|
45
|
+
const usage = this.recordResolvedUsage(resolved, input.runId, input.recordUsage);
|
|
46
|
+
if (!usage.ok)
|
|
47
|
+
return usage;
|
|
48
|
+
usageRecorded += 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return ok({
|
|
52
|
+
context: {
|
|
53
|
+
...(contextProject !== undefined ? { project: contextProject } : {}),
|
|
54
|
+
...(contextScope !== undefined ? { scope: contextScope } : {}),
|
|
55
|
+
...(agent.value?.id !== undefined ? { agentId: agent.value.id } : {}),
|
|
56
|
+
...(agent.value?.name !== undefined ? { agentName: agent.value.name } : {}),
|
|
57
|
+
...(input.workflow !== undefined ? { workflow: input.workflow } : {}),
|
|
58
|
+
...(input.phase !== undefined ? { phase: input.phase } : {}),
|
|
59
|
+
...(input.providerAdapter !== undefined ? { providerAdapter: input.providerAdapter } : {}),
|
|
60
|
+
...(input.runId !== undefined ? { runId: input.runId } : {}),
|
|
61
|
+
},
|
|
62
|
+
skills: [...bySkill.values()].map(toResolvedSkill),
|
|
63
|
+
skipped,
|
|
64
|
+
usageRecorded,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
resolveAgent(input) {
|
|
68
|
+
if (input.agentId !== undefined)
|
|
69
|
+
return this.agents.getById(input.agentId);
|
|
70
|
+
if (input.agentName === undefined)
|
|
71
|
+
return ok(undefined);
|
|
72
|
+
if (input.project === undefined)
|
|
73
|
+
return validationFailure('--project is required when resolving by agent name');
|
|
74
|
+
return this.agents.getByName(input.project, input.scope ?? 'project', input.agentName);
|
|
75
|
+
}
|
|
76
|
+
targetCandidates(input, agent) {
|
|
77
|
+
const targets = [];
|
|
78
|
+
if (agent !== undefined) {
|
|
79
|
+
targets.push({ targetType: agent.mode, targetKey: agent.id });
|
|
80
|
+
targets.push({ targetType: agent.mode, targetKey: agent.name });
|
|
81
|
+
targets.push({ targetType: agent.mode, targetKey: `${agent.project}/${agent.scope}/${agent.name}` });
|
|
82
|
+
}
|
|
83
|
+
if (input.workflow !== undefined && input.phase !== undefined)
|
|
84
|
+
targets.push({ targetType: 'workflow-phase', targetKey: `${input.workflow}:${input.phase}` });
|
|
85
|
+
if (input.phase !== undefined)
|
|
86
|
+
targets.push({ targetType: 'workflow-phase', targetKey: input.phase });
|
|
87
|
+
if (input.providerAdapter !== undefined)
|
|
88
|
+
targets.push({ targetType: 'provider-adapter', targetKey: input.providerAdapter });
|
|
89
|
+
return dedupeTargets(targets);
|
|
90
|
+
}
|
|
91
|
+
agentSkillCandidates(agent) {
|
|
92
|
+
if (agent === undefined)
|
|
93
|
+
return [];
|
|
94
|
+
return agent.skills.map((name) => ({ name, source: { kind: 'agent-definition', targetType: agent.mode, targetKey: agent.id, reason: 'agent.skills' } }));
|
|
95
|
+
}
|
|
96
|
+
attachmentCandidates(targets) {
|
|
97
|
+
const attachments = this.skills.listAttachmentsForTargets(targets);
|
|
98
|
+
if (!attachments.ok)
|
|
99
|
+
return attachments;
|
|
100
|
+
return ok(attachments.value.map((attachment) => {
|
|
101
|
+
const candidate = { skillId: attachment.skillId, source: attachmentSource(attachment) };
|
|
102
|
+
if (attachment.versionId !== undefined)
|
|
103
|
+
candidate.versionId = attachment.versionId;
|
|
104
|
+
return candidate;
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
resolveSkill(candidate, project, scope) {
|
|
108
|
+
if (candidate.skillId !== undefined)
|
|
109
|
+
return this.skills.getById(candidate.skillId);
|
|
110
|
+
if (candidate.name === undefined)
|
|
111
|
+
return validationFailure('Resolved skill candidate has neither skill id nor name');
|
|
112
|
+
if (project === undefined)
|
|
113
|
+
return validationFailure(`Skill ${candidate.name} needs a project context`);
|
|
114
|
+
return this.skills.getByName(project, scope, candidate.name);
|
|
115
|
+
}
|
|
116
|
+
resolveVersion(skill, candidateVersionId) {
|
|
117
|
+
const versionId = candidateVersionId ?? skill.currentVersionId;
|
|
118
|
+
if (versionId === undefined)
|
|
119
|
+
return validationFailure(`Skill ${skill.name} has no current version`);
|
|
120
|
+
const version = this.skills.getVersion(versionId);
|
|
121
|
+
if (!version.ok)
|
|
122
|
+
return version;
|
|
123
|
+
if (version.value.status !== 'active')
|
|
124
|
+
return validationFailure(`Skill ${skill.name} version ${version.value.version} is ${version.value.status}, not active`);
|
|
125
|
+
return version;
|
|
126
|
+
}
|
|
127
|
+
recordResolvedUsage(resolved, runId, outcome) {
|
|
128
|
+
const firstAttachment = resolved.sources.find((source) => source.kind === 'attachment');
|
|
129
|
+
const input = {
|
|
130
|
+
skillId: resolved.skill.id,
|
|
131
|
+
versionId: resolved.version.id,
|
|
132
|
+
runId,
|
|
133
|
+
outcome,
|
|
134
|
+
notes: `skill resolution v1 ${outcome}`,
|
|
135
|
+
};
|
|
136
|
+
if (firstAttachment?.targetType !== undefined && firstAttachment.targetKey !== undefined) {
|
|
137
|
+
input.targetType = firstAttachment.targetType;
|
|
138
|
+
input.targetKey = firstAttachment.targetKey;
|
|
139
|
+
}
|
|
140
|
+
return this.skills.recordUsage(input);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function toResolvedSkill(value) {
|
|
144
|
+
const sourceMetadata = value.version.source.inlineMetadata ?? {};
|
|
145
|
+
const summary = stringMetadata(sourceMetadata.summary);
|
|
146
|
+
const content = stringMetadata(sourceMetadata.content);
|
|
147
|
+
return {
|
|
148
|
+
skillId: value.skill.id,
|
|
149
|
+
name: value.skill.name,
|
|
150
|
+
description: value.skill.description,
|
|
151
|
+
versionId: value.version.id,
|
|
152
|
+
version: value.version.version,
|
|
153
|
+
source: value.version.source,
|
|
154
|
+
sourceMetadata,
|
|
155
|
+
...(summary !== undefined ? { summary } : {}),
|
|
156
|
+
...(content !== undefined ? { content } : {}),
|
|
157
|
+
attachmentSources: value.sources,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function attachmentSource(attachment) {
|
|
161
|
+
const reason = stringMetadata(attachment.metadata.reason);
|
|
162
|
+
return {
|
|
163
|
+
kind: 'attachment',
|
|
164
|
+
targetType: attachment.targetType,
|
|
165
|
+
targetKey: attachment.targetKey,
|
|
166
|
+
...(reason !== undefined ? { reason } : {}),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function dedupeTargets(targets) {
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
return targets.filter((target) => {
|
|
172
|
+
const key = `${target.targetType}:${target.targetKey}`;
|
|
173
|
+
if (seen.has(key))
|
|
174
|
+
return false;
|
|
175
|
+
seen.add(key);
|
|
176
|
+
return true;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function stringMetadata(value) {
|
|
180
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
181
|
+
}
|
|
182
|
+
function skippedResolution(source, reason, skillId, name) {
|
|
183
|
+
const skipped = { source, reason };
|
|
184
|
+
if (skillId !== undefined)
|
|
185
|
+
skipped.skillId = skillId;
|
|
186
|
+
if (name !== undefined)
|
|
187
|
+
skipped.name = name;
|
|
188
|
+
return skipped;
|
|
189
|
+
}
|
|
190
|
+
function ok(value) { return { ok: true, value }; }
|
|
191
|
+
function validationFailure(message) { return { ok: false, error: { code: 'validation_failed', message } }; }
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { buildSandboxExecutionPlan, executeSandboxedProcessSync } from '../runs/sandbox-process-execution.js';
|
|
3
|
+
export const defaultCommandAllowlist = Object.freeze({
|
|
4
|
+
typecheck: Object.freeze({ id: 'typecheck', executable: 'npm', args: ['run', 'typecheck'], timeoutMs: 60_000, outputLimitBytes: 256 * 1024, description: 'Run the project TypeScript typecheck script.' }),
|
|
5
|
+
'node-version': Object.freeze({ id: 'node-version', executable: 'node', args: ['--version'], timeoutMs: 5_000, outputLimitBytes: 8 * 1024, description: 'Print the local Node.js version for a harmless bounded-process smoke check.' }),
|
|
6
|
+
});
|
|
7
|
+
export class CommandAllowlistAdapter {
|
|
8
|
+
id = 'command-allowlist-adapter';
|
|
9
|
+
commandId;
|
|
10
|
+
plan;
|
|
11
|
+
entry;
|
|
12
|
+
runner;
|
|
13
|
+
request;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
const allowlist = options.allowlist ?? defaultCommandAllowlist;
|
|
16
|
+
const entry = allowlist[options.commandId];
|
|
17
|
+
if (entry === undefined)
|
|
18
|
+
throw new Error(`Command is not allowlisted: ${options.commandId}`);
|
|
19
|
+
this.commandId = options.commandId;
|
|
20
|
+
this.entry = entry;
|
|
21
|
+
this.runner = options.runner ?? executeSandboxedProcessSync;
|
|
22
|
+
this.request = {
|
|
23
|
+
workspaceRoot: options.workspaceRoot,
|
|
24
|
+
cwd: options.cwd === undefined ? options.workspaceRoot : resolve(options.workspaceRoot, options.cwd),
|
|
25
|
+
command: entry.executable,
|
|
26
|
+
args: entry.args,
|
|
27
|
+
allowedCommands: [entry.executable],
|
|
28
|
+
timeoutMs: entry.timeoutMs,
|
|
29
|
+
outputLimitBytes: entry.outputLimitBytes,
|
|
30
|
+
targetPaths: options.targetPaths ?? [],
|
|
31
|
+
};
|
|
32
|
+
const plan = buildSandboxExecutionPlan(this.request);
|
|
33
|
+
if (!plan.ok)
|
|
34
|
+
throw new Error(`${plan.error.code}: ${plan.error.message}`);
|
|
35
|
+
this.plan = plan.value;
|
|
36
|
+
}
|
|
37
|
+
dispatch(_request) {
|
|
38
|
+
const result = this.runner(this.request);
|
|
39
|
+
if (!result.ok)
|
|
40
|
+
throw new Error(`${result.error.code}: ${result.error.message}`);
|
|
41
|
+
const output = {
|
|
42
|
+
command: this.commandId,
|
|
43
|
+
executable: this.entry.executable,
|
|
44
|
+
args: this.entry.args,
|
|
45
|
+
exitCode: result.value.exitCode,
|
|
46
|
+
signal: result.value.signal,
|
|
47
|
+
timedOut: result.value.timedOut,
|
|
48
|
+
outputTruncated: result.value.outputTruncated,
|
|
49
|
+
stdout: result.value.stdout,
|
|
50
|
+
stderr: result.value.stderr,
|
|
51
|
+
};
|
|
52
|
+
const failed = result.value.timedOut || result.value.exitCode !== 0;
|
|
53
|
+
return {
|
|
54
|
+
output,
|
|
55
|
+
audit: {
|
|
56
|
+
adapter: this.id,
|
|
57
|
+
command: this.commandId,
|
|
58
|
+
description: this.entry.description,
|
|
59
|
+
boundedProcess: true,
|
|
60
|
+
shell: false,
|
|
61
|
+
argv: [this.entry.executable, ...this.entry.args],
|
|
62
|
+
cwd: this.plan.request.cwd,
|
|
63
|
+
},
|
|
64
|
+
...(failed ? { failure: { reason: result.value.timedOut ? 'command-timed-out' : 'command-exit-nonzero', message: `Allowlisted command ${this.commandId} exited with ${result.value.exitCode ?? result.value.signal ?? 'unknown'}.` } } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function commandAllowlistIds(allowlist = defaultCommandAllowlist) {
|
|
69
|
+
return Object.keys(allowlist).sort();
|
|
70
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
export class SafeNonDispatchingWorkflowExecutor {
|
|
2
|
+
id = 'safe-non-dispatching-workflow-executor';
|
|
3
|
+
kind = 'safe-non-dispatching';
|
|
4
|
+
execute(request) {
|
|
5
|
+
const base = this.basePayload(request);
|
|
6
|
+
const preflight = request.preflight;
|
|
7
|
+
if (preflight === undefined) {
|
|
8
|
+
return this.blocked(request, base, 'missing-preflight', 'Workflow execution requires an auditable preflight record before any dispatch decision.', 'Record preflight through the existing safety gate, then retry execute.');
|
|
9
|
+
}
|
|
10
|
+
if (!hasPreflightAudit(preflight)) {
|
|
11
|
+
return this.blocked(request, base, 'missing-preflight-audit', 'Workflow execution preflight is missing required audit identifiers.', 'Inspect the run safety events, then retry after a complete preflight is recorded.');
|
|
12
|
+
}
|
|
13
|
+
if (preflight.outcome === 'blocked') {
|
|
14
|
+
return this.blocked(request, base, 'preflight-blocked', 'Provider delegation preflight blocked this execution request.', 'Inspect preflight audit and resolve the denied permission or sandbox blocker before retrying.');
|
|
15
|
+
}
|
|
16
|
+
const approvalId = preflight.approval?.id ?? preflight.audit.approvalId;
|
|
17
|
+
const nextAction = preflight.outcome === 'approval-needed'
|
|
18
|
+
? `Review approval ${approvalId ?? '<pending>'}; safe provider execution is not automated by ${this.id}.`
|
|
19
|
+
: `Preflight allowed the request, but ${this.id} never calls providers; dispatch manually only after human review or install an approved concrete executor.`;
|
|
20
|
+
const reason = preflight.outcome === 'approval-needed' ? 'approval-required' : 'no-approved-concrete-executor';
|
|
21
|
+
const message = preflight.outcome === 'approval-needed'
|
|
22
|
+
? 'Preflight requires human approval; the safe default executor recorded readiness without dispatching.'
|
|
23
|
+
: 'No approved concrete provider executor is wired; the safe default executor recorded readiness without dispatching.';
|
|
24
|
+
return {
|
|
25
|
+
status: 'needs-human',
|
|
26
|
+
dispatched: false,
|
|
27
|
+
dispatchMode: 'ready-to-dispatch',
|
|
28
|
+
executorId: this.id,
|
|
29
|
+
executorKind: this.kind,
|
|
30
|
+
reason,
|
|
31
|
+
message,
|
|
32
|
+
audit: executorAudit(this, preflight),
|
|
33
|
+
checkpointLabel: 'workflow-execution-requested',
|
|
34
|
+
checkpointPayload: {
|
|
35
|
+
...base,
|
|
36
|
+
status: 'needs-human',
|
|
37
|
+
dispatched: false,
|
|
38
|
+
dispatchMode: 'ready-to-dispatch',
|
|
39
|
+
executorId: this.id,
|
|
40
|
+
executorKind: this.kind,
|
|
41
|
+
reason,
|
|
42
|
+
message,
|
|
43
|
+
safetyGates: safetyGatesPayload(request, preflight),
|
|
44
|
+
nextAction,
|
|
45
|
+
},
|
|
46
|
+
nextAction,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
blocked(request, base, reason, message, nextAction) {
|
|
50
|
+
const audit = request.preflight === undefined ? { executorId: this.id, executorKind: this.kind, preflightPresent: false } : executorAudit(this, request.preflight);
|
|
51
|
+
return {
|
|
52
|
+
status: 'blocked',
|
|
53
|
+
dispatched: false,
|
|
54
|
+
dispatchMode: 'not-dispatched',
|
|
55
|
+
executorId: this.id,
|
|
56
|
+
executorKind: this.kind,
|
|
57
|
+
reason,
|
|
58
|
+
message,
|
|
59
|
+
audit,
|
|
60
|
+
checkpointLabel: 'workflow-execution-blocked',
|
|
61
|
+
checkpointPayload: {
|
|
62
|
+
...base,
|
|
63
|
+
status: 'blocked',
|
|
64
|
+
dispatched: false,
|
|
65
|
+
dispatchMode: 'not-dispatched',
|
|
66
|
+
executorId: this.id,
|
|
67
|
+
executorKind: this.kind,
|
|
68
|
+
reason,
|
|
69
|
+
message,
|
|
70
|
+
safetyGates: safetyGatesPayload(request, request.preflight),
|
|
71
|
+
nextAction,
|
|
72
|
+
},
|
|
73
|
+
nextAction,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
basePayload(request) {
|
|
77
|
+
return {
|
|
78
|
+
kind: 'workflow-execution-requested',
|
|
79
|
+
version: 1,
|
|
80
|
+
runId: request.run.id,
|
|
81
|
+
workflow: request.workflow.id,
|
|
82
|
+
phase: request.run.phase,
|
|
83
|
+
selectedAgent: request.selectedAgentSummary,
|
|
84
|
+
requestedOperation: operationMetadataForWorkflowExecution(request.requestedOperation),
|
|
85
|
+
recommendation: request.plannerRecommendation.recommendation,
|
|
86
|
+
planner: request.plannerRecommendation.planner,
|
|
87
|
+
safety: workflowExecutorSafety(request.preflight !== undefined),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export class GuardedProviderWorkflowExecutor {
|
|
92
|
+
id = 'guarded-provider-workflow-executor';
|
|
93
|
+
kind = 'provider-adapter';
|
|
94
|
+
adapter;
|
|
95
|
+
capabilities;
|
|
96
|
+
constructor(options = {}) {
|
|
97
|
+
this.adapter = options.adapter;
|
|
98
|
+
this.capabilities = {
|
|
99
|
+
sandboxEnforceable: options.capabilities?.sandboxEnforceable === true,
|
|
100
|
+
processExecutionEnforceable: options.capabilities?.processExecutionEnforceable === true,
|
|
101
|
+
workspaceBoundaryEnforced: options.capabilities?.workspaceBoundaryEnforced === true,
|
|
102
|
+
providerConfigWritesBlocked: options.capabilities?.providerConfigWritesBlocked === true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
execute(request) {
|
|
106
|
+
const base = basePayload(request);
|
|
107
|
+
const preflight = request.preflight;
|
|
108
|
+
const blocker = this.firstBlocker(request, preflight);
|
|
109
|
+
if (blocker !== undefined) {
|
|
110
|
+
const status = blocker.needsHuman === true ? 'needs-human' : 'blocked';
|
|
111
|
+
return result({
|
|
112
|
+
request,
|
|
113
|
+
base,
|
|
114
|
+
executor: this,
|
|
115
|
+
status,
|
|
116
|
+
dispatched: false,
|
|
117
|
+
dispatchMode: 'not-dispatched',
|
|
118
|
+
reason: blocker.reason,
|
|
119
|
+
message: blocker.message,
|
|
120
|
+
nextAction: blocker.nextAction,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (this.adapter === undefined) {
|
|
124
|
+
return result({
|
|
125
|
+
request,
|
|
126
|
+
base,
|
|
127
|
+
executor: this,
|
|
128
|
+
status: 'blocked',
|
|
129
|
+
dispatched: false,
|
|
130
|
+
dispatchMode: 'not-dispatched',
|
|
131
|
+
reason: 'missing-provider-dispatch-adapter',
|
|
132
|
+
message: 'Provider executor safety gates passed, but no explicit dispatch adapter was injected.',
|
|
133
|
+
nextAction: 'Inject an approved provider dispatch adapter in a controlled runtime; production defaults do not dispatch providers.',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const adapterResult = this.adapter.dispatch(request);
|
|
138
|
+
const audit = providerExecutorAudit(this, request.preflight, effectiveCapabilities(this.capabilities, request.sandboxExecutionEvidence), request.sandboxExecutionEvidence, this.adapter, adapterResult.audit);
|
|
139
|
+
const failure = adapterResult.failure;
|
|
140
|
+
const failed = failure !== undefined;
|
|
141
|
+
return {
|
|
142
|
+
status: failed ? 'failed' : 'dispatched',
|
|
143
|
+
dispatched: true,
|
|
144
|
+
dispatchMode: 'provider-adapter',
|
|
145
|
+
executorId: this.id,
|
|
146
|
+
executorKind: this.kind,
|
|
147
|
+
reason: failure?.reason ?? 'provider-dispatch-adapter-invoked',
|
|
148
|
+
message: failure?.message ?? 'Provider dispatch adapter invoked after all enforced safety gates passed.',
|
|
149
|
+
audit,
|
|
150
|
+
checkpointLabel: 'workflow-execution-dispatched',
|
|
151
|
+
checkpointPayload: {
|
|
152
|
+
...base,
|
|
153
|
+
status: failed ? 'failed' : 'dispatched',
|
|
154
|
+
dispatched: true,
|
|
155
|
+
dispatchMode: 'provider-adapter',
|
|
156
|
+
executorId: this.id,
|
|
157
|
+
executorKind: this.kind,
|
|
158
|
+
reason: failure?.reason ?? 'provider-dispatch-adapter-invoked',
|
|
159
|
+
message: failure?.message ?? 'Provider dispatch adapter invoked after all enforced safety gates passed.',
|
|
160
|
+
safetyGates: safetyGatesPayload(request, request.preflight),
|
|
161
|
+
providerCapabilities: effectiveCapabilities(this.capabilities, request.sandboxExecutionEvidence),
|
|
162
|
+
sandboxExecutionEvidence: request.sandboxExecutionEvidence,
|
|
163
|
+
adapter: { id: this.adapter.id },
|
|
164
|
+
output: adapterResult.output ?? null,
|
|
165
|
+
nextAction: failed ? 'Inspect allowlisted command failure output before retrying or finalizing the run.' : 'Inspect provider adapter audit/output and continue run finalization manually.',
|
|
166
|
+
},
|
|
167
|
+
nextAction: failed ? 'Inspect allowlisted command failure output before retrying or finalizing the run.' : 'Inspect provider adapter audit/output and continue run finalization manually.',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
const message = error instanceof Error ? error.message : 'Provider dispatch adapter threw an unknown error.';
|
|
172
|
+
return result({
|
|
173
|
+
request,
|
|
174
|
+
base,
|
|
175
|
+
executor: this,
|
|
176
|
+
status: 'failed',
|
|
177
|
+
dispatched: false,
|
|
178
|
+
dispatchMode: 'not-dispatched',
|
|
179
|
+
reason: 'provider-dispatch-adapter-failed',
|
|
180
|
+
message,
|
|
181
|
+
nextAction: 'Inspect adapter failure audit before retrying; do not assume provider execution succeeded.',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
firstBlocker(request, preflight) {
|
|
186
|
+
if (preflight === undefined)
|
|
187
|
+
return { reason: 'missing-preflight', message: 'Provider dispatch requires an auditable preflight record.', nextAction: 'Record preflight through the existing safety gate, then retry execute.' };
|
|
188
|
+
if (!hasPreflightAudit(preflight))
|
|
189
|
+
return { reason: 'missing-preflight-audit', message: 'Provider dispatch preflight is missing required audit identifiers.', nextAction: 'Inspect run safety events, then retry after complete preflight is recorded.' };
|
|
190
|
+
if (preflight.outcome === 'blocked')
|
|
191
|
+
return { reason: 'preflight-blocked', message: 'Provider dispatch preflight blocked this execution request.', nextAction: 'Resolve the denied permission or sandbox blocker before retrying.' };
|
|
192
|
+
if (preflight.outcome === 'approval-needed')
|
|
193
|
+
return { reason: 'approval-required', message: 'Provider dispatch is blocked until the approval is resolved and a fresh allowed preflight is recorded.', nextAction: `Resolve approval ${preflight.approval?.id ?? preflight.audit.approvalId ?? '<pending>'}, then re-run execute for a fresh preflight.`, needsHuman: true };
|
|
194
|
+
if (!preflight.eligibleForFutureExecution)
|
|
195
|
+
return { reason: 'preflight-not-eligible-for-execution', message: 'Preflight did not mark this operation eligible for future execution.', nextAction: 'Inspect execution plan blockers before retrying.' };
|
|
196
|
+
const evidence = request.sandboxExecutionEvidence;
|
|
197
|
+
if (evidence === undefined)
|
|
198
|
+
return { reason: 'missing-enforceable-sandbox', message: 'Provider dispatch requires enforceable sandbox/process evidence; planning-only execution plans are insufficient.', nextAction: 'Build and attach bounded-process sandbox evidence after approval/preflight before injecting a provider adapter.' };
|
|
199
|
+
if (!evidence.enforceable || !evidence.validation.accepted)
|
|
200
|
+
return { reason: 'sandbox-evidence-not-enforceable', message: 'Sandbox/process evidence exists but is not enforceable and accepted.', nextAction: 'Rebuild sandbox evidence with accepted realpath, argv, allowlist, timeout, and output-cap checks.' };
|
|
201
|
+
if (preflight.plan.executable !== true)
|
|
202
|
+
return { reason: 'execution-plan-not-executable', message: 'Execution plan is not executable.', nextAction: 'Use an execution plan with enforceable sandbox/process capability before dispatch.' };
|
|
203
|
+
const providerExecutionPlanned = preflight.plan.executesProvider === true;
|
|
204
|
+
const boundedCommandAdapter = this.adapter?.id === 'command-allowlist-adapter' && evidence.strategy === 'bounded-process';
|
|
205
|
+
if (!providerExecutionPlanned && !boundedCommandAdapter)
|
|
206
|
+
return { reason: 'execution-plan-planning-only', message: 'Current execution plan is planning-only and does not authorize provider execution even though sandbox evidence is present.', nextAction: 'Use an approved executable provider execution plan plus enforceable sandbox evidence before dispatch.' };
|
|
207
|
+
if (preflight.plan.limitations.some((limitation) => limitation.startsWith('planning-only:')) && request.sandboxExecutionEvidence?.strategy !== 'bounded-process')
|
|
208
|
+
return { reason: 'execution-plan-planning-only', message: 'Current execution plan explicitly says it is planning-only.', nextAction: 'Use an executable provider execution plan before provider dispatch.' };
|
|
209
|
+
if (request.workspaceRoot.length === 0 || request.requestedOperation.workspaceRoot === undefined)
|
|
210
|
+
return { reason: 'missing-workspace-boundary', message: 'Provider dispatch requires explicit workspace root constraints.', nextAction: 'Retry with an explicit workspace root after boundary enforcement is available.' };
|
|
211
|
+
const capabilities = effectiveCapabilities(this.capabilities, evidence);
|
|
212
|
+
if (!capabilities.workspaceBoundaryEnforced)
|
|
213
|
+
return { reason: 'workspace-boundary-not-enforceable', message: 'Workspace boundary constraints are present but not enforceable in this runtime.', nextAction: 'Enable realpath/workspace boundary enforcement before dispatch.' };
|
|
214
|
+
if (!capabilities.sandboxEnforceable || !capabilities.processExecutionEnforceable)
|
|
215
|
+
return { reason: 'sandbox-not-enforceable', message: 'Sandbox/process execution capability is not enforceable; provider dispatch is refused.', nextAction: 'Add an enforceable sandbox/process runner before using --executor provider.' };
|
|
216
|
+
if (!capabilities.providerConfigWritesBlocked)
|
|
217
|
+
return { reason: 'provider-config-write-guard-missing', message: 'Provider config write attempts are not proven blocked for dispatch.', nextAction: 'Install a provider config write guard before dispatch.' };
|
|
218
|
+
if (request.requestedOperation.operation.toLowerCase().includes('config') || request.requestedOperation.providerToolName?.toLowerCase().includes('config'))
|
|
219
|
+
return { reason: 'provider-config-write-intent', message: 'Provider dispatch refuses operations that appear to target provider configuration.', nextAction: 'Use a read-only workflow dispatch operation, not provider config mutation.' };
|
|
220
|
+
if (request.selectedAgent.id.length === 0 || request.workflow.id !== request.run.workflow)
|
|
221
|
+
return { reason: 'missing-agent-or-workflow-metadata', message: 'Selected agent and matching workflow metadata are required before provider dispatch.', nextAction: 'Resolve run-selected agent and workflow metadata before retrying.' };
|
|
222
|
+
if (request.plannerRecommendation.recommendation === null)
|
|
223
|
+
return { reason: 'missing-planner-recommendation', message: 'Planner recommendation metadata is required before provider dispatch.', nextAction: 'Regenerate workflow execution recommendation before retrying.' };
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export function workflowExecutorSafety(preflightRecorded) {
|
|
228
|
+
return {
|
|
229
|
+
executed: false,
|
|
230
|
+
dispatched: false,
|
|
231
|
+
recordsRuns: true,
|
|
232
|
+
callsProvider: false,
|
|
233
|
+
providerExecutorInvoked: false,
|
|
234
|
+
preflightRecorded,
|
|
235
|
+
editsFiles: false,
|
|
236
|
+
writesProviderConfig: false,
|
|
237
|
+
createsSubagents: false,
|
|
238
|
+
mutatesSddArtifacts: false,
|
|
239
|
+
notes: [
|
|
240
|
+
'Delegated execution is opt-in only and never happens through normal workflow run.',
|
|
241
|
+
'The safe default workflow executor records an auditable execution request/preflight state; it does not call providers or shell out to OpenCode.',
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
export function providerWorkflowExecutorSafety(preflightRecorded, capabilities) {
|
|
246
|
+
return {
|
|
247
|
+
...workflowExecutorSafety(preflightRecorded),
|
|
248
|
+
providerExecutorInvoked: true,
|
|
249
|
+
callsProvider: false,
|
|
250
|
+
providerDispatchGuarded: true,
|
|
251
|
+
enforceableCapabilities: capabilities,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
export function operationMetadataForWorkflowExecution(operation) {
|
|
255
|
+
return {
|
|
256
|
+
category: operation.category,
|
|
257
|
+
operation: operation.operation,
|
|
258
|
+
providerToolName: operation.providerToolName ?? null,
|
|
259
|
+
workspaceRoot: operation.workspaceRoot ?? null,
|
|
260
|
+
ambiguous: operation.ambiguous === true,
|
|
261
|
+
input: operation.input ?? null,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function safetyGatesPayload(request, preflight) {
|
|
265
|
+
return {
|
|
266
|
+
...request.gates,
|
|
267
|
+
preflightOutcome: preflight?.outcome ?? null,
|
|
268
|
+
preflightAudit: preflight?.audit ?? null,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function basePayload(request) {
|
|
272
|
+
return {
|
|
273
|
+
kind: 'workflow-execution-requested',
|
|
274
|
+
version: 1,
|
|
275
|
+
runId: request.run.id,
|
|
276
|
+
workflow: request.workflow.id,
|
|
277
|
+
phase: request.run.phase,
|
|
278
|
+
selectedAgent: request.selectedAgentSummary,
|
|
279
|
+
requestedOperation: operationMetadataForWorkflowExecution(request.requestedOperation),
|
|
280
|
+
recommendation: request.plannerRecommendation.recommendation,
|
|
281
|
+
planner: request.plannerRecommendation.planner,
|
|
282
|
+
safety: workflowExecutorSafety(request.preflight !== undefined),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function result(input) {
|
|
286
|
+
return {
|
|
287
|
+
status: input.status,
|
|
288
|
+
dispatched: input.dispatched,
|
|
289
|
+
dispatchMode: input.dispatchMode,
|
|
290
|
+
executorId: input.executor.id,
|
|
291
|
+
executorKind: input.executor.kind,
|
|
292
|
+
reason: input.reason,
|
|
293
|
+
message: input.message,
|
|
294
|
+
audit: input.request.preflight === undefined ? { executorId: input.executor.id, executorKind: input.executor.kind, preflightPresent: false, dispatched: false } : executorAudit(input.executor, input.request.preflight),
|
|
295
|
+
checkpointLabel: input.status === 'needs-human' ? 'workflow-execution-requested' : 'workflow-execution-blocked',
|
|
296
|
+
checkpointPayload: {
|
|
297
|
+
...input.base,
|
|
298
|
+
status: input.status,
|
|
299
|
+
dispatched: input.dispatched,
|
|
300
|
+
dispatchMode: input.dispatchMode,
|
|
301
|
+
executorId: input.executor.id,
|
|
302
|
+
executorKind: input.executor.kind,
|
|
303
|
+
reason: input.reason,
|
|
304
|
+
message: input.message,
|
|
305
|
+
safetyGates: safetyGatesPayload(input.request, input.request.preflight),
|
|
306
|
+
nextAction: input.nextAction,
|
|
307
|
+
},
|
|
308
|
+
nextAction: input.nextAction,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function executorAudit(executor, preflight) {
|
|
312
|
+
return {
|
|
313
|
+
executorId: executor.id,
|
|
314
|
+
executorKind: executor.kind,
|
|
315
|
+
preflightOutcome: preflight.outcome,
|
|
316
|
+
preflightAudit: preflight.audit,
|
|
317
|
+
eligibleForFutureExecution: preflight.eligibleForFutureExecution,
|
|
318
|
+
dispatched: false,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function providerExecutorAudit(executor, preflight, capabilities, sandboxExecutionEvidence, adapter, adapterAudit) {
|
|
322
|
+
return {
|
|
323
|
+
executorId: executor.id,
|
|
324
|
+
executorKind: executor.kind,
|
|
325
|
+
preflightOutcome: preflight?.outcome ?? null,
|
|
326
|
+
preflightAudit: preflight?.audit ?? null,
|
|
327
|
+
eligibleForFutureExecution: preflight?.eligibleForFutureExecution ?? null,
|
|
328
|
+
dispatched: true,
|
|
329
|
+
adapter: { id: adapter.id },
|
|
330
|
+
providerCapabilities: capabilities,
|
|
331
|
+
sandboxExecutionEvidence: sandboxExecutionEvidence ?? null,
|
|
332
|
+
adapterAudit: adapterAudit ?? null,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function effectiveCapabilities(options, evidence) {
|
|
336
|
+
return {
|
|
337
|
+
sandboxEnforceable: options.sandboxEnforceable || evidence?.capabilities.sandboxEnforceable === true,
|
|
338
|
+
processExecutionEnforceable: options.processExecutionEnforceable || evidence?.capabilities.processExecutionEnforceable === true,
|
|
339
|
+
workspaceBoundaryEnforced: options.workspaceBoundaryEnforced || evidence?.capabilities.workspaceBoundaryEnforced === true,
|
|
340
|
+
providerConfigWritesBlocked: options.providerConfigWritesBlocked || evidence?.capabilities.providerConfigWritesBlocked === true,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function hasPreflightAudit(preflight) {
|
|
344
|
+
return preflight.audit.permissionEventId.length > 0 && preflight.audit.planEventId.length > 0;
|
|
345
|
+
}
|