agent-hitch 0.1.0 → 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/README.md +240 -126
- package/README.zh-CN.md +330 -0
- package/dist/bin/hitch.js +9 -0
- package/dist/bin/hitch.js.map +1 -0
- package/dist/scripts/check-release.js +25 -0
- package/dist/scripts/check-release.js.map +1 -0
- package/dist/scripts/check-syntax.js +32 -0
- package/dist/scripts/check-syntax.js.map +1 -0
- package/dist/src/adapters.js +461 -0
- package/dist/src/adapters.js.map +1 -0
- package/dist/src/artifacts.js +1081 -0
- package/dist/src/artifacts.js.map +1 -0
- package/dist/src/cli.js +699 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.js +53 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/controller-runtime/hash.js +353 -0
- package/dist/src/controller-runtime/hash.js.map +1 -0
- package/dist/src/controller-runtime/store.js +323 -0
- package/dist/src/controller-runtime/store.js.map +1 -0
- package/dist/src/daemon.js +461 -0
- package/dist/src/daemon.js.map +1 -0
- package/dist/src/domain/types.js +10 -0
- package/dist/src/domain/types.js.map +1 -0
- package/dist/src/domain/validate.js +240 -0
- package/dist/src/domain/validate.js.map +1 -0
- package/dist/src/engine.js +465 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/errors.js +14 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/eval-tools.js +318 -0
- package/dist/src/eval-tools.js.map +1 -0
- package/dist/src/evals.js +313 -0
- package/dist/src/evals.js.map +1 -0
- package/dist/src/events.js +80 -0
- package/dist/src/events.js.map +1 -0
- package/dist/src/feedback/service.js +315 -0
- package/dist/src/feedback/service.js.map +1 -0
- package/dist/src/fs.js +45 -0
- package/dist/src/fs.js.map +1 -0
- package/dist/src/harbor-backend.js +280 -0
- package/dist/src/harbor-backend.js.map +1 -0
- package/dist/src/harness-reference.js +83 -0
- package/dist/src/harness-reference.js.map +1 -0
- package/dist/src/line-stream.js +20 -0
- package/dist/src/line-stream.js.map +1 -0
- package/dist/src/locks.js +38 -0
- package/dist/src/locks.js.map +1 -0
- package/dist/src/package-root.js +40 -0
- package/dist/src/package-root.js.map +1 -0
- package/dist/src/process.js +50 -0
- package/dist/src/process.js.map +1 -0
- package/dist/src/registry.js +81 -0
- package/dist/src/registry.js.map +1 -0
- package/dist/src/scheduler.js +162 -0
- package/dist/src/scheduler.js.map +1 -0
- package/dist/src/trajectories/contract.js +17 -0
- package/dist/src/trajectories/contract.js.map +1 -0
- package/dist/src/trajectories/format.js +127 -0
- package/dist/src/trajectories/format.js.map +1 -0
- package/dist/src/trajectories/projector.js +385 -0
- package/dist/src/trajectories/projector.js.map +1 -0
- package/dist/src/trajectories/store.js +281 -0
- package/dist/src/trajectories/store.js.map +1 -0
- package/dist/src/workspaces.js +858 -0
- package/dist/src/workspaces.js.map +1 -0
- package/docs/schemas/controller-runtime-manifest.schema.json +57 -0
- package/docs/schemas/controller-runtime-ref.schema.json +16 -0
- package/docs/schemas/message-feedback.schema.json +37 -0
- package/docs/schemas/trajectory-ref.schema.json +30 -0
- package/integrations/harbor/hitch_harbor_agent.py +176 -4
- package/package.json +19 -11
- package/bin/hitch.js +0 -9
- package/src/adapters.js +0 -361
- package/src/artifacts.js +0 -948
- package/src/cli.js +0 -505
- package/src/config.js +0 -51
- package/src/daemon.js +0 -416
- package/src/engine.js +0 -386
- package/src/errors.js +0 -12
- package/src/eval-tools.js +0 -331
- package/src/evals.js +0 -276
- package/src/events.js +0 -69
- package/src/fs.js +0 -43
- package/src/harbor-backend.js +0 -285
- package/src/harness-reference.js +0 -78
- package/src/line-stream.js +0 -17
- package/src/locks.js +0 -33
- package/src/process.js +0 -49
- package/src/registry.js +0 -84
- package/src/scheduler.js +0 -156
- package/src/workspaces.js +0 -893
package/src/engine.js
DELETED
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { stat } from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
|
-
import { getAdapter, normalizeRequest } from "./adapters.js";
|
|
6
|
-
import { EventSink } from "./events.js";
|
|
7
|
-
import { HitchError, invalidInput } from "./errors.js";
|
|
8
|
-
import { atomicWriteJSON, ensureDir } from "./fs.js";
|
|
9
|
-
import { consumeLines } from "./line-stream.js";
|
|
10
|
-
import { terminateProcess } from "./process.js";
|
|
11
|
-
import { SCHEMA_VERSION } from "./config.js";
|
|
12
|
-
import { parseHarnessReference } from "./harness-reference.js";
|
|
13
|
-
import { prepareHarness, resolveHarness } from "./artifacts.js";
|
|
14
|
-
import {
|
|
15
|
-
abandonPlannedWorkspace,
|
|
16
|
-
finalizeWorkspace,
|
|
17
|
-
inspectWorkspace,
|
|
18
|
-
markWorkspaceRunning,
|
|
19
|
-
markWorkspaceFinalizationFailed,
|
|
20
|
-
planWorkspace,
|
|
21
|
-
prepareWorkspace,
|
|
22
|
-
WORKSPACE_MODES,
|
|
23
|
-
workspaceManifestFields,
|
|
24
|
-
workspaceRecordPath,
|
|
25
|
-
} from "./workspaces.js";
|
|
26
|
-
|
|
27
|
-
export async function validateRunRequest(input) {
|
|
28
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
29
|
-
throw invalidInput("run request must be a JSON object");
|
|
30
|
-
}
|
|
31
|
-
const allowedFields = new Set(["schema_version", "harness_ref", "agent", "model", "cwd", "workspace_mode", "prompt", "timeout_ms", "agent_args"]);
|
|
32
|
-
const unexpectedField = Object.keys(input).find((field) => !allowedFields.has(field));
|
|
33
|
-
if (unexpectedField) throw invalidInput(`unknown run request field: ${unexpectedField}`);
|
|
34
|
-
if (input.schema_version !== undefined && input.schema_version !== SCHEMA_VERSION) {
|
|
35
|
-
throw invalidInput(`unsupported schema_version: ${input.schema_version}`);
|
|
36
|
-
}
|
|
37
|
-
if (input.harness_ref !== undefined && input.agent !== undefined) {
|
|
38
|
-
throw invalidInput("use only one of harness_ref and the legacy agent field");
|
|
39
|
-
}
|
|
40
|
-
if (typeof input.agent === "string" && input.agent.includes("@")) {
|
|
41
|
-
throw invalidInput("the legacy agent field accepts only a harness name; use harness_ref for revision selection");
|
|
42
|
-
}
|
|
43
|
-
const referenceValue = input.harness_ref ?? input.agent;
|
|
44
|
-
if (typeof referenceValue !== "string" || !referenceValue.trim()) {
|
|
45
|
-
throw invalidInput("harness_ref must be a non-empty string");
|
|
46
|
-
}
|
|
47
|
-
if (typeof input.prompt !== "string" || !input.prompt.trim()) throw invalidInput("prompt must be a non-empty string");
|
|
48
|
-
if (input.cwd !== undefined && (typeof input.cwd !== "string" || !input.cwd.trim())) {
|
|
49
|
-
throw invalidInput("cwd must be a non-empty string");
|
|
50
|
-
}
|
|
51
|
-
if (input.workspace_mode !== undefined && !WORKSPACE_MODES.has(input.workspace_mode)) {
|
|
52
|
-
throw invalidInput(`workspace_mode must be one of: ${[...WORKSPACE_MODES].join(", ")}`);
|
|
53
|
-
}
|
|
54
|
-
if (input.model !== undefined && typeof input.model !== "string") throw invalidInput("model must be a string");
|
|
55
|
-
if (input.timeout_ms !== undefined && (typeof input.timeout_ms !== "number" || !Number.isFinite(input.timeout_ms) || input.timeout_ms < 0)) {
|
|
56
|
-
throw invalidInput("timeout_ms must be a non-negative number");
|
|
57
|
-
}
|
|
58
|
-
if (input.agent_args !== undefined && (!Array.isArray(input.agent_args) || input.agent_args.some((arg) => typeof arg !== "string"))) {
|
|
59
|
-
throw invalidInput("agent_args must be an array of strings");
|
|
60
|
-
}
|
|
61
|
-
const request = normalizeRequest(input);
|
|
62
|
-
const reference = parseHarnessReference(request.harness_ref);
|
|
63
|
-
getAdapter(reference.harness_id);
|
|
64
|
-
let workspace;
|
|
65
|
-
try {
|
|
66
|
-
workspace = await stat(request.cwd);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
throw invalidInput(`workspace does not exist: ${request.cwd}`, { cause: error });
|
|
69
|
-
}
|
|
70
|
-
if (!workspace.isDirectory()) throw invalidInput(`workspace is not a directory: ${request.cwd}`);
|
|
71
|
-
return request;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function newRunId() {
|
|
75
|
-
return `run_${randomUUID().replaceAll("-", "")}`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function buildManifest(runId, request, workspacePlan) {
|
|
79
|
-
const reference = parseHarnessReference(request.harness_ref);
|
|
80
|
-
return {
|
|
81
|
-
schema_version: SCHEMA_VERSION,
|
|
82
|
-
run_id: runId,
|
|
83
|
-
status: "queued",
|
|
84
|
-
harness_id: reference.harness_id,
|
|
85
|
-
requested_harness_ref: reference.raw,
|
|
86
|
-
canonical_harness_ref: reference.canonical,
|
|
87
|
-
agent: reference.harness_id,
|
|
88
|
-
requested_model: request.model || null,
|
|
89
|
-
effective_model: request.model || null,
|
|
90
|
-
workspace: request.cwd,
|
|
91
|
-
workspace_mode: request.workspace_mode,
|
|
92
|
-
...workspaceManifestFields(workspacePlan),
|
|
93
|
-
timeout_ms: request.timeout_ms,
|
|
94
|
-
agent_args_count: request.agent_args.length,
|
|
95
|
-
agent_args_sha256: request.agent_args.length > 0
|
|
96
|
-
? createHash("sha256").update(JSON.stringify(request.agent_args)).digest("hex")
|
|
97
|
-
: null,
|
|
98
|
-
created_at: new Date().toISOString(),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export async function executeRun({ runId, request, runsRoot, root = path.dirname(runsRoot), resolvedRevision, workspacePlan, onEvent, onProcess, signal }) {
|
|
103
|
-
const normalized = await validateRunRequest(request);
|
|
104
|
-
workspacePlan ||= await planWorkspace({ runId, sourceCwd: normalized.cwd, mode: normalized.workspace_mode, root });
|
|
105
|
-
const runDirectory = path.join(runsRoot, runId);
|
|
106
|
-
await ensureDir(runDirectory);
|
|
107
|
-
const workspacePath = workspaceRecordPath(root, runId);
|
|
108
|
-
await atomicWriteJSON(workspacePath, workspacePlan);
|
|
109
|
-
const manifestPath = path.join(runDirectory, "manifest.json");
|
|
110
|
-
const resultPath = path.join(runDirectory, "result.json");
|
|
111
|
-
const existingManifest = buildManifest(runId, normalized, workspacePlan);
|
|
112
|
-
let manifest = existingManifest;
|
|
113
|
-
const startedAt = new Date();
|
|
114
|
-
await atomicWriteJSON(path.join(runDirectory, "request.json"), {
|
|
115
|
-
schema_version: SCHEMA_VERSION,
|
|
116
|
-
...normalized,
|
|
117
|
-
});
|
|
118
|
-
await atomicWriteJSON(manifestPath, existingManifest);
|
|
119
|
-
|
|
120
|
-
let sink;
|
|
121
|
-
let sinkOpened = false;
|
|
122
|
-
let child;
|
|
123
|
-
let timeout;
|
|
124
|
-
let timedOut = false;
|
|
125
|
-
let cancelled = false;
|
|
126
|
-
const messageParts = [];
|
|
127
|
-
let finalMessage;
|
|
128
|
-
let abortHandler;
|
|
129
|
-
let result;
|
|
130
|
-
let stage = "event_setup";
|
|
131
|
-
let resolution = resolvedRevision;
|
|
132
|
-
let artifact;
|
|
133
|
-
let workspaceLease;
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
sink = new EventSink(runDirectory, runId, onEvent);
|
|
137
|
-
await sink.open();
|
|
138
|
-
sinkOpened = true;
|
|
139
|
-
|
|
140
|
-
stage = "resolution";
|
|
141
|
-
const reference = parseHarnessReference(normalized.harness_ref);
|
|
142
|
-
resolution ||= await resolveHarness(reference, { root });
|
|
143
|
-
await atomicWriteJSON(path.join(runDirectory, "resolution.json"), resolution);
|
|
144
|
-
manifest = {
|
|
145
|
-
...existingManifest,
|
|
146
|
-
status: "preparing",
|
|
147
|
-
resolved_revision: resolution,
|
|
148
|
-
revision_identity: resolution.identity,
|
|
149
|
-
};
|
|
150
|
-
await atomicWriteJSON(manifestPath, manifest);
|
|
151
|
-
|
|
152
|
-
stage = "preparation";
|
|
153
|
-
artifact = await prepareHarness(resolution, { root, signal });
|
|
154
|
-
|
|
155
|
-
stage = "workspace_preparation";
|
|
156
|
-
workspaceLease = await prepareWorkspace(workspacePlan, { recordPath: workspacePath, signal });
|
|
157
|
-
|
|
158
|
-
manifest = {
|
|
159
|
-
...manifest,
|
|
160
|
-
status: "running",
|
|
161
|
-
artifact_id: artifact.artifact_id,
|
|
162
|
-
artifact_source_type: artifact.source_type,
|
|
163
|
-
executable: artifact.executable,
|
|
164
|
-
artifact_entrypoint: artifact.entrypoint_args?.[0] || artifact.executable,
|
|
165
|
-
agent_version: artifact.observed_version || resolution.revision.version || null,
|
|
166
|
-
agent_identity: resolution.identity,
|
|
167
|
-
...workspaceManifestFields(workspaceLease),
|
|
168
|
-
started_at: startedAt.toISOString(),
|
|
169
|
-
};
|
|
170
|
-
await atomicWriteJSON(manifestPath, manifest);
|
|
171
|
-
|
|
172
|
-
sink.emit({
|
|
173
|
-
type: "workspace.ready",
|
|
174
|
-
mode: workspaceLease.mode,
|
|
175
|
-
workspace: workspaceLease.execution_workspace,
|
|
176
|
-
retained: workspaceLease.retained,
|
|
177
|
-
});
|
|
178
|
-
sink.emit({
|
|
179
|
-
type: "run.started",
|
|
180
|
-
harness: reference.harness_id,
|
|
181
|
-
agent: reference.harness_id,
|
|
182
|
-
model: normalized.model || null,
|
|
183
|
-
revision_identity: resolution.identity,
|
|
184
|
-
artifact_id: artifact.artifact_id,
|
|
185
|
-
workspace_mode: workspaceLease.mode,
|
|
186
|
-
workspace: workspaceLease.execution_workspace,
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
stage = "adapter_setup";
|
|
190
|
-
const adapter = getAdapter(reference.harness_id);
|
|
191
|
-
const adapterState = {};
|
|
192
|
-
const executionRequest = { ...normalized, cwd: workspaceLease.execution_workspace };
|
|
193
|
-
const specification = adapter.process(executionRequest, artifact.executable, {
|
|
194
|
-
observed_version: artifact.observed_version,
|
|
195
|
-
resolution,
|
|
196
|
-
});
|
|
197
|
-
if (artifact.entrypoint_args?.length) specification.args.unshift(...artifact.entrypoint_args);
|
|
198
|
-
if (signal?.aborted) {
|
|
199
|
-
result = failureResult(runId, startedAt, "cancelled", "agent run cancelled before launch", 9);
|
|
200
|
-
sink.emit({ type: "run.failed", status: "cancelled", error: result.error });
|
|
201
|
-
} else {
|
|
202
|
-
stage = "launch";
|
|
203
|
-
workspaceLease = await markWorkspaceRunning(workspaceLease, { recordPath: workspacePath });
|
|
204
|
-
const childEnvironment = { ...process.env, PWD: workspaceLease.execution_workspace };
|
|
205
|
-
delete childEnvironment.OLDPWD;
|
|
206
|
-
child = spawn(specification.executable, specification.args, {
|
|
207
|
-
cwd: workspaceLease.execution_workspace,
|
|
208
|
-
env: childEnvironment,
|
|
209
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
210
|
-
detached: process.platform !== "win32",
|
|
211
|
-
windowsHide: true,
|
|
212
|
-
});
|
|
213
|
-
child.stdin.on("error", () => { /* Process-level error/exit determines the typed result. */ });
|
|
214
|
-
onProcess?.({ child });
|
|
215
|
-
abortHandler = () => {
|
|
216
|
-
cancelled = true;
|
|
217
|
-
terminateProcess(child).catch(() => {});
|
|
218
|
-
};
|
|
219
|
-
signal?.addEventListener("abort", abortHandler, { once: true });
|
|
220
|
-
|
|
221
|
-
consumeLines(child.stdout, (line) => {
|
|
222
|
-
sink.writeStdout(line);
|
|
223
|
-
let native;
|
|
224
|
-
try {
|
|
225
|
-
native = JSON.parse(line);
|
|
226
|
-
} catch {
|
|
227
|
-
sink.emit({ type: "process.stdout", text: line });
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
for (const event of adapter.translate(native, adapterState)) {
|
|
231
|
-
if (event.type === "message.delta" && event.text) messageParts.push(event.text);
|
|
232
|
-
if (event.type === "message.completed" && typeof event.text === "string") finalMessage = event.text;
|
|
233
|
-
sink.emit(event);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
consumeLines(child.stderr, (line) => {
|
|
237
|
-
sink.writeStderr(line);
|
|
238
|
-
sink.emit({ type: "process.stderr", text: line });
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
await new Promise((resolve, reject) => {
|
|
242
|
-
child.once("spawn", resolve);
|
|
243
|
-
child.once("error", reject);
|
|
244
|
-
});
|
|
245
|
-
stage = "running";
|
|
246
|
-
const close = new Promise((resolve, reject) => {
|
|
247
|
-
child.once("error", reject);
|
|
248
|
-
child.once("close", (code, processSignal) => resolve({ code, signal: processSignal }));
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
if (normalized.timeout_ms > 0) {
|
|
252
|
-
timeout = setTimeout(async () => {
|
|
253
|
-
timedOut = true;
|
|
254
|
-
await terminateProcess(child).catch(() => {});
|
|
255
|
-
}, normalized.timeout_ms);
|
|
256
|
-
timeout.unref?.();
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
child.stdin.end(specification.input);
|
|
260
|
-
const exit = await close;
|
|
261
|
-
if (timeout) clearTimeout(timeout);
|
|
262
|
-
|
|
263
|
-
if (timedOut) {
|
|
264
|
-
result = failureResult(runId, startedAt, "timed_out", "agent run timed out", 8, exit);
|
|
265
|
-
sink.emit({ type: "run.failed", status: "timed_out", error: result.error });
|
|
266
|
-
} else if (cancelled) {
|
|
267
|
-
result = failureResult(runId, startedAt, "cancelled", "agent run cancelled", 9, exit);
|
|
268
|
-
sink.emit({ type: "run.failed", status: "cancelled", error: result.error });
|
|
269
|
-
} else if (exit.code !== 0) {
|
|
270
|
-
const message = `agent exited with code ${exit.code ?? "null"}${exit.signal ? ` (${exit.signal})` : ""}`;
|
|
271
|
-
result = failureResult(runId, startedAt, "agent_failed", message, 7, exit);
|
|
272
|
-
sink.emit({ type: "run.failed", status: "failed", error: result.error });
|
|
273
|
-
} else {
|
|
274
|
-
result = {
|
|
275
|
-
schema_version: SCHEMA_VERSION,
|
|
276
|
-
run_id: runId,
|
|
277
|
-
status: "succeeded",
|
|
278
|
-
exit_code: 0,
|
|
279
|
-
process_exit_code: exit.code,
|
|
280
|
-
output: finalMessage ?? messageParts.join(""),
|
|
281
|
-
harness_id: reference.harness_id,
|
|
282
|
-
revision_identity: resolution.identity,
|
|
283
|
-
artifact_id: artifact.artifact_id,
|
|
284
|
-
started_at: startedAt.toISOString(),
|
|
285
|
-
completed_at: new Date().toISOString(),
|
|
286
|
-
};
|
|
287
|
-
sink.emit({ type: "run.completed", status: "succeeded", exit_code: 0 });
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} catch (error) {
|
|
291
|
-
if (timeout) clearTimeout(timeout);
|
|
292
|
-
if (child) await terminateProcess(child).catch(() => {});
|
|
293
|
-
const launchStage = stage === "adapter_setup" || stage === "launch";
|
|
294
|
-
const code = error instanceof HitchError ? error.code : launchStage ? "launch_failed" : "internal_error";
|
|
295
|
-
const exitCode = error instanceof HitchError ? error.exitCode : launchStage ? 6 : 12;
|
|
296
|
-
result = failureResult(runId, startedAt, code, error?.message || String(error), exitCode);
|
|
297
|
-
if (sinkOpened) {
|
|
298
|
-
try { sink.emit({ type: "run.failed", status: result.status, error: result.error }); } catch { /* Finalization below remains authoritative. */ }
|
|
299
|
-
}
|
|
300
|
-
} finally {
|
|
301
|
-
if (abortHandler) signal?.removeEventListener("abort", abortHandler);
|
|
302
|
-
onProcess?.(null);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (workspaceLease) {
|
|
306
|
-
try {
|
|
307
|
-
stage = "workspace_finalization";
|
|
308
|
-
workspaceLease = await finalizeWorkspace(workspaceLease, { recordPath: workspacePath });
|
|
309
|
-
manifest = { ...manifest, ...workspaceManifestFields(workspaceLease) };
|
|
310
|
-
result.workspace = {
|
|
311
|
-
mode: workspaceLease.mode,
|
|
312
|
-
source: workspaceLease.source_workspace,
|
|
313
|
-
execution: workspaceLease.execution_workspace,
|
|
314
|
-
retained: workspaceLease.retained,
|
|
315
|
-
changed: workspaceLease.changed,
|
|
316
|
-
};
|
|
317
|
-
} catch (error) {
|
|
318
|
-
const warning = { code: "workspace_finalization_failed", message: error?.message || String(error) };
|
|
319
|
-
try {
|
|
320
|
-
workspaceLease = await markWorkspaceFinalizationFailed(workspaceLease, {
|
|
321
|
-
recordPath: workspacePath,
|
|
322
|
-
warning,
|
|
323
|
-
});
|
|
324
|
-
} catch {
|
|
325
|
-
workspaceLease = { ...workspaceLease, retained: workspaceLease.mode !== "shared", changed: null };
|
|
326
|
-
}
|
|
327
|
-
manifest = { ...manifest, ...workspaceManifestFields(workspaceLease), workspace_warning: warning };
|
|
328
|
-
result.workspace = {
|
|
329
|
-
mode: workspaceLease.mode,
|
|
330
|
-
source: workspaceLease.source_workspace,
|
|
331
|
-
execution: workspaceLease.execution_workspace,
|
|
332
|
-
retained: workspaceLease.retained,
|
|
333
|
-
changed: workspaceLease.changed,
|
|
334
|
-
};
|
|
335
|
-
result.workspace_warning = warning;
|
|
336
|
-
}
|
|
337
|
-
} else {
|
|
338
|
-
const workspaceStatus = await inspectWorkspace({ root, runId }).catch(() => null);
|
|
339
|
-
const currentWorkspace = workspaceStatus?.status === "planned"
|
|
340
|
-
? await abandonPlannedWorkspace({ root, runId, status: result.status === "cancelled" ? "cancelled" : "unused" })
|
|
341
|
-
: workspaceStatus;
|
|
342
|
-
manifest = { ...manifest, ...workspaceManifestFields(currentWorkspace) };
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (sinkOpened) {
|
|
346
|
-
try {
|
|
347
|
-
await sink.close();
|
|
348
|
-
} catch (error) {
|
|
349
|
-
result = failureResult(runId, startedAt, "event_recording_failed", error.message, 12);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
if (!result) result = failureResult(runId, startedAt, "internal_error", "run ended without a result", 12);
|
|
353
|
-
await atomicWriteJSON(resultPath, result);
|
|
354
|
-
await atomicWriteJSON(manifestPath, { ...manifest, status: result.status, completed_at: result.completed_at });
|
|
355
|
-
return result;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
export async function createQueuedRun({ runId = newRunId(), request, runsRoot, root = path.dirname(runsRoot) }) {
|
|
359
|
-
const normalized = await validateRunRequest(request);
|
|
360
|
-
const resolvedRevision = await resolveHarness(normalized.harness_ref, { root });
|
|
361
|
-
const workspacePlan = await planWorkspace({ runId, sourceCwd: normalized.cwd, mode: normalized.workspace_mode, root });
|
|
362
|
-
const directory = await ensureDir(path.join(runsRoot, runId));
|
|
363
|
-
await atomicWriteJSON(workspaceRecordPath(root, runId), workspacePlan);
|
|
364
|
-
await atomicWriteJSON(path.join(directory, "manifest.json"), {
|
|
365
|
-
...buildManifest(runId, normalized, workspacePlan),
|
|
366
|
-
resolved_revision: resolvedRevision,
|
|
367
|
-
revision_identity: resolvedRevision.identity,
|
|
368
|
-
});
|
|
369
|
-
await atomicWriteJSON(path.join(directory, "request.json"), { ...normalized, schema_version: SCHEMA_VERSION });
|
|
370
|
-
await atomicWriteJSON(path.join(directory, "resolution.json"), resolvedRevision);
|
|
371
|
-
return { runId, request: normalized, resolvedRevision, workspacePlan, directory };
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function failureResult(runId, startedAt, code, message, exitCode, processExit = {}) {
|
|
375
|
-
return {
|
|
376
|
-
schema_version: SCHEMA_VERSION,
|
|
377
|
-
run_id: runId,
|
|
378
|
-
status: code === "cancelled" ? "cancelled" : code === "timed_out" ? "timed_out" : "failed",
|
|
379
|
-
exit_code: exitCode,
|
|
380
|
-
process_exit_code: processExit.code ?? null,
|
|
381
|
-
signal: processExit.signal ?? null,
|
|
382
|
-
error: { code, message },
|
|
383
|
-
started_at: startedAt.toISOString(),
|
|
384
|
-
completed_at: new Date().toISOString(),
|
|
385
|
-
};
|
|
386
|
-
}
|
package/src/errors.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export class HitchError extends Error {
|
|
2
|
-
constructor(message, { code = "internal_error", exitCode = 12, cause } = {}) {
|
|
3
|
-
super(message, { cause });
|
|
4
|
-
this.name = "HitchError";
|
|
5
|
-
this.code = code;
|
|
6
|
-
this.exitCode = exitCode;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function invalidInput(message, { cause } = {}) {
|
|
11
|
-
return new HitchError(message, { code: "invalid_input", exitCode: 2, cause });
|
|
12
|
-
}
|