@agent-compose/sdk 0.7.0 → 0.8.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 +66 -39
- package/dist/agent/__tests__/runtime-json-schema.test.d.ts +10 -0
- package/dist/agent/agent-context.d.ts +21 -1
- package/dist/agent/agent-loop.d.ts +24 -1
- package/dist/client.d.ts +338 -534
- package/dist/directives.d.ts +112 -0
- package/dist/display.d.ts +242 -0
- package/dist/errors.d.ts +24 -1
- package/dist/index.d.ts +24 -12
- package/dist/index.js +3545 -1667
- package/dist/pause/wrappers.d.ts +31 -9
- package/dist/runtimes/_acp-client.d.ts +46 -1
- package/dist/runtimes/_cli-agent.d.ts +49 -4
- package/dist/runtimes/_jsonl-guard.d.ts +103 -0
- package/dist/runtimes/amp.d.ts +2 -2
- package/dist/runtimes/claude-code.d.ts +59 -0
- package/dist/runtimes/claude-code.test.d.ts +14 -0
- package/dist/runtimes/claude.d.ts +16 -0
- package/dist/runtimes/claude.test.d.ts +8 -0
- package/dist/runtimes/codex.d.ts +9 -3
- package/dist/runtimes/cursor.d.ts +2 -2
- package/dist/runtimes/droid.d.ts +2 -2
- package/dist/runtimes/jsonl-guard.test.d.ts +19 -0
- package/dist/runtimes/openai-desktop.js +2691 -864
- package/dist/runtimes/opencode.d.ts +2 -2
- package/dist/runtimes/vercel.js +12 -1
- package/dist/sandbox/devbox.d.ts +42 -0
- package/dist/sandbox/exec-stream.d.ts +14 -0
- package/dist/sandbox/network-policy.d.ts +100 -0
- package/dist/sandbox/provider-def.d.ts +79 -0
- package/dist/sandbox/providers/desktop.d.ts +10 -0
- package/dist/sandbox/providers/e2b.d.ts +17 -0
- package/dist/sandbox/providers/local.d.ts +11 -0
- package/dist/sandbox/providers/vercel.d.ts +18 -0
- package/dist/sandbox/registry.d.ts +45 -0
- package/dist/sandbox/sizes.d.ts +68 -0
- package/dist/sandbox.d.ts +24 -299
- package/dist/step-invocation/__tests__/foreground-recovery.test.d.ts +1 -0
- package/dist/step-invocation/invoker.d.ts +10 -0
- package/dist/step-invocation/protocol.d.ts +5 -0
- package/dist/types/api-compliance.d.ts +71 -0
- package/dist/types/api-conversations.d.ts +492 -0
- package/dist/types/api-factory.d.ts +309 -0
- package/dist/types/api-projects.d.ts +131 -0
- package/dist/types/api-runs.d.ts +377 -0
- package/dist/types/api-scopes.d.ts +102 -0
- package/dist/types/conversation-stream.d.ts +191 -0
- package/dist/types/execution-context.d.ts +12 -2
- package/dist/types/protocol.d.ts +30 -1
- package/dist/types/sandbox-environment.d.ts +8 -5
- package/dist/types/sandbox.d.ts +74 -4
- package/dist/types/workflow-metadata.d.ts +33 -8
- package/dist/types/workflow-plan.d.ts +10 -0
- package/dist/types/workflow.d.ts +18 -205
- package/dist/utils/bundler.d.ts +12 -1
- package/dist/workflow-steps/index.d.ts +1 -1
- package/dist/workflow-steps/observability.d.ts +8 -1
- package/dist/workflow-steps/runner.d.ts +3 -3
- package/dist/workflow-steps/step.d.ts +15 -1
- package/dist/workflow-steps/types.d.ts +19 -5
- package/dist/workflow-steps/workflow.d.ts +22 -1
- package/dist/workflows/engine.d.ts +3 -2
- package/dist/workflows/invoke-child.d.ts +2 -2
- package/package.json +1 -1
- package/src/agent/agent-context.ts +186 -3
- package/src/agent/agent-loop.ts +31 -2
- package/src/client.ts +909 -621
- package/src/directives.ts +184 -0
- package/src/display.ts +788 -0
- package/src/errors.ts +39 -0
- package/src/index.ts +104 -10
- package/src/pause/wrappers.ts +44 -9
- package/src/runtimes/_acp-client.ts +72 -3
- package/src/runtimes/_cli-agent.ts +159 -36
- package/src/runtimes/_jsonl-guard.ts +219 -0
- package/src/runtimes/claude-code.ts +246 -0
- package/src/runtimes/claude.ts +32 -2
- package/src/runtimes/codex.ts +55 -3
- package/src/runtimes/openai-desktop.ts +59 -14
- package/src/sandbox/devbox.ts +48 -0
- package/src/sandbox/exec-stream.ts +48 -0
- package/src/sandbox/network-policy.ts +181 -0
- package/src/sandbox/provider-def.ts +94 -0
- package/src/sandbox/providers/desktop.ts +57 -0
- package/src/sandbox/providers/e2b.ts +354 -0
- package/src/sandbox/providers/local.ts +106 -0
- package/src/sandbox/providers/vercel.ts +331 -0
- package/src/sandbox/registry.ts +198 -0
- package/src/sandbox/sizes.ts +95 -0
- package/src/sandbox.ts +59 -1275
- package/src/step-invocation/invoker.ts +151 -28
- package/src/step-invocation/protocol.ts +8 -0
- package/src/types/api-compliance.ts +79 -0
- package/src/types/api-conversations.ts +522 -0
- package/src/types/api-factory.ts +336 -0
- package/src/types/api-projects.ts +140 -0
- package/src/types/api-runs.ts +412 -0
- package/src/types/api-scopes.ts +102 -0
- package/src/types/conversation-stream.ts +231 -0
- package/src/types/execution-context.ts +10 -2
- package/src/types/protocol.ts +33 -0
- package/src/types/sandbox-environment.ts +28 -9
- package/src/types/sandbox.ts +73 -4
- package/src/types/workflow-metadata.ts +35 -8
- package/src/types/workflow-plan.ts +11 -0
- package/src/types/workflow.ts +25 -292
- package/src/utils/bundler.ts +32 -5
- package/src/utils/errors.ts +16 -1
- package/src/workflow-steps/index.ts +1 -0
- package/src/workflow-steps/observability.ts +19 -8
- package/src/workflow-steps/runner.ts +4 -4
- package/src/workflow-steps/step.ts +49 -1
- package/src/workflow-steps/types.ts +20 -5
- package/src/workflow-steps/workflow.ts +22 -1
- package/src/workflows/engine.ts +3 -2
- package/src/workflows/invoke-child.ts +2 -2
|
@@ -25,7 +25,7 @@ import { randomBytes } from "node:crypto";
|
|
|
25
25
|
import { z } from "zod";
|
|
26
26
|
import { SandboxUnavailableError } from "../sandbox-errors.js";
|
|
27
27
|
import type { SandboxProvider, SandboxBackgroundProcess } from "../types/sandbox.js";
|
|
28
|
-
import { RUNNER_COMMAND, STEP_ENV, stepResultLinePrefix, stepPauseLinePrefix, requestContextPath, stepInputPath, stepResultFilePath, stepLogFilePath } from "./protocol.js";
|
|
28
|
+
import { RUNNER_COMMAND, STEP_ENV, stepResultLinePrefix, stepPauseLinePrefix, requestContextPath, stepInputPath, stepResultFilePath, stepLogFilePath, stepPidFilePath } from "./protocol.js";
|
|
29
29
|
import { StepPauseRequestSchema } from "./types.js";
|
|
30
30
|
import type { StepRequest, StepResult } from "./types.js";
|
|
31
31
|
import type { StepObservability } from "../workflow-steps/observability.js";
|
|
@@ -201,6 +201,11 @@ export interface InvokeStepOptions {
|
|
|
201
201
|
* is the hook to emit a structured alert so the degradation is visible/paged.
|
|
202
202
|
* Best-effort: keep it cheap and non-throwing. */
|
|
203
203
|
onStreamDegraded?: (info: { error: unknown; runnerPid: number; resultToken: string }) => void;
|
|
204
|
+
/** Ceiling on the durable-result recovery poll after a live-stream fault
|
|
205
|
+
* (default 45m — the wedged-runner backstop). The activity passes its step
|
|
206
|
+
* ceiling so a stream fault on a step with hours of legitimate work left
|
|
207
|
+
* recovers instead of timing out; startToClose is the real wall. */
|
|
208
|
+
recoveryDeadlineMs?: number;
|
|
204
209
|
}
|
|
205
210
|
|
|
206
211
|
/** A step running as a background command (ADR-0028). The activity races its
|
|
@@ -331,20 +336,43 @@ async function classifyRunnerOutcome<TOutput>(
|
|
|
331
336
|
* pause/resume) AND the launch path after a live-stream transport fault.
|
|
332
337
|
* `cat`-ing the result file is itself immune to the large-frame compression bug
|
|
333
338
|
* — it is a single small JSON line, far below any compression threshold. The
|
|
334
|
-
* activity's `startToCloseTimeout` is the real upper bound;
|
|
335
|
-
* backstop so a wedged runner can't leak this loop in the worker forever
|
|
339
|
+
* activity's `startToCloseTimeout` is the real upper bound; the deadline here
|
|
340
|
+
* is a backstop so a wedged runner can't leak this loop in the worker forever
|
|
341
|
+
* (default 45m; a worker-death recovery reconnecting mid-step passes the full
|
|
342
|
+
* step ceiling instead — hours of legitimate work may remain). */
|
|
336
343
|
async function pollDurableResult<TOutput>(
|
|
337
344
|
sandbox: SandboxProvider,
|
|
338
345
|
runnerPid: number,
|
|
339
346
|
resultToken: string,
|
|
340
|
-
signal?: AbortSignal,
|
|
347
|
+
opts?: { signal?: AbortSignal; deadlineMs?: number },
|
|
341
348
|
): Promise<StepResult<TOutput>> {
|
|
349
|
+
const signal = opts?.signal;
|
|
342
350
|
const resultFile = stepResultFilePath(resultToken);
|
|
343
351
|
const POLL_MS = 1000;
|
|
344
|
-
const
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
352
|
+
const deadlineMs = opts?.deadlineMs ?? 45 * 60_000;
|
|
353
|
+
const DEADLINE = Date.now() + deadlineMs;
|
|
354
|
+
// Both internal commands carry an explicit budget. Required for Vercel:
|
|
355
|
+
// without `timeoutMs` there is no hard client-side deadline in the provider's
|
|
356
|
+
// `commands.run`, so a wedged VM hangs one `cat` forever and the poll
|
|
357
|
+
// deadline never advances. A timeout throws → the `.catch(() => null)`
|
|
358
|
+
// treats it as a failed read/probe and the loop continues (a hung probe
|
|
359
|
+
// never misclassifies as dead). Harmless on E2B — a 1-line cat is ms-fast.
|
|
360
|
+
const CMD_TIMEOUT_MS = 10_000;
|
|
361
|
+
// A destroyed sandbox (e.g. run cancelled → sandbox killed mid-recovery)
|
|
362
|
+
// rejects EVERY command, which a lone `.catch(() => null)` renders
|
|
363
|
+
// indistinguishable from a live runner — the loop would spin until the
|
|
364
|
+
// deadline (hours) against a VM that no longer exists. Track consecutive
|
|
365
|
+
// iterations where BOTH the result read and the liveness probe rejected and
|
|
366
|
+
// exit with an honest runner-exit once the streak is unambiguous. 20
|
|
367
|
+
// iterations ≈ 30s–7min of continuous rejections (each command may burn its
|
|
368
|
+
// 10s budget) — long enough to ride out a transient provider-API blip,
|
|
369
|
+
// short enough that a dead sandbox doesn't hold the worker slot for hours.
|
|
370
|
+
const UNREACHABLE_LIMIT = 20;
|
|
371
|
+
let unreachableStreak = 0;
|
|
372
|
+
const readResult = async (): Promise<StepResult<TOutput> | "unreachable" | null> => {
|
|
373
|
+
const r = await sandbox.commands.run(`cat ${resultFile} 2>/dev/null || true`, { timeoutMs: CMD_TIMEOUT_MS }).catch(() => "unreachable" as const);
|
|
374
|
+
if (r === "unreachable") return "unreachable";
|
|
375
|
+
return r.stdout ? (parseStepResult<TOutput>(r.stdout, resultToken) ?? null) : null;
|
|
348
376
|
};
|
|
349
377
|
for (let attempt = 1; ; attempt++) {
|
|
350
378
|
// Aborted = the activity already resolved via the pause watcher (a re-pause);
|
|
@@ -354,11 +382,26 @@ async function pollDurableResult<TOutput>(
|
|
|
354
382
|
return { ok: false, error: { kind: "runner-exit", message: "reconnectStep aborted (run re-paused)", exitCode: 1 } };
|
|
355
383
|
}
|
|
356
384
|
const fromFile = await readResult();
|
|
357
|
-
if (fromFile) return fromFile;
|
|
385
|
+
if (fromFile !== null && fromFile !== "unreachable") return fromFile;
|
|
358
386
|
|
|
359
387
|
const probe = await sandbox.commands
|
|
360
|
-
.run(`test -d /proc/${runnerPid} && echo alive || echo dead
|
|
388
|
+
.run(`test -d /proc/${runnerPid} && echo alive || echo dead`, { timeoutMs: CMD_TIMEOUT_MS })
|
|
361
389
|
.catch(() => null);
|
|
390
|
+
if (fromFile === "unreachable" && probe === null) {
|
|
391
|
+
unreachableStreak++;
|
|
392
|
+
if (unreachableStreak >= UNREACHABLE_LIMIT) {
|
|
393
|
+
return {
|
|
394
|
+
ok: false,
|
|
395
|
+
error: {
|
|
396
|
+
kind: "runner-exit",
|
|
397
|
+
message: `sandbox stopped responding while waiting for runner pid ${runnerPid} to emit a result (${UNREACHABLE_LIMIT} consecutive failed polls)`,
|
|
398
|
+
exitCode: 1,
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
} else {
|
|
403
|
+
unreachableStreak = 0;
|
|
404
|
+
}
|
|
362
405
|
const dead = (probe?.stdout ?? "").includes("dead");
|
|
363
406
|
process.stderr.write(
|
|
364
407
|
`[recover] poll ${attempt}: result-file=absent runner=${dead ? "dead" : "alive"} pid=${runnerPid}\n`,
|
|
@@ -366,7 +409,7 @@ async function pollDurableResult<TOutput>(
|
|
|
366
409
|
if (dead) {
|
|
367
410
|
// Close the write-then-exit race with one final read, else classify a crash.
|
|
368
411
|
const finalRead = await readResult();
|
|
369
|
-
if (finalRead) return finalRead;
|
|
412
|
+
if (finalRead !== null && finalRead !== "unreachable") return finalRead;
|
|
370
413
|
return {
|
|
371
414
|
ok: false,
|
|
372
415
|
error: { kind: "runner-exit", message: `runner pid ${runnerPid} exited without writing a result file`, exitCode: 1 },
|
|
@@ -375,7 +418,11 @@ async function pollDurableResult<TOutput>(
|
|
|
375
418
|
if (Date.now() > DEADLINE) {
|
|
376
419
|
return {
|
|
377
420
|
ok: false,
|
|
378
|
-
error: {
|
|
421
|
+
error: {
|
|
422
|
+
kind: "runner-exit",
|
|
423
|
+
message: `timed out after ${Math.round(deadlineMs / 60_000)}m waiting for runner pid ${runnerPid} to emit a result`,
|
|
424
|
+
exitCode: 1,
|
|
425
|
+
},
|
|
379
426
|
};
|
|
380
427
|
}
|
|
381
428
|
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
@@ -397,8 +444,9 @@ async function recoverLogsAndResult<TOutput>(
|
|
|
397
444
|
resultToken: string,
|
|
398
445
|
liveSplitters: { stdoutConsumedChars: () => number },
|
|
399
446
|
opts: Pick<InvokeStepOptions, "onStdout" | "onStderr"> | undefined,
|
|
447
|
+
pollOpts?: { deadlineMs?: number },
|
|
400
448
|
): Promise<StepResult<TOutput>> {
|
|
401
|
-
const result = await pollDurableResult<TOutput>(sandbox, runnerPid, resultToken);
|
|
449
|
+
const result = await pollDurableResult<TOutput>(sandbox, runnerPid, resultToken, pollOpts);
|
|
402
450
|
// Runner has exited → the tee'd log file is complete. Best-effort: a failed
|
|
403
451
|
// readback just means the recovered run keeps the live logs it already had.
|
|
404
452
|
if (sandbox.files.read && opts?.onStdout) {
|
|
@@ -416,6 +464,38 @@ async function recoverLogsAndResult<TOutput>(
|
|
|
416
464
|
return result;
|
|
417
465
|
}
|
|
418
466
|
|
|
467
|
+
/** Read the foreground launch's pidfile back — the /proc liveness handle for
|
|
468
|
+
* the recovery poll. Returns `null` when recovery is impossible, for either
|
|
469
|
+
* reason: the sandbox cannot run ANY command (genuinely dead VM), or the
|
|
470
|
+
* pidfile never appeared. The pidfile write is the FIRST statement of the
|
|
471
|
+
* runner pipeline, so a still-absent pid after the retries means the runner
|
|
472
|
+
* never started and no result file will ever exist — recovery would poll
|
|
473
|
+
* blind for the full step ceiling and then misclassify; the caller must
|
|
474
|
+
* rethrow the original terminal error instead (fast, honest). The retries
|
|
475
|
+
* cover both a transiently-unreachable sandbox and the launch race where the
|
|
476
|
+
* stream fault fired before the VM executed the pidfile write. */
|
|
477
|
+
async function readRunnerPid(
|
|
478
|
+
sandbox: SandboxProvider,
|
|
479
|
+
resultToken: string,
|
|
480
|
+
): Promise<number | null> {
|
|
481
|
+
const ATTEMPTS = 3;
|
|
482
|
+
const RETRY_DELAY_MS = 2_000;
|
|
483
|
+
for (let attempt = 1; attempt <= ATTEMPTS; attempt++) {
|
|
484
|
+
try {
|
|
485
|
+
const r = await sandbox.commands.run(
|
|
486
|
+
`cat ${stepPidFilePath(resultToken)} 2>/dev/null || true`,
|
|
487
|
+
{ timeoutMs: 10_000 },
|
|
488
|
+
);
|
|
489
|
+
const pid = Number.parseInt(r.stdout.trim(), 10);
|
|
490
|
+
if (Number.isInteger(pid) && pid > 0) return pid;
|
|
491
|
+
} catch {
|
|
492
|
+
// Sandbox unreachable this attempt — fall through to the retry delay.
|
|
493
|
+
}
|
|
494
|
+
if (attempt < ATTEMPTS) await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
|
|
419
499
|
/** Write the step's input + request-context files and build the runner env.
|
|
420
500
|
* Shared by the foreground and background launch paths. Returns the
|
|
421
501
|
* per-invocation `resultToken` + the env map. */
|
|
@@ -453,21 +533,53 @@ export async function invokeStep<TOutput = unknown>(
|
|
|
453
533
|
// directly: the walk disappears entirely. Egress is edge-enforced with NO
|
|
454
534
|
// root exemption (see sandbox.ts), so root is confined exactly like the
|
|
455
535
|
// non-root user. `HOME=/root` so the spawned `claude` finds root's skills.
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
536
|
+
//
|
|
537
|
+
// Same pipeline pattern as `launchStep`: tee the runner's stdout to the
|
|
538
|
+
// durable token-keyed log file so a live-stream fault can backfill the
|
|
539
|
+
// undelivered tail, and write the wrapping shell's pid (`$$`) to a pidfile
|
|
540
|
+
// first — the shell waits on the pipeline, so `/proc/<pid>` liveness is a
|
|
541
|
+
// correct "runner may still write a result" proxy for the recovery poll.
|
|
542
|
+
// `set -o pipefail` is REQUIRED: without it the pipeline's exit code is
|
|
543
|
+
// tee's (0), masking a non-zero runner exit and breaking the runner-exit
|
|
544
|
+
// classification (the provider execs via `sh -c`, where pipefail works).
|
|
545
|
+
result = await sandbox.commands.run(
|
|
546
|
+
`set -o pipefail; echo $$ > ${stepPidFilePath(resultToken)}; ${RUNNER_COMMAND} | tee ${stepLogFilePath(resultToken)}`,
|
|
547
|
+
{
|
|
548
|
+
envs: { ...envs, HOME: "/root", IS_SANDBOX: "1" },
|
|
549
|
+
sudo: true,
|
|
550
|
+
timeoutMs: 0,
|
|
551
|
+
...(splitters.onStdout ? { onStdout: splitters.onStdout } : {}),
|
|
552
|
+
...(splitters.onStderr ? { onStderr: splitters.onStderr } : {}),
|
|
553
|
+
},
|
|
554
|
+
);
|
|
463
555
|
} catch (e) {
|
|
464
556
|
// Typed sandbox-infrastructure failure: the engine's recovery contract
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
|
|
470
|
-
|
|
557
|
+
// keys on retryability. Its `[sandbox-unavailable:*]` message prefix must
|
|
558
|
+
// reach the workflow as the failure leaf, not ride an embedded stderr tail
|
|
559
|
+
// that a later slice(-2000) can drop. Handle before the CommandExitError
|
|
560
|
+
// downgrade below.
|
|
561
|
+
if (e instanceof SandboxUnavailableError) {
|
|
562
|
+
// Pre-launch (retryable): nothing user-side ran — propagate INTACT so the
|
|
563
|
+
// workflow layer re-provisions (identity + prefix contract; pinned test).
|
|
564
|
+
if (e.retryable) throw e;
|
|
565
|
+
// Post-launch terminal: the LIVE stream/wait call died (e.g. Vercel's raw
|
|
566
|
+
// "The operation timed out.") while the runner is very likely still alive
|
|
567
|
+
// and will write its durable result file. Recover instead of failing —
|
|
568
|
+
// NEVER re-launch the runner. A null pid means recovery is impossible
|
|
569
|
+
// (sandbox can't run ANY command, or the pidfile never appeared because
|
|
570
|
+
// the runner never actually started): rethrow the original terminal
|
|
571
|
+
// classification fast rather than polling blind for the step ceiling.
|
|
572
|
+
// We do NOT flush the live splitter here — the recovery backfills from
|
|
573
|
+
// the last whole line, re-reading any in-flight partial line whole from
|
|
574
|
+
// the durable log (avoids a split/duplicated line).
|
|
575
|
+
const runnerPid = await readRunnerPid(sandbox, resultToken);
|
|
576
|
+
if (runnerPid === null) throw e;
|
|
577
|
+
opts?.onStreamDegraded?.({ error: e, runnerPid, resultToken });
|
|
578
|
+
return recoverLogsAndResult<TOutput>(
|
|
579
|
+
sandbox, runnerPid, resultToken, splitters, opts,
|
|
580
|
+
opts?.recoveryDeadlineMs !== undefined ? { deadlineMs: opts.recoveryDeadlineMs } : undefined,
|
|
581
|
+
);
|
|
582
|
+
}
|
|
471
583
|
// Some providers (E2B) throw a CommandExitError on a non-zero exit instead of
|
|
472
584
|
// returning it. Recover stdout/stderr/exitCode from the error so we can STILL
|
|
473
585
|
// parse the runner's structured `__AC_STEP_RESULT__` payload — otherwise the
|
|
@@ -564,7 +676,14 @@ export async function launchStep<TOutput = unknown>(
|
|
|
564
676
|
export async function reconnectStep<TOutput = unknown>(
|
|
565
677
|
sandbox: SandboxProvider,
|
|
566
678
|
resume: { runnerPid: number; resultToken: string; stepIndex: number },
|
|
567
|
-
opts?: Pick<InvokeStepOptions, "onStdout" | "onStderr"> & {
|
|
679
|
+
opts?: Pick<InvokeStepOptions, "onStdout" | "onStderr"> & {
|
|
680
|
+
signal?: AbortSignal;
|
|
681
|
+
/** Ceiling on the durable-result poll (default 45m — the wedged-runner
|
|
682
|
+
* backstop). A worker-death recovery reconnecting MID-step passes the
|
|
683
|
+
* step ceiling instead: hours of legitimate work may remain, and the
|
|
684
|
+
* activity's per-attempt startToClose already bounds it server-side. */
|
|
685
|
+
pollDeadlineMs?: number;
|
|
686
|
+
},
|
|
568
687
|
): Promise<RunningStep<TOutput>> {
|
|
569
688
|
if (!sandbox.commands.connectProcess) {
|
|
570
689
|
throw new Error("reconnectStep requires a provider with background-command support (commands.connectProcess)");
|
|
@@ -572,6 +691,7 @@ export async function reconnectStep<TOutput = unknown>(
|
|
|
572
691
|
const { runnerPid, resultToken } = resume;
|
|
573
692
|
const splitters = makeStreamSplitters(opts, resultToken);
|
|
574
693
|
const signal = opts?.signal;
|
|
694
|
+
const pollDeadlineMs = opts?.pollDeadlineMs;
|
|
575
695
|
|
|
576
696
|
// Re-attach to the suspended runner for LIVE stdout streaming only. This is
|
|
577
697
|
// best-effort: a reconnect that throws after the native resume just means no
|
|
@@ -600,7 +720,10 @@ export async function reconnectStep<TOutput = unknown>(
|
|
|
600
720
|
// runner's durable result file + `/proc` liveness instead — the same
|
|
601
721
|
// authoritative signal the launch-path recovery uses (`pollDurableResult`).
|
|
602
722
|
try {
|
|
603
|
-
const result = await pollDurableResult<TOutput>(sandbox, runnerPid, resultToken,
|
|
723
|
+
const result = await pollDurableResult<TOutput>(sandbox, runnerPid, resultToken, {
|
|
724
|
+
...(signal ? { signal } : {}),
|
|
725
|
+
...(pollDeadlineMs !== undefined ? { deadlineMs: pollDeadlineMs } : {}),
|
|
726
|
+
});
|
|
604
727
|
splitters.flush();
|
|
605
728
|
return result;
|
|
606
729
|
} finally {
|
|
@@ -81,6 +81,14 @@ export function stepResultFilePath(token: string): string {
|
|
|
81
81
|
return `/tmp/wf/step-result-${token}.json`;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/** Sandbox-side pidfile the FOREGROUND launch writes (`echo $$ > …`) before
|
|
85
|
+
* spawning the runner pipeline — the wrapping shell's pid. The recovery path
|
|
86
|
+
* reads it back to probe `/proc/<pid>` liveness when the live stream/wait
|
|
87
|
+
* call dies mid-step (providers with no background-process pid — Vercel). */
|
|
88
|
+
export function stepPidFilePath(token: string): string {
|
|
89
|
+
return `/tmp/wf/step-pid-${token}.pid`;
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
/** Sandbox-side path where the runner's stdout is tee'd as a durable LOG file,
|
|
85
93
|
* keyed by the per-invocation token. The live output rides E2B's connect-web
|
|
86
94
|
* command stream, which THROWS on a compressed large frame (gRPC-web cannot
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Break-glass compliance session wire types (ADR-0051).
|
|
3
|
+
*
|
|
4
|
+
* A team admin has no default access to a scoped (Private/Shared) document.
|
|
5
|
+
* To view one they don't hold a grant on, they open a READ-ONLY,
|
|
6
|
+
* owner-approved, time-boxed compliance session over a scope (one factory, or
|
|
7
|
+
* the whole team). These are the shapes the server's `/api/v1/compliance/*`
|
|
8
|
+
* routes emit — `client.ts` is a thin typed wrapper over them.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type ComplianceScopeKind = "factory" | "team";
|
|
12
|
+
export type ComplianceStatus = "pending" | "active" | "expired" | "revoked";
|
|
13
|
+
|
|
14
|
+
/** One break-glass compliance session. `expiresAt` is null while pending —
|
|
15
|
+
* the TTL clock starts at approval, not request. */
|
|
16
|
+
export interface ComplianceSession {
|
|
17
|
+
id: string;
|
|
18
|
+
teamId: string;
|
|
19
|
+
requestedByUserId: string | null;
|
|
20
|
+
requestedByLabel: string | null;
|
|
21
|
+
approvedByUserId: string | null;
|
|
22
|
+
approvedByLabel: string | null;
|
|
23
|
+
reason: string;
|
|
24
|
+
scopeKind: ComplianceScopeKind;
|
|
25
|
+
/** The factory this session covers (factory scope); null for team scope. */
|
|
26
|
+
scopeRef: string | null;
|
|
27
|
+
status: ComplianceStatus;
|
|
28
|
+
ttlSeconds: number;
|
|
29
|
+
expiresAt: string | null;
|
|
30
|
+
revokedByUserId: string | null;
|
|
31
|
+
revokedByLabel: string | null;
|
|
32
|
+
createdAt: string;
|
|
33
|
+
approvedAt: string | null;
|
|
34
|
+
revokedAt: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One per-document access recorded under an active session — the individual
|
|
38
|
+
* audit trail the session's reasoned entry authorizes. */
|
|
39
|
+
export interface ComplianceAccess {
|
|
40
|
+
id: string;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
/** Nulled when the accessed file is later deleted (ON DELETE SET NULL);
|
|
43
|
+
* `filePath` is the durable snapshot that survives. */
|
|
44
|
+
fileId: string | null;
|
|
45
|
+
filePath: string;
|
|
46
|
+
accessedByUserId: string | null;
|
|
47
|
+
accessedByLabel: string | null;
|
|
48
|
+
accessedAt: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface RequestComplianceSessionInput {
|
|
52
|
+
scopeKind: ComplianceScopeKind;
|
|
53
|
+
/** Required for factory scope; must be omitted for team scope. */
|
|
54
|
+
scopeRef?: string | null;
|
|
55
|
+
reason: string;
|
|
56
|
+
/** Session lifetime once approved — 15 min floor, 7 day ceiling. */
|
|
57
|
+
ttlSeconds: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ListComplianceSessionsOptions {
|
|
61
|
+
status?: ComplianceStatus;
|
|
62
|
+
limit?: number;
|
|
63
|
+
cursor?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ComplianceSessionsPage {
|
|
67
|
+
sessions: ComplianceSession[];
|
|
68
|
+
nextCursor: string | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ListComplianceAccessesOptions {
|
|
72
|
+
limit?: number;
|
|
73
|
+
cursor?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ComplianceAccessesPage {
|
|
77
|
+
accesses: ComplianceAccess[];
|
|
78
|
+
nextCursor: string | null;
|
|
79
|
+
}
|