harnery 0.9.0 → 0.11.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/README.md +1 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +6 -0
- package/dist/commands/agents.d.ts +24 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +244 -13
- package/dist/commands/backup.d.ts +5 -4
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/backup.js +15 -14
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +27 -1
- package/dist/commands/claude-desktop.d.ts +19 -0
- package/dist/commands/claude-desktop.d.ts.map +1 -0
- package/dist/commands/claude-desktop.js +168 -0
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +149 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +2 -3
- package/dist/commands/eml.d.ts +32 -0
- package/dist/commands/eml.d.ts.map +1 -1
- package/dist/commands/eml.js +16 -3
- package/dist/commands/harness.d.ts +11 -0
- package/dist/commands/harness.d.ts.map +1 -0
- package/dist/commands/harness.js +118 -0
- package/dist/commands/presence.d.ts +9 -4
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +88 -5
- package/dist/commands/relay.d.ts +9 -0
- package/dist/commands/relay.d.ts.map +1 -0
- package/dist/commands/relay.js +143 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +5 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +53 -17
- package/dist/core/agents/canonical-emit.d.ts +15 -0
- package/dist/core/agents/canonical-emit.d.ts.map +1 -1
- package/dist/core/agents/canonical-emit.js +41 -3
- package/dist/core/agents/cli.js +4 -2
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +44 -3
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +26 -1
- package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/commit-conflict.js +4 -37
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +16 -43
- package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-projector.js +11 -2
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +3 -3
- package/dist/core/agents/state/names.d.ts +38 -3
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +46 -7
- package/dist/core/agents/state/pidmap.d.ts +5 -1
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +58 -2
- package/dist/core/agents/state/stale-sweep.d.ts +3 -2
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +6 -6
- package/dist/core/config.d.ts +68 -11
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +191 -25
- package/dist/core/context/index.d.ts +144 -0
- package/dist/core/context/index.d.ts.map +1 -0
- package/dist/core/context/index.js +380 -0
- package/dist/core/harnesses/bench.d.ts +29 -0
- package/dist/core/harnesses/bench.d.ts.map +1 -0
- package/dist/core/harnesses/bench.js +151 -0
- package/dist/core/harnesses/index.d.ts +9 -0
- package/dist/core/harnesses/index.d.ts.map +1 -0
- package/dist/core/harnesses/index.js +4 -0
- package/dist/core/harnesses/profiles.d.ts +62 -0
- package/dist/core/harnesses/profiles.d.ts.map +1 -0
- package/dist/core/harnesses/profiles.js +115 -0
- package/dist/core/harnesses/registry.d.ts +15 -0
- package/dist/core/harnesses/registry.d.ts.map +1 -0
- package/dist/core/harnesses/registry.js +113 -0
- package/dist/core/harnesses/types.d.ts +64 -0
- package/dist/core/harnesses/types.d.ts.map +1 -0
- package/dist/core/harnesses/types.js +20 -0
- package/dist/core/hooks/cli.js +280 -63
- package/dist/core/hooks/events/schema.d.ts +38 -1
- package/dist/core/hooks/events/schema.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.js +3 -0
- package/dist/core/hooks/harness/parse.d.ts +1 -1
- package/dist/core/hooks/harness/parse.d.ts.map +1 -1
- package/dist/core/hooks/harness/parse.js +4 -0
- package/dist/core/presence/blob.d.ts +40 -0
- package/dist/core/presence/blob.d.ts.map +1 -0
- package/dist/core/presence/blob.js +91 -0
- package/dist/core/presence/git.d.ts +68 -0
- package/dist/core/presence/git.d.ts.map +1 -0
- package/dist/core/presence/git.js +173 -0
- package/dist/core/presence/index.d.ts +74 -0
- package/dist/core/presence/index.d.ts.map +1 -0
- package/dist/core/presence/index.js +224 -0
- package/dist/core/presence/relay-client.d.ts +39 -0
- package/dist/core/presence/relay-client.d.ts.map +1 -0
- package/dist/core/presence/relay-client.js +295 -0
- package/dist/core/presence/relay-protocol.d.ts +94 -0
- package/dist/core/presence/relay-protocol.d.ts.map +1 -0
- package/dist/core/presence/relay-protocol.js +169 -0
- package/dist/core/workflow/billing.d.ts +1 -1
- package/dist/core/workflow/billing.d.ts.map +1 -1
- package/dist/core/workflow/billing.js +7 -1
- package/dist/core/workflow/engine.d.ts +5 -0
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +164 -16
- package/dist/core/workflow/harnesses.d.ts +3 -3
- package/dist/core/workflow/harnesses.d.ts.map +1 -1
- package/dist/core/workflow/harnesses.js +8 -17
- package/dist/core/workflow/index.d.ts +5 -0
- package/dist/core/workflow/index.d.ts.map +1 -0
- package/dist/core/workflow/index.js +3 -0
- package/dist/core/workflow/proof.d.ts +42 -0
- package/dist/core/workflow/proof.d.ts.map +1 -0
- package/dist/core/workflow/proof.js +342 -0
- package/dist/core/workflow/spawn-claude.d.ts +4 -1
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +39 -18
- package/dist/core/workflow/spawn-codex.d.ts +4 -1
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +44 -22
- package/dist/core/workflow/spawn-cursor.d.ts +4 -1
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +35 -16
- package/dist/core/workflow/types.d.ts +156 -2
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/types.js +1 -1
- package/dist/lib/browser/client.d.ts +16 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +22 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +1 -0
- package/dist/lib/browser/launch-args.d.ts +21 -0
- package/dist/lib/browser/launch-args.d.ts.map +1 -0
- package/dist/lib/browser/launch-args.js +32 -0
- package/dist/lib/claude-desktop.d.ts +105 -0
- package/dist/lib/claude-desktop.d.ts.map +1 -0
- package/dist/lib/claude-desktop.js +217 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +5 -2
- package/dist/lib/identities/assume.d.ts +51 -0
- package/dist/lib/identities/assume.d.ts.map +1 -0
- package/dist/lib/identities/assume.js +274 -0
- package/dist/lib/identities/index.d.ts +7 -7
- package/dist/lib/identities/index.d.ts.map +1 -1
- package/dist/lib/identities/index.js +24 -24
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +15 -2
- package/package.json +22 -6
- package/schemas/config.schema.json +88 -19
- package/src/commander.ts +6 -0
- package/src/commands/agents.ts +280 -14
- package/src/commands/backup.ts +17 -16
- package/src/commands/browse.ts +32 -0
- package/src/commands/claude-desktop.ts +215 -0
- package/src/commands/context.ts +174 -1
- package/src/commands/doctor.ts +2 -3
- package/src/commands/eml.ts +22 -4
- package/src/commands/harness.ts +147 -0
- package/src/commands/presence.ts +111 -5
- package/src/commands/relay.ts +160 -0
- package/src/commands/sync.ts +4 -0
- package/src/commands/workflow.ts +62 -19
- package/src/core/agents/canonical-emit.ts +41 -3
- package/src/core/agents/cli.ts +4 -2
- package/src/core/agents/render/prompt-context.ts +48 -3
- package/src/core/agents/render/session-context.ts +27 -1
- package/src/core/agents/rules/commit-conflict.ts +4 -33
- package/src/core/agents/session-events.ts +15 -39
- package/src/core/agents/state/heartbeat-projector.ts +10 -1
- package/src/core/agents/state/heartbeat-writer.ts +3 -2
- package/src/core/agents/state/names.ts +68 -9
- package/src/core/agents/state/pidmap.ts +59 -2
- package/src/core/agents/state/stale-sweep.ts +6 -11
- package/src/core/config.ts +248 -24
- package/src/core/context/index.ts +575 -0
- package/src/core/harnesses/bench.ts +214 -0
- package/src/core/harnesses/index.ts +31 -0
- package/src/core/harnesses/profiles.ts +141 -0
- package/src/core/harnesses/registry.ts +140 -0
- package/src/core/harnesses/types.ts +90 -0
- package/src/core/hooks/cli.ts +331 -62
- package/src/core/hooks/events/schema.ts +68 -0
- package/src/core/hooks/harness/events.ts +3 -0
- package/src/core/hooks/harness/parse.ts +7 -1
- package/src/core/presence/blob.ts +125 -0
- package/src/core/presence/git.ts +191 -0
- package/src/core/presence/index.ts +274 -0
- package/src/core/presence/relay-client.ts +322 -0
- package/src/core/presence/relay-protocol.ts +246 -0
- package/src/core/workflow/billing.ts +8 -2
- package/src/core/workflow/engine.ts +182 -16
- package/src/core/workflow/harnesses.ts +14 -19
- package/src/core/workflow/index.ts +43 -0
- package/src/core/workflow/proof.ts +434 -0
- package/src/core/workflow/spawn-claude.ts +39 -20
- package/src/core/workflow/spawn-codex.ts +47 -28
- package/src/core/workflow/spawn-cursor.ts +37 -18
- package/src/core/workflow/types.ts +176 -2
- package/src/lib/browser/client.ts +37 -0
- package/src/lib/browser/index.ts +1 -0
- package/src/lib/browser/launch-args.ts +33 -0
- package/src/lib/claude-desktop.ts +293 -0
- package/src/lib/docs-lint.ts +5 -2
- package/src/lib/identities/assume.ts +363 -0
- package/src/lib/identities/index.ts +28 -24
- package/src/lib/instructions/templates.ts +16 -2
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { WORKFLOW_PROOF_SCHEMA_VERSION } from "./types.js";
|
|
5
|
+
const MAX_ACCEPTANCE_CRITERIA = 50;
|
|
6
|
+
const MAX_EVIDENCE_RECORDS = 200;
|
|
7
|
+
const MAX_PACKET_BYTES = 512 * 1024;
|
|
8
|
+
const MAX_NAME_CHARS = 200;
|
|
9
|
+
const MAX_OBJECTIVE_CHARS = 2_000;
|
|
10
|
+
const MAX_CRITERION_CHARS = 500;
|
|
11
|
+
const MAX_LABEL_CHARS = 200;
|
|
12
|
+
const MAX_SUMMARY_CHARS = 2_000;
|
|
13
|
+
const MAX_REF_CHARS = 1_000;
|
|
14
|
+
const ACCEPTANCE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
15
|
+
const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/;
|
|
16
|
+
export function normalizeWorkflowMeta(meta, fallbackName) {
|
|
17
|
+
const name = boundedRequired(meta?.name ?? fallbackName, "workflow name", MAX_NAME_CHARS);
|
|
18
|
+
const objective = boundedOptional(meta?.objective, "workflow objective", MAX_OBJECTIVE_CHARS);
|
|
19
|
+
const description = boundedOptional(meta?.description, "workflow description", MAX_OBJECTIVE_CHARS);
|
|
20
|
+
const acceptance = meta?.acceptance ?? [];
|
|
21
|
+
if (!Array.isArray(acceptance))
|
|
22
|
+
throw new Error("workflow acceptance must be an array");
|
|
23
|
+
if (acceptance.length > MAX_ACCEPTANCE_CRITERIA) {
|
|
24
|
+
throw new Error(`workflow acceptance exceeds ${MAX_ACCEPTANCE_CRITERIA} criteria`);
|
|
25
|
+
}
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
const normalized = acceptance.map((criterion, index) => {
|
|
28
|
+
if (!criterion || typeof criterion !== "object") {
|
|
29
|
+
throw new Error(`workflow acceptance[${index}] must be an object`);
|
|
30
|
+
}
|
|
31
|
+
const id = boundedRequired(criterion.id, `workflow acceptance[${index}].id`, 64);
|
|
32
|
+
if (!ACCEPTANCE_ID.test(id)) {
|
|
33
|
+
throw new Error(`workflow acceptance id ${JSON.stringify(id)} must match ${ACCEPTANCE_ID.source}`);
|
|
34
|
+
}
|
|
35
|
+
if (seen.has(id))
|
|
36
|
+
throw new Error(`duplicate workflow acceptance id ${JSON.stringify(id)}`);
|
|
37
|
+
seen.add(id);
|
|
38
|
+
return {
|
|
39
|
+
id,
|
|
40
|
+
statement: boundedRequired(criterion.statement, `workflow acceptance[${index}].statement`, MAX_CRITERION_CHARS),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
return { name, description, objective, acceptance: normalized };
|
|
44
|
+
}
|
|
45
|
+
export function createEvidenceRecord(input) {
|
|
46
|
+
if (input.sequence > MAX_EVIDENCE_RECORDS) {
|
|
47
|
+
throw new Error(`workflow evidence exceeds ${MAX_EVIDENCE_RECORDS} records`);
|
|
48
|
+
}
|
|
49
|
+
const value = input.value;
|
|
50
|
+
const acceptanceIds = value.acceptanceIds ?? [];
|
|
51
|
+
if (!Array.isArray(acceptanceIds))
|
|
52
|
+
throw new Error("evidence acceptanceIds must be an array");
|
|
53
|
+
const uniqueAcceptanceIds = [...new Set(acceptanceIds)];
|
|
54
|
+
for (const id of uniqueAcceptanceIds) {
|
|
55
|
+
if (typeof id !== "string" || !input.acceptanceIds.has(id)) {
|
|
56
|
+
throw new Error(`evidence references unknown acceptance id ${JSON.stringify(id)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
id: `e${input.sequence}`,
|
|
61
|
+
source: "workflow",
|
|
62
|
+
recorded_at: input.recordedAt ?? new Date().toISOString(),
|
|
63
|
+
kind: enumValue(value.kind, ["test", "command", "artifact", "change", "review", "observation"], "evidence kind"),
|
|
64
|
+
status: enumValue(value.status, ["passed", "failed", "observed", "unknown"], "evidence status"),
|
|
65
|
+
label: boundedRequired(value.label, "evidence label", MAX_LABEL_CHARS),
|
|
66
|
+
summary: boundedOptional(value.summary, "evidence summary", MAX_SUMMARY_CHARS),
|
|
67
|
+
ref: boundedOptional(value.ref, "evidence ref", MAX_REF_CHARS),
|
|
68
|
+
stage: boundedOptional(input.stage, "evidence stage", MAX_LABEL_CHARS),
|
|
69
|
+
acceptance_ids: uniqueAcceptanceIds,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export function rollupAcceptance(criteria, evidence) {
|
|
73
|
+
const results = criteria.map((criterion) => {
|
|
74
|
+
const attached = evidence.filter((item) => item.acceptance_ids.includes(criterion.id));
|
|
75
|
+
const failed = attached.filter((item) => item.status === "failed");
|
|
76
|
+
const passed = attached.filter((item) => item.status === "passed");
|
|
77
|
+
const decisive = failed.length > 0 ? failed : passed;
|
|
78
|
+
const status = failed.length > 0 ? "unsatisfied" : passed.length > 0 ? "satisfied" : "unknown";
|
|
79
|
+
return {
|
|
80
|
+
...criterion,
|
|
81
|
+
status,
|
|
82
|
+
evidence_ids: attached.map((item) => item.id),
|
|
83
|
+
sources: [...new Set(decisive.map((item) => item.source))],
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
criteria: results,
|
|
88
|
+
summary: {
|
|
89
|
+
satisfied: results.filter((item) => item.status === "satisfied").length,
|
|
90
|
+
unsatisfied: results.filter((item) => item.status === "unsatisfied").length,
|
|
91
|
+
unknown: results.filter((item) => item.status === "unknown").length,
|
|
92
|
+
total: results.length,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function digestResult(value, kind) {
|
|
97
|
+
const resolvedKind = kind ?? (typeof value === "string" ? "text" : "json");
|
|
98
|
+
const serialized = resolvedKind === "text" ? String(value) : (JSON.stringify(value) ?? "null");
|
|
99
|
+
return {
|
|
100
|
+
kind: resolvedKind,
|
|
101
|
+
sha256: createHash("sha256").update(serialized).digest("hex"),
|
|
102
|
+
bytes: Buffer.byteLength(serialized),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function buildWorkflowProof(input) {
|
|
106
|
+
const acceptance = rollupAcceptance(input.meta.acceptance, input.evidence);
|
|
107
|
+
const repository = buildRepoEvidence(input.before, input.after);
|
|
108
|
+
const agents = input.agents.map((agent) => ({
|
|
109
|
+
...agent,
|
|
110
|
+
label: clipped(agent.label, MAX_LABEL_CHARS),
|
|
111
|
+
model: clippedOptional(agent.model, MAX_LABEL_CHARS),
|
|
112
|
+
session_id: clippedOptional(agent.session_id, MAX_REF_CHARS),
|
|
113
|
+
error: clippedOptional(agent.error, MAX_SUMMARY_CHARS),
|
|
114
|
+
}));
|
|
115
|
+
const harnesses = buildHarnessCoverage(agents, input.harnessEvidence);
|
|
116
|
+
const unknowns = buildUnknowns(agents, harnesses, repository);
|
|
117
|
+
const journal = readFileSync(input.journalPath);
|
|
118
|
+
return {
|
|
119
|
+
schema_version: WORKFLOW_PROOF_SCHEMA_VERSION,
|
|
120
|
+
run: {
|
|
121
|
+
id: input.runId,
|
|
122
|
+
name: input.meta.name,
|
|
123
|
+
status: input.status,
|
|
124
|
+
started_at: input.startedAt,
|
|
125
|
+
ended_at: input.endedAt,
|
|
126
|
+
duration_ms: input.durationMs,
|
|
127
|
+
objective: input.meta.objective,
|
|
128
|
+
error: clippedOptional(input.error, MAX_SUMMARY_CHARS),
|
|
129
|
+
result: input.result === undefined ? undefined : digestResult(input.result),
|
|
130
|
+
},
|
|
131
|
+
acceptance,
|
|
132
|
+
agents,
|
|
133
|
+
evidence: input.evidence,
|
|
134
|
+
repository,
|
|
135
|
+
harnesses,
|
|
136
|
+
unknowns,
|
|
137
|
+
integrity: {
|
|
138
|
+
journal: {
|
|
139
|
+
path: "journal.jsonl",
|
|
140
|
+
sha256: createHash("sha256").update(journal).digest("hex"),
|
|
141
|
+
bytes: journal.byteLength,
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export function writeWorkflowProof(path, proof) {
|
|
147
|
+
const body = `${JSON.stringify(proof, null, 2)}\n`;
|
|
148
|
+
const bytes = Buffer.byteLength(body);
|
|
149
|
+
if (bytes > MAX_PACKET_BYTES) {
|
|
150
|
+
throw new Error(`workflow proof is ${bytes} bytes; limit is ${MAX_PACKET_BYTES}`);
|
|
151
|
+
}
|
|
152
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
153
|
+
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
154
|
+
try {
|
|
155
|
+
writeFileSync(temporary, body, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
156
|
+
renameSync(temporary, path);
|
|
157
|
+
chmodSync(path, 0o600);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
if (existsSync(temporary))
|
|
161
|
+
unlinkSync(temporary);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export function readWorkflowProof(coordRoot, runId) {
|
|
165
|
+
if (!RUN_ID.test(runId))
|
|
166
|
+
throw new Error(`invalid workflow run id ${JSON.stringify(runId)}`);
|
|
167
|
+
const path = join(coordRoot, ".harnery", "workflows", runId, "proof.json");
|
|
168
|
+
if (!existsSync(path))
|
|
169
|
+
throw new Error(`workflow run ${runId} has no proof packet at ${path}`);
|
|
170
|
+
const size = statSync(path).size;
|
|
171
|
+
if (size > MAX_PACKET_BYTES) {
|
|
172
|
+
throw new Error(`workflow proof is ${size} bytes; limit is ${MAX_PACKET_BYTES}`);
|
|
173
|
+
}
|
|
174
|
+
let proof;
|
|
175
|
+
try {
|
|
176
|
+
proof = JSON.parse(readFileSync(path, "utf8"));
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
throw new Error(`cannot parse workflow proof at ${path}: ${error.message}`);
|
|
180
|
+
}
|
|
181
|
+
if (proof.schema_version !== WORKFLOW_PROOF_SCHEMA_VERSION || proof.run?.id !== runId) {
|
|
182
|
+
throw new Error(`workflow proof at ${path} has an unsupported or mismatched schema`);
|
|
183
|
+
}
|
|
184
|
+
return proof;
|
|
185
|
+
}
|
|
186
|
+
export function renderWorkflowProof(proof) {
|
|
187
|
+
const lines = [
|
|
188
|
+
`run ${proof.run.id} (${proof.run.name}): ${proof.run.status}`,
|
|
189
|
+
`duration: ${Math.round(proof.run.duration_ms / 1000)}s`,
|
|
190
|
+
];
|
|
191
|
+
if (proof.run.objective)
|
|
192
|
+
lines.push(`objective: ${proof.run.objective}`);
|
|
193
|
+
const summary = proof.acceptance.summary;
|
|
194
|
+
lines.push(`acceptance: ${summary.satisfied} satisfied, ${summary.unsatisfied} unsatisfied, ${summary.unknown} unknown`);
|
|
195
|
+
for (const criterion of proof.acceptance.criteria) {
|
|
196
|
+
const mark = criterion.status === "satisfied" ? "PASS" : criterion.status === "unsatisfied" ? "FAIL" : "?";
|
|
197
|
+
const refs = criterion.evidence_ids.length > 0 ? ` [${criterion.evidence_ids.join(", ")}]` : "";
|
|
198
|
+
lines.push(` ${mark} ${criterion.id}: ${criterion.statement}${refs}`);
|
|
199
|
+
}
|
|
200
|
+
lines.push(`evidence: ${proof.evidence.length} record(s); agents: ${proof.agents.length}`);
|
|
201
|
+
const repo = proof.repository;
|
|
202
|
+
const drift = repo.drift;
|
|
203
|
+
lines.push(`repository: branch ${repo.before.branch ?? "unknown"} -> ${repo.after.branch ?? "unknown"}; ` +
|
|
204
|
+
`HEAD ${short(repo.before.head)} -> ${short(repo.after.head)}; ` +
|
|
205
|
+
`${drift.dirty_paths_added.length} dirty added, ${drift.dirty_paths_cleared.length} cleared`);
|
|
206
|
+
if (proof.unknowns.length > 0) {
|
|
207
|
+
lines.push(`unknowns: ${proof.unknowns.length}`);
|
|
208
|
+
for (const unknown of proof.unknowns)
|
|
209
|
+
lines.push(` - ${unknown.message}`);
|
|
210
|
+
}
|
|
211
|
+
lines.push(`journal sha256: ${proof.integrity.journal.sha256}`);
|
|
212
|
+
return `${lines.join("\n")}\n`;
|
|
213
|
+
}
|
|
214
|
+
function buildRepoEvidence(beforeRaw, afterRaw) {
|
|
215
|
+
const before = normalizeRepoSnapshot(beforeRaw);
|
|
216
|
+
const after = normalizeRepoSnapshot(afterRaw);
|
|
217
|
+
const beforeDirty = new Set(before.dirty_paths);
|
|
218
|
+
const afterDirty = new Set(after.dirty_paths);
|
|
219
|
+
const retained = after.dirty_paths.filter((path) => beforeDirty.has(path));
|
|
220
|
+
const incomplete = Boolean(before.dirty_paths_truncated || after.dirty_paths_truncated || retained.length > 0);
|
|
221
|
+
return {
|
|
222
|
+
source: "engine",
|
|
223
|
+
before,
|
|
224
|
+
after,
|
|
225
|
+
drift: {
|
|
226
|
+
branch_changed: before.branch !== after.branch,
|
|
227
|
+
head_changed: before.head !== after.head,
|
|
228
|
+
dirty_paths_added: after.dirty_paths.filter((path) => !beforeDirty.has(path)),
|
|
229
|
+
dirty_paths_cleared: before.dirty_paths.filter((path) => !afterDirty.has(path)),
|
|
230
|
+
dirty_paths_retained: retained,
|
|
231
|
+
incomplete,
|
|
232
|
+
note: incomplete
|
|
233
|
+
? "Snapshots cannot prove whether retained dirty paths changed during the run, and truncated lists may omit paths."
|
|
234
|
+
: undefined,
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function normalizeRepoSnapshot(snapshot) {
|
|
239
|
+
return {
|
|
240
|
+
cwd: snapshot.cwd,
|
|
241
|
+
root: snapshot.root,
|
|
242
|
+
branch: snapshot.branch,
|
|
243
|
+
head: snapshot.head,
|
|
244
|
+
dirty_paths: snapshot.dirty_paths,
|
|
245
|
+
dirty_paths_truncated: snapshot.dirty_paths_truncated,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function buildHarnessCoverage(agents, claims) {
|
|
249
|
+
return [...new Set(agents.map((agent) => agent.harness))].map((harness) => {
|
|
250
|
+
const harnessAgents = agents.filter((agent) => agent.harness === harness);
|
|
251
|
+
return {
|
|
252
|
+
harness,
|
|
253
|
+
tool_evidence: claims?.[harness]?.toolEvidence ?? {
|
|
254
|
+
support: "unknown",
|
|
255
|
+
note: "No harness capability claim was supplied to this workflow run.",
|
|
256
|
+
},
|
|
257
|
+
observed: {
|
|
258
|
+
final_results: harnessAgents.filter((agent) => agent.result).length,
|
|
259
|
+
session_ids: harnessAgents.filter((agent) => agent.session_id).length,
|
|
260
|
+
costs: harnessAgents.filter((agent) => agent.cost_usd !== undefined).length,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function buildUnknowns(agents, harnesses, repository) {
|
|
266
|
+
const unknowns = [];
|
|
267
|
+
for (const harness of harnesses) {
|
|
268
|
+
if (harness.tool_evidence.support === "unknown") {
|
|
269
|
+
unknowns.push({
|
|
270
|
+
code: "harness_capability_unregistered",
|
|
271
|
+
harness: harness.harness,
|
|
272
|
+
message: `${harness.harness}: tool-evidence capability was not registered for this run.`,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
else if (harness.tool_evidence.support !== "supported") {
|
|
276
|
+
unknowns.push({
|
|
277
|
+
code: "tool_evidence_unavailable",
|
|
278
|
+
harness: harness.harness,
|
|
279
|
+
message: `${harness.harness}: adapter-native tool evidence is ${harness.tool_evidence.support}.`,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
for (const agent of agents.filter((item) => item.status !== "failed")) {
|
|
284
|
+
if (agent.cost_usd === undefined) {
|
|
285
|
+
unknowns.push({
|
|
286
|
+
code: "agent_cost_unreported",
|
|
287
|
+
harness: agent.harness,
|
|
288
|
+
agent_id: agent.id,
|
|
289
|
+
message: `${agent.id}: ${agent.harness} did not report per-run cost.`,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
if (!agent.session_id) {
|
|
293
|
+
unknowns.push({
|
|
294
|
+
code: "agent_session_unreported",
|
|
295
|
+
harness: agent.harness,
|
|
296
|
+
agent_id: agent.id,
|
|
297
|
+
message: `${agent.id}: ${agent.harness} did not report a child session id.`,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (repository.drift.incomplete) {
|
|
302
|
+
unknowns.push({
|
|
303
|
+
code: "repository_drift_incomplete",
|
|
304
|
+
message: repository.drift.note ?? "Repository drift is incomplete.",
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return unknowns;
|
|
308
|
+
}
|
|
309
|
+
function boundedRequired(value, field, max) {
|
|
310
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
311
|
+
throw new Error(`${field} is required`);
|
|
312
|
+
const normalized = value.trim();
|
|
313
|
+
if (normalized.length > max)
|
|
314
|
+
throw new Error(`${field} exceeds ${max} characters`);
|
|
315
|
+
return normalized;
|
|
316
|
+
}
|
|
317
|
+
function boundedOptional(value, field, max) {
|
|
318
|
+
if (value === undefined)
|
|
319
|
+
return undefined;
|
|
320
|
+
if (typeof value !== "string")
|
|
321
|
+
throw new Error(`${field} must be a string`);
|
|
322
|
+
const normalized = value.trim();
|
|
323
|
+
if (normalized === "")
|
|
324
|
+
return undefined;
|
|
325
|
+
if (normalized.length > max)
|
|
326
|
+
throw new Error(`${field} exceeds ${max} characters`);
|
|
327
|
+
return normalized;
|
|
328
|
+
}
|
|
329
|
+
function enumValue(value, values, field) {
|
|
330
|
+
if (typeof value === "string" && values.includes(value))
|
|
331
|
+
return value;
|
|
332
|
+
throw new Error(`${field} must be one of: ${values.join(", ")}`);
|
|
333
|
+
}
|
|
334
|
+
function short(value) {
|
|
335
|
+
return value ? value.slice(0, 8) : "unknown";
|
|
336
|
+
}
|
|
337
|
+
function clipped(value, max) {
|
|
338
|
+
return value.length <= max ? value : `${value.slice(0, Math.max(0, max - 1))}…`;
|
|
339
|
+
}
|
|
340
|
+
function clippedOptional(value, max) {
|
|
341
|
+
return value === undefined ? undefined : clipped(value, max);
|
|
342
|
+
}
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
* gets HARNERY_WORKFLOW_CHILD=1 and the stop-hook rule exempts it
|
|
18
18
|
* (stop-hook.ts), keeping heartbeats + events on.
|
|
19
19
|
*/
|
|
20
|
-
import type {
|
|
20
|
+
import type { HarnessInvocation, HarnessRawResult } from "../harnesses/types.js";
|
|
21
|
+
import type { Spawner, SpawnRequest, SpawnResult } from "./types.js";
|
|
22
|
+
export declare function buildClaudeInvocation(req: SpawnRequest): HarnessInvocation;
|
|
23
|
+
export declare function normalizeClaudeResult(raw: HarnessRawResult): SpawnResult;
|
|
21
24
|
export declare const claudeCodeSpawner: Spawner;
|
|
22
25
|
//# sourceMappingURL=spawn-claude.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"spawn-claude.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAYrE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAc1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAgDxE;AAED,eAAO,MAAM,iBAAiB,EAAE,OAc/B,CAAC"}
|
|
@@ -18,10 +18,11 @@
|
|
|
18
18
|
* (stop-hook.ts), keeping heartbeats + events on.
|
|
19
19
|
*/
|
|
20
20
|
import { exec } from "../../lib/exec.js";
|
|
21
|
+
import { validateHarnessEffort } from "../harnesses/profiles.js";
|
|
21
22
|
import { buildChildEnv } from "./child-env.js";
|
|
22
23
|
import { notFoundError } from "./harnesses.js";
|
|
23
|
-
export
|
|
24
|
-
|
|
24
|
+
export function buildClaudeInvocation(req) {
|
|
25
|
+
validateHarnessEffort("claude-code", req.effort);
|
|
25
26
|
const argv = [
|
|
26
27
|
"claude",
|
|
27
28
|
"-p",
|
|
@@ -33,33 +34,37 @@ export const claudeCodeSpawner = async (req) => {
|
|
|
33
34
|
];
|
|
34
35
|
if (req.model)
|
|
35
36
|
argv.push("--model", req.model);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
if (req.effort)
|
|
38
|
+
argv.push("--effort", req.effort);
|
|
39
|
+
return { argv };
|
|
40
|
+
}
|
|
41
|
+
export function normalizeClaudeResult(raw) {
|
|
42
|
+
if (raw.exitCode === 127) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
text: "",
|
|
46
|
+
durationMs: raw.durationMs,
|
|
47
|
+
error: notFoundError("claude-code"),
|
|
48
|
+
};
|
|
44
49
|
}
|
|
45
|
-
if (
|
|
50
|
+
if (raw.exitCode !== 0) {
|
|
46
51
|
return {
|
|
47
52
|
ok: false,
|
|
48
53
|
text: "",
|
|
49
|
-
durationMs,
|
|
50
|
-
error: `claude exited ${
|
|
54
|
+
durationMs: raw.durationMs,
|
|
55
|
+
error: `claude exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
|
|
51
56
|
};
|
|
52
57
|
}
|
|
53
58
|
let envelope;
|
|
54
59
|
try {
|
|
55
|
-
envelope = JSON.parse(
|
|
60
|
+
envelope = JSON.parse(raw.stdout);
|
|
56
61
|
}
|
|
57
62
|
catch {
|
|
58
63
|
return {
|
|
59
64
|
ok: false,
|
|
60
65
|
text: "",
|
|
61
|
-
durationMs,
|
|
62
|
-
error: `result envelope was not JSON: ${
|
|
66
|
+
durationMs: raw.durationMs,
|
|
67
|
+
error: `result envelope was not JSON: ${raw.stdout.slice(0, 300)}`,
|
|
63
68
|
};
|
|
64
69
|
}
|
|
65
70
|
if (envelope.is_error) {
|
|
@@ -68,7 +73,7 @@ export const claudeCodeSpawner = async (req) => {
|
|
|
68
73
|
text: String(envelope.result ?? ""),
|
|
69
74
|
sessionId: envelope.session_id,
|
|
70
75
|
costUsd: envelope.total_cost_usd,
|
|
71
|
-
durationMs,
|
|
76
|
+
durationMs: raw.durationMs,
|
|
72
77
|
error: `harness error (${envelope.subtype ?? "unknown"}): ${(envelope.errors ?? []).join("; ") || "see envelope"}`,
|
|
73
78
|
};
|
|
74
79
|
}
|
|
@@ -77,6 +82,22 @@ export const claudeCodeSpawner = async (req) => {
|
|
|
77
82
|
text: String(envelope.result ?? ""),
|
|
78
83
|
sessionId: envelope.session_id,
|
|
79
84
|
costUsd: envelope.total_cost_usd,
|
|
80
|
-
durationMs,
|
|
85
|
+
durationMs: raw.durationMs,
|
|
81
86
|
};
|
|
87
|
+
}
|
|
88
|
+
export const claudeCodeSpawner = async (req) => {
|
|
89
|
+
const t0 = Date.now();
|
|
90
|
+
let invocation;
|
|
91
|
+
try {
|
|
92
|
+
invocation = buildClaudeInvocation(req);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return { ok: false, text: "", durationMs: 0, error: error.message };
|
|
96
|
+
}
|
|
97
|
+
const r = await exec(invocation.argv, {
|
|
98
|
+
cwd: req.cwd,
|
|
99
|
+
env: buildChildEnv(req.runId, { subscriptionOnly: req.subscriptionOnly }),
|
|
100
|
+
timeout: req.timeoutMs,
|
|
101
|
+
});
|
|
102
|
+
return normalizeClaudeResult({ ...r, durationMs: Date.now() - t0 });
|
|
82
103
|
};
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* - No max-turns equivalent → `maxTurns` is accepted and ignored (documented
|
|
15
15
|
* in the CLI docs page).
|
|
16
16
|
*/
|
|
17
|
-
import type {
|
|
17
|
+
import type { HarnessInvocation, HarnessRawResult } from "../harnesses/types.js";
|
|
18
|
+
import type { Spawner, SpawnRequest, SpawnResult } from "./types.js";
|
|
19
|
+
export declare function buildCodexInvocation(req: SpawnRequest, resultFile?: string): HarnessInvocation;
|
|
20
|
+
export declare function normalizeCodexResult(raw: HarnessRawResult): SpawnResult;
|
|
18
21
|
export declare const codexSpawner: Spawner;
|
|
19
22
|
//# sourceMappingURL=spawn-codex.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"spawn-codex.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAErE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAgB9F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAiBvE;AAED,eAAO,MAAM,YAAY,EAAE,OA2B1B,CAAC"}
|
|
@@ -19,46 +19,68 @@ import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
|
19
19
|
import { tmpdir } from "node:os";
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { exec } from "../../lib/exec.js";
|
|
22
|
+
import { validateHarnessEffort } from "../harnesses/profiles.js";
|
|
22
23
|
import { buildChildEnv } from "./child-env.js";
|
|
23
24
|
import { notFoundError } from "./harnesses.js";
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
export function buildCodexInvocation(req, resultFile) {
|
|
26
|
+
validateHarnessEffort("codex", req.effort);
|
|
27
|
+
if (!resultFile)
|
|
28
|
+
throw new Error("codex adapter requires a final-message result file");
|
|
27
29
|
const argv = [
|
|
28
30
|
"codex",
|
|
29
31
|
"exec",
|
|
30
32
|
req.prompt,
|
|
31
33
|
"--output-last-message",
|
|
32
|
-
|
|
34
|
+
resultFile,
|
|
33
35
|
"--skip-git-repo-check",
|
|
34
36
|
"--sandbox",
|
|
35
37
|
"workspace-write",
|
|
36
38
|
];
|
|
37
39
|
if (req.model)
|
|
38
40
|
argv.push("--model", req.model);
|
|
41
|
+
if (req.effort)
|
|
42
|
+
argv.push("-c", `model_reasoning_effort=${JSON.stringify(req.effort)}`);
|
|
43
|
+
return { argv, resultFile };
|
|
44
|
+
}
|
|
45
|
+
export function normalizeCodexResult(raw) {
|
|
46
|
+
if (raw.exitCode === 127) {
|
|
47
|
+
return { ok: false, text: "", durationMs: raw.durationMs, error: notFoundError("codex") };
|
|
48
|
+
}
|
|
49
|
+
if (raw.exitCode !== 0) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
text: "",
|
|
53
|
+
durationMs: raw.durationMs,
|
|
54
|
+
error: `codex exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
text: (raw.resultFileText ?? raw.stdout).trim(),
|
|
60
|
+
durationMs: raw.durationMs,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export const codexSpawner = async (req) => {
|
|
64
|
+
const t0 = Date.now();
|
|
65
|
+
const outFile = join(tmpdir(), `harnery-codex-${process.pid}-${randomBytes(4).toString("hex")}.txt`);
|
|
39
66
|
try {
|
|
40
|
-
|
|
67
|
+
let invocation;
|
|
68
|
+
try {
|
|
69
|
+
invocation = buildCodexInvocation(req, outFile);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return { ok: false, text: "", durationMs: 0, error: error.message };
|
|
73
|
+
}
|
|
74
|
+
const r = await exec(invocation.argv, {
|
|
41
75
|
cwd: req.cwd,
|
|
42
76
|
env: buildChildEnv(req.runId, { subscriptionOnly: req.subscriptionOnly }),
|
|
43
77
|
timeout: req.timeoutMs,
|
|
44
78
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
ok: false,
|
|
52
|
-
text: "",
|
|
53
|
-
durationMs,
|
|
54
|
-
error: `codex exited ${r.exitCode}: ${(r.stderr || r.stdout).slice(0, 500)}`,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
if (!existsSync(outFile)) {
|
|
58
|
-
// Exit 0 but no last-message file: fall back to stdout (contract drift guard).
|
|
59
|
-
return { ok: true, text: r.stdout, durationMs };
|
|
60
|
-
}
|
|
61
|
-
return { ok: true, text: readFileSync(outFile, "utf8").trim(), durationMs };
|
|
79
|
+
return normalizeCodexResult({
|
|
80
|
+
...r,
|
|
81
|
+
durationMs: Date.now() - t0,
|
|
82
|
+
resultFileText: existsSync(outFile) ? readFileSync(outFile, "utf8") : undefined,
|
|
83
|
+
});
|
|
62
84
|
}
|
|
63
85
|
finally {
|
|
64
86
|
rmSync(outFile, { force: true });
|
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
* - No per-run cost surface → undefined. No max-turns equivalent → `maxTurns`
|
|
16
16
|
* accepted and ignored (documented in the CLI docs page).
|
|
17
17
|
*/
|
|
18
|
-
import type {
|
|
18
|
+
import type { HarnessInvocation, HarnessRawResult } from "../harnesses/types.js";
|
|
19
|
+
import type { Spawner, SpawnRequest, SpawnResult } from "./types.js";
|
|
19
20
|
/** Exported for unit tests (no live binary to test against). */
|
|
20
21
|
export declare function parseCursorOutput(stdout: string): {
|
|
21
22
|
text: string;
|
|
22
23
|
sessionId?: string;
|
|
23
24
|
isError: boolean;
|
|
24
25
|
};
|
|
26
|
+
export declare function buildCursorInvocation(req: SpawnRequest): HarnessInvocation;
|
|
27
|
+
export declare function normalizeCursorResult(raw: HarnessRawResult): SpawnResult;
|
|
25
28
|
export declare const cursorSpawner: Spawner;
|
|
26
29
|
//# sourceMappingURL=spawn-cursor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"spawn-cursor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/spawn-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASrE,gEAAgE;AAChE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,CAWA;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,YAAY,GAAG,iBAAiB,CAS1E;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,WAAW,CA6BxE;AAED,eAAO,MAAM,aAAa,EAAE,OAe3B,CAAC"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* accepted and ignored (documented in the CLI docs page).
|
|
17
17
|
*/
|
|
18
18
|
import { exec } from "../../lib/exec.js";
|
|
19
|
+
import { validateHarnessEffort } from "../harnesses/profiles.js";
|
|
19
20
|
import { buildChildEnv } from "./child-env.js";
|
|
20
21
|
import { notFoundError } from "./harnesses.js";
|
|
21
22
|
/** Exported for unit tests (no live binary to test against). */
|
|
@@ -32,8 +33,8 @@ export function parseCursorOutput(stdout) {
|
|
|
32
33
|
return { text: stdout, isError: false };
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
|
-
export
|
|
36
|
-
|
|
36
|
+
export function buildCursorInvocation(req) {
|
|
37
|
+
validateHarnessEffort("cursor", req.effort);
|
|
37
38
|
// --trust: headless cursor-agent refuses untrusted workspaces (exit 1,
|
|
38
39
|
// "Workspace Trust Required"). Workflow children run in the engine's cwd
|
|
39
40
|
// deliberately and may edit files — the same posture as the codex adapter's
|
|
@@ -41,32 +42,50 @@ export const cursorSpawner = async (req) => {
|
|
|
41
42
|
const argv = ["cursor-agent", "-p", req.prompt, "--output-format", "json", "--trust"];
|
|
42
43
|
if (req.model)
|
|
43
44
|
argv.push("--model", req.model);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const durationMs = Date.now() - t0;
|
|
50
|
-
if (r.exitCode === 127) {
|
|
51
|
-
return { ok: false, text: "", durationMs, error: notFoundError("cursor") };
|
|
45
|
+
return { argv };
|
|
46
|
+
}
|
|
47
|
+
export function normalizeCursorResult(raw) {
|
|
48
|
+
if (raw.exitCode === 127) {
|
|
49
|
+
return { ok: false, text: "", durationMs: raw.durationMs, error: notFoundError("cursor") };
|
|
52
50
|
}
|
|
53
|
-
if (
|
|
51
|
+
if (raw.exitCode !== 0) {
|
|
54
52
|
return {
|
|
55
53
|
ok: false,
|
|
56
54
|
text: "",
|
|
57
|
-
durationMs,
|
|
58
|
-
error: `cursor-agent exited ${
|
|
55
|
+
durationMs: raw.durationMs,
|
|
56
|
+
error: `cursor-agent exited ${raw.exitCode}: ${(raw.stderr || raw.stdout).slice(0, 500)}`,
|
|
59
57
|
};
|
|
60
58
|
}
|
|
61
|
-
const parsed = parseCursorOutput(
|
|
59
|
+
const parsed = parseCursorOutput(raw.stdout);
|
|
62
60
|
if (parsed.isError) {
|
|
63
61
|
return {
|
|
64
62
|
ok: false,
|
|
65
63
|
text: parsed.text,
|
|
66
64
|
sessionId: parsed.sessionId,
|
|
67
|
-
durationMs,
|
|
65
|
+
durationMs: raw.durationMs,
|
|
68
66
|
error: `cursor-agent reported is_error: ${parsed.text.slice(0, 300)}`,
|
|
69
67
|
};
|
|
70
68
|
}
|
|
71
|
-
return {
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
text: parsed.text,
|
|
72
|
+
sessionId: parsed.sessionId,
|
|
73
|
+
durationMs: raw.durationMs,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export const cursorSpawner = async (req) => {
|
|
77
|
+
const t0 = Date.now();
|
|
78
|
+
let invocation;
|
|
79
|
+
try {
|
|
80
|
+
invocation = buildCursorInvocation(req);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return { ok: false, text: "", durationMs: 0, error: error.message };
|
|
84
|
+
}
|
|
85
|
+
const r = await exec(invocation.argv, {
|
|
86
|
+
cwd: req.cwd,
|
|
87
|
+
env: buildChildEnv(req.runId, { subscriptionOnly: req.subscriptionOnly }),
|
|
88
|
+
timeout: req.timeoutMs,
|
|
89
|
+
});
|
|
90
|
+
return normalizeCursorResult({ ...r, durationMs: Date.now() - t0 });
|
|
72
91
|
};
|