@agent-delivery-harness/cli 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/package.json +8 -3
- package/src/boundary.ts +282 -5
- package/src/commands/emit.ts +351 -0
- package/src/commands/gate.ts +101 -17
- package/src/commands/maintain.ts +202 -0
- package/src/commands/managed.ts +477 -0
- package/src/commands/prepare.ts +8 -1
- package/src/commands/record.ts +2 -5
- package/src/commands/runs.ts +255 -0
- package/src/commands/verify.ts +150 -3
- package/src/index.ts +27 -4
- package/src/main.ts +23 -0
- package/src/provider-rails.ts +705 -0
- package/src/run-projection.ts +278 -0
- package/src/run-server.ts +660 -0
- package/src/run-surface.ts +261 -0
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-delivery-harness/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Delivery harness CLI: prepare, review-context, submit-evidence, gate, record, verify, check",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/kwam1na/agent-delivery-harness.git",
|
|
10
|
+
"directory": "packages/cli"
|
|
11
|
+
},
|
|
7
12
|
"engines": {
|
|
8
|
-
"node": ">=22"
|
|
13
|
+
"node": ">=22.6.0"
|
|
9
14
|
},
|
|
10
15
|
"dependencies": {
|
|
11
|
-
"@agent-delivery-harness/kernel": "0.
|
|
16
|
+
"@agent-delivery-harness/kernel": "0.2.0"
|
|
12
17
|
},
|
|
13
18
|
"exports": {
|
|
14
19
|
".": "./src/index.ts"
|
package/src/boundary.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The one boundary every CLI command runs behind.
|
|
3
3
|
*
|
|
4
|
-
* WHY ONE BOUNDARY.
|
|
5
|
-
* classifies exit codes, and renders failures. A command never
|
|
6
|
-
* `process`, never prints a stack, never chooses an exit code of its own: it
|
|
4
|
+
* WHY ONE BOUNDARY. Nine config-loading commands, one place that loads config,
|
|
5
|
+
* wires the repo, classifies exit codes, and renders failures. A command never
|
|
6
|
+
* touches `process`, never prints a stack, never chooses an exit code of its own: it
|
|
7
7
|
* returns a typed result and the boundary maps it. That is what keeps the three
|
|
8
8
|
* exit semantics — policy block, usage error, interruption — identical across
|
|
9
9
|
* commands, and what keeps every operator-facing byte flowing through the one
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* disagreement unconstructible in the first place.
|
|
27
27
|
*/
|
|
28
28
|
import path from "node:path";
|
|
29
|
+
import { randomUUID } from "node:crypto";
|
|
29
30
|
import { pathToFileURL } from "node:url";
|
|
30
31
|
import {
|
|
31
32
|
BlockedError,
|
|
@@ -37,12 +38,14 @@ import {
|
|
|
37
38
|
evaluateCandidateActivation,
|
|
38
39
|
renderBlockers,
|
|
39
40
|
resolveRecordStorage,
|
|
41
|
+
submitManifest,
|
|
40
42
|
validateHarnessConfig,
|
|
41
43
|
withDeliverableIdentity,
|
|
42
44
|
type ArtifactsPort,
|
|
43
45
|
type Blocker,
|
|
44
46
|
type CaptureCandidate,
|
|
45
47
|
type CapturedCandidate,
|
|
48
|
+
type CompiledAdopterPolicyBinding,
|
|
46
49
|
type EnvSnapshot,
|
|
47
50
|
type ExecutionContext,
|
|
48
51
|
type HarnessConfig,
|
|
@@ -51,6 +54,13 @@ import {
|
|
|
51
54
|
type ReviewActivationProjection,
|
|
52
55
|
type WaiverPrompt,
|
|
53
56
|
} from "@agent-delivery-harness/kernel";
|
|
57
|
+
import {
|
|
58
|
+
invokeProviderRail,
|
|
59
|
+
openProviderRailProcess,
|
|
60
|
+
type ProviderRailInvocationResult,
|
|
61
|
+
type ProviderRailSession,
|
|
62
|
+
} from "./provider-rails.ts";
|
|
63
|
+
import { buildRunEvent, resolveRunSurface } from "./run-surface.ts";
|
|
54
64
|
|
|
55
65
|
// ── Exit codes ───────────────────────────────────────────────────────────────
|
|
56
66
|
|
|
@@ -96,6 +106,8 @@ export interface RepoWiring {
|
|
|
96
106
|
export interface CommandContext {
|
|
97
107
|
readonly rootDir: string;
|
|
98
108
|
readonly config: HarnessConfig;
|
|
109
|
+
/** Optional adopter binding; managed commands also load the persisted binding after registration. */
|
|
110
|
+
readonly policyBinding?: CompiledAdopterPolicyBinding;
|
|
99
111
|
readonly env: EnvSnapshot;
|
|
100
112
|
readonly stdinIsTTY: boolean;
|
|
101
113
|
readonly stdoutIsTTY: boolean;
|
|
@@ -111,6 +123,12 @@ export interface CommandContext {
|
|
|
111
123
|
/** Present only when the run can ask a human; the boundary gates it on a TTY. */
|
|
112
124
|
readonly promptForWaiver?: WaiverPrompt;
|
|
113
125
|
readonly liveResults?: readonly LiveProviderResult[];
|
|
126
|
+
/** Runs one configured provider through the neutral stdio rail, if it has a command. */
|
|
127
|
+
readonly invokeProvider?: (input: {
|
|
128
|
+
readonly providerId: string;
|
|
129
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
130
|
+
readonly requiresEvidence: boolean;
|
|
131
|
+
}) => Promise<ProviderRailInvocationResult | undefined>;
|
|
114
132
|
/** Emits one line of operator-facing output to stdout. */
|
|
115
133
|
readonly write: (text: string) => void;
|
|
116
134
|
/** Classifies the execution context from this invocation's env + TTY. */
|
|
@@ -125,6 +143,53 @@ export interface CommandDescriptor {
|
|
|
125
143
|
run(context: CommandContext): Promise<CommandResult>;
|
|
126
144
|
}
|
|
127
145
|
|
|
146
|
+
// ── The config-free command class ────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* What a config-free command may reach. Deliberately much less than
|
|
150
|
+
* {@link CommandContext}: no config, no wiring, no provider rail, no artifacts
|
|
151
|
+
* port. A run event belongs to the repository, not to a configured gate, and
|
|
152
|
+
* `emit` has to work in a repository that has no `harness.config.ts` at all.
|
|
153
|
+
*
|
|
154
|
+
* The ONE thing a config-free command may do with `harness.config.ts` is ask
|
|
155
|
+
* whether the path exists (`lstat`, no follow) at a worktree root. It never
|
|
156
|
+
* imports it, loads it, or parses it — which is why the config loader is not
|
|
157
|
+
* reachable from here even as a seam.
|
|
158
|
+
*/
|
|
159
|
+
export interface ConfigFreeCommandContext {
|
|
160
|
+
readonly rootDir: string;
|
|
161
|
+
readonly env: EnvSnapshot;
|
|
162
|
+
/** Positional and flag arguments after the command name. */
|
|
163
|
+
readonly args: readonly string[];
|
|
164
|
+
/** The payload channel: everything on stdin, when a command reads one. */
|
|
165
|
+
readStdin(): Promise<string>;
|
|
166
|
+
/** Emits one line of operator-facing output to stdout. */
|
|
167
|
+
readonly write: (text: string) => void;
|
|
168
|
+
/**
|
|
169
|
+
* The invocation's cancellation, for the one config-free command that does
|
|
170
|
+
* not return on its own. `runs serve` holds a socket open until the operator
|
|
171
|
+
* ends it; without a signal it serves forever, which is exactly right for a
|
|
172
|
+
* terminal and useless for a caller that has to get its process back.
|
|
173
|
+
*/
|
|
174
|
+
readonly signal?: AbortSignal;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface ConfigFreeCommandDescriptor {
|
|
178
|
+
readonly name: string;
|
|
179
|
+
readonly sourceId: string;
|
|
180
|
+
readonly summary: string;
|
|
181
|
+
/** The discriminator the boundary dispatches on, before any config load. */
|
|
182
|
+
readonly configFree: true;
|
|
183
|
+
run(context: ConfigFreeCommandContext): Promise<CommandResult>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Either command class; the registry holds both. */
|
|
187
|
+
export type AnyCommandDescriptor = CommandDescriptor | ConfigFreeCommandDescriptor;
|
|
188
|
+
|
|
189
|
+
export function isConfigFreeCommand(descriptor: AnyCommandDescriptor): descriptor is ConfigFreeCommandDescriptor {
|
|
190
|
+
return (descriptor as ConfigFreeCommandDescriptor).configFree === true;
|
|
191
|
+
}
|
|
192
|
+
|
|
128
193
|
// ── Runtime the boundary is driven with ──────────────────────────────────────
|
|
129
194
|
|
|
130
195
|
export interface CliRuntime {
|
|
@@ -136,11 +201,23 @@ export interface CliRuntime {
|
|
|
136
201
|
readonly stderr: (text: string) => void;
|
|
137
202
|
/** Loads the consumer config. Defaults to importing `harness.config.ts`. */
|
|
138
203
|
readonly loadConfig?: (rootDir: string) => Promise<HarnessConfig>;
|
|
204
|
+
/** An embedding adopter may pass its already-compiled policy directly. */
|
|
205
|
+
readonly policyBinding?: CompiledAdopterPolicyBinding;
|
|
139
206
|
/** The interactive waiver prompt. Only ever offered under a TTY. */
|
|
140
207
|
readonly promptForWaiver?: WaiverPrompt;
|
|
141
208
|
/** The filesystem port. Defaults to one rooted in the system temp directory. */
|
|
142
209
|
readonly artifacts?: ArtifactsPort;
|
|
143
210
|
readonly liveResults?: readonly LiveProviderResult[];
|
|
211
|
+
readonly signal?: AbortSignal;
|
|
212
|
+
/** Reads the whole of stdin, for the one command whose payload arrives there. */
|
|
213
|
+
readonly readStdin?: () => Promise<string>;
|
|
214
|
+
/** Test/embedding seam. The ordinary runtime opens the provider's configured command. */
|
|
215
|
+
readonly openProviderRail?: (input: {
|
|
216
|
+
readonly providerId: string;
|
|
217
|
+
readonly command: readonly [string, ...string[]];
|
|
218
|
+
readonly cwd: string;
|
|
219
|
+
readonly env: EnvSnapshot;
|
|
220
|
+
}) => Promise<ProviderRailSession>;
|
|
144
221
|
}
|
|
145
222
|
|
|
146
223
|
// ── Blocker helpers ──────────────────────────────────────────────────────────
|
|
@@ -225,7 +302,7 @@ export async function wireRepo(rootDir: string, config: HarnessConfig): Promise<
|
|
|
225
302
|
|
|
226
303
|
// ── The boundary ─────────────────────────────────────────────────────────────
|
|
227
304
|
|
|
228
|
-
const USAGE = (commands: readonly
|
|
305
|
+
const USAGE = (commands: readonly AnyCommandDescriptor[]): string =>
|
|
229
306
|
[
|
|
230
307
|
"Usage: delivery-harness <command> [options]",
|
|
231
308
|
"",
|
|
@@ -233,6 +310,76 @@ const USAGE = (commands: readonly CommandDescriptor[]): string =>
|
|
|
233
310
|
...commands.map((command) => ` ${command.name.padEnd(16)}${command.summary}`),
|
|
234
311
|
].join("\n");
|
|
235
312
|
|
|
313
|
+
/**
|
|
314
|
+
* THE WRAPPED COMMANDS, NAMED ONE BY ONE.
|
|
315
|
+
*
|
|
316
|
+
* Exactly the candidate-facing loop plus its preflight. `managed` and
|
|
317
|
+
* `maintain` are deliberately absent: they are host-facing and
|
|
318
|
+
* installation-scoped, and a completion event for them would describe
|
|
319
|
+
* something that is not a step of the delivery run the journal is about.
|
|
320
|
+
* `emit` and `runs` are absent because a viewer that recorded its own
|
|
321
|
+
* invocations would fill the journal it renders.
|
|
322
|
+
*
|
|
323
|
+
* An allowlist rather than a denylist: a command added to the registry is
|
|
324
|
+
* unwrapped until someone decides it belongs here, which is the direction that
|
|
325
|
+
* fails safe for a store nothing authoritative may read.
|
|
326
|
+
*/
|
|
327
|
+
export const COMPLETION_WRAPPED_COMMANDS: readonly string[] = [
|
|
328
|
+
"check",
|
|
329
|
+
"prepare",
|
|
330
|
+
"review-context",
|
|
331
|
+
"submit-evidence",
|
|
332
|
+
"gate",
|
|
333
|
+
"record",
|
|
334
|
+
"verify",
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
/** The four exit codes, as the closed outcome enum `command.completed` carries. */
|
|
338
|
+
function outcomeOfExit(code: number): "ok" | "policy" | "usage" | "interrupted" {
|
|
339
|
+
if (code === EXIT_OK) return "ok";
|
|
340
|
+
if (code === EXIT_USAGE) return "usage";
|
|
341
|
+
if (code === EXIT_INTERRUPTED) return "interrupted";
|
|
342
|
+
return "policy";
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Appends this invocation's `command.completed`, when — and only when — a run
|
|
347
|
+
* is current for the invoking worktree.
|
|
348
|
+
*
|
|
349
|
+
* BEST-EFFORT, TOTAL, AND SILENT. Every failure is swallowed: an unresolvable
|
|
350
|
+
* repository, a refused pointer, a store that will not accept the append. The
|
|
351
|
+
* caller has already decided the exit code, and a run journal that could
|
|
352
|
+
* change a gate's verdict would be evidence. It is not evidence, so it is not
|
|
353
|
+
* allowed to matter. A refused append still lands one bounded line in the
|
|
354
|
+
* run's note, which the store writes.
|
|
355
|
+
*/
|
|
356
|
+
async function recordCommandCompletion(input: {
|
|
357
|
+
readonly cwd: string;
|
|
358
|
+
readonly command: string;
|
|
359
|
+
readonly exitCode: number;
|
|
360
|
+
readonly durationMs: number;
|
|
361
|
+
}): Promise<void> {
|
|
362
|
+
try {
|
|
363
|
+
const resolved = await resolveRunSurface(input.cwd);
|
|
364
|
+
if (!resolved.ok) return;
|
|
365
|
+
const { store, commonDir, worktreeKey } = resolved.surface;
|
|
366
|
+
const current = await store.current(worktreeKey);
|
|
367
|
+
if (!current.ok || current.runId === undefined) return;
|
|
368
|
+
await store.append(
|
|
369
|
+
current.runId,
|
|
370
|
+
buildRunEvent({
|
|
371
|
+
runId: current.runId,
|
|
372
|
+
commonDir,
|
|
373
|
+
kind: "command.completed",
|
|
374
|
+
role: "cli",
|
|
375
|
+
payload: { command: input.command, outcome: outcomeOfExit(input.exitCode), durationMs: input.durationMs },
|
|
376
|
+
}),
|
|
377
|
+
);
|
|
378
|
+
} catch {
|
|
379
|
+
// Deliberately silent. See the paragraph above.
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
236
383
|
/**
|
|
237
384
|
* Runs one CLI invocation to an exit code. Total: it maps every command result
|
|
238
385
|
* and every throw to one of the four codes, and renders every failure through
|
|
@@ -240,7 +387,7 @@ const USAGE = (commands: readonly CommandDescriptor[]): string =>
|
|
|
240
387
|
*/
|
|
241
388
|
export async function runCliBoundary(
|
|
242
389
|
argv: readonly string[],
|
|
243
|
-
commands: readonly
|
|
390
|
+
commands: readonly AnyCommandDescriptor[],
|
|
244
391
|
runtime: CliRuntime,
|
|
245
392
|
): Promise<number> {
|
|
246
393
|
const [commandName, ...args] = argv;
|
|
@@ -256,6 +403,76 @@ export async function runCliBoundary(
|
|
|
256
403
|
return EXIT_USAGE;
|
|
257
404
|
}
|
|
258
405
|
|
|
406
|
+
// CONFIG-FREE COMMANDS ARE DISPATCHED FIRST, before any config load. That
|
|
407
|
+
// ordering is the whole point of the class: `emit` runs in a repository with
|
|
408
|
+
// no `harness.config.ts`, and no other command's `config_unloadable` timing
|
|
409
|
+
// moves because of it.
|
|
410
|
+
if (isConfigFreeCommand(descriptor)) {
|
|
411
|
+
return runConfigFreeCommand(descriptor, args, runtime);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const startedAt = Date.now();
|
|
415
|
+
const code = await runConfiguredCommand(descriptor, args, runtime);
|
|
416
|
+
if (COMPLETION_WRAPPED_COMMANDS.includes(descriptor.name)) {
|
|
417
|
+
await recordCommandCompletion({
|
|
418
|
+
cwd: runtime.cwd,
|
|
419
|
+
command: descriptor.name,
|
|
420
|
+
exitCode: code,
|
|
421
|
+
durationMs: Date.now() - startedAt,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return code;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** The config-free path: a typed result mapped to an exit code, and nothing else. */
|
|
428
|
+
async function runConfigFreeCommand(
|
|
429
|
+
descriptor: ConfigFreeCommandDescriptor,
|
|
430
|
+
args: readonly string[],
|
|
431
|
+
runtime: CliRuntime,
|
|
432
|
+
): Promise<number> {
|
|
433
|
+
try {
|
|
434
|
+
const result = await descriptor.run({
|
|
435
|
+
rootDir: runtime.cwd,
|
|
436
|
+
env: runtime.env,
|
|
437
|
+
args,
|
|
438
|
+
readStdin: runtime.readStdin ?? (async () => ""),
|
|
439
|
+
write: (text) => runtime.stdout(`${text}\n`),
|
|
440
|
+
...(runtime.signal === undefined ? {} : { signal: runtime.signal }),
|
|
441
|
+
});
|
|
442
|
+
if (result.kind === "ok") {
|
|
443
|
+
if (result.summary !== undefined && result.summary !== "") runtime.stdout(`${result.summary}\n`);
|
|
444
|
+
return EXIT_OK;
|
|
445
|
+
}
|
|
446
|
+
if (result.kind === "usage") {
|
|
447
|
+
runtime.stderr(`${result.message}\n`);
|
|
448
|
+
return EXIT_USAGE;
|
|
449
|
+
}
|
|
450
|
+
runtime.stderr(`${renderBlockers(result.blockers)}\n`);
|
|
451
|
+
return EXIT_POLICY;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
if (error instanceof CliInterruption) {
|
|
454
|
+
runtime.stderr(`${error.message}\n`);
|
|
455
|
+
return EXIT_INTERRUPTED;
|
|
456
|
+
}
|
|
457
|
+
if (error instanceof BlockedError) {
|
|
458
|
+
runtime.stderr(`${renderBlockers(error.blockers)}\n`);
|
|
459
|
+
return EXIT_POLICY;
|
|
460
|
+
}
|
|
461
|
+
const blocker = createInternalErrorBlocker({
|
|
462
|
+
source: { kind: "command", id: descriptor.sourceId },
|
|
463
|
+
error,
|
|
464
|
+
reproduce: ["delivery-harness", descriptor.name],
|
|
465
|
+
});
|
|
466
|
+
runtime.stderr(`${renderBlockers([blocker])}\n`);
|
|
467
|
+
return EXIT_POLICY;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function runConfiguredCommand(
|
|
472
|
+
descriptor: CommandDescriptor,
|
|
473
|
+
args: readonly string[],
|
|
474
|
+
runtime: CliRuntime,
|
|
475
|
+
): Promise<number> {
|
|
259
476
|
const loadConfig = runtime.loadConfig ?? importHarnessConfig;
|
|
260
477
|
const artifacts = runtime.artifacts ?? createArtifactsPort();
|
|
261
478
|
|
|
@@ -271,6 +488,7 @@ export async function runCliBoundary(
|
|
|
271
488
|
const context: CommandContext = {
|
|
272
489
|
rootDir: runtime.cwd,
|
|
273
490
|
config,
|
|
491
|
+
...(runtime.policyBinding === undefined ? {} : { policyBinding: runtime.policyBinding }),
|
|
274
492
|
env: runtime.env,
|
|
275
493
|
stdinIsTTY: runtime.stdinIsTTY,
|
|
276
494
|
stdoutIsTTY: runtime.stdoutIsTTY,
|
|
@@ -283,6 +501,65 @@ export async function runCliBoundary(
|
|
|
283
501
|
? { promptForWaiver: runtime.promptForWaiver }
|
|
284
502
|
: {}),
|
|
285
503
|
...(runtime.liveResults === undefined ? {} : { liveResults: runtime.liveResults }),
|
|
504
|
+
invokeProvider: async ({ providerId, payload, requiresEvidence }) => {
|
|
505
|
+
const provider = config.providers.find((registration) => registration.id === providerId);
|
|
506
|
+
if (provider?.command === undefined) return undefined;
|
|
507
|
+
const command = provider.command;
|
|
508
|
+
const requestId = randomUUID();
|
|
509
|
+
const allocation = await artifacts.allocateRunRoot({ providerId, runId: requestId });
|
|
510
|
+
if (!allocation.ok) {
|
|
511
|
+
throw new BlockedError([
|
|
512
|
+
commandBlocker({
|
|
513
|
+
code: "provider_rail_run_root_refused",
|
|
514
|
+
sourceId: "delivery-harness.cli.provider-rails",
|
|
515
|
+
summary: "The provider run root could not be allocated.",
|
|
516
|
+
details: `${providerId}/${requestId}: ${allocation.reason}`,
|
|
517
|
+
remediations: [
|
|
518
|
+
{
|
|
519
|
+
id: "check-provider-identity",
|
|
520
|
+
kind: "code_change",
|
|
521
|
+
summary: "Correct the provider id or restore the harness run-root location, then retry.",
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
}),
|
|
525
|
+
]);
|
|
526
|
+
}
|
|
527
|
+
const wiring = await wire();
|
|
528
|
+
const interruptController = runtime.signal === undefined ? new AbortController() : undefined;
|
|
529
|
+
const onInterrupt = (): void => interruptController?.abort();
|
|
530
|
+
if (interruptController !== undefined) process.once("SIGINT", onInterrupt);
|
|
531
|
+
try {
|
|
532
|
+
return await invokeProviderRail(
|
|
533
|
+
{
|
|
534
|
+
providerId,
|
|
535
|
+
requestId,
|
|
536
|
+
idempotencyKey: randomUUID(),
|
|
537
|
+
payload: { ...payload, runId: requestId, runRoot: allocation.runRoot.path },
|
|
538
|
+
requiresEvidence,
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
open: () =>
|
|
542
|
+
runtime.openProviderRail === undefined
|
|
543
|
+
? openProviderRailProcess({ command, cwd: runtime.cwd, env: { ...runtime.env } })
|
|
544
|
+
: runtime.openProviderRail({ providerId, command, cwd: runtime.cwd, env: runtime.env }),
|
|
545
|
+
publishManifest: (manifestPath) =>
|
|
546
|
+
submitManifest(
|
|
547
|
+
{ rootDir: runtime.cwd, config, manifestPath },
|
|
548
|
+
{
|
|
549
|
+
captureCandidate: wiring.captureCandidate,
|
|
550
|
+
artifacts,
|
|
551
|
+
expectedProviderAttempt: { providerId, runId: requestId, runRootPath: allocation.runRoot.path },
|
|
552
|
+
...wiring.storageOptions,
|
|
553
|
+
},
|
|
554
|
+
),
|
|
555
|
+
signal: runtime.signal ?? interruptController?.signal,
|
|
556
|
+
cancellationId: randomUUID(),
|
|
557
|
+
},
|
|
558
|
+
);
|
|
559
|
+
} finally {
|
|
560
|
+
if (interruptController !== undefined) process.off("SIGINT", onInterrupt);
|
|
561
|
+
}
|
|
562
|
+
},
|
|
286
563
|
write: (text) => runtime.stdout(`${text}\n`),
|
|
287
564
|
classifyContext: () =>
|
|
288
565
|
classifyExecutionContext({
|