create-agdf 0.11.2 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/generated/.agdf/control/templates/MASTER_BACKLOG.md +1 -1
- package/generated/.github/skills/agdf-gate-check/SKILL.md +9 -0
- package/generated/.github/skills/agdf-interaction-locales.json +38 -0
- package/generated/.github/skills/contracts/control-scaffold.md +2 -0
- package/generated/.github/skills/contracts/interaction.md +19 -0
- package/generated/.opencode/agdf-interaction-locales.json +38 -0
- package/generated/.opencode/contracts/control-scaffold.md +2 -0
- package/generated/.opencode/contracts/interaction.md +19 -0
- package/generated/.opencode/skills/agdf-gate-check/SKILL.md +9 -0
- package/generated/plugins/agdf/.claude-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/.codex-plugin/plugin.json +1 -1
- package/generated/plugins/agdf/control/templates/MASTER_BACKLOG.md +1 -1
- package/generated/plugins/agdf/meta/agdf-interaction-locales.json +38 -0
- package/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
- package/generated/plugins/agdf/meta/contracts/control-scaffold.md +2 -0
- package/generated/plugins/agdf/meta/contracts/interaction.md +19 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-interaction-locales.json +38 -0
- package/generated/plugins/agdf/runtime/create-agdf/generated/plugins/agdf/meta/agdf-plugin.definition.json +2 -1
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/command-registry.js +2 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/cli/delivery-path-search-command.js +78 -14
- package/generated/plugins/agdf/runtime/create-agdf/lib/control-evaluation/shared.js +1 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/claude.js +2 -11
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/codex.js +2 -11
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/opencode.js +234 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/evaluators/prompt.js +13 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/search-engine.js +1 -0
- package/generated/plugins/agdf/runtime/create-agdf/lib/delivery-path-search/surfaces/capabilities.js +15 -2
- package/generated/plugins/agdf/runtime/create-agdf/lib/interaction-presentation.js +63 -0
- package/generated/plugins/agdf/runtime/create-agdf/package.json +3 -2
- package/generated/plugins/agdf/runtime/runtime-manifest.json +2 -2
- package/generated/plugins/agdf/scripts/check-runtime-integrity.mjs +22 -0
- package/generated/plugins/agdf/skills/gate-check/SKILL.md +9 -0
- package/lib/cli/application.js +17 -2
- package/lib/cli/command-registry.js +2 -2
- package/lib/cli/delivery-path-search-command.js +78 -14
- package/lib/control-evaluation/shared.js +1 -0
- package/lib/delivery-path-search/evaluators/claude.js +2 -11
- package/lib/delivery-path-search/evaluators/codex.js +2 -11
- package/lib/delivery-path-search/evaluators/opencode.js +234 -0
- package/lib/delivery-path-search/evaluators/prompt.js +13 -0
- package/lib/delivery-path-search/search-engine.js +1 -0
- package/lib/delivery-path-search/surfaces/capabilities.js +15 -2
- package/lib/installers/opencode.js +214 -10
- package/lib/interaction-presentation.js +63 -0
- package/lib/lifecycle/operations.js +4 -0
- package/opencode-plugin.js +19 -2
- package/package.json +3 -2
|
@@ -3,6 +3,7 @@ import { searchInputFromControl } from '../delivery-path-search/state-adapter.js
|
|
|
3
3
|
import { runDeliveryPathSearch } from '../delivery-path-search/search-engine.js';
|
|
4
4
|
import { codexEvaluator } from '../delivery-path-search/evaluators/codex.js';
|
|
5
5
|
import { claudeEvaluator } from '../delivery-path-search/evaluators/claude.js';
|
|
6
|
+
import { openCodeEvaluator, preflightOpenCodeEvaluator } from '../delivery-path-search/evaluators/opencode.js';
|
|
6
7
|
import { fixtureEvaluator } from '../delivery-path-search/evaluators/protocol.js';
|
|
7
8
|
import { codexGenerator } from '../delivery-path-search/generators/codex.js';
|
|
8
9
|
import { claudeGenerator } from '../delivery-path-search/generators/claude.js';
|
|
@@ -10,12 +11,56 @@ import { fixtureGenerator } from '../delivery-path-search/generators/protocol.js
|
|
|
10
11
|
import { enforcementForSurface } from '../delivery-path-search/surfaces/capabilities.js';
|
|
11
12
|
import { persistSearchResult } from '../delivery-path-search/persistence.js';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
function unavailableOpenCodeResult({ options, preflight, input = null, error = null }) {
|
|
15
|
+
const mutation = error?.code === "OPENCODE_EVALUATOR_MUTATION_DETECTED";
|
|
16
|
+
const attempted = Boolean(input);
|
|
17
|
+
const nextAction = mutation
|
|
18
|
+
? "Investigate and revert the detected repository mutation before any evaluator fallback or retry."
|
|
19
|
+
: "Repair the reported OpenCode capability and retry, or use the existing instruction-only Delivery Path Search workflow.";
|
|
20
|
+
return {
|
|
21
|
+
contract_version: "1",
|
|
22
|
+
surface: "opencode",
|
|
23
|
+
scope_key: input?.scope_key ?? options.runId ?? null,
|
|
24
|
+
current_gate: input?.current_gate ?? null,
|
|
25
|
+
status: mutation ? "evaluator_error" : "evaluator_unavailable",
|
|
26
|
+
recommendation: null,
|
|
27
|
+
enforcement: enforcementForSurface("opencode"),
|
|
28
|
+
evaluator: {
|
|
29
|
+
name: "opencode",
|
|
30
|
+
preflight: preflight?.code ?? "not_run",
|
|
31
|
+
failure_code: error?.code ?? preflight?.code,
|
|
32
|
+
detail: error?.message ?? preflight?.detail,
|
|
33
|
+
evidence: preflight?.evidence ?? [],
|
|
34
|
+
},
|
|
35
|
+
executable_evaluator: {
|
|
36
|
+
attempted,
|
|
37
|
+
preflight: preflight?.status ?? "failed",
|
|
38
|
+
failure_code: error?.code ?? preflight?.code,
|
|
39
|
+
evidence: preflight?.evidence ?? [],
|
|
40
|
+
},
|
|
41
|
+
stopping_reason: mutation ? "opencode_mutation_detected" : attempted ? "opencode_evaluator_failed" : "opencode_preflight_failed",
|
|
42
|
+
next_action: nextAction,
|
|
43
|
+
next_gate_action: nextAction,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function executeDeliveryPathSearch(options, io = console, dependencies = {}) {
|
|
14
48
|
const fixture = options.fixture ? JSON.parse(readFileSync(options.fixture, "utf8")) : null;
|
|
15
|
-
const
|
|
49
|
+
const preflight = !fixture && options.surface === "opencode"
|
|
50
|
+
? (dependencies.preflightOpenCodeEvaluator ?? preflightOpenCodeEvaluator)({ cwd: options.dir })
|
|
51
|
+
: null;
|
|
52
|
+
if (preflight?.status === "failed") {
|
|
53
|
+
const result = unavailableOpenCodeResult({ options, preflight });
|
|
54
|
+
printDeliveryPathSearchResult(result, options, io);
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
const enforcement = fixture?.input?.enforcement
|
|
58
|
+
?? (preflight
|
|
59
|
+
? enforcementForSurface("opencode", preflight.evidence, { preflight })
|
|
60
|
+
: enforcementForSurface(options.surface));
|
|
16
61
|
const fixtureGeneration = fixture?.generator_response;
|
|
17
62
|
const generationEnabled = Boolean(fixtureGeneration || options.generateCandidates);
|
|
18
|
-
const baseInput = fixture?.input ?? searchInputFromControl(options.dir, {
|
|
63
|
+
const baseInput = fixture?.input ?? (dependencies.searchInputFromControl ?? searchInputFromControl)(options.dir, {
|
|
19
64
|
scopeKey: options.runId,
|
|
20
65
|
enforcement,
|
|
21
66
|
generation: generationEnabled ? {
|
|
@@ -41,9 +86,11 @@ export async function executeDeliveryPathSearch(options, io = console) {
|
|
|
41
86
|
? codexEvaluator({ cwd: options.dir, model: options.model })
|
|
42
87
|
: options.surface === "claude"
|
|
43
88
|
? claudeEvaluator({ cwd: options.dir, model: options.model })
|
|
44
|
-
:
|
|
89
|
+
: options.surface === "opencode"
|
|
90
|
+
? (dependencies.openCodeEvaluator ?? openCodeEvaluator)({ cwd: options.dir, model: options.model, preflight })
|
|
91
|
+
: null;
|
|
45
92
|
if (!evaluator) {
|
|
46
|
-
throw new Error(`${options.surface} has no executable evaluator in this release. Codex and
|
|
93
|
+
throw new Error(`${options.surface} has no executable evaluator in this release. Codex, Claude and preflight-qualified OpenCode are the executable adapters; --fixture is available for deterministic contract testing.`);
|
|
47
94
|
}
|
|
48
95
|
const generator = fixtureGeneration
|
|
49
96
|
? fixtureGenerator(fixtureGeneration)
|
|
@@ -55,21 +102,38 @@ export async function executeDeliveryPathSearch(options, io = console) {
|
|
|
55
102
|
if (options.generateCandidates && !generator) {
|
|
56
103
|
throw new Error(`${options.surface} has no executable candidate generator; Codex and Claude are tool-enforced reference transports.`);
|
|
57
104
|
}
|
|
58
|
-
|
|
59
|
-
|
|
105
|
+
let result;
|
|
106
|
+
try {
|
|
107
|
+
result = await (dependencies.runDeliveryPathSearch ?? runDeliveryPathSearch)(input, evaluator, { candidates: fixture?.candidates, generator });
|
|
108
|
+
if (!fixture && options.surface === "opencode" && result.budgets?.evaluations === 0) {
|
|
109
|
+
result.enforcement = enforcementForSurface("opencode");
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (!error?.fatalEvaluator || options.surface !== "opencode") throw error;
|
|
113
|
+
result = unavailableOpenCodeResult({ options, preflight, input, error });
|
|
114
|
+
}
|
|
115
|
+
if (options.persist && ["recommendation", "no_safe_recommendation"].includes(result.status)) {
|
|
116
|
+
result.persistence = (dependencies.persistSearchResult ?? persistSearchResult)(options.dir, result);
|
|
117
|
+
}
|
|
118
|
+
printDeliveryPathSearchResult(result, options, io);
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function printDeliveryPathSearchResult(result, options, io) {
|
|
60
123
|
if (options.json) {
|
|
61
124
|
io.log(JSON.stringify(result, null, 2));
|
|
62
|
-
return
|
|
125
|
+
return;
|
|
63
126
|
}
|
|
64
127
|
io.log(`AGDF Delivery Path Search: ${result.status}`);
|
|
65
|
-
io.log(`Current gate: ${result.current_gate}`);
|
|
128
|
+
io.log(`Current gate: ${result.current_gate ?? "unknown"}`);
|
|
66
129
|
io.log(`Enforcement: ${result.enforcement.level}`);
|
|
67
130
|
io.log(`Recommendation: ${result.recommendation?.action ?? "none"}`);
|
|
68
|
-
io.log(`Evaluations: ${result.budgets.evaluations}`);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
131
|
+
if (result.budgets) io.log(`Evaluations: ${result.budgets.evaluations}`);
|
|
132
|
+
if (result.generation) {
|
|
133
|
+
io.log(`Generation: ${result.generation.status} (${result.generation.accepted}/${result.generation.returned} accepted)`);
|
|
134
|
+
io.log(`Generation budget: ${result.generation.cost_units} cost units, ${result.generation.duration_ms} ms`);
|
|
135
|
+
if (result.generation.failure_code) io.log(`Generation failure: ${result.generation.failure_code}`);
|
|
136
|
+
}
|
|
72
137
|
io.log(`Stopping reason: ${result.stopping_reason}`);
|
|
73
138
|
io.log(result.next_gate_action);
|
|
74
|
-
return result;
|
|
75
139
|
}
|
|
@@ -56,6 +56,7 @@ const backlogStatusLabels = new Map([
|
|
|
56
56
|
["blocked", "blocked"],
|
|
57
57
|
["awaiting qa", "awaiting_qa"],
|
|
58
58
|
["awaiting uat", "awaiting_uat"],
|
|
59
|
+
["awaiting or", "awaiting_or"],
|
|
59
60
|
["completed", "completed"],
|
|
60
61
|
["superseded", "superseded"],
|
|
61
62
|
["abandoned", "abandoned"],
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { evaluatorOutputSchema, validateEvaluation } from "../contracts.js";
|
|
3
3
|
import { guardedExecFileSync } from "../transports/read-only-guard.js";
|
|
4
|
+
import { buildEvaluatorPrompt } from "./prompt.js";
|
|
4
5
|
|
|
5
6
|
export function claudeEvaluator(options = {}) {
|
|
6
7
|
const claudeBin = options.claudeBin ?? "claude";
|
|
@@ -13,17 +14,7 @@ export function claudeEvaluator(options = {}) {
|
|
|
13
14
|
name: "claude",
|
|
14
15
|
metadata: { name: "claude", runtime, model: options.model ?? "configured-default" },
|
|
15
16
|
async evaluate(input, candidate) {
|
|
16
|
-
const prompt =
|
|
17
|
-
"You are an evaluation-only component. Do not use tools, execute commands, or modify files.",
|
|
18
|
-
`Return JSON matching contract version ${input.contract_version}.`,
|
|
19
|
-
`Objective: ${input.objective}`,
|
|
20
|
-
`Current gate: ${input.current_gate}`,
|
|
21
|
-
`Candidate id: ${candidate.id}`,
|
|
22
|
-
`Candidate action: ${candidate.action}`,
|
|
23
|
-
"Score scope_fit, gate_readiness, risk_reduction, evidence_gain, testability, reversibility and cost from 0 to 5.",
|
|
24
|
-
"Cost 0 is low and 5 is high. uncertainty is 0 low to 5 high.",
|
|
25
|
-
"Keep rationale concise. child_actions must only contain actions permitted by the current gate.",
|
|
26
|
-
].join("\n");
|
|
17
|
+
const prompt = buildEvaluatorPrompt(input, candidate);
|
|
27
18
|
const args = [
|
|
28
19
|
"-p", "--disallowedTools", "Edit,Write,Bash",
|
|
29
20
|
"--output-format", "json", "--json-schema", JSON.stringify(evaluatorOutputSchema()),
|
|
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { evaluatorOutputSchema, validateEvaluation } from "../contracts.js";
|
|
6
6
|
import { guardedExecFileSync } from "../transports/read-only-guard.js";
|
|
7
|
+
import { buildEvaluatorPrompt } from "./prompt.js";
|
|
7
8
|
|
|
8
9
|
export function codexEvaluator(options = {}) {
|
|
9
10
|
const codexBin = options.codexBin ?? "codex";
|
|
@@ -21,17 +22,7 @@ export function codexEvaluator(options = {}) {
|
|
|
21
22
|
const outputPath = join(temp, "output.json");
|
|
22
23
|
try {
|
|
23
24
|
writeFileSync(schemaPath, `${JSON.stringify(evaluatorOutputSchema(), null, 2)}\n`, "utf8");
|
|
24
|
-
const prompt =
|
|
25
|
-
"You are an evaluation-only component. Do not use tools, execute commands, or modify files.",
|
|
26
|
-
`Return JSON matching contract version ${input.contract_version}.`,
|
|
27
|
-
`Objective: ${input.objective}`,
|
|
28
|
-
`Current gate: ${input.current_gate}`,
|
|
29
|
-
`Candidate id: ${candidate.id}`,
|
|
30
|
-
`Candidate action: ${candidate.action}`,
|
|
31
|
-
"Score scope_fit, gate_readiness, risk_reduction, evidence_gain, testability, reversibility and cost from 0 to 5.",
|
|
32
|
-
"Cost 0 is low and 5 is high. uncertainty is 0 low to 5 high.",
|
|
33
|
-
"Keep rationale concise. child_actions must only contain actions permitted by the current gate.",
|
|
34
|
-
].join("\n");
|
|
25
|
+
const prompt = buildEvaluatorPrompt(input, candidate);
|
|
35
26
|
const args = [
|
|
36
27
|
"exec", "--sandbox", "read-only", "--ephemeral", "--ignore-user-config",
|
|
37
28
|
"--output-schema", schemaPath, "--output-last-message", outputPath, "--color", "never",
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { validateEvaluation } from "../contracts.js";
|
|
4
|
+
import { guardedExecFileSync } from "../transports/read-only-guard.js";
|
|
5
|
+
import { buildEvaluatorPrompt } from "./prompt.js";
|
|
6
|
+
|
|
7
|
+
export const OPENCODE_EVALUATOR_AGENT = "agdf-evaluator";
|
|
8
|
+
|
|
9
|
+
export const OPENCODE_DENY_PERMISSIONS = Object.freeze({
|
|
10
|
+
"*": "deny",
|
|
11
|
+
read: "deny",
|
|
12
|
+
edit: "deny",
|
|
13
|
+
bash: "deny",
|
|
14
|
+
task: "deny",
|
|
15
|
+
webfetch: "deny",
|
|
16
|
+
websearch: "deny",
|
|
17
|
+
skill: "deny",
|
|
18
|
+
lsp: "deny",
|
|
19
|
+
todowrite: "deny",
|
|
20
|
+
question: "deny",
|
|
21
|
+
external_directory: "deny",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const executablePermissions = Object.freeze([
|
|
25
|
+
"edit", "bash", "task", "webfetch", "websearch", "skill", "lsp", "todowrite", "question",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export function openCodePermissionEnvironment(env = process.env) {
|
|
29
|
+
return {
|
|
30
|
+
...env,
|
|
31
|
+
OPENCODE_PERMISSION: JSON.stringify(OPENCODE_DENY_PERMISSIONS),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function preflightFailure(code, detail, evidence = []) {
|
|
36
|
+
return {
|
|
37
|
+
surface: "opencode",
|
|
38
|
+
status: "failed",
|
|
39
|
+
code,
|
|
40
|
+
detail,
|
|
41
|
+
evidence,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function extractAgentPermissions(output, agentName) {
|
|
46
|
+
const marker = `${agentName} (`;
|
|
47
|
+
const lines = output.split(/\r?\n/);
|
|
48
|
+
const start = lines.findIndex((line) => line.startsWith(marker));
|
|
49
|
+
if (start < 0) return null;
|
|
50
|
+
const mode = lines[start].slice(marker.length).split(")")[0];
|
|
51
|
+
let jsonStart = -1;
|
|
52
|
+
let depth = 0;
|
|
53
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
54
|
+
const line = lines[index].trim();
|
|
55
|
+
if (jsonStart < 0 && line === "[") {
|
|
56
|
+
jsonStart = index;
|
|
57
|
+
depth = 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (jsonStart >= 0) {
|
|
61
|
+
for (const character of line) {
|
|
62
|
+
if (character === "[") depth += 1;
|
|
63
|
+
if (character === "]") depth -= 1;
|
|
64
|
+
}
|
|
65
|
+
if (depth === 0) return { mode, permissions: JSON.parse(lines.slice(jsonStart, index + 1).join("\n")) };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function terminalPermissionDecision(permissions, permissionName) {
|
|
72
|
+
let decision = null;
|
|
73
|
+
for (const entry of permissions) {
|
|
74
|
+
if (entry?.pattern === "*" && (entry.permission === "*" || entry.permission === permissionName)) {
|
|
75
|
+
decision = entry.action;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return decision;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function preflightOpenCodeEvaluator(options = {}) {
|
|
82
|
+
const executable = options.openCodeBin ?? process.env.AGDF_OPENCODE_BIN ?? "opencode";
|
|
83
|
+
const run = options.execFileSync ?? execFileSync;
|
|
84
|
+
const cwd = options.cwd ?? process.cwd();
|
|
85
|
+
const agentName = options.agentName ?? OPENCODE_EVALUATOR_AGENT;
|
|
86
|
+
const env = openCodePermissionEnvironment(options.env ?? process.env);
|
|
87
|
+
let runtime;
|
|
88
|
+
let help;
|
|
89
|
+
let agents;
|
|
90
|
+
try {
|
|
91
|
+
runtime = run(executable, ["--version"], {
|
|
92
|
+
cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000,
|
|
93
|
+
}).trim();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return preflightFailure("opencode_host_unavailable", (error.stderr || error.message).toString().trim());
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
if (options.execFileSync) {
|
|
99
|
+
help = run(executable, ["run", "--help"], {
|
|
100
|
+
cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000,
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
const helpProbe = (options.spawnSync ?? spawnSync)(executable, ["run", "--help"], {
|
|
104
|
+
cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000,
|
|
105
|
+
});
|
|
106
|
+
if (helpProbe.error || helpProbe.status !== 0) throw helpProbe.error ?? new Error(helpProbe.stderr || "OpenCode run help failed.");
|
|
107
|
+
help = `${helpProbe.stdout ?? ""}\n${helpProbe.stderr ?? ""}`;
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return preflightFailure("opencode_run_unavailable", (error.stderr || error.message).toString().trim(), [`opencode ${runtime}`]);
|
|
111
|
+
}
|
|
112
|
+
const requiredFlags = ["--pure", "--agent", "--format", "--dir"];
|
|
113
|
+
const missingFlags = requiredFlags.filter((flag) => !help.includes(flag));
|
|
114
|
+
if (missingFlags.length) {
|
|
115
|
+
return preflightFailure("opencode_run_capability_missing", `Missing required flags: ${missingFlags.join(", ")}`, [`opencode ${runtime}`]);
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
agents = run(executable, ["agent", "list", "--pure"], {
|
|
119
|
+
cwd, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 10000,
|
|
120
|
+
});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
return preflightFailure("opencode_agent_probe_failed", (error.stderr || error.message).toString().trim(), [`opencode ${runtime}`]);
|
|
123
|
+
}
|
|
124
|
+
let projection;
|
|
125
|
+
try {
|
|
126
|
+
projection = extractAgentPermissions(agents, agentName);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return preflightFailure("opencode_agent_permissions_unparseable", error.message, [`opencode ${runtime}`]);
|
|
129
|
+
}
|
|
130
|
+
if (!projection) {
|
|
131
|
+
return preflightFailure("opencode_evaluator_agent_missing", `OpenCode agent ${agentName} is not available.`, [`opencode ${runtime}`]);
|
|
132
|
+
}
|
|
133
|
+
if (projection.mode !== "primary") {
|
|
134
|
+
return preflightFailure("opencode_evaluator_agent_incompatible", `OpenCode agent ${agentName} must be primary for opencode run --agent; observed ${projection.mode || "unknown"}.`, [
|
|
135
|
+
`opencode ${runtime}`,
|
|
136
|
+
`agent:${agentName}`,
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
const unsafe = executablePermissions.filter((permission) => terminalPermissionDecision(projection.permissions, permission) !== "deny");
|
|
140
|
+
if (unsafe.length) {
|
|
141
|
+
return preflightFailure("opencode_evaluator_permissions_unsafe", `Terminal deny is not effective for: ${unsafe.join(", ")}`, [
|
|
142
|
+
`opencode ${runtime}`,
|
|
143
|
+
`agent:${agentName}`,
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
surface: "opencode",
|
|
148
|
+
status: "passed",
|
|
149
|
+
code: "opencode_evaluator_ready",
|
|
150
|
+
invocation_id: `${process.pid}-${Date.now()}`,
|
|
151
|
+
runtime,
|
|
152
|
+
agent: agentName,
|
|
153
|
+
evidence: [
|
|
154
|
+
`opencode ${runtime}`,
|
|
155
|
+
"opencode run --pure --agent agdf-evaluator --format json",
|
|
156
|
+
"effective executable permissions terminal-deny",
|
|
157
|
+
],
|
|
158
|
+
env,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function parseOpenCodeEvaluatorOutput(raw) {
|
|
163
|
+
const text = [];
|
|
164
|
+
for (const line of raw.split(/\r?\n/).filter(Boolean)) {
|
|
165
|
+
const event = JSON.parse(line);
|
|
166
|
+
if (event?.type === "error") {
|
|
167
|
+
const status = event.error?.data?.statusCode;
|
|
168
|
+
const detail = event.error?.data?.message || event.error?.message || "unknown OpenCode evaluator error";
|
|
169
|
+
const error = new Error(`OpenCode evaluator error${status ? ` (${status})` : ""}: ${detail}`);
|
|
170
|
+
error.code = status === 401 || /auth|credential|provider available/i.test(detail)
|
|
171
|
+
? "OPENCODE_EVALUATOR_AUTHENTICATION_FAILED"
|
|
172
|
+
: "OPENCODE_EVALUATOR_UNAVAILABLE";
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
if (event?.part?.type === "text" && typeof event.part.text === "string") text.push(event.part.text);
|
|
176
|
+
else if (event?.type === "text" && typeof event.text === "string") text.push(event.text);
|
|
177
|
+
}
|
|
178
|
+
if (!text.length) throw new Error("OpenCode evaluator returned no text event.");
|
|
179
|
+
if (text.length !== 1) throw new Error(`OpenCode evaluator returned ${text.length} text events; exactly one final payload is required.`);
|
|
180
|
+
return JSON.parse(text[0]);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function fatalEvaluatorError(error) {
|
|
184
|
+
const wrapped = new Error(error.message);
|
|
185
|
+
wrapped.code = typeof error.code === "string" && error.code.startsWith("OPENCODE_EVALUATOR_")
|
|
186
|
+
? error.code
|
|
187
|
+
: error.code === "GENERATOR_MUTATION_DETECTED"
|
|
188
|
+
? "OPENCODE_EVALUATOR_MUTATION_DETECTED"
|
|
189
|
+
: error.code === "GENERATOR_TIMEOUT"
|
|
190
|
+
? "OPENCODE_EVALUATOR_TIMEOUT"
|
|
191
|
+
: error.code === "ENOENT"
|
|
192
|
+
? "OPENCODE_EVALUATOR_UNAVAILABLE"
|
|
193
|
+
: /auth|unauthorized|credential/i.test(`${error.message}\n${error.stderr ?? ""}`)
|
|
194
|
+
? "OPENCODE_EVALUATOR_AUTHENTICATION_FAILED"
|
|
195
|
+
: "OPENCODE_EVALUATOR_OUTPUT_INVALID";
|
|
196
|
+
wrapped.fatalEvaluator = true;
|
|
197
|
+
return wrapped;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function openCodeEvaluator(options = {}) {
|
|
201
|
+
if (options.preflight?.status !== "passed" || options.preflight.surface !== "opencode") {
|
|
202
|
+
throw new Error("OpenCode evaluator requires a successful current-invocation preflight.");
|
|
203
|
+
}
|
|
204
|
+
const executable = options.openCodeBin ?? process.env.AGDF_OPENCODE_BIN ?? "opencode";
|
|
205
|
+
const agentName = options.preflight.agent ?? OPENCODE_EVALUATOR_AGENT;
|
|
206
|
+
return {
|
|
207
|
+
name: "opencode",
|
|
208
|
+
metadata: {
|
|
209
|
+
name: "opencode",
|
|
210
|
+
runtime: options.preflight.runtime,
|
|
211
|
+
agent: agentName,
|
|
212
|
+
preflight: options.preflight.code,
|
|
213
|
+
},
|
|
214
|
+
async evaluate(input, candidate) {
|
|
215
|
+
const args = [
|
|
216
|
+
"run", "--pure", "--agent", agentName, "--format", "json", "--dir", options.cwd,
|
|
217
|
+
];
|
|
218
|
+
if (options.model) args.push("--model", options.model);
|
|
219
|
+
args.push(buildEvaluatorPrompt(input, candidate));
|
|
220
|
+
try {
|
|
221
|
+
const raw = guardedExecFileSync(executable, args, {
|
|
222
|
+
cwd: options.cwd,
|
|
223
|
+
env: options.preflight.env,
|
|
224
|
+
encoding: "utf8",
|
|
225
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
226
|
+
timeout: options.timeoutMs ?? 120000,
|
|
227
|
+
});
|
|
228
|
+
return validateEvaluation(parseOpenCodeEvaluatorOutput(raw), candidate.id);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
throw fatalEvaluatorError(error);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function buildEvaluatorPrompt(input, candidate) {
|
|
2
|
+
return [
|
|
3
|
+
"You are an evaluation-only component. Do not use tools, execute commands, inspect files, delegate work or modify state.",
|
|
4
|
+
`Return only one JSON object matching contract version ${input.contract_version}.`,
|
|
5
|
+
`Objective: ${input.objective}`,
|
|
6
|
+
`Current gate: ${input.current_gate}`,
|
|
7
|
+
`Candidate id: ${candidate.id}`,
|
|
8
|
+
`Candidate action: ${candidate.action}`,
|
|
9
|
+
"Score scope_fit, gate_readiness, risk_reduction, evidence_gain, testability, reversibility and cost from 0 to 5.",
|
|
10
|
+
"Cost 0 is low and 5 is high. uncertainty is 0 low to 5 high.",
|
|
11
|
+
"Keep rationale concise. child_actions must only contain actions permitted by the current gate.",
|
|
12
|
+
].join("\n");
|
|
13
|
+
}
|
|
@@ -114,6 +114,7 @@ export async function runDeliveryPathSearch(inputValue, evaluator, options = {})
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
} catch (error) {
|
|
117
|
+
if (error?.fatalEvaluator === true) throw error;
|
|
117
118
|
rejected.push({ candidate_id: candidate.id, action: candidate.action, reason: `invalid_evaluation: ${error.message}` });
|
|
118
119
|
}
|
|
119
120
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { validateEnforcement } from "../contracts.js";
|
|
2
2
|
|
|
3
|
-
export function enforcementForSurface(surface, evidence = []) {
|
|
3
|
+
export function enforcementForSurface(surface, evidence = [], context = {}) {
|
|
4
4
|
const defaults = {
|
|
5
5
|
codex: { level: "tool_enforced", evidence: ["codex exec --sandbox read-only --ephemeral"] },
|
|
6
6
|
claude: { level: "tool_enforced", evidence: ["claude -p --disallowedTools Edit,Write,Bash"] },
|
|
@@ -9,6 +9,19 @@ export function enforcementForSurface(surface, evidence = []) {
|
|
|
9
9
|
generic: { level: "instruction_only", evidence: [] },
|
|
10
10
|
};
|
|
11
11
|
const selected = structuredClone(defaults[surface] ?? defaults.generic);
|
|
12
|
-
if (
|
|
12
|
+
if (surface === "opencode") {
|
|
13
|
+
const preflight = context.preflight;
|
|
14
|
+
if (preflight?.surface === "opencode"
|
|
15
|
+
&& preflight.status === "passed"
|
|
16
|
+
&& typeof preflight.invocation_id === "string"
|
|
17
|
+
&& preflight.invocation_id
|
|
18
|
+
&& Array.isArray(preflight.evidence)
|
|
19
|
+
&& preflight.evidence === evidence) {
|
|
20
|
+
selected.level = "tool_enforced";
|
|
21
|
+
selected.evidence = [...evidence];
|
|
22
|
+
}
|
|
23
|
+
} else if (evidence.length) {
|
|
24
|
+
selected.evidence = evidence;
|
|
25
|
+
}
|
|
13
26
|
return validateEnforcement(selected);
|
|
14
27
|
}
|