@odla-ai/harness 0.1.1 → 0.2.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/dist/{chunk-QTUEF2HZ.js → chunk-3QP4VDQS.js} +1 -1
- package/dist/{chunk-QTUEF2HZ.js.map → chunk-3QP4VDQS.js.map} +1 -1
- package/dist/{chunk-GMVZ4LZH.js → chunk-5FFR7U4L.js} +1173 -374
- package/dist/chunk-5FFR7U4L.js.map +1 -0
- package/dist/{chunk-GE6CCN7W.js → chunk-C5VQI2IF.js} +2 -2
- package/dist/{chunk-PHXQH4YM.js → chunk-GKDKIU4P.js} +4 -3
- package/dist/{chunk-ATKV6VTU.js → chunk-KD7IN3NJ.js} +4 -4
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/code-runtime-cli.cjs +1233 -680
- package/dist/code-runtime-cli.cjs.map +1 -1
- package/dist/code-runtime-cli.js +5 -6
- package/dist/code-runtime-cli.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/node.cjs +1530 -443
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +591 -10
- package/dist/node.d.ts +591 -10
- package/dist/node.js +304 -6
- package/dist/node.js.map +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/{types-D12vK3K9.d.cts → types-0_H9TKkO.d.cts} +1 -1
- package/dist/{types-D12vK3K9.d.ts → types-0_H9TKkO.d.ts} +1 -1
- package/package.json +8 -14
- package/dist/chunk-GMVZ4LZH.js.map +0 -1
- /package/dist/{chunk-GE6CCN7W.js.map → chunk-C5VQI2IF.js.map} +0 -0
- /package/dist/{chunk-PHXQH4YM.js.map → chunk-GKDKIU4P.js.map} +0 -0
- /package/dist/{chunk-ATKV6VTU.js.map → chunk-KD7IN3NJ.js.map} +0 -0
package/dist/node.js
CHANGED
|
@@ -1,28 +1,45 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runHarnessRunner,
|
|
3
3
|
runLeasedAttempt
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-KD7IN3NJ.js";
|
|
5
5
|
import {
|
|
6
6
|
CODE_RUNTIME_PROTOCOL_VERSION,
|
|
7
7
|
CodePiRuntimeEngine,
|
|
8
8
|
CodeRuntimeCheckpointManager,
|
|
9
9
|
CodeRuntimeControlError,
|
|
10
10
|
CodeRuntimeReconciler,
|
|
11
|
+
SYSTEM_PROMPT_FOR,
|
|
12
|
+
V1_SYSTEM_PROMPT,
|
|
13
|
+
V2_SYSTEM_PROMPT,
|
|
14
|
+
V3_SYSTEM_PROMPT,
|
|
15
|
+
applyCodePatch,
|
|
11
16
|
assertCodeBuildRecipe,
|
|
12
17
|
attachCodeRuntimeReferences,
|
|
13
18
|
buildRecipeContainerArgs,
|
|
19
|
+
codeSkill,
|
|
14
20
|
createCodeRuntimeControlClient,
|
|
21
|
+
createCodeRuntimeInference,
|
|
15
22
|
createCodeToolBroker,
|
|
16
23
|
createCodeWorkspaceCheckpoint,
|
|
17
24
|
createContainerRecipeExecutor,
|
|
25
|
+
describePatchFailure,
|
|
18
26
|
digestStagedWorkspace,
|
|
19
27
|
isCheckpointEffectCompleted,
|
|
20
28
|
materializeCodeRuntimeSource,
|
|
29
|
+
materializeCommandWorkspace,
|
|
21
30
|
prepareRuntimeCheckpoint,
|
|
31
|
+
registeredFiles,
|
|
32
|
+
resolveCodePath,
|
|
22
33
|
restoreCodeWorkspaceCheckpoint,
|
|
34
|
+
runCodeAgent,
|
|
35
|
+
runCodeAgentAttempt,
|
|
23
36
|
runCodeRuntimeHeartbeatLoop,
|
|
37
|
+
runGoal,
|
|
38
|
+
stripPatchEnvelope,
|
|
39
|
+
validateCodePatch,
|
|
40
|
+
validateRelativePath,
|
|
24
41
|
verifyCodeCandidate
|
|
25
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-5FFR7U4L.js";
|
|
26
43
|
import {
|
|
27
44
|
assertPinnedImage,
|
|
28
45
|
buildContainerRunArgs,
|
|
@@ -33,39 +50,320 @@ import {
|
|
|
33
50
|
stageWorkspace,
|
|
34
51
|
stageWorkspacePair,
|
|
35
52
|
verifyContainerEngineBoundary
|
|
36
|
-
} from "./chunk-
|
|
37
|
-
import "./chunk-
|
|
38
|
-
import "./chunk-
|
|
53
|
+
} from "./chunk-GKDKIU4P.js";
|
|
54
|
+
import "./chunk-C5VQI2IF.js";
|
|
55
|
+
import "./chunk-3QP4VDQS.js";
|
|
56
|
+
|
|
57
|
+
// src/code-goal-race.ts
|
|
58
|
+
function selectWinner(outcomes) {
|
|
59
|
+
const ranked = [...outcomes].sort((left, right) => {
|
|
60
|
+
if (left.gatePassed !== right.gatePassed) return left.gatePassed ? -1 : 1;
|
|
61
|
+
const leftBroken = left.error ? 1 : 0;
|
|
62
|
+
const rightBroken = right.error ? 1 : 0;
|
|
63
|
+
if (leftBroken !== rightBroken) return leftBroken - rightBroken;
|
|
64
|
+
const steps = (left.steps ?? Number.MAX_SAFE_INTEGER) - (right.steps ?? Number.MAX_SAFE_INTEGER);
|
|
65
|
+
if (steps !== 0) return steps;
|
|
66
|
+
const leftPriced = left.costUsd !== void 0;
|
|
67
|
+
const rightPriced = right.costUsd !== void 0;
|
|
68
|
+
if (leftPriced !== rightPriced) return leftPriced ? -1 : 1;
|
|
69
|
+
if (leftPriced && rightPriced && left.costUsd !== right.costUsd) {
|
|
70
|
+
return left.costUsd - right.costUsd;
|
|
71
|
+
}
|
|
72
|
+
return (left.patchBytes ?? Number.MAX_SAFE_INTEGER) - (right.patchBytes ?? Number.MAX_SAFE_INTEGER);
|
|
73
|
+
});
|
|
74
|
+
return ranked[0];
|
|
75
|
+
}
|
|
76
|
+
function racedAttempt(options) {
|
|
77
|
+
if (!Number.isSafeInteger(options.racers) || options.racers < 1) {
|
|
78
|
+
throw new TypeError("racers must be a positive integer");
|
|
79
|
+
}
|
|
80
|
+
const select = options.select ?? selectWinner;
|
|
81
|
+
return async (input) => {
|
|
82
|
+
const outcomes = await Promise.all(
|
|
83
|
+
Array.from({ length: options.racers }, async (_unused, index) => {
|
|
84
|
+
const racer = index + 1;
|
|
85
|
+
try {
|
|
86
|
+
return { ...await options.attempt({ ...input, racer }), racer };
|
|
87
|
+
} catch (cause) {
|
|
88
|
+
return {
|
|
89
|
+
racer,
|
|
90
|
+
gatePassed: false,
|
|
91
|
+
feedback: "",
|
|
92
|
+
tokens: 0,
|
|
93
|
+
error: (cause instanceof Error ? cause.message : String(cause)).slice(0, 500)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
const winner = select(outcomes);
|
|
99
|
+
options.onRound?.(outcomes, winner);
|
|
100
|
+
const tokens = outcomes.reduce((total, outcome) => total + outcome.tokens, 0);
|
|
101
|
+
const priced = outcomes.filter((outcome) => outcome.costUsd !== void 0);
|
|
102
|
+
const costUsd = priced.length === outcomes.length ? priced.reduce((total, outcome) => total + (outcome.costUsd ?? 0), 0) : void 0;
|
|
103
|
+
return {
|
|
104
|
+
gatePassed: winner.gatePassed,
|
|
105
|
+
feedback: winner.feedback,
|
|
106
|
+
tokens,
|
|
107
|
+
...costUsd === void 0 ? {} : { costUsd },
|
|
108
|
+
...winner.steps === void 0 ? {} : { steps: winner.steps },
|
|
109
|
+
...winner.patchBytes === void 0 ? {} : { patchBytes: winner.patchBytes },
|
|
110
|
+
// The round only failed to RUN if every racer did. One survivor is a round.
|
|
111
|
+
...outcomes.every((outcome) => outcome.error) ? { error: winner.error } : {}
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/code-goal-decompose.ts
|
|
117
|
+
import { partition } from "@odla-ai/graph";
|
|
118
|
+
import { buildCodeGraph, FILE, IMPORTS, READS, WRITES } from "@odla-ai/graph/code";
|
|
119
|
+
var DecompositionError = class extends Error {
|
|
120
|
+
constructor(message) {
|
|
121
|
+
super(message);
|
|
122
|
+
this.name = "DecompositionError";
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function assertDisjointPlan(plan) {
|
|
126
|
+
if (plan.length === 0) throw new DecompositionError("a plan needs at least one sub-goal");
|
|
127
|
+
const ids = /* @__PURE__ */ new Set();
|
|
128
|
+
const owner = /* @__PURE__ */ new Map();
|
|
129
|
+
for (const sub of plan) {
|
|
130
|
+
if (!sub.id.trim()) throw new DecompositionError("every sub-goal needs an id");
|
|
131
|
+
if (ids.has(sub.id)) throw new DecompositionError(`duplicate sub-goal id "${sub.id}"`);
|
|
132
|
+
ids.add(sub.id);
|
|
133
|
+
if (!sub.goal.trim()) throw new DecompositionError(`sub-goal "${sub.id}" has no instruction`);
|
|
134
|
+
if (sub.files.length === 0) {
|
|
135
|
+
throw new DecompositionError(`sub-goal "${sub.id}" declares no files; it cannot be checked for collisions`);
|
|
136
|
+
}
|
|
137
|
+
for (const file of sub.files) {
|
|
138
|
+
const claimed = owner.get(file);
|
|
139
|
+
if (claimed !== void 0) {
|
|
140
|
+
throw new DecompositionError(
|
|
141
|
+
`sub-goals "${claimed}" and "${sub.id}" both declare "${file}"; a plan must partition the files it touches`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
owner.set(file, sub.id);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function patchPaths(patch, maxBytes = 256 * 1024) {
|
|
149
|
+
try {
|
|
150
|
+
return validateCodePatch(patch, maxBytes);
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function straySubGoalFiles(sub, patch) {
|
|
156
|
+
const declared = new Set(sub.files);
|
|
157
|
+
return (patchPaths(patch) ?? []).filter((path) => !declared.has(path));
|
|
158
|
+
}
|
|
159
|
+
async function integrateSubGoals(options) {
|
|
160
|
+
const steps = [];
|
|
161
|
+
let tokens = 0;
|
|
162
|
+
let costUsd = 0;
|
|
163
|
+
let priced = true;
|
|
164
|
+
let met = false;
|
|
165
|
+
for (const sub of options.plan) {
|
|
166
|
+
const result = options.results.find((entry) => entry.sub.id === sub.id);
|
|
167
|
+
if (!result) {
|
|
168
|
+
steps.push({ subGoalId: sub.id, gatePassed: false, applied: false, reason: "sub_goal_failed", detail: "no result" });
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
tokens += result.tokens;
|
|
172
|
+
if (result.costUsd === void 0) priced = false;
|
|
173
|
+
else costUsd += result.costUsd;
|
|
174
|
+
if (result.error) {
|
|
175
|
+
steps.push({ subGoalId: sub.id, gatePassed: false, applied: false, reason: "sub_goal_failed", detail: result.error });
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
if (!result.patch.trim()) {
|
|
179
|
+
steps.push({ subGoalId: sub.id, gatePassed: false, applied: false, reason: "no_changes" });
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
const stray = straySubGoalFiles(sub, result.patch);
|
|
183
|
+
if (stray.length > 0) {
|
|
184
|
+
steps.push({
|
|
185
|
+
subGoalId: sub.id,
|
|
186
|
+
gatePassed: false,
|
|
187
|
+
applied: false,
|
|
188
|
+
reason: "stray_files",
|
|
189
|
+
detail: `touched undeclared files: ${stray.join(", ")}`
|
|
190
|
+
});
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
if (!await options.apply(result.patch)) {
|
|
194
|
+
steps.push({ subGoalId: sub.id, gatePassed: false, applied: false, reason: "did_not_apply" });
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
const gatePassed = await options.gate();
|
|
198
|
+
steps.push({ subGoalId: sub.id, gatePassed, applied: true, ...gatePassed ? {} : { reason: "gate_failed" } });
|
|
199
|
+
met = gatePassed;
|
|
200
|
+
}
|
|
201
|
+
return { met, steps, tokens, ...priced && options.results.length > 0 ? { costUsd } : {} };
|
|
202
|
+
}
|
|
203
|
+
async function planReachCollisions(plan, workspace) {
|
|
204
|
+
const graph = await buildCodeGraph({ paths: workspace.paths, read: workspace.read });
|
|
205
|
+
return partition(graph, plan.map((sub) => sub.files.map((file) => `${FILE}:${file}`)), {
|
|
206
|
+
kinds: [IMPORTS, READS, WRITES],
|
|
207
|
+
direction: "out"
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/code-goal-strategy.ts
|
|
212
|
+
var MEASURED_PREMIUM = Object.freeze({
|
|
213
|
+
/** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
|
|
214
|
+
racePerRacer: 0.55,
|
|
215
|
+
/** Decomposition across 3 sub-agents: 10,897 / 6,474. */
|
|
216
|
+
decomposePerSubGoal: 0.23
|
|
217
|
+
});
|
|
218
|
+
var actionable = (feedback) => {
|
|
219
|
+
const text = feedback.trim();
|
|
220
|
+
if (text.length < 12) return false;
|
|
221
|
+
return /\b(expected|assert|error|fail(?:ed|ure)?|exit|line \d+|\.[a-z]{1,4}:\d+)\b/i.test(text) || /\.(js|ts|tsx|jsx|mjs|cjs|py|go|rs|java|rb)\b/i.test(text);
|
|
222
|
+
};
|
|
223
|
+
function chooseStrategy(signals = {}) {
|
|
224
|
+
const width = Math.max(1, signals.width ?? 3);
|
|
225
|
+
const prior = signals.priorAttempts ?? [];
|
|
226
|
+
const overflows = signals.estimatedContextTokens !== void 0 && signals.contextLimit !== void 0 && signals.estimatedContextTokens > signals.contextLimit;
|
|
227
|
+
if (overflows && signals.partitionable) {
|
|
228
|
+
return {
|
|
229
|
+
strategy: "decompose",
|
|
230
|
+
reason: `the goal needs ~${signals.estimatedContextTokens} tokens against a ${signals.contextLimit} limit, and the plan partitions`,
|
|
231
|
+
expectedCostMultiplier: 1 + MEASURED_PREMIUM.decomposePerSubGoal * (width - 1),
|
|
232
|
+
// No fixture this large has been measured. This branch is reasoned, not
|
|
233
|
+
// observed, and says so rather than borrowing the others' credibility.
|
|
234
|
+
measured: false
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (overflows) {
|
|
238
|
+
return {
|
|
239
|
+
strategy: "sequential",
|
|
240
|
+
reason: "the goal exceeds one context but no disjoint partition was produced; decomposing without one would collide",
|
|
241
|
+
expectedCostMultiplier: 1,
|
|
242
|
+
measured: true
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const unhelpful = prior.length >= 2 && prior.slice(-2).every((attempt) => !attempt.gatePassed && !attempt.error && !actionable(attempt.feedback));
|
|
246
|
+
if (unhelpful) {
|
|
247
|
+
return {
|
|
248
|
+
strategy: "race",
|
|
249
|
+
reason: `the last ${Math.min(2, prior.length)} gate failures carried nothing actionable, so depth has nothing to build on`,
|
|
250
|
+
expectedCostMultiplier: 1 + MEASURED_PREMIUM.racePerRacer * (width - 1),
|
|
251
|
+
measured: true
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (signals.latencyBound) {
|
|
255
|
+
const strategy = signals.partitionable ? "decompose" : "race";
|
|
256
|
+
const premium = strategy === "decompose" ? MEASURED_PREMIUM.decomposePerSubGoal : MEASURED_PREMIUM.racePerRacer;
|
|
257
|
+
return {
|
|
258
|
+
strategy,
|
|
259
|
+
reason: `wall clock is the binding constraint; ${strategy} finishes sooner at a measured premium per solve`,
|
|
260
|
+
expectedCostMultiplier: 1 + premium * (width - 1),
|
|
261
|
+
measured: true
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
strategy: "sequential",
|
|
266
|
+
reason: prior.length === 0 ? "no evidence yet favours paying a fan-out premium" : "the gate is still saying something the next attempt can act on",
|
|
267
|
+
expectedCostMultiplier: 1,
|
|
268
|
+
measured: true
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
var feedbackIsActionable = actionable;
|
|
272
|
+
|
|
273
|
+
// src/code-recipe-dependencies.ts
|
|
274
|
+
import { lstat, rm, symlink } from "fs/promises";
|
|
275
|
+
import { isAbsolute, join } from "path";
|
|
276
|
+
var RESERVED_MOUNTS = /* @__PURE__ */ new Set(["node_modules", "dist", "coverage"]);
|
|
277
|
+
function withRecipeDependencies(executor, dependencies) {
|
|
278
|
+
const mountAs = dependencies.mountAs ?? "node_modules";
|
|
279
|
+
if (!isAbsolute(dependencies.source)) {
|
|
280
|
+
throw new TypeError("recipe dependency source must be an absolute path");
|
|
281
|
+
}
|
|
282
|
+
if (!RESERVED_MOUNTS.has(mountAs)) {
|
|
283
|
+
throw new TypeError(`recipe dependencies must mount at a reserved name, not "${mountAs}"`);
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
run: async (input) => {
|
|
287
|
+
const target = join(input.workspaceDir, mountAs);
|
|
288
|
+
let linked = false;
|
|
289
|
+
try {
|
|
290
|
+
const existing = await lstat(target).catch(() => null);
|
|
291
|
+
if (!existing) {
|
|
292
|
+
await symlink(dependencies.source, target, "dir");
|
|
293
|
+
linked = true;
|
|
294
|
+
}
|
|
295
|
+
return await executor.run(input);
|
|
296
|
+
} finally {
|
|
297
|
+
if (linked) await rm(target, { force: true, recursive: false }).catch(() => void 0);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
async function installedDependencies(repoRoot) {
|
|
303
|
+
const source = join(repoRoot, "node_modules");
|
|
304
|
+
const info = await lstat(source).catch(() => null);
|
|
305
|
+
return info?.isDirectory() ? { source } : null;
|
|
306
|
+
}
|
|
39
307
|
export {
|
|
40
308
|
CODE_RUNTIME_PROTOCOL_VERSION,
|
|
41
309
|
CodePiRuntimeEngine,
|
|
42
310
|
CodeRuntimeCheckpointManager,
|
|
43
311
|
CodeRuntimeControlError,
|
|
44
312
|
CodeRuntimeReconciler,
|
|
313
|
+
DecompositionError,
|
|
314
|
+
MEASURED_PREMIUM,
|
|
315
|
+
SYSTEM_PROMPT_FOR,
|
|
316
|
+
V1_SYSTEM_PROMPT,
|
|
317
|
+
V2_SYSTEM_PROMPT,
|
|
318
|
+
V3_SYSTEM_PROMPT,
|
|
319
|
+
applyCodePatch,
|
|
45
320
|
assertCodeBuildRecipe,
|
|
321
|
+
assertDisjointPlan,
|
|
46
322
|
assertPinnedImage,
|
|
47
323
|
attachCodeRuntimeReferences,
|
|
48
324
|
buildContainerRunArgs,
|
|
49
325
|
buildRecipeContainerArgs,
|
|
326
|
+
chooseStrategy,
|
|
327
|
+
codeSkill,
|
|
50
328
|
createCodeRuntimeControlClient,
|
|
329
|
+
createCodeRuntimeInference,
|
|
51
330
|
createCodeToolBroker,
|
|
52
331
|
createCodeWorkspaceCheckpoint,
|
|
53
332
|
createContainerRecipeExecutor,
|
|
333
|
+
describePatchFailure,
|
|
54
334
|
digestStagedWorkspace,
|
|
335
|
+
feedbackIsActionable,
|
|
336
|
+
installedDependencies,
|
|
337
|
+
integrateSubGoals,
|
|
55
338
|
isCheckpointEffectCompleted,
|
|
56
339
|
materializeCodeRuntimeSource,
|
|
340
|
+
materializeCommandWorkspace,
|
|
57
341
|
materializeGitTree,
|
|
342
|
+
patchPaths,
|
|
343
|
+
planReachCollisions,
|
|
58
344
|
prepareRuntimeCheckpoint,
|
|
345
|
+
racedAttempt,
|
|
346
|
+
registeredFiles,
|
|
347
|
+
resolveCodePath,
|
|
59
348
|
restoreCodeWorkspaceCheckpoint,
|
|
349
|
+
runCodeAgent,
|
|
350
|
+
runCodeAgentAttempt,
|
|
60
351
|
runCodeRuntimeHeartbeatLoop,
|
|
61
352
|
runContainerAttempt,
|
|
353
|
+
runGoal,
|
|
62
354
|
runHarnessRunner,
|
|
63
355
|
runLeasedAttempt,
|
|
64
356
|
safeWorkspaceLabel,
|
|
65
357
|
selectContainerEngine,
|
|
358
|
+
selectWinner,
|
|
66
359
|
stageWorkspace,
|
|
67
360
|
stageWorkspacePair,
|
|
361
|
+
straySubGoalFiles,
|
|
362
|
+
stripPatchEnvelope,
|
|
363
|
+
validateCodePatch,
|
|
364
|
+
validateRelativePath,
|
|
68
365
|
verifyCodeCandidate,
|
|
69
|
-
verifyContainerEngineBoundary
|
|
366
|
+
verifyContainerEngineBoundary,
|
|
367
|
+
withRecipeDependencies
|
|
70
368
|
};
|
|
71
369
|
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/code-goal-race.ts","../src/code-goal-decompose.ts","../src/code-goal-strategy.ts","../src/code-recipe-dependencies.ts"],"sourcesContent":["// Racing: N attempts at the same goal, in parallel, winner picked by receipt.\n//\n// This is an ATTEMPT STRATEGY, not a second runner. `racedAttempt` returns a\n// GoalAttempt, so runGoal is unchanged and racing composes with everything it\n// already does — budgets, deadlines, re-prompting on the winner's feedback.\n//\n// It answers a different failure than re-prompting does. Re-prompting helps when\n// the gate said something actionable; racing helps when attempts fail\n// DIFFERENTLY each time and there is nothing to act on, only variance to sample\n// out. Measured on this corpus: gpt-5.6-terra failed two-stage-gate once in a\n// single-attempt run, then passed it twice.\n\nimport type { GoalAttempt, GoalAttemptInput, GoalAttemptOutcome } from \"./code-goal-runner\";\n\n/** One racer's result, tagged with which racer produced it. */\nexport interface RacedOutcome extends GoalAttemptOutcome {\n racer: number;\n}\n\n/**\n * One race: N independent attempts at the same goal, cheapest winner taken.\n *\n * The run is charged for every racer, not just the winner. Racing answers\n * variance, not size; at equal budget, depth beat it on every axis measured.\n */\nexport interface RacedAttemptOptions {\n /** How many attempts run per round. */\n racers: number;\n /** Runs one racer. Each MUST get an isolated workspace — racers edit in\n * parallel, and a shared tree would have them overwrite each other. */\n attempt(input: GoalAttemptInput & { racer: number }): Promise<GoalAttemptOutcome>;\n /** Override the ranking. Default: {@link selectWinner}. */\n select?(outcomes: RacedOutcome[]): RacedOutcome;\n /** Observe the whole field, for reporting what racing cost and bought. */\n onRound?(outcomes: RacedOutcome[], winner: RacedOutcome): void;\n}\n\n/**\n * Rank a field of racers.\n *\n * Passing candidates first — that is the only thing that decides the goal. Then\n * fewest steps, then cheapest, then smallest patch: three tie-breaks that all\n * prefer the attempt which did LESS to get there, on the view that a smaller\n * change reaching the same verified state is the better one to keep.\n *\n * Unknown cost ranks LAST among passing candidates rather than cheapest. An\n * unpriced racer must not win a cost comparison it never entered.\n */\nexport function selectWinner(outcomes: RacedOutcome[]): RacedOutcome {\n const ranked = [...outcomes].sort((left, right) => {\n if (left.gatePassed !== right.gatePassed) return left.gatePassed ? -1 : 1;\n // An attempt that could not run is worse than one that ran and failed: it\n // produced no evidence at all.\n const leftBroken = left.error ? 1 : 0;\n const rightBroken = right.error ? 1 : 0;\n if (leftBroken !== rightBroken) return leftBroken - rightBroken;\n const steps = (left.steps ?? Number.MAX_SAFE_INTEGER) - (right.steps ?? Number.MAX_SAFE_INTEGER);\n if (steps !== 0) return steps;\n // Unknown cost is not a number to compare, so handle it as its own case:\n // both unknown is a tie, one unknown loses to any known value. Subtracting\n // sentinels here quietly produced Infinity for the one-unknown case, which a\n // finite-check then skipped — leaving the unpriced racer to win by position.\n const leftPriced = left.costUsd !== undefined;\n const rightPriced = right.costUsd !== undefined;\n if (leftPriced !== rightPriced) return leftPriced ? -1 : 1;\n if (leftPriced && rightPriced && left.costUsd !== right.costUsd) {\n return left.costUsd! - right.costUsd!;\n }\n return (left.patchBytes ?? Number.MAX_SAFE_INTEGER) - (right.patchBytes ?? Number.MAX_SAFE_INTEGER);\n });\n return ranked[0]!;\n}\n\n/**\n * Build a GoalAttempt that races `racers` attempts and returns the winner.\n *\n * The returned outcome reports the round's TOTAL tokens and cost, not the\n * winner's. The runner charges its budget from what an attempt reports, and\n * charging only the winner would make racing look free — three racers would\n * cost what one did, and every budget in the system would be wrong by a factor\n * of N. Racing trades depth for breadth at honest spend, or it is not a trade.\n */\nexport function racedAttempt(options: RacedAttemptOptions): GoalAttempt {\n if (!Number.isSafeInteger(options.racers) || options.racers < 1) {\n throw new TypeError(\"racers must be a positive integer\");\n }\n const select = options.select ?? selectWinner;\n return async (input) => {\n const outcomes = await Promise.all(\n Array.from({ length: options.racers }, async (_unused, index): Promise<RacedOutcome> => {\n const racer = index + 1;\n try {\n return { ...(await options.attempt({ ...input, racer })), racer };\n } catch (cause) {\n return {\n racer, gatePassed: false, feedback: \"\", tokens: 0,\n error: (cause instanceof Error ? cause.message : String(cause)).slice(0, 500),\n };\n }\n }),\n );\n const winner = select(outcomes);\n options.onRound?.(outcomes, winner);\n\n const tokens = outcomes.reduce((total, outcome) => total + outcome.tokens, 0);\n const priced = outcomes.filter((outcome) => outcome.costUsd !== undefined);\n // Only report a cost when EVERY racer priced. A partial sum would understate\n // the round and let an unpriced racer hide spend inside a budget check.\n const costUsd = priced.length === outcomes.length\n ? priced.reduce((total, outcome) => total + (outcome.costUsd ?? 0), 0)\n : undefined;\n\n return {\n gatePassed: winner.gatePassed,\n feedback: winner.feedback,\n tokens,\n ...(costUsd === undefined ? {} : { costUsd }),\n ...(winner.steps === undefined ? {} : { steps: winner.steps }),\n ...(winner.patchBytes === undefined ? {} : { patchBytes: winner.patchBytes }),\n // The round only failed to RUN if every racer did. One survivor is a round.\n ...(outcomes.every((outcome) => outcome.error) ? { error: winner.error } : {}),\n };\n };\n}\n","// Decomposition: split a goal into sub-goals that cannot collide, work them in\n// parallel, integrate them one at a time.\n//\n// The obstacle is specific. The workspace model is single-candidate: every\n// attempt owns one staged tree, and the verifier's contract is \"one patch, from\n// one trusted base, re-applied clean\". It cannot tell you that three partial\n// patches compose. So decomposition here does NOT merge.\n//\n// Instead the partition is made CHECKABLE. A plan declares, per sub-goal, the\n// files it will touch; overlapping declarations are rejected before any agent\n// starts; sub-agents work in isolated trees from the same base; and integration\n// applies each sub-patch to an accumulating tree, gating after each. Parallel\n// exploration, sequential application — no merge algorithm anywhere, and a\n// sub-agent that strays outside its declared files fails its own integration.\n\nimport { validateCodePatch } from \"./code-patch\";\nimport { partition, type PartitionVerdict } from \"@odla-ai/graph\";\nimport { buildCodeGraph, FILE, IMPORTS, READS, WRITES } from \"@odla-ai/graph/code\";\n\n/** One independent slice of a goal, scoped to the files it may touch. */\nexport interface SubGoal {\n id: string;\n /** What this sub-agent is asked to do. */\n goal: string;\n /** The files it declares it will touch. Its patch is rejected if it strays. */\n files: string[];\n}\n\n/** A plan that cannot be run in parallel — overlapping files, or a stray edit. */\nexport class DecompositionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DecompositionError\";\n }\n}\n\n/**\n * Reject a plan that cannot be worked in parallel, BEFORE any agent starts.\n *\n * Two sub-goals declaring the same file is the whole failure mode: they would\n * produce patches against the same lines and one of them could not be applied.\n * Catching it here costs nothing; catching it at integration costs every token\n * both sub-agents spent.\n */\nexport function assertDisjointPlan(plan: readonly SubGoal[]): void {\n if (plan.length === 0) throw new DecompositionError(\"a plan needs at least one sub-goal\");\n const ids = new Set<string>();\n const owner = new Map<string, string>();\n for (const sub of plan) {\n if (!sub.id.trim()) throw new DecompositionError(\"every sub-goal needs an id\");\n if (ids.has(sub.id)) throw new DecompositionError(`duplicate sub-goal id \"${sub.id}\"`);\n ids.add(sub.id);\n if (!sub.goal.trim()) throw new DecompositionError(`sub-goal \"${sub.id}\" has no instruction`);\n if (sub.files.length === 0) {\n throw new DecompositionError(`sub-goal \"${sub.id}\" declares no files; it cannot be checked for collisions`);\n }\n for (const file of sub.files) {\n const claimed = owner.get(file);\n if (claimed !== undefined) {\n throw new DecompositionError(\n `sub-goals \"${claimed}\" and \"${sub.id}\" both declare \"${file}\"; a plan must partition the files it touches`,\n );\n }\n owner.set(file, sub.id);\n }\n }\n}\n\n/** Files a patch actually touches, or null when it is not a valid patch. */\nexport function patchPaths(patch: string, maxBytes = 256 * 1024): string[] | null {\n try { return validateCodePatch(patch, maxBytes); }\n catch { return null; }\n}\n\n/**\n * Check a sub-agent stayed inside what it declared.\n *\n * This is what makes the partition a guarantee rather than a hope: the plan is\n * checked up front, and each patch is checked against the plan. A sub-agent that\n * wandered into a neighbour's file is refused even if its patch would have\n * applied cleanly, because the NEXT sub-patch was written against a tree where\n * that edit does not exist.\n */\nexport function straySubGoalFiles(sub: SubGoal, patch: string): string[] {\n const declared = new Set(sub.files);\n return (patchPaths(patch) ?? []).filter((path) => !declared.has(path));\n}\n\n/** What one sub-goal produced: its patch, its cost, and whether it worked. */\nexport interface SubGoalResult {\n sub: SubGoal;\n /** The candidate patch, or empty when the sub-agent changed nothing. */\n patch: string;\n tokens: number;\n costUsd?: number;\n error?: string;\n}\n\n/** Applying one sub-goal's patch to the accumulating tree, and the verdict. */\nexport interface IntegrationStep {\n subGoalId: string;\n /** Did the accumulated tree still satisfy the proof after applying this one? */\n gatePassed: boolean;\n applied: boolean;\n reason?: \"stray_files\" | \"did_not_apply\" | \"gate_failed\" | \"sub_goal_failed\" | \"no_changes\";\n detail?: string;\n}\n\n/**\n * A decomposed pursuit end to end.\n *\n * Measured at 1.68x the tokens of straight depth for 0.88x the wall clock, so\n * this buys latency, not efficiency — see chooseStrategy for when that trade\n * is worth making.\n */\nexport interface DecomposedRun {\n met: boolean;\n steps: IntegrationStep[];\n tokens: number;\n costUsd?: number;\n}\n\n/** The plan, its results, and the two callbacks that apply and judge them. */\nexport interface IntegrateOptions {\n plan: readonly SubGoal[];\n results: readonly SubGoalResult[];\n /** Apply one patch to the accumulating tree; reject means it did not apply. */\n apply(patch: string): Promise<boolean>;\n /** Run the proof against the accumulating tree. */\n gate(): Promise<boolean>;\n}\n\n/**\n * Integrate sub-results one at a time, gating after each.\n *\n * Order follows the plan, and the first failure stops integration. Continuing\n * past one would mean gating a tree whose earlier layer is already known bad,\n * so every later verdict would describe a state nobody intends to ship.\n */\nexport async function integrateSubGoals(options: IntegrateOptions): Promise<DecomposedRun> {\n const steps: IntegrationStep[] = [];\n let tokens = 0;\n let costUsd = 0;\n let priced = true;\n let met = false;\n\n for (const sub of options.plan) {\n const result = options.results.find((entry) => entry.sub.id === sub.id);\n if (!result) {\n steps.push({ subGoalId: sub.id, gatePassed: false, applied: false, reason: \"sub_goal_failed\", detail: \"no result\" });\n break;\n }\n tokens += result.tokens;\n if (result.costUsd === undefined) priced = false;\n else costUsd += result.costUsd;\n\n if (result.error) {\n steps.push({ subGoalId: sub.id, gatePassed: false, applied: false, reason: \"sub_goal_failed\", detail: result.error });\n break;\n }\n if (!result.patch.trim()) {\n steps.push({ subGoalId: sub.id, gatePassed: false, applied: false, reason: \"no_changes\" });\n break;\n }\n const stray = straySubGoalFiles(sub, result.patch);\n if (stray.length > 0) {\n steps.push({\n subGoalId: sub.id, gatePassed: false, applied: false, reason: \"stray_files\",\n detail: `touched undeclared files: ${stray.join(\", \")}`,\n });\n break;\n }\n if (!(await options.apply(result.patch))) {\n steps.push({ subGoalId: sub.id, gatePassed: false, applied: false, reason: \"did_not_apply\" });\n break;\n }\n const gatePassed = await options.gate();\n steps.push({ subGoalId: sub.id, gatePassed, applied: true, ...(gatePassed ? {} : { reason: \"gate_failed\" as const }) });\n met = gatePassed;\n // A mid-plan gate failure is expected — the goal is only whole once every\n // sub-goal has landed — so integration continues. What stops it is a patch\n // that could not be applied at all, above.\n }\n\n return { met, steps, tokens, ...(priced && options.results.length > 0 ? { costUsd } : {}) };\n}\n\n/**\n * The check `assertDisjointPlan` cannot make: do the sub-goals collide through\n * what they *reach*, not merely through what they declare?\n *\n * Collision ids are graph ids (`file:src/a.ts`, `table:orders`), because the\n * answer is no longer only about files — a shared table is a real collision and\n * naming it as a bare path would be a lie about what it is.\n *\n * Declared files being disjoint is necessary and not sufficient. Two sub-goals\n * can own different modules and still both depend on a third; a change either\n * one makes to that shared module lands in one patch and is invisible to the\n * other's verification. The import graph is the only thing that can see this,\n * and it is cheap — the graph over 3,400 files builds in ~400ms, against the\n * cost of running two agents to completion and discovering it at integration.\n *\n * Reported rather than thrown, because a shared dependency is sometimes fine:\n * two sub-goals may both READ a types module neither intends to touch. The\n * caller decides whether the overlap is one it can live with.\n */\nexport async function planReachCollisions(\n plan: readonly SubGoal[],\n workspace: { paths: readonly string[]; read(path: string): Promise<string> },\n): Promise<PartitionVerdict> {\n const graph = await buildCodeGraph({ paths: workspace.paths, read: workspace.read });\n // Imports AND data, in one traversal. Two sub-goals that never import each\n // other can still both write the same table, and that collision is the one\n // structural analysis alone can never see.\n return partition(graph, plan.map((sub) => sub.files.map((file) => `${FILE}:${file}`)), {\n kinds: [IMPORTS, READS, WRITES], direction: \"out\",\n });\n}\n","// Choosing how to pursue a goal: sequentially, by racing, or by decomposing.\n//\n// The plan that produced this file assumed fan-out would usually win. Measured,\n// it usually loses. On every fixture tried, depth beat breadth on $/solve:\n//\n// racing, equal budget of 6 agent runs (two-stage-gate, gpt-5.6-terra)\n// 1 racer x 6 attempts 7,936 tokens/solve 16.9 s/solve\n// 2 racers x 3 attempts 14,055 tokens/solve 16.8 s/solve\n// 3 racers x 2 attempts 21,044 tokens/solve 19.7 s/solve\n//\n// decomposition (three-modules, gpt-5.6-terra)\n// sequential 6,474 tokens/solve 9.7 s/solve\n// decomposed x3 10,897 tokens/solve 8.5 s/solve\n//\n// One cause explains both: every parallel worker re-pays the orientation cost —\n// reading the project, taking the system prompt, finding its feet — while a\n// sequential run amortizes that across one conversation. Breadth buys wall\n// clock and nothing else.\n//\n// So this chooser is deliberately biased toward sequential, and every fan-out\n// branch has to justify itself against a measured premium.\n\n/** What is known about a goal before choosing how to pursue it. */\nexport interface StrategySignals {\n /** Prior attempts, oldest first. Empty on the first pass. */\n priorAttempts?: Array<{ gatePassed: boolean; feedback: string; error?: string }>;\n /** True when wall clock is the binding constraint — a human is waiting, or a\n * deadline is close — and paying more per solve to finish sooner is correct. */\n latencyBound?: boolean;\n /** Estimated tokens needed to hold the whole goal at once. */\n estimatedContextTokens?: number;\n /** What one agent can actually hold. */\n contextLimit?: number;\n /** A checked disjoint partition, when the planner produced one. */\n partitionable?: boolean;\n /** Racers/sub-agents available if fanning out. */\n width?: number;\n}\n\n/** How to spend a goal's budget: straight depth, N racers, or a split plan. */\nexport type GoalStrategy = \"sequential\" | \"race\" | \"decompose\";\n\n/** The chosen strategy, why, and what it is expected to cost against depth. */\nexport interface StrategyChoice {\n strategy: GoalStrategy;\n /** Why, in terms a human reviewing a run can check. */\n reason: string;\n /** Expected cost multiplier vs sequential, from the measurements above.\n * 1 for sequential; fan-out is charged what it was observed to cost. */\n expectedCostMultiplier: number;\n /** False when the branch rests on something not yet measured. */\n measured: boolean;\n}\n\n/** Observed premiums. Update these from the bench, not from expectation. */\nexport const MEASURED_PREMIUM = Object.freeze({\n /** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */\n racePerRacer: 0.55,\n /** Decomposition across 3 sub-agents: 10,897 / 6,474. */\n decomposePerSubGoal: 0.23,\n});\n\nconst actionable = (feedback: string): boolean => {\n const text = feedback.trim();\n if (text.length < 12) return false;\n // Something the next attempt can act on names a place or an expectation. A\n // bare \"it failed\" leaves depth nothing to build on, which is the one case\n // where sampling another attempt beats trying again.\n return /\\b(expected|assert|error|fail(?:ed|ure)?|exit|line \\d+|\\.[a-z]{1,4}:\\d+)\\b/i.test(text)\n || /\\.(js|ts|tsx|jsx|mjs|cjs|py|go|rs|java|rb)\\b/i.test(text);\n};\n\n/**\n * Choose a strategy.\n *\n * Order matters and encodes the evidence: depth first, because it won every\n * measured comparison; fan-out only where depth demonstrably cannot help.\n */\nexport function chooseStrategy(signals: StrategySignals = {}): StrategyChoice {\n const width = Math.max(1, signals.width ?? 3);\n const prior = signals.priorAttempts ?? [];\n\n // 1. The goal does not fit. Depth cannot help — every attempt starts from the\n // same too-large problem — so this is the one case where decomposition is\n // not a premium but the only option.\n const overflows = signals.estimatedContextTokens !== undefined\n && signals.contextLimit !== undefined\n && signals.estimatedContextTokens > signals.contextLimit;\n if (overflows && signals.partitionable) {\n return {\n strategy: \"decompose\",\n reason: `the goal needs ~${signals.estimatedContextTokens} tokens against a ${signals.contextLimit} limit, and the plan partitions`,\n expectedCostMultiplier: 1 + MEASURED_PREMIUM.decomposePerSubGoal * (width - 1),\n // No fixture this large has been measured. This branch is reasoned, not\n // observed, and says so rather than borrowing the others' credibility.\n measured: false,\n };\n }\n if (overflows) {\n return {\n strategy: \"sequential\",\n reason: \"the goal exceeds one context but no disjoint partition was produced; decomposing without one would collide\",\n expectedCostMultiplier: 1,\n measured: true,\n };\n }\n\n // 2. Attempts are failing with nothing to act on. Re-prompting repeats itself;\n // another sample is the only thing that changes.\n const unhelpful = prior.length >= 2\n && prior.slice(-2).every((attempt) => !attempt.gatePassed && !attempt.error && !actionable(attempt.feedback));\n if (unhelpful) {\n return {\n strategy: \"race\",\n reason: `the last ${Math.min(2, prior.length)} gate failures carried nothing actionable, so depth has nothing to build on`,\n expectedCostMultiplier: 1 + MEASURED_PREMIUM.racePerRacer * (width - 1),\n measured: true,\n };\n }\n\n // 3. Latency is the constraint and the caller has accepted the premium.\n if (signals.latencyBound) {\n const strategy = signals.partitionable ? \"decompose\" : \"race\";\n const premium = strategy === \"decompose\"\n ? MEASURED_PREMIUM.decomposePerSubGoal\n : MEASURED_PREMIUM.racePerRacer;\n return {\n strategy,\n reason: `wall clock is the binding constraint; ${strategy} finishes sooner at a measured premium per solve`,\n expectedCostMultiplier: 1 + premium * (width - 1),\n measured: true,\n };\n }\n\n // 4. Everything else. Depth won every comparison run so far, so it is not a\n // fallback — it is the answer unless something above overrides it.\n return {\n strategy: \"sequential\",\n reason: prior.length === 0\n ? \"no evidence yet favours paying a fan-out premium\"\n : \"the gate is still saying something the next attempt can act on\",\n expectedCostMultiplier: 1,\n measured: true,\n };\n}\n\n/** Whether the gate's output gives the next attempt something to work with. */\nexport const feedbackIsActionable = actionable;\n","// Making installed dependencies available to a recipe, without copying them and\n// without exposing them to the agent.\n//\n// These are three separate questions that were previously answered by one list:\n//\n// 1. can the agent READ or PATCH it? No. `RESERVED` in code-patch already\n// refuses node_modules paths whatever is on disk, so the agent never sees\n// dependencies in `list`, `search`, `read`, or a candidate patch.\n// 2. is it COPIED into every staged tree? No, and it must not be: 70,834\n// files and 1.0GB against a 20,000-file cap, staged at least four times per\n// gated attempt (the attempt, each run_recipe, the verifier's base, and\n// each clean-verify recipe).\n// 3. is it PRESENT when the recipe runs? That is this file. Linked in for\n// the duration of one recipe and removed after, so the tree the verifier\n// diffs and digests is unchanged.\n//\n// The third answer is what makes a real test runner usable as a proof, which in\n// turn is what makes an end-to-end test expressible as a gate at all.\n\nimport { lstat, rm, symlink } from \"node:fs/promises\";\nimport { isAbsolute, join } from \"node:path\";\nimport type { CodeRecipeExecutor, CodeRecipeResult } from \"./code-tool-types\";\n\n/**\n * An installed dependency tree lent to a recipe for the length of one run.\n *\n * Mounted under a reserved name and removed in a finally, so the agent can run\n * a real build without the tree ever being addressable by a patch.\n */\nexport interface RecipeDependencies {\n /** Absolute path to an installed dependency tree on the host. */\n source: string;\n /** Where it appears inside the workspace. Must be a reserved name, so the\n * agent still cannot address it. */\n mountAs?: string;\n}\n\nconst RESERVED_MOUNTS = new Set([\"node_modules\", \"dist\", \"coverage\"]);\n\n/**\n * Wrap an executor so `dependencies.source` is present during each recipe run.\n *\n * It is linked, not copied. A link costs nothing per run, and dependencies are\n * identical across every attempt, racer and verification — copying them would\n * multiply the largest thing in the tree by the number of stages.\n *\n * The link is removed in a `finally`, so a recipe that times out or throws\n * cannot leave it behind for `workspace.patch()` to diff or\n * `digestStagedWorkspace` to hash.\n */\nexport function withRecipeDependencies(\n executor: CodeRecipeExecutor,\n dependencies: RecipeDependencies,\n): CodeRecipeExecutor {\n const mountAs = dependencies.mountAs ?? \"node_modules\";\n if (!isAbsolute(dependencies.source)) {\n throw new TypeError(\"recipe dependency source must be an absolute path\");\n }\n // Mounting at a name the agent CAN address would hand it a writable path into\n // the host's dependency tree through apply_patch.\n if (!RESERVED_MOUNTS.has(mountAs)) {\n throw new TypeError(`recipe dependencies must mount at a reserved name, not \"${mountAs}\"`);\n }\n\n return {\n run: async (input): Promise<CodeRecipeResult> => {\n const target = join(input.workspaceDir, mountAs);\n let linked = false;\n try {\n // An existing entry is left alone: a workspace that already carries its\n // own dependencies is not ours to replace.\n const existing = await lstat(target).catch(() => null);\n if (!existing) {\n await symlink(dependencies.source, target, \"dir\");\n linked = true;\n }\n return await executor.run(input);\n } finally {\n if (linked) await rm(target, { force: true, recursive: false }).catch(() => undefined);\n }\n },\n };\n}\n\n/** Resolve the dependency tree for a repository root, when it has one. */\nexport async function installedDependencies(repoRoot: string): Promise<RecipeDependencies | null> {\n const source = join(repoRoot, \"node_modules\");\n const info = await lstat(source).catch(() => null);\n return info?.isDirectory() ? { source } : null;\n}\n"],"mappings":"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","names":[]}
|
package/dist/testing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts","../src/types.ts"],"sourcesContent":["import type { OracleResponse } from \"@odla-ai/ai\";\nimport {\n HARNESS_PROTOCOL_VERSION,\n type HarnessCompletion,\n type HarnessControlPlane,\n type HarnessEventInput,\n type HarnessInferenceRequest,\n type HarnessInferenceResponse,\n type HarnessLease,\n} from \"./types\";\n\n/** Create a deterministic, valid lease for harness unit and integration tests. */\nexport function fixtureLease(overrides: Partial<HarnessLease> = {}): HarnessLease {\n return {\n protocolVersion: HARNESS_PROTOCOL_VERSION,\n leaseId: \"lease_fixture\",\n generation: 1,\n expiresAt: Date.now() + 60_000,\n task: {\n taskId: \"task_fixture\",\n attemptId: \"attempt_fixture\",\n title: \"Fixture task\",\n prompt: \"Create HARNESS_RESULT.md\",\n workspace: \"fixture\",\n aiRoute: \"coding\",\n policy: {\n network: \"none\",\n timeoutMs: 30_000,\n maxOutputBytes: 1_000_000,\n maxPatchBytes: 1_000_000,\n },\n },\n ...overrides,\n };\n}\n\n/** In-memory control plane that records runner interactions without network access. */\nexport class FakeHarnessControlPlane implements HarnessControlPlane {\n readonly leases: HarnessLease[] = [];\n readonly events: HarnessEventInput[] = [];\n readonly completions: HarnessCompletion[] = [];\n readonly inferenceRequests: HarnessInferenceRequest[] = [];\n cancelRequested = false;\n response: OracleResponse = {\n id: \"oracle_fixture\",\n model: \"fixture-model\",\n provider: \"openai\",\n role: \"assistant\",\n content: [{ type: \"text\", text: \"deterministic fixture response\" }],\n stopReason: \"end_turn\",\n usage: { inputTokens: 5, outputTokens: 4 },\n };\n\n constructor(...leases: HarnessLease[]) { this.leases.push(...leases); }\n\n async lease(workspaces: string[]): Promise<HarnessLease | null> {\n const index = this.leases.findIndex((candidate) => workspaces.includes(candidate.task.workspace));\n return index < 0 ? null : this.leases.splice(index, 1)[0]!;\n }\n async heartbeat(): Promise<{ cancelRequested: boolean; expiresAt: number }> {\n return { cancelRequested: this.cancelRequested, expiresAt: Date.now() + 60_000 };\n }\n async appendEvents(_attemptId: string, _leaseId: string, events: HarnessEventInput[]): Promise<void> {\n this.events.push(...events);\n }\n async infer(_attemptId: string, _leaseId: string, request: HarnessInferenceRequest): Promise<HarnessInferenceResponse> {\n this.inferenceRequests.push(request);\n return {\n requestId: request.requestId,\n response: this.response,\n receipt: {\n provider: this.response.provider,\n model: this.response.model,\n policyVersion: 1,\n inputTokens: this.response.usage.inputTokens ?? 0,\n outputTokens: this.response.usage.outputTokens ?? 0,\n },\n };\n }\n async complete(_attemptId: string, _leaseId: string, completion: HarnessCompletion): Promise<void> {\n this.completions.push(completion);\n }\n}\n","import type { ChatInput, OracleResponse } from \"@odla-ai/ai\";\n\n/** Current JSONL protocol version exchanged between a runner and an agent container. */\nexport const HARNESS_PROTOCOL_VERSION = 1 as const;\n\n/** Default control-plane route used for model inference requested by coding agents. */\nexport const DEFAULT_AI_ROUTE = \"coding\" as const;\n\n/** Lifecycle state reported for a harness task and its active attempt. */\nexport type HarnessTaskStatus =\n | \"queued\"\n | \"running\"\n | \"cancel_requested\"\n | \"completed\"\n | \"failed\"\n | \"cancelled\";\n\n/** Lifecycle state of one execution attempt for a task. */\nexport type HarnessAttemptStatus = HarnessTaskStatus;\n\n/** Trusted or untrusted participant that emitted a harness event. */\nexport type HarnessActor = \"operator\" | \"runner\" | \"agent\" | \"model\" | \"system\";\n\n/** Resource and isolation limits enforced while an untrusted task executes. */\nexport interface HarnessPolicy {\n network: \"none\";\n timeoutMs: number;\n maxOutputBytes: number;\n maxPatchBytes: number;\n}\n\n/** Immutable task instructions and execution policy delivered with a lease. */\nexport interface HarnessTaskSpec {\n taskId: string;\n attemptId: string;\n title: string;\n prompt: string;\n workspace: string;\n aiRoute: string;\n policy: HarnessPolicy;\n parentAttemptId?: string | null;\n checkpointSeq?: number | null;\n}\n\n/** Time-bound assignment authorizing a runner to execute one task attempt. */\nexport interface HarnessLease {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n leaseId: string;\n generation: number;\n expiresAt: number;\n task: HarnessTaskSpec;\n}\n\n/** Runner-supplied event before the control plane assigns sequence and task metadata. */\nexport interface HarnessEventInput {\n eventId: string;\n kind: string;\n actor: HarnessActor;\n payload: unknown;\n createdAt: number;\n}\n\n/** Persisted, ordered event associated with a specific task attempt. */\nexport interface HarnessEvent extends HarnessEventInput {\n seq: number;\n taskId: string;\n attemptId: string;\n}\n\n/** List-view metadata for a task and its current attempt. */\nexport interface HarnessTaskSummary {\n taskId: string;\n attemptId: string;\n appId: string;\n env: string;\n title: string;\n prompt: string;\n workspace: string;\n aiRoute: string;\n status: HarnessTaskStatus;\n parentAttemptId: string | null;\n checkpointSeq: number | null;\n runnerId: string | null;\n generation: number;\n createdAt: number;\n updatedAt: number;\n}\n\n/** List-view metadata for one attempt, including retry ancestry and runner ownership. */\nexport interface HarnessAttemptSummary {\n attemptId: string;\n taskId: string;\n parentAttemptId: string | null;\n checkpointSeq: number | null;\n status: HarnessAttemptStatus;\n runnerId: string | null;\n generation: number;\n createdAt: number;\n updatedAt: number;\n}\n\n/** Complete task view including attempts, events, result, and generated patch. */\nexport interface HarnessTaskDetail extends HarnessTaskSummary {\n attempts: HarnessAttemptSummary[];\n events: HarnessEvent[];\n patch: string | null;\n result: unknown;\n}\n\n/** Public control-plane view of a registered harness runner. */\nexport interface HarnessRunnerView {\n runnerId: string;\n appId: string;\n env: string;\n name: string;\n createdAt: number;\n lastSeenAt: number | null;\n revokedAt: number | null;\n}\n\n/** Credential-free normalized model request sent from an agent through the runner. */\nexport interface HarnessInferenceRequest {\n requestId: string;\n /** Exact initial, follow-up, or resume command whose budget this call consumes. */\n interactionId?: string;\n call: ChatInput;\n}\n\n/** Normalized model response plus auditable provider, policy, and token metadata. */\nexport interface HarnessInferenceResponse {\n requestId: string;\n response: OracleResponse;\n receipt: {\n provider: string;\n model: string;\n policyVersion: number;\n inputTokens: number;\n outputTokens: number;\n };\n}\n\n/** Bounded, content-free session activity emitted by a Code runtime. Message\n * bodies are projected separately into the app's owner-private odla-db chat. */\nexport type CodeSessionEventData =\n | { type: \"message\"; actor: \"agent\" | \"system\"; body: string }\n | { type: \"diagnostic\"; level: \"error\"; message: string }\n | { type: \"thinking\"; available: true; durationMs: number }\n | { type: \"tool\"; phase: \"started\"; tool: HarnessToolName }\n | { type: \"tool\"; phase: \"completed\"; tool: HarnessToolName; ok: boolean; durationMs: number }\n | {\n type: \"usage\"; provider: string; model: string;\n inputTokens: number; outputTokens: number; durationMs: number;\n interactionId?: string; interactionTokens?: number; interactionMaxTokens?: number;\n }\n | {\n type: \"status\"; status: \"running\" | \"idle\" | \"failed\" | \"checkpointed\";\n durationMs?: number;\n };\n\n/** Registry-assigned cursor and timestamp for an owner-visible Code event. */\nexport type CodeSessionEvent = CodeSessionEventData & {\n eventId: string; sequence: number; createdAt: number;\n};\n\n/** Closed set of effects an agent container may request from its trusted broker. */\nexport type HarnessToolName = \"sandbox.read\" | \"sandbox.apply_patch\" | \"sandbox.run_recipe\";\n/** Correlated, structured tool request emitted by an untrusted agent container. */\nexport interface HarnessToolRequest {\n requestId: string;\n tool: HarnessToolName;\n input: Record<string, unknown>;\n}\n/** Bounded tool result returned to an agent after trusted policy evaluation. */\nexport interface HarnessToolResponse {\n requestId: string;\n ok: boolean;\n content: string;\n details?: Record<string, unknown>;\n}\n\n/** Validated JSONL message emitted by an untrusted agent container. */\nexport type HarnessAgentOutput =\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"event\";\n kind: string;\n payload?: unknown;\n }\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"inference.request\";\n requestId: string;\n call: ChatInput;\n }\n | ({ protocolVersion: typeof HARNESS_PROTOCOL_VERSION; type: \"tool.request\" } & HarnessToolRequest)\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"attempt.complete\";\n status: \"completed\" | \"failed\" | \"cancelled\";\n result?: unknown;\n };\n\n/** JSONL command or inference result written by the trusted runner to an agent. */\nexport type HarnessAgentInput =\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"task.start\";\n task: HarnessTaskSpec;\n }\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"inference.response\";\n requestId: string;\n response: OracleResponse;\n }\n | ({ protocolVersion: typeof HARNESS_PROTOCOL_VERSION; type: \"tool.response\" } & HarnessToolResponse)\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"attempt.cancel\";\n reason: string;\n };\n\n/** Terminal attempt report submitted by a runner to the control plane. */\nexport interface HarnessCompletion {\n status: \"completed\" | \"failed\" | \"cancelled\";\n result?: unknown;\n patch?: string;\n error?: string;\n}\n\n/** Operations a credentialed runner may perform against the harness control plane. */\nexport interface HarnessControlPlane {\n lease(workspaces: string[]): Promise<HarnessLease | null>;\n heartbeat(attemptId: string, leaseId: string): Promise<{ cancelRequested: boolean; expiresAt: number }>;\n appendEvents(attemptId: string, leaseId: string, events: HarnessEventInput[]): Promise<void>;\n infer(attemptId: string, leaseId: string, request: HarnessInferenceRequest): Promise<HarnessInferenceResponse>;\n complete(attemptId: string, leaseId: string, completion: HarnessCompletion): Promise<void>;\n}\n\n/** Trusted inference bridge used to keep model credentials outside agent containers. */\nexport interface HarnessAiConnection {\n infer(request: HarnessInferenceRequest): Promise<HarnessInferenceResponse>;\n}\n\n/** Trusted tool boundary. Implementations must evaluate CaMeL policy before effects. */\nexport interface HarnessToolBroker {\n execute(\n context: { lease: HarnessLease; workspaceDir: string; signal?: AbortSignal },\n request: HarnessToolRequest,\n ): Promise<HarnessToolResponse>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGO,IAAM,2BAA2B;;;ADSjC,SAAS,aAAa,YAAmC,CAAC,GAAiB;AAChF,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,WAAW,KAAK,IAAI,IAAI;AAAA,IACxB,MAAM;AAAA,MACJ,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,SAAS;AAAA,MACT,QAAQ;AAAA,QACN,SAAS;AAAA,QACT,WAAW;AAAA,QACX,gBAAgB;AAAA,QAChB,eAAe;AAAA,MACjB;AAAA,IACF;AAAA,IACA,GAAG;AAAA,EACL;AACF;AAGO,IAAM,0BAAN,MAA6D;AAAA,EACzD,SAAyB,CAAC;AAAA,EAC1B,SAA8B,CAAC;AAAA,EAC/B,cAAmC,CAAC;AAAA,EACpC,oBAA+C,CAAC;AAAA,EACzD,kBAAkB;AAAA,EAClB,WAA2B;AAAA,IACzB,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,MAAM;AAAA,IACN,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,iCAAiC,CAAC;AAAA,IAClE,YAAY;AAAA,IACZ,OAAO,EAAE,aAAa,GAAG,cAAc,EAAE;AAAA,EAC3C;AAAA,EAEA,eAAe,QAAwB;AAAE,SAAK,OAAO,KAAK,GAAG,MAAM;AAAA,EAAG;AAAA,EAEtE,MAAM,MAAM,YAAoD;AAC9D,UAAM,QAAQ,KAAK,OAAO,UAAU,CAAC,cAAc,WAAW,SAAS,UAAU,KAAK,SAAS,CAAC;AAChG,WAAO,QAAQ,IAAI,OAAO,KAAK,OAAO,OAAO,OAAO,CAAC,EAAE,CAAC;AAAA,EAC1D;AAAA,EACA,MAAM,YAAsE;AAC1E,WAAO,EAAE,iBAAiB,KAAK,iBAAiB,WAAW,KAAK,IAAI,IAAI,IAAO;AAAA,EACjF;AAAA,EACA,MAAM,aAAa,YAAoB,UAAkB,QAA4C;AACnG,SAAK,OAAO,KAAK,GAAG,MAAM;AAAA,EAC5B;AAAA,EACA,MAAM,MAAM,YAAoB,UAAkB,SAAqE;AACrH,SAAK,kBAAkB,KAAK,OAAO;AACnC,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,SAAS;AAAA,QACP,UAAU,KAAK,SAAS;AAAA,QACxB,OAAO,KAAK,SAAS;AAAA,QACrB,eAAe;AAAA,QACf,aAAa,KAAK,SAAS,MAAM,eAAe;AAAA,QAChD,cAAc,KAAK,SAAS,MAAM,gBAAgB;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,SAAS,YAAoB,UAAkB,YAA8C;AACjG,SAAK,YAAY,KAAK,UAAU;AAAA,EAClC;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts","../src/types.ts"],"sourcesContent":["import type { OracleResponse } from \"@odla-ai/ai\";\nimport {\n HARNESS_PROTOCOL_VERSION,\n type HarnessCompletion,\n type HarnessControlPlane,\n type HarnessEventInput,\n type HarnessInferenceRequest,\n type HarnessInferenceResponse,\n type HarnessLease,\n} from \"./types\";\n\n/** Create a deterministic, valid lease for harness unit and integration tests. */\nexport function fixtureLease(overrides: Partial<HarnessLease> = {}): HarnessLease {\n return {\n protocolVersion: HARNESS_PROTOCOL_VERSION,\n leaseId: \"lease_fixture\",\n generation: 1,\n expiresAt: Date.now() + 60_000,\n task: {\n taskId: \"task_fixture\",\n attemptId: \"attempt_fixture\",\n title: \"Fixture task\",\n prompt: \"Create HARNESS_RESULT.md\",\n workspace: \"fixture\",\n aiRoute: \"coding\",\n policy: {\n network: \"none\",\n timeoutMs: 30_000,\n maxOutputBytes: 1_000_000,\n maxPatchBytes: 1_000_000,\n },\n },\n ...overrides,\n };\n}\n\n/** In-memory control plane that records runner interactions without network access. */\nexport class FakeHarnessControlPlane implements HarnessControlPlane {\n readonly leases: HarnessLease[] = [];\n readonly events: HarnessEventInput[] = [];\n readonly completions: HarnessCompletion[] = [];\n readonly inferenceRequests: HarnessInferenceRequest[] = [];\n cancelRequested = false;\n response: OracleResponse = {\n id: \"oracle_fixture\",\n model: \"fixture-model\",\n provider: \"openai\",\n role: \"assistant\",\n content: [{ type: \"text\", text: \"deterministic fixture response\" }],\n stopReason: \"end_turn\",\n usage: { inputTokens: 5, outputTokens: 4 },\n };\n\n constructor(...leases: HarnessLease[]) { this.leases.push(...leases); }\n\n async lease(workspaces: string[]): Promise<HarnessLease | null> {\n const index = this.leases.findIndex((candidate) => workspaces.includes(candidate.task.workspace));\n return index < 0 ? null : this.leases.splice(index, 1)[0]!;\n }\n async heartbeat(): Promise<{ cancelRequested: boolean; expiresAt: number }> {\n return { cancelRequested: this.cancelRequested, expiresAt: Date.now() + 60_000 };\n }\n async appendEvents(_attemptId: string, _leaseId: string, events: HarnessEventInput[]): Promise<void> {\n this.events.push(...events);\n }\n async infer(_attemptId: string, _leaseId: string, request: HarnessInferenceRequest): Promise<HarnessInferenceResponse> {\n this.inferenceRequests.push(request);\n return {\n requestId: request.requestId,\n response: this.response,\n receipt: {\n provider: this.response.provider,\n model: this.response.model,\n policyVersion: 1,\n inputTokens: this.response.usage.inputTokens ?? 0,\n outputTokens: this.response.usage.outputTokens ?? 0,\n },\n };\n }\n async complete(_attemptId: string, _leaseId: string, completion: HarnessCompletion): Promise<void> {\n this.completions.push(completion);\n }\n}\n","import type { ChatInput, OracleResponse } from \"@odla-ai/ai\";\n\n/** Current JSONL protocol version exchanged between a runner and an agent container. */\nexport const HARNESS_PROTOCOL_VERSION = 1 as const;\n\n/** Default control-plane route used for model inference requested by coding agents. */\nexport const DEFAULT_AI_ROUTE = \"coding\" as const;\n\n/** Lifecycle state reported for a harness task and its active attempt. */\nexport type HarnessTaskStatus =\n | \"queued\"\n | \"running\"\n | \"cancel_requested\"\n | \"completed\"\n | \"failed\"\n | \"cancelled\";\n\n/** Lifecycle state of one execution attempt for a task. */\nexport type HarnessAttemptStatus = HarnessTaskStatus;\n\n/** Trusted or untrusted participant that emitted a harness event. */\nexport type HarnessActor = \"operator\" | \"runner\" | \"agent\" | \"model\" | \"system\";\n\n/** Resource and isolation limits enforced while an untrusted task executes. */\nexport interface HarnessPolicy {\n network: \"none\";\n timeoutMs: number;\n maxOutputBytes: number;\n maxPatchBytes: number;\n}\n\n/** Immutable task instructions and execution policy delivered with a lease. */\nexport interface HarnessTaskSpec {\n taskId: string;\n attemptId: string;\n title: string;\n prompt: string;\n workspace: string;\n aiRoute: string;\n policy: HarnessPolicy;\n parentAttemptId?: string | null;\n checkpointSeq?: number | null;\n}\n\n/** Time-bound assignment authorizing a runner to execute one task attempt. */\nexport interface HarnessLease {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n leaseId: string;\n generation: number;\n expiresAt: number;\n task: HarnessTaskSpec;\n}\n\n/** Runner-supplied event before the control plane assigns sequence and task metadata. */\nexport interface HarnessEventInput {\n eventId: string;\n kind: string;\n actor: HarnessActor;\n payload: unknown;\n createdAt: number;\n}\n\n/** Persisted, ordered event associated with a specific task attempt. */\nexport interface HarnessEvent extends HarnessEventInput {\n seq: number;\n taskId: string;\n attemptId: string;\n}\n\n/** List-view metadata for a task and its current attempt. */\nexport interface HarnessTaskSummary {\n taskId: string;\n attemptId: string;\n appId: string;\n env: string;\n title: string;\n prompt: string;\n workspace: string;\n aiRoute: string;\n status: HarnessTaskStatus;\n parentAttemptId: string | null;\n checkpointSeq: number | null;\n runnerId: string | null;\n generation: number;\n createdAt: number;\n updatedAt: number;\n}\n\n/** List-view metadata for one attempt, including retry ancestry and runner ownership. */\nexport interface HarnessAttemptSummary {\n attemptId: string;\n taskId: string;\n parentAttemptId: string | null;\n checkpointSeq: number | null;\n status: HarnessAttemptStatus;\n runnerId: string | null;\n generation: number;\n createdAt: number;\n updatedAt: number;\n}\n\n/** Complete task view including attempts, events, result, and generated patch. */\nexport interface HarnessTaskDetail extends HarnessTaskSummary {\n attempts: HarnessAttemptSummary[];\n events: HarnessEvent[];\n patch: string | null;\n result: unknown;\n}\n\n/** Public control-plane view of a registered harness runner. */\nexport interface HarnessRunnerView {\n runnerId: string;\n appId: string;\n env: string;\n name: string;\n createdAt: number;\n lastSeenAt: number | null;\n revokedAt: number | null;\n}\n\n/** Credential-free normalized model request sent from an agent through the runner. */\nexport interface HarnessInferenceRequest {\n requestId: string;\n /** Exact initial, follow-up, or resume command whose budget this call consumes. */\n interactionId?: string;\n call: ChatInput;\n}\n\n/** Normalized model response plus auditable provider, policy, and token metadata. */\nexport interface HarnessInferenceResponse {\n requestId: string;\n response: OracleResponse;\n receipt: {\n provider: string;\n model: string;\n policyVersion: number;\n inputTokens: number;\n outputTokens: number;\n };\n}\n\n/** Bounded, content-free session activity emitted by a Code runtime. Message\n * bodies are projected separately into the app's owner-private odla-db chat. */\nexport type CodeSessionEventData =\n | { type: \"message\"; actor: \"agent\" | \"system\"; body: string }\n | { type: \"diagnostic\"; level: \"error\"; message: string }\n | { type: \"thinking\"; available: true; durationMs: number }\n | { type: \"tool\"; phase: \"started\"; tool: HarnessToolName }\n | { type: \"tool\"; phase: \"completed\"; tool: HarnessToolName; ok: boolean; durationMs: number }\n | {\n type: \"usage\"; provider: string; model: string;\n inputTokens: number; outputTokens: number; durationMs: number;\n interactionId?: string; interactionTokens?: number; interactionMaxTokens?: number;\n }\n | {\n type: \"status\"; status: \"running\" | \"idle\" | \"failed\" | \"checkpointed\";\n durationMs?: number;\n };\n\n/** Registry-assigned cursor and timestamp for an owner-visible Code event. */\nexport type CodeSessionEvent = CodeSessionEventData & {\n eventId: string; sequence: number; createdAt: number;\n};\n\n/** Closed set of effects an agent container may request from its trusted broker. */\nexport type HarnessToolName =\n | \"sandbox.read\"\n | \"sandbox.list\"\n | \"sandbox.search\"\n | \"sandbox.overview\"\n | \"sandbox.where_is\"\n | \"sandbox.who_imports\"\n | \"sandbox.who_touches\"\n | \"sandbox.apply_patch\"\n | \"sandbox.run_recipe\";\n/** Correlated, structured tool request emitted by an untrusted agent container. */\nexport interface HarnessToolRequest {\n requestId: string;\n tool: HarnessToolName;\n input: Record<string, unknown>;\n}\n/** Bounded tool result returned to an agent after trusted policy evaluation. */\nexport interface HarnessToolResponse {\n requestId: string;\n ok: boolean;\n content: string;\n details?: Record<string, unknown>;\n}\n\n/** Validated JSONL message emitted by an untrusted agent container. */\nexport type HarnessAgentOutput =\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"event\";\n kind: string;\n payload?: unknown;\n }\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"inference.request\";\n requestId: string;\n call: ChatInput;\n }\n | ({ protocolVersion: typeof HARNESS_PROTOCOL_VERSION; type: \"tool.request\" } & HarnessToolRequest)\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"attempt.complete\";\n status: \"completed\" | \"failed\" | \"cancelled\";\n result?: unknown;\n };\n\n/** JSONL command or inference result written by the trusted runner to an agent. */\nexport type HarnessAgentInput =\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"task.start\";\n task: HarnessTaskSpec;\n }\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"inference.response\";\n requestId: string;\n response: OracleResponse;\n }\n | ({ protocolVersion: typeof HARNESS_PROTOCOL_VERSION; type: \"tool.response\" } & HarnessToolResponse)\n | {\n protocolVersion: typeof HARNESS_PROTOCOL_VERSION;\n type: \"attempt.cancel\";\n reason: string;\n };\n\n/** Terminal attempt report submitted by a runner to the control plane. */\nexport interface HarnessCompletion {\n status: \"completed\" | \"failed\" | \"cancelled\";\n result?: unknown;\n patch?: string;\n error?: string;\n}\n\n/** Operations a credentialed runner may perform against the harness control plane. */\nexport interface HarnessControlPlane {\n lease(workspaces: string[]): Promise<HarnessLease | null>;\n heartbeat(attemptId: string, leaseId: string): Promise<{ cancelRequested: boolean; expiresAt: number }>;\n appendEvents(attemptId: string, leaseId: string, events: HarnessEventInput[]): Promise<void>;\n infer(attemptId: string, leaseId: string, request: HarnessInferenceRequest): Promise<HarnessInferenceResponse>;\n complete(attemptId: string, leaseId: string, completion: HarnessCompletion): Promise<void>;\n}\n\n/** Trusted inference bridge used to keep model credentials outside agent containers. */\nexport interface HarnessAiConnection {\n infer(request: HarnessInferenceRequest): Promise<HarnessInferenceResponse>;\n}\n\n/** Trusted tool boundary. Implementations must evaluate CaMeL policy before effects. */\nexport interface HarnessToolBroker {\n execute(\n context: { lease: HarnessLease; workspaceDir: string; signal?: AbortSignal },\n request: HarnessToolRequest,\n ): Promise<HarnessToolResponse>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGO,IAAM,2BAA2B;;;ADSjC,SAAS,aAAa,YAAmC,CAAC,GAAiB;AAChF,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,WAAW,KAAK,IAAI,IAAI;AAAA,IACxB,MAAM;AAAA,MACJ,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,SAAS;AAAA,MACT,QAAQ;AAAA,QACN,SAAS;AAAA,QACT,WAAW;AAAA,QACX,gBAAgB;AAAA,QAChB,eAAe;AAAA,MACjB;AAAA,IACF;AAAA,IACA,GAAG;AAAA,EACL;AACF;AAGO,IAAM,0BAAN,MAA6D;AAAA,EACzD,SAAyB,CAAC;AAAA,EAC1B,SAA8B,CAAC;AAAA,EAC/B,cAAmC,CAAC;AAAA,EACpC,oBAA+C,CAAC;AAAA,EACzD,kBAAkB;AAAA,EAClB,WAA2B;AAAA,IACzB,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,UAAU;AAAA,IACV,MAAM;AAAA,IACN,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,iCAAiC,CAAC;AAAA,IAClE,YAAY;AAAA,IACZ,OAAO,EAAE,aAAa,GAAG,cAAc,EAAE;AAAA,EAC3C;AAAA,EAEA,eAAe,QAAwB;AAAE,SAAK,OAAO,KAAK,GAAG,MAAM;AAAA,EAAG;AAAA,EAEtE,MAAM,MAAM,YAAoD;AAC9D,UAAM,QAAQ,KAAK,OAAO,UAAU,CAAC,cAAc,WAAW,SAAS,UAAU,KAAK,SAAS,CAAC;AAChG,WAAO,QAAQ,IAAI,OAAO,KAAK,OAAO,OAAO,OAAO,CAAC,EAAE,CAAC;AAAA,EAC1D;AAAA,EACA,MAAM,YAAsE;AAC1E,WAAO,EAAE,iBAAiB,KAAK,iBAAiB,WAAW,KAAK,IAAI,IAAI,IAAO;AAAA,EACjF;AAAA,EACA,MAAM,aAAa,YAAoB,UAAkB,QAA4C;AACnG,SAAK,OAAO,KAAK,GAAG,MAAM;AAAA,EAC5B;AAAA,EACA,MAAM,MAAM,YAAoB,UAAkB,SAAqE;AACrH,SAAK,kBAAkB,KAAK,OAAO;AACnC,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,SAAS;AAAA,QACP,UAAU,KAAK,SAAS;AAAA,QACxB,OAAO,KAAK,SAAS;AAAA,QACrB,eAAe;AAAA,QACf,aAAa,KAAK,SAAS,MAAM,eAAe;AAAA,QAChD,cAAc,KAAK,SAAS,MAAM,gBAAgB;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,SAAS,YAAoB,UAAkB,YAA8C;AACjG,SAAK,YAAY,KAAK,UAAU;AAAA,EAClC;AACF;","names":[]}
|
package/dist/testing.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OracleResponse } from '@odla-ai/ai';
|
|
2
|
-
import { H as HarnessControlPlane, a as HarnessLease, b as HarnessEventInput, c as HarnessCompletion, d as HarnessInferenceRequest, e as HarnessInferenceResponse } from './types-
|
|
2
|
+
import { H as HarnessControlPlane, a as HarnessLease, b as HarnessEventInput, c as HarnessCompletion, d as HarnessInferenceRequest, e as HarnessInferenceResponse } from './types-0_H9TKkO.cjs';
|
|
3
3
|
|
|
4
4
|
/** Create a deterministic, valid lease for harness unit and integration tests. */
|
|
5
5
|
declare function fixtureLease(overrides?: Partial<HarnessLease>): HarnessLease;
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OracleResponse } from '@odla-ai/ai';
|
|
2
|
-
import { H as HarnessControlPlane, a as HarnessLease, b as HarnessEventInput, c as HarnessCompletion, d as HarnessInferenceRequest, e as HarnessInferenceResponse } from './types-
|
|
2
|
+
import { H as HarnessControlPlane, a as HarnessLease, b as HarnessEventInput, c as HarnessCompletion, d as HarnessInferenceRequest, e as HarnessInferenceResponse } from './types-0_H9TKkO.js';
|
|
3
3
|
|
|
4
4
|
/** Create a deterministic, valid lease for harness unit and integration tests. */
|
|
5
5
|
declare function fixtureLease(overrides?: Partial<HarnessLease>): HarnessLease;
|
package/dist/testing.js
CHANGED
|
@@ -163,7 +163,7 @@ type CodeSessionEvent = CodeSessionEventData & {
|
|
|
163
163
|
createdAt: number;
|
|
164
164
|
};
|
|
165
165
|
/** Closed set of effects an agent container may request from its trusted broker. */
|
|
166
|
-
type HarnessToolName = "sandbox.read" | "sandbox.apply_patch" | "sandbox.run_recipe";
|
|
166
|
+
type HarnessToolName = "sandbox.read" | "sandbox.list" | "sandbox.search" | "sandbox.overview" | "sandbox.where_is" | "sandbox.who_imports" | "sandbox.who_touches" | "sandbox.apply_patch" | "sandbox.run_recipe";
|
|
167
167
|
/** Correlated, structured tool request emitted by an untrusted agent container. */
|
|
168
168
|
interface HarnessToolRequest {
|
|
169
169
|
requestId: string;
|
|
@@ -163,7 +163,7 @@ type CodeSessionEvent = CodeSessionEventData & {
|
|
|
163
163
|
createdAt: number;
|
|
164
164
|
};
|
|
165
165
|
/** Closed set of effects an agent container may request from its trusted broker. */
|
|
166
|
-
type HarnessToolName = "sandbox.read" | "sandbox.apply_patch" | "sandbox.run_recipe";
|
|
166
|
+
type HarnessToolName = "sandbox.read" | "sandbox.list" | "sandbox.search" | "sandbox.overview" | "sandbox.where_is" | "sandbox.who_imports" | "sandbox.who_touches" | "sandbox.apply_patch" | "sandbox.run_recipe";
|
|
167
167
|
/** Correlated, structured tool request emitted by an untrusted agent container. */
|
|
168
168
|
interface HarnessToolRequest {
|
|
169
169
|
requestId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@odla-ai/harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Safe, inspectable coding-task protocol and credentialless container runner for odla Studio.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://odla.ai/docs/packages/harness",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"bin": {
|
|
34
|
-
"odla-harness": "
|
|
35
|
-
"odla-code-runtime": "
|
|
34
|
+
"odla-harness": "dist/cli.js",
|
|
35
|
+
"odla-code-runtime": "dist/code-runtime-cli.js"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"dist",
|
|
@@ -61,21 +61,15 @@
|
|
|
61
61
|
"typecheck": "tsc --noEmit",
|
|
62
62
|
"test": "vitest run",
|
|
63
63
|
"test:container": "ODLA_HARNESS_CONTAINER_TEST=1 vitest run test/container.integration.test.ts",
|
|
64
|
-
"prepublishOnly": "npm run test && npm run typecheck && npm run build"
|
|
65
|
-
|
|
66
|
-
"peerDependencies": {
|
|
67
|
-
"@odla-ai/ai": ">=0.4.0 <1.0.0"
|
|
68
|
-
},
|
|
69
|
-
"peerDependenciesMeta": {
|
|
70
|
-
"@odla-ai/ai": {
|
|
71
|
-
"optional": true
|
|
72
|
-
}
|
|
64
|
+
"prepublishOnly": "npm run test && npm run typecheck && npm run build",
|
|
65
|
+
"test:live": "ODLA_CODE_LIVE_TEST=1 vitest run test/code-runtime-live.integration.test.ts"
|
|
73
66
|
},
|
|
74
67
|
"dependencies": {
|
|
75
|
-
"@odla-ai/
|
|
68
|
+
"@odla-ai/ai": "*",
|
|
69
|
+
"@odla-ai/camel": "*",
|
|
70
|
+
"@odla-ai/graph": "*"
|
|
76
71
|
},
|
|
77
72
|
"devDependencies": {
|
|
78
|
-
"@odla-ai/ai": "*",
|
|
79
73
|
"@types/node": "^26.1.0",
|
|
80
74
|
"tsup": "^8.5.1",
|
|
81
75
|
"typescript": "^6.0.3",
|