@kici-dev/agent 0.1.27 → 0.3.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.
Files changed (37) hide show
  1. package/dist/bootstrap/ensure-init-runner.d.ts +23 -22
  2. package/dist/bootstrap/payload-source.d.ts +32 -0
  3. package/dist/bootstrap/probe-platform.d.ts +21 -0
  4. package/dist/bootstrap/restage-agent.d.ts +43 -0
  5. package/dist/bootstrap/run-restage.d.ts +12 -0
  6. package/dist/bootstrap/s3-payload-source.d.ts +35 -0
  7. package/dist/bootstrap/ssh-exec.d.ts +14 -0
  8. package/dist/bootstrap/stage-agent-payload.d.ts +41 -0
  9. package/dist/checkout/changed-files.d.ts +34 -0
  10. package/dist/config.d.ts +36 -14
  11. package/dist/container-ts-loader-hook.js +147710 -0
  12. package/dist/execution/artifacts/artifact-engine.d.ts +51 -0
  13. package/dist/execution/dep-installer.d.ts +3 -3
  14. package/dist/execution/job-runner.d.ts +24 -6
  15. package/dist/execution/log-streamer.d.ts +17 -2
  16. package/dist/execution/rule-evaluator.d.ts +1 -12
  17. package/dist/execution/sandbox/container-hardening.d.ts +80 -0
  18. package/dist/execution/sandbox/container-sandbox.d.ts +54 -0
  19. package/dist/execution/sandbox/container-ts-loader-hook.d.ts +26 -0
  20. package/dist/execution/sandbox/fork-runner.d.ts +14 -0
  21. package/dist/execution/sandbox/index.d.ts +2 -1
  22. package/dist/execution/sandbox/ipc-protocol.d.ts +79 -3
  23. package/dist/execution/sandbox/step-loop.d.ts +4 -0
  24. package/dist/execution/sandbox/types.d.ts +25 -4
  25. package/dist/execution/sandbox/workflow-runner.d.ts +111 -5
  26. package/dist/execution/streaming-zx-log.d.ts +11 -3
  27. package/dist/execution/tmp-gc.d.ts +22 -7
  28. package/dist/execution/workflow-loader.d.ts +32 -1
  29. package/dist/index.js +44 -45
  30. package/dist/provenance/statement-builder.d.ts +3 -2
  31. package/dist/server.d.ts +1 -1
  32. package/dist/server.js +1256 -171
  33. package/dist/workflow-runner-bundle.js +215403 -0
  34. package/dist/workflow-runner.js +886 -244
  35. package/dist/ws/orchestrator-client.d.ts +105 -1
  36. package/package.json +14 -12
  37. package/sbom.spdx.json +1090 -1721
@@ -0,0 +1,51 @@
1
+ import type { ArtifactsApi } from '@kici-dev/sdk';
2
+ import type { ArtifactRejectReason, ArtifactUploadOutcome, ArtifactDownloadOutcome } from '@kici-dev/engine';
3
+ import { type CacheRoots } from '../cache/cache-engine.js';
4
+ /** Outcome of a begin-upload grant request. */
5
+ export interface ArtifactBeginUploadResult {
6
+ outcome: ArtifactUploadOutcome;
7
+ /** Presigned PUT URL — present only on `granted`. */
8
+ uploadUrl?: string;
9
+ /** Storage key echoed back on complete — present only on `granted`. */
10
+ storageKey?: string;
11
+ /** Enforcement-gate rejection reason — present only on an enforcement `rejected`. */
12
+ reason?: ArtifactRejectReason;
13
+ /**
14
+ * Non-enforcement refusal detail — present only on `rejected` when no
15
+ * enforcement `reason` applies (a name that violates the artifact-name
16
+ * contract, orchestrator misconfiguration, or an internal error). A safe,
17
+ * fixed string from the orchestrator; never a raw exception.
18
+ */
19
+ error?: string;
20
+ }
21
+ /** Outcome of a download lookup. */
22
+ export interface ArtifactDownloadLookup {
23
+ outcome: ArtifactDownloadOutcome;
24
+ /** Presigned GET URL — present only on `found`. */
25
+ downloadUrl?: string;
26
+ /** Artifact size in bytes — present only on `found`. */
27
+ sizeBytes?: number;
28
+ /** SHA-256 (hex) of the tarball bytes — present only on `found`. */
29
+ sha256?: string;
30
+ /**
31
+ * Internal-failure detail — present only on `not_found` when the outcome
32
+ * reflects an orchestrator failure rather than a genuinely missing artifact. A
33
+ * safe, fixed string from the orchestrator; never a raw exception.
34
+ */
35
+ error?: string;
36
+ }
37
+ /**
38
+ * Transport the artifacts engine uses to reach the orchestrator over IPC -> WS.
39
+ * Backed by the agent's request/response relay (mirrors {@link CacheTransport}).
40
+ */
41
+ export interface ArtifactTransport {
42
+ /** Request a presigned PUT for `name` at `declaredSizeBytes`; enforced before minting. */
43
+ beginUpload(name: string, declaredSizeBytes: number): Promise<ArtifactBeginUploadResult>;
44
+ /** Confirm the upload finished — records the DB row. */
45
+ completeUpload(name: string, sizeBytes: number, sha256: string, storageKey: string): Promise<void>;
46
+ /** Resolve a named artifact of this run to a presigned GET + its size/sha256. */
47
+ download(name: string): Promise<ArtifactDownloadLookup>;
48
+ }
49
+ /** Build the imperative `ctx.artifacts` API bound to a workDir + transport. */
50
+ export declare function createArtifactsApi(workDir: string, transport: ArtifactTransport, roots?: CacheRoots): ArtifactsApi;
51
+ //# sourceMappingURL=artifact-engine.d.ts.map
@@ -41,9 +41,9 @@ export interface InstallDepsOptions {
41
41
  * Install `.kici/` dependencies inline with the repo's package manager.
42
42
  *
43
43
  * Falls back to this when the dep cache is unavailable or a download fails.
44
- * The install runs with an isolated cache/store directory (created in
45
- * `os.tmpdir()`) to prevent cache poisoning between build jobs; the directory
46
- * is removed after installation.
44
+ * The install runs with an isolated cache/store directory (allocated under the
45
+ * global temp base, which honors `KICI_TMPDIR`) to prevent cache poisoning
46
+ * between build jobs; the directory is removed after installation.
47
47
  *
48
48
  * If `opts.npmRegistries` / `opts.installEnvSecrets` is provided, a job-scoped
49
49
  * `.kici/.npmrc` overlay is synthesized for the install, restored in `finally`,
@@ -1,15 +1,13 @@
1
1
  import type { AgentToOrchestratorMessage, JobDispatch } from '@kici-dev/engine';
2
2
  import type { AppConfig } from '../config.js';
3
3
  import { buildNeedsContext } from '@kici-dev/sdk';
4
- import type { CacheRequestIpc, CacheResponseIpc, ProvenanceRequestIpc, ProvenanceResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc } from './sandbox/index.js';
4
+ import type { CacheRequestIpc, CacheResponseIpc, ProvenanceRequestIpc, ProvenanceResponseIpc, ArtifactRequestIpc, ArtifactResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc } from './sandbox/index.js';
5
5
  /**
6
6
  * Dependencies injected into JobRunner.
7
7
  */
8
8
  export interface JobRunnerDeps {
9
- /** Send function for WS messages (buffered) */
9
+ /** Send function for WS messages (buffered; replayed on reconnect) */
10
10
  send: (msg: AgentToOrchestratorMessage) => void;
11
- /** Send direct function (bypasses buffer, for protocol messages) */
12
- sendDirect: (msg: AgentToOrchestratorMessage) => void;
13
11
  /** Agent config */
14
12
  config: AppConfig;
15
13
  /** Request a pre-signed S3 upload URL from the orchestrator via WS request-response. */
@@ -99,6 +97,14 @@ export interface JobRunnerDeps {
99
97
  * compatibility (callers that don't support provenance).
100
98
  */
101
99
  relayProvenance?: (jobId: string, request: ProvenanceRequestIpc) => Promise<ProvenanceResponseIpc>;
100
+ /**
101
+ * Relay a user-facing artifact request to the orchestrator and await the
102
+ * response. Translates the sandbox `artifacts.request` IPC into the matching
103
+ * `artifacts.upload.request` / `.complete` / `artifacts.download.request` WS
104
+ * message and returns the orchestrator's response mapped back onto the IPC
105
+ * response shape. Optional for backward compatibility.
106
+ */
107
+ requestUserArtifact?: (jobId: string, request: ArtifactRequestIpc) => Promise<ArtifactResponseIpc>;
102
108
  /**
103
109
  * Relay a step-level approval request to the orchestrator and await the
104
110
  * resolution. Translates the sandbox `approval.request` IPC into a
@@ -113,6 +119,17 @@ interface ActiveJob {
113
119
  completionPromise: Promise<void>;
114
120
  runId: string;
115
121
  }
122
+ /**
123
+ * Derive the self-contained runner bundle path from the resolved runner path.
124
+ *
125
+ * The container backend mounts the runner as a single file into the customer
126
+ * job container, so it must run `workflow-runner-bundle.js` (zx + `@kici-dev/*`
127
+ * inlined) rather than the external `workflow-runner.js`, which cannot resolve
128
+ * its bare imports without the agent's node_modules / pnpm workspace. The
129
+ * bundle is a flat sibling emitted alongside the runner by build-service.mjs.
130
+ * bwrap / firecracker keep the external runner (they bind the workspace).
131
+ */
132
+ export declare function resolveRunnerBundlePath(runnerPath: string): string;
116
133
  /**
117
134
  * Build the result-aware `ctx.needs` for a dynamic eval from its frozen upstream
118
135
  * snapshot. Returns undefined for an event-only generator (no snapshot).
@@ -155,7 +172,6 @@ export declare function resolveJobWorkDir(inPlace: boolean, repoUrl: string | un
155
172
  */
156
173
  export declare class JobRunner {
157
174
  private readonly send;
158
- private readonly sendDirect;
159
175
  private readonly config;
160
176
  private readonly requestUploadUrl;
161
177
  private readonly sendUploadComplete;
@@ -168,6 +184,7 @@ export declare class JobRunner {
168
184
  private readonly _sendApiRequest?;
169
185
  private readonly _requestUserCache?;
170
186
  private readonly _relayProvenance?;
187
+ private readonly _requestUserArtifact?;
171
188
  private readonly _sendStepApproval?;
172
189
  /** Tracks running jobs for concurrency and cancellation */
173
190
  readonly activeJobs: Map<string, ActiveJob>;
@@ -222,7 +239,8 @@ export declare class JobRunner {
222
239
  /**
223
240
  * Drive `sandbox.executeJob` with IPC callbacks wired to the WS pipeline.
224
241
  *
225
- * Lazily creates per-step LogStreamers, forwards step + log + event-emit +
242
+ * Lazily populates `logStreamers` (owned by the caller so a throw still
243
+ * leaves the buffered output flushable), forwards step + log + event-emit +
226
244
  * concurrency-report + api-request messages, and emits the
227
245
  * `agent.execution.start` / `agent.execution.end` lifecycle events.
228
246
  */
@@ -1,4 +1,4 @@
1
- import type { AgentLogChunk } from '@kici-dev/engine';
1
+ import { LogStream, type AgentLogChunk } from '@kici-dev/engine';
2
2
  interface LogStreamerOptions {
3
3
  /** Callback to send log.chunk messages */
4
4
  send: (msg: AgentLogChunk) => void;
@@ -47,6 +47,8 @@ export declare class LogStreamer {
47
47
  private flushTimer;
48
48
  private totalBytes;
49
49
  private truncated;
50
+ /** Which stream the currently-buffered lines came from. */
51
+ private bufferStream;
50
52
  /** Number of lines dropped due to backpressure (drop mode). */
51
53
  private droppedCount;
52
54
  /** Whether we are currently in a backpressured state (pause mode). */
@@ -73,8 +75,21 @@ export declare class LogStreamer {
73
75
  /**
74
76
  * Add a line to the buffer. Triggers flush if threshold reached,
75
77
  * otherwise schedules a timer-based flush.
78
+ *
79
+ * A chunk carries a single stream, so a kind flip closes the pending chunk
80
+ * before the new line is buffered. Chunks are delivered in order, so the
81
+ * stdout/stderr interleaving is preserved across that boundary.
82
+ *
83
+ * `bufferStream` is only advanced once the buffer is actually empty. Under
84
+ * pause-mode backpressure `flush()` deliberately leaves the buffer intact
85
+ * until the socket drains, so the flip cannot close the pending chunk;
86
+ * advancing the tag there would relabel the already-buffered lines as the
87
+ * newly-arrived stream. The buffer keeps the tag of its first lines instead,
88
+ * which means a chunk assembled while backpressured reports one stream for
89
+ * lines that came from both — stderr in such a chunk is under-reported as
90
+ * stdout. Separating them needs a queue of pending per-stream chunks.
76
91
  */
77
- addLine(line: string): void;
92
+ addLine(line: string, stream?: LogStream): void;
78
93
  /**
79
94
  * Flush buffered lines as a log.chunk message.
80
95
  * No-op if buffer is empty.
@@ -1,13 +1,2 @@
1
- import type { RuleContext, FanoutPosition } from '@kici-dev/sdk';
2
- /**
3
- * Create RuleContext for agent-side rule evaluation.
4
- *
5
- * @param event - Event payload from the dispatch message
6
- * @param changedFiles - List of files changed in this event
7
- * @param env - Merged environment variables
8
- * @param dispatchInputs - Operator dispatch inputs (`ctx.dispatchInputs`)
9
- * @param fanout - Fan-out position (`ctx.fanout`); undefined on a non-fan-out job
10
- */
11
- export declare function createRuleContext(event: Record<string, unknown>, changedFiles?: string[], env?: Record<string, string | undefined>, dispatchInputs?: Readonly<Record<string, string | number | boolean | null>>, fanout?: FanoutPosition): RuleContext;
12
- export { evaluateRules, type RuleEvaluationResult } from '@kici-dev/sdk';
1
+ export { createRuleContext, ChangedFilesUnavailableError, evaluateRules, type RuleEvaluationResult, } from '@kici-dev/sdk';
13
2
  //# sourceMappingURL=rule-evaluator.d.ts.map
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Container sandbox hardening posture builder.
3
+ *
4
+ * A pure function that produces the dockerode `HostConfig` fragment (plus an
5
+ * optional top-level `User`) applied to every per-job container sandbox. It
6
+ * brings the container backend to parity with the bare-metal bwrap sandbox
7
+ * (`fork-runner.ts`) — which runs rootless, with a read-only system tree,
8
+ * private dev/proc/tmp, and namespace isolation — and exceeds it with cgroup
9
+ * resource caps that bwrap (a namespace tool, not a cgroup tool) cannot set.
10
+ *
11
+ * The default posture ("hardened"):
12
+ * - `CapDrop: ['ALL']` — drop every Linux capability (no default add-back).
13
+ * - `SecurityOpt: ['no-new-privileges']` — a step can never gain privileges via
14
+ * setuid binaries, matching bwrap's `--new-session` lifecycle posture.
15
+ * - `PidsLimit` / `Memory` / `NanoCpus` — cgroup caps bounding fork-bomb, memory
16
+ * and CPU DoS against the host.
17
+ * - `Tmpfs: { '/tmp': '' }` — a private, non-persistent /tmp (mirrors bwrap's
18
+ * `--tmpfs /tmp`).
19
+ * - `User` — honored as configured on the image; an explicit override sets it,
20
+ * but a root image is never silently rewritten (parity is best-effort here:
21
+ * bwrap runs as the unprivileged invoker uid by construction).
22
+ * - `ReadonlyRootfs` — OPT-IN only (many images write outside /workspace: npm
23
+ * cache, /home, tool state), enabled via the resolved `readonlyRootfs` input.
24
+ * - `NetworkMode` — `none` for the isolated network posture (bwrap's
25
+ * `--unshare-net`); the default (bridge) otherwise.
26
+ *
27
+ * The `grant` input is the dispatch-resolved per-job escape hatch (allow-listed
28
+ * orchestrator-side). It is honored strictly additively on top of the hardened
29
+ * baseline: requested capabilities become `CapAdd` entries while `CapDrop:
30
+ * ['ALL']` still applies, and a grant may switch the network to `host` or set
31
+ * an explicit user / read-only rootfs. The builder never reads an allow-list —
32
+ * it applies whatever grant dispatch already resolved (single enforcement
33
+ * point). When `hardened` is false (the documented-temporary
34
+ * `KICI_SANDBOX_HARDENED` rollback affordance), the builder emits an empty
35
+ * posture, reproducing the unhardened container behavior.
36
+ */
37
+ import type Docker from 'dockerode';
38
+ import type { ResolvedSandboxGrant, SandboxNetworkMode } from '@kici-dev/engine';
39
+ export type { ResolvedSandboxGrant, SandboxNetworkMode };
40
+ /** Inputs to the hardening builder, already resolved from agent config + dispatch. */
41
+ export interface SandboxHardeningOptions {
42
+ /**
43
+ * Master switch. When false (the `KICI_SANDBOX_HARDENED=false` rollback
44
+ * affordance) the builder emits an empty posture — the legacy unhardened
45
+ * container behavior. Defaults are ON in the shipping config.
46
+ */
47
+ hardened: boolean;
48
+ /** Opt-in read-only rootfs (config `KICI_SANDBOX_READONLY_ROOTFS`). */
49
+ readonlyRootfs: boolean;
50
+ /** Explicit user override (config `KICI_SANDBOX_USER`); honors the image user when unset. */
51
+ user?: string;
52
+ /** Max PIDs in the container cgroup. */
53
+ pidsLimit: number;
54
+ /** Memory cap in bytes for the container cgroup. */
55
+ memoryBytes: number;
56
+ /** CPU cap in nano-CPUs (1 CPU = 1_000_000_000). */
57
+ nanoCpus: number;
58
+ /** Config-derived network posture (`isolated` → `none`, else `default`). */
59
+ networkMode: SandboxNetworkMode;
60
+ /** Optional dispatch-resolved escape hatch (Sub-wish B populates this at dispatch). */
61
+ grant?: ResolvedSandboxGrant;
62
+ }
63
+ /** The builder output: a HostConfig fragment merged into createContainer, plus an optional top-level User. */
64
+ export interface ContainerHardening {
65
+ hostConfig: Partial<Docker.HostConfig>;
66
+ user?: string;
67
+ }
68
+ /** Default cgroup caps — operator-visible constants, overridable via scaler `resources`. */
69
+ export declare const DEFAULT_PIDS_LIMIT = 512;
70
+ export declare const DEFAULT_MEMORY_BYTES: number;
71
+ export declare const DEFAULT_NANO_CPUS: number;
72
+ /**
73
+ * Build the hardened HostConfig fragment for a job sandbox container.
74
+ *
75
+ * Pure and side-effect-free: given resolved inputs it returns the fields the
76
+ * container-sandbox merges into `docker.createContainer`. When `hardened` is
77
+ * false it returns an empty posture (the rollback affordance).
78
+ */
79
+ export declare function buildContainerHardening(opts: SandboxHardeningOptions): ContainerHardening;
80
+ //# sourceMappingURL=container-hardening.d.ts.map
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import Docker from 'dockerode';
19
19
  import type { ExecutionSandbox, SandboxSetupOptions, JobExecutionOptions, JobExecutionResult } from './types.js';
20
+ import { type SandboxHardeningOptions } from './container-hardening.js';
20
21
  interface ContainerSandboxOptions {
21
22
  /** Dockerode instance (from orchestrator/scaler or created locally). */
22
23
  docker: Docker;
@@ -26,21 +27,39 @@ interface ContainerSandboxOptions {
26
27
  runnerPath: string;
27
28
  /** Mount target inside container (default: /opt/kici/workflow-runner.js). */
28
29
  runnerMountPath?: string;
30
+ /**
31
+ * Path to the pure-JS container loader-hook bundle on the HOST (bind-mounted
32
+ * read-only). Defaults to `container-ts-loader-hook.js` next to `runnerPath`.
33
+ */
34
+ hookPath?: string;
29
35
  /** Pre-sanitized environment variables for the container. */
30
36
  env: Record<string, string>;
31
37
  /** Whether to keep failed containers for debugging. */
32
38
  keepFailed?: boolean;
33
39
  /** Job ID for container labeling and orphan cleanup. */
34
40
  jobId?: string;
41
+ /**
42
+ * Resolved hardening posture for the job container (cap-drop, no-new-privileges,
43
+ * cgroup caps, tmpfs, user, network). When omitted, no hardening is applied —
44
+ * the production caller (job-runner) always supplies this from agent config so
45
+ * the secure-by-default posture is in force; leaving it optional keeps the
46
+ * constructor testable and lets non-production callers opt out explicitly.
47
+ */
48
+ hardening?: SandboxHardeningOptions;
35
49
  }
36
50
  export declare class ContainerSandbox implements ExecutionSandbox {
37
51
  private readonly docker;
38
52
  private readonly image;
39
53
  private readonly runnerPath;
40
54
  private readonly runnerMountPath;
55
+ /** Host path to the pure-JS container loader-hook bundle (bind-mounted :ro). */
56
+ private readonly hookHostPath;
41
57
  private readonly env;
42
58
  private readonly keepFailed;
43
59
  private readonly jobId;
60
+ private readonly hardening?;
61
+ /** Resolved container user (image-user override / grant), applied to createContainer + each exec. */
62
+ private resolvedUser?;
44
63
  /** The running container instance (set during setup). */
45
64
  private container;
46
65
  /** The active exec stream (set during executeJob, used for abort). */
@@ -50,7 +69,42 @@ export declare class ContainerSandbox implements ExecutionSandbox {
50
69
  /** Container name for logging/debugging. */
51
70
  private containerName;
52
71
  constructor(options: ContainerSandboxOptions);
72
+ /**
73
+ * Ensure the job's container image is present locally, pulling it on demand
74
+ * when it is not.
75
+ *
76
+ * dockerode's `createContainer` — unlike `docker run` / `podman run` — never
77
+ * auto-pulls a missing image; it fails with `(HTTP code 404) ... No such
78
+ * image`. A bare-metal executor that aggressively prunes unused images under
79
+ * disk pressure can leave a container job with nothing to run, so the agent
80
+ * pulls the image itself. Already-present images (the common case, and how
81
+ * private images pre-pulled with registry auth stay working) skip the pull.
82
+ */
83
+ private ensureImagePresent;
53
84
  setup(options: SandboxSetupOptions): Promise<void>;
85
+ /**
86
+ * Build the container's read-only bind list.
87
+ *
88
+ * The workspace is NOT bound here — it is a container-owned anonymous volume
89
+ * (`Volumes: { '/workspace': {} }` on the container config), so the container
90
+ * user can write it on every runtime with `CapDrop: ['ALL']` intact. This
91
+ * method binds the workflow runner (read-only) plus two parity affordances
92
+ * that mirror the bare-metal bwrap sandbox — both strictly additive and gated,
93
+ * so the default posture for a production (https-source, bridge-network) job is
94
+ * unchanged:
95
+ *
96
+ * - **`file://` clone-source dir(s)** (`options.extraReadOnlyBinds`): the
97
+ * workflow runner clones the repo from inside the container, so a local
98
+ * `file://` source dir must be exposed read-only or `git clone` fails.
99
+ * Empty for https/ssh remotes — mirrors fork-runner's `extraReadOnlyBinds`.
100
+ * - **Host name-resolution files under host networking**: when the effective
101
+ * network posture is `host` (`KICI_SANDBOX_NETWORK=host`, or a per-job host
102
+ * grant), bind the host's `/etc/hosts` (+ `/etc/nsswitch.conf`) read-only so
103
+ * an `/etc/hosts`-only name the host resolves — e.g. a private registry —
104
+ * resolves inside the container too. Mirrors fork-runner's
105
+ * `--ro-bind /etc/hosts` for the bwrap host-network mode.
106
+ */
107
+ private buildBinds;
54
108
  executeJob(options: JobExecutionOptions): Promise<JobExecutionResult>;
55
109
  /**
56
110
  * Phase 1 of executeJob: create the docker exec, start it in hijack mode,
@@ -0,0 +1,26 @@
1
+ type ResolveContext = {
2
+ parentURL?: string;
3
+ conditions: string[];
4
+ importAttributes: Record<string, string>;
5
+ };
6
+ type ResolveResult = {
7
+ url: string;
8
+ shortCircuit?: boolean;
9
+ format?: string | null;
10
+ };
11
+ type NextResolve = (specifier: string, context?: ResolveContext) => ResolveResult | Promise<ResolveResult>;
12
+ type LoadContext = {
13
+ format?: string | null;
14
+ importAttributes: Record<string, string>;
15
+ conditions: string[];
16
+ };
17
+ type LoadResult = {
18
+ format: string;
19
+ source?: string | ArrayBuffer | Uint8Array;
20
+ shortCircuit?: boolean;
21
+ };
22
+ type NextLoad = (url: string, context?: LoadContext) => LoadResult | Promise<LoadResult>;
23
+ export declare function resolve(specifier: string, context: ResolveContext, nextResolve: NextResolve): ResolveResult | Promise<ResolveResult>;
24
+ export declare function load(url: string, context: LoadContext, nextLoad: NextLoad): Promise<LoadResult>;
25
+ export {};
26
+ //# sourceMappingURL=container-ts-loader-hook.d.ts.map
@@ -83,6 +83,20 @@ export declare function buildRequest(dispatch: JobDispatch, workDir: string): Jo
83
83
  * is simpler and more secure than selective blocking.
84
84
  */
85
85
  export declare function buildBwrapArgs(workDir: string, nodeExecPath: string, networkIsolation?: boolean, runnerPath?: string, extraReadOnlyBinds?: string[]): string[];
86
+ /**
87
+ * Derive the read-only bind path(s) for a `file://` clone source. The workflow
88
+ * runner clones the repo from inside the sandbox, so a local `file://` URL's
89
+ * source directory must be exposed read-only or `git clone` fails with
90
+ * `does not appear to be a git repository`. Returns `[]` for non-`file://`
91
+ * remotes (https/ssh need no host bind) and for a malformed `file://` URL (the
92
+ * clone then surfaces the real error rather than being masked here).
93
+ *
94
+ * Shared by both sandboxes that clone a local source: the bare-metal (bwrap)
95
+ * backend threads the result into `buildBwrapArgs`'s `extraReadOnlyBinds`, and
96
+ * the container backend binds each `<dir>:<dir>:ro` into the job container —
97
+ * the same clone-source affordance across both isolation models.
98
+ */
99
+ export declare function fileCloneSourceBinds(repoUrl: string | undefined): string[];
86
100
  /**
87
101
  * Spawn the workflow runner as a child process with IPC channel.
88
102
  *
@@ -5,10 +5,11 @@
5
5
  * import { BareMetalSandbox, ContainerSandbox, buildSanitizedEnv } from './sandbox/index.js';
6
6
  */
7
7
  export type { ExecutionSandbox, SandboxSetupOptions, JobExecutionOptions, JobExecutionResult, SandboxStepResult, } from './types.js';
8
- export type { RunnerToAgentMessage, AgentToRunnerMessage, EventEmitRequest, EventEmitResponse, CacheRequestIpc, CacheResponseIpc, ProvenanceRequestIpc, ProvenanceResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc, JobExecutionRequest, } from './ipc-protocol.js';
8
+ export type { RunnerToAgentMessage, AgentToRunnerMessage, EventEmitRequest, EventEmitResponse, CacheRequestIpc, CacheResponseIpc, ProvenanceRequestIpc, ProvenanceResponseIpc, ArtifactRequestIpc, ArtifactResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc, JobExecutionRequest, } from './ipc-protocol.js';
9
9
  export { buildSanitizedEnv } from './env-sanitizer.js';
10
10
  export { ALLOWED_SYSTEM_VARS, KICI_AGENT_ENV_PREFIX, AGENT_REQUIRED_KICI_VARS, } from '@kici-dev/engine';
11
11
  export { BareMetalSandbox } from './bare-metal-sandbox.js';
12
12
  export { FirecrackerSandbox } from './firecracker-sandbox.js';
13
13
  export { ContainerSandbox } from './container-sandbox.js';
14
+ export { fileCloneSourceBinds } from './fork-runner.js';
14
15
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { CheckMode, CheckStepOutcome } from '@kici-dev/engine';
1
+ import type { CheckMode, CheckStepOutcome, LogStream } from '@kici-dev/engine';
2
2
  import type { SandboxStepResult } from './types.js';
3
3
  /**
4
4
  * Structured clone auth. Wire-compatible with `gitAuthSchema` on the
@@ -78,6 +78,8 @@ interface LogLineMessage {
78
78
  type: 'log.line';
79
79
  stepIndex: number;
80
80
  line: string;
81
+ /** Which subprocess stream the line came from. Absent means stdout. */
82
+ stream?: LogStream;
81
83
  }
82
84
  /**
83
85
  * Discriminator for {@link StepSecretMountMessage} -- distinguishes a bare
@@ -248,7 +250,32 @@ export interface ProvenanceRequestIpc {
248
250
  /** Ephemeral public key JWK the envelope was signed with. `defer` only. */
249
251
  publicKey?: unknown;
250
252
  }
251
- export type RunnerToAgentMessage = ReadyMessage | StepStartMessage | StepCompleteMessage | LogLineMessage | StepSecretMountMessage | JobCompleteMessage | EventEmitRequest | ConcurrencyReportMessage | AgentApiRequestIpc | CacheRequestIpc | ProvenanceRequestIpc | StepApprovalRequestIpc;
253
+ /** Which artifact operation to relay. */
254
+ export type ArtifactRequestOp = 'beginUpload' | 'completeUpload' | 'download';
255
+ /**
256
+ * Request a user-facing artifact operation (runner -> agent). The agent relays
257
+ * it over the WS as an `artifacts.upload.request` / `.complete` /
258
+ * `artifacts.download.request` and pipes the response back as an
259
+ * {@link ArtifactResponseIpc}. Mirrors the {@link CacheRequestIpc} relay pattern.
260
+ */
261
+ export interface ArtifactRequestIpc {
262
+ type: 'artifacts.request';
263
+ /** UUID for correlating the response. */
264
+ requestId: string;
265
+ /** Which artifact operation to perform. */
266
+ op: ArtifactRequestOp;
267
+ /** Artifact name (all ops). */
268
+ name: string;
269
+ /** Packed tarball size in bytes — drives the enforcement gates. `beginUpload` only. */
270
+ declaredSizeBytes?: number;
271
+ /** Confirmed tarball size in bytes. `completeUpload` only. */
272
+ sizeBytes?: number;
273
+ /** SHA-256 of the tarball bytes. `completeUpload` only. */
274
+ sha256?: string;
275
+ /** Storage key echoed from the grant response. `completeUpload` only. */
276
+ storageKey?: string;
277
+ }
278
+ export type RunnerToAgentMessage = ReadyMessage | StepStartMessage | StepCompleteMessage | LogLineMessage | StepSecretMountMessage | JobCompleteMessage | EventEmitRequest | ConcurrencyReportMessage | AgentApiRequestIpc | CacheRequestIpc | ProvenanceRequestIpc | ArtifactRequestIpc | StepApprovalRequestIpc;
252
279
  /** Instruct the workflow runner to execute a job. */
253
280
  interface ExecuteMessage {
254
281
  type: 'execute';
@@ -353,7 +380,50 @@ export interface ProvenanceResponseIpc {
353
380
  /** Error description (present when the relay or orchestrator failed). */
354
381
  error?: string;
355
382
  }
356
- export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResponse | ConcurrencyAckMessage | AgentApiResponseIpc | CacheResponseIpc | ProvenanceResponseIpc | StepApprovalResolvedIpc;
383
+ /**
384
+ * Response to an {@link ArtifactRequestIpc} (agent -> runner). Relayed from the
385
+ * orchestrator's `artifacts.upload.response` / `artifacts.download.response` WS
386
+ * message. Carries the union of the upload-grant (`uploadOutcome` / `uploadUrl`
387
+ * / `storageKey` / `reason`) and download-lookup (`downloadOutcome` /
388
+ * `downloadUrl` / `sizeBytes` / `sha256`) fields; `completeUpload` resolves with
389
+ * an empty (no-field) response. `error` is set when the relay itself failed;
390
+ * `rejectionDetail` carries an orchestrator-side non-enforcement explanation
391
+ * that the workflow-facing render surfaces without throwing a relay error.
392
+ */
393
+ export interface ArtifactResponseIpc {
394
+ type: 'artifacts.response';
395
+ /** Matches the original request's requestId. */
396
+ requestId: string;
397
+ /** beginUpload: `granted` (presigned PUT minted) or `rejected` (named reason). */
398
+ uploadOutcome?: 'granted' | 'rejected';
399
+ /** beginUpload: presigned PUT URL (present only on `granted`). */
400
+ uploadUrl?: string;
401
+ /** beginUpload: storage key to echo back on complete (present only on `granted`). */
402
+ storageKey?: string;
403
+ /** beginUpload: enforcement-gate refusal reason (present only on `rejected`). */
404
+ reason?: 'duplicate_name' | 'size_cap' | 'run_cap' | 'org_quota';
405
+ /**
406
+ * Orchestrator non-enforcement refusal detail — set when a `rejected` upload
407
+ * or a `not_found` download reflects a name that violates the artifact-name
408
+ * contract, or an orchestrator problem (artifacts not configured, unresolvable
409
+ * run, internal error), rather than an enforcement gate or a genuinely missing
410
+ * artifact. Distinct from `error` below, which is a relay/transport failure
411
+ * the sandbox turns into a thrown error: this field flows into the normal
412
+ * rejection render instead.
413
+ */
414
+ rejectionDetail?: string;
415
+ /** download: `found` (presigned GET minted) or `not_found`. */
416
+ downloadOutcome?: 'found' | 'not_found';
417
+ /** download: presigned GET URL (present only on `found`). */
418
+ downloadUrl?: string;
419
+ /** download: artifact size in bytes (present only on `found`). */
420
+ sizeBytes?: number;
421
+ /** download: SHA-256 of the tarball bytes (present only on `found`). */
422
+ sha256?: string;
423
+ /** Error description (present when the relay or orchestrator failed). */
424
+ error?: string;
425
+ }
426
+ export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResponse | ConcurrencyAckMessage | AgentApiResponseIpc | CacheResponseIpc | ProvenanceResponseIpc | ArtifactResponseIpc | StepApprovalResolvedIpc;
357
427
  /**
358
428
  * All data the workflow runner needs to execute a job inside the sandbox.
359
429
  *
@@ -528,6 +598,12 @@ export interface JobExecutionRequest {
528
598
  hasConcurrencyGroup?: boolean;
529
599
  /** Concurrency group evaluation timeout in milliseconds (default: 30000). */
530
600
  concurrencyEvaluationTimeoutMs?: number;
601
+ /**
602
+ * Orchestrator-resolved concurrency-slot wait timeout (ms), pushed on
603
+ * `job.dispatch` from the fleet-wide `cluster_settings.concurrency_wait_timeout_ms`.
604
+ * Absent for older orchestrators — the runner falls back to its own default.
605
+ */
606
+ concurrencyWaitTimeoutMs?: number;
531
607
  /** Git branch for concurrency group context. */
532
608
  branch?: string;
533
609
  /** Plain outputs from upstream jobs (keyed by job name, then by step name). For ctx.jobOutputs(). */
@@ -208,6 +208,10 @@ interface StepLoopResult {
208
208
  stepResults: SandboxStepResult[];
209
209
  failureReason?: string;
210
210
  }
211
+ /** Most lines of a failing step's error message that reach the run log. */
212
+ export declare const STEP_FAILURE_LOG_MAX_LINES = 100;
213
+ /** Most characters of a failing step's error message that reach the run log. */
214
+ export declare const STEP_FAILURE_LOG_MAX_CHARS = 8192;
211
215
  /**
212
216
  * Per-step iteration outcome returned by `runStepIteration`.
213
217
  */
@@ -1,5 +1,5 @@
1
- import type { JobDispatch } from '@kici-dev/engine';
2
- import type { EventEmitRequest, EventEmitResponse, ConcurrencyReportMessage, ConcurrencyAckMessage, CacheRequestIpc, CacheResponseIpc, ProvenanceRequestIpc, ProvenanceResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc } from './ipc-protocol.js';
1
+ import type { JobDispatch, LogStream } from '@kici-dev/engine';
2
+ import type { EventEmitRequest, EventEmitResponse, ConcurrencyReportMessage, ConcurrencyAckMessage, CacheRequestIpc, CacheResponseIpc, ProvenanceRequestIpc, ProvenanceResponseIpc, ArtifactRequestIpc, ArtifactResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc } from './ipc-protocol.js';
3
3
  /**
4
4
  * Common interface for all execution sandbox backends.
5
5
  *
@@ -50,6 +50,15 @@ export interface SandboxSetupOptions {
50
50
  workDir: string;
51
51
  /** Sanitized environment variables (user env + secrets, NO agent credentials). */
52
52
  env: Record<string, string>;
53
+ /**
54
+ * Extra read-only host paths to expose in the sandbox beyond the workspace +
55
+ * runner — the `file://` clone-source dir(s) so the in-sandbox `git clone`
56
+ * can read a local source. Derived from the dispatch `repoUrl` by the
57
+ * job-runner (empty for https/ssh remotes). The container backend binds each
58
+ * as `<dir>:<dir>:ro`; the bare-metal backend derives its own equivalent
59
+ * inside `executeJob`, so it ignores this field.
60
+ */
61
+ extraReadOnlyBinds?: string[];
53
62
  }
54
63
  /** Options for executing a job inside the sandbox. */
55
64
  export interface JobExecutionOptions {
@@ -57,8 +66,11 @@ export interface JobExecutionOptions {
57
66
  dispatch: JobDispatch;
58
67
  /** Callback for real-time step status updates (start, success, failed). */
59
68
  onStepStatus: (stepIndex: number, name: string, state: string, data?: Record<string, unknown>) => void;
60
- /** Callback for real-time log line forwarding. */
61
- onLogLine: (stepIndex: number, line: string) => void;
69
+ /**
70
+ * Callback for real-time log line forwarding. `stream` names the subprocess
71
+ * stream the line came from; absent means stdout.
72
+ */
73
+ onLogLine: (stepIndex: number, line: string, stream?: LogStream) => void;
62
74
  /** Abort signal for cancellation. */
63
75
  signal: AbortSignal;
64
76
  /**
@@ -97,6 +109,15 @@ export interface JobExecutionOptions {
97
109
  * working — the runner falls back to a "not configured" error response.
98
110
  */
99
111
  onProvenanceRequest?: (request: ProvenanceRequestIpc) => Promise<ProvenanceResponseIpc>;
112
+ /**
113
+ * Callback for relaying a user-facing artifact request from the sandbox to the
114
+ * orchestrator. The sandbox runner sends `artifacts.request` IPC; the agent
115
+ * wraps it in the matching `artifacts.upload.*` / `artifacts.download.*` WS
116
+ * message and forwards to the orchestrator. Optional so harnesses that don't
117
+ * thread artifacts keep working — the runner falls back to a "not configured"
118
+ * error response.
119
+ */
120
+ onArtifactRequest?: (request: ArtifactRequestIpc) => Promise<ArtifactResponseIpc>;
100
121
  /**
101
122
  * Callback for relaying a step-level approval request from the sandbox to the
102
123
  * orchestrator. The sandbox runner sends `approval.request` IPC; the agent