@kici-dev/agent 0.0.0 → 0.1.1
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/LICENSE +661 -0
- package/README.md +1 -6
- package/dist/checkout/git-clone.d.ts +59 -0
- package/dist/checkout/ssh-auth.d.ts +34 -0
- package/dist/config.d.ts +109 -0
- package/dist/execution/console-capture.d.ts +35 -0
- package/dist/execution/dep-installer.d.ts +44 -0
- package/dist/execution/dep-packer.d.ts +25 -0
- package/dist/execution/dep-restore.d.ts +85 -0
- package/dist/execution/download.d.ts +29 -0
- package/dist/execution/dynamic-job-serializer.d.ts +51 -0
- package/dist/execution/hook-executor.d.ts +46 -0
- package/dist/execution/init-runner.d.ts +33 -0
- package/dist/execution/job-runner.d.ts +266 -0
- package/dist/execution/log-streamer.d.ts +126 -0
- package/dist/execution/npm-registry-config.d.ts +63 -0
- package/dist/execution/npm-resolver.d.ts +40 -0
- package/dist/execution/overlay-applier.d.ts +51 -0
- package/dist/execution/rule-evaluator.d.ts +11 -0
- package/dist/execution/sandbox/bare-metal-sandbox.d.ts +69 -0
- package/dist/execution/sandbox/container-sandbox.d.ts +100 -0
- package/dist/execution/sandbox/env-sanitizer.d.ts +43 -0
- package/dist/execution/sandbox/firecracker-sandbox.d.ts +65 -0
- package/dist/execution/sandbox/fork-runner.d.ts +94 -0
- package/dist/execution/sandbox/index.d.ts +14 -0
- package/dist/execution/sandbox/ipc-protocol.d.ts +311 -0
- package/dist/execution/sandbox/log-masker.d.ts +45 -0
- package/dist/execution/sandbox/secret-encryption.d.ts +37 -0
- package/dist/execution/sandbox/secret-merge.d.ts +18 -0
- package/dist/execution/sandbox/step-loop.d.ts +77 -0
- package/dist/execution/sandbox/types.d.ts +142 -0
- package/dist/execution/sandbox/workflow-runner.d.ts +17 -0
- package/dist/execution/source-packer.d.ts +18 -0
- package/dist/execution/source-restore.d.ts +23 -0
- package/dist/execution/timeout-util.d.ts +11 -0
- package/dist/execution/workflow-loader.d.ts +70 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +128 -0
- package/dist/metrics/metrics-reporter.d.ts +32 -0
- package/dist/metrics/prometheus.d.ts +95 -0
- package/dist/routes/health.d.ts +27 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +5347 -0
- package/dist/workflow-runner.js +2978 -0
- package/dist/ws/event-buffer.d.ts +16 -0
- package/dist/ws/log-buffer.d.ts +15 -0
- package/dist/ws/orchestrator-client.d.ts +269 -0
- package/package.json +59 -6
- package/sbom.spdx.json +10125 -0
- package/index.js +0 -3
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container execution sandbox implementation.
|
|
3
|
+
*
|
|
4
|
+
* The strongest isolation model: the agent runs on the host while the entire
|
|
5
|
+
* job lifecycle (clone, dependency install, compile, step execution) runs
|
|
6
|
+
* inside a disposable Docker/Podman container. Communication uses stdin/stdout
|
|
7
|
+
* JSON-lines via dockerode's exec API.
|
|
8
|
+
*
|
|
9
|
+
* Key properties:
|
|
10
|
+
* - Container stays alive for the entire job (sleep infinity)
|
|
11
|
+
* - Workflow runner is bind-mounted read-only into the container
|
|
12
|
+
* - Agent-internal credentials (KICI_*, KICI_DATABASE_URL, etc.) NEVER enter the container
|
|
13
|
+
* - IPC uses demuxed Docker stream with JSON-line parsing on stdout
|
|
14
|
+
*
|
|
15
|
+
* The container image MUST have Node.js installed (a kici/runner base image
|
|
16
|
+
* is deferred -- for now this is a documented requirement).
|
|
17
|
+
*/
|
|
18
|
+
import Docker from 'dockerode';
|
|
19
|
+
import type { ExecutionSandbox, SandboxSetupOptions, JobExecutionOptions, JobExecutionResult } from './types.js';
|
|
20
|
+
interface ContainerSandboxOptions {
|
|
21
|
+
/** Dockerode instance (from orchestrator/scaler or created locally). */
|
|
22
|
+
docker: Docker;
|
|
23
|
+
/** Container image to use (from job config or scaler label-set). */
|
|
24
|
+
image: string;
|
|
25
|
+
/** Path to workflow-runner.js on the HOST (will be bind-mounted). */
|
|
26
|
+
runnerPath: string;
|
|
27
|
+
/** Mount target inside container (default: /opt/kici/workflow-runner.js). */
|
|
28
|
+
runnerMountPath?: string;
|
|
29
|
+
/** Pre-sanitized environment variables for the container. */
|
|
30
|
+
env: Record<string, string>;
|
|
31
|
+
/** Whether to keep failed containers for debugging. */
|
|
32
|
+
keepFailed?: boolean;
|
|
33
|
+
/** Job ID for container labeling and orphan cleanup. */
|
|
34
|
+
jobId?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare class ContainerSandbox implements ExecutionSandbox {
|
|
37
|
+
private readonly docker;
|
|
38
|
+
private readonly image;
|
|
39
|
+
private readonly runnerPath;
|
|
40
|
+
private readonly runnerMountPath;
|
|
41
|
+
private readonly env;
|
|
42
|
+
private readonly keepFailed;
|
|
43
|
+
private readonly jobId;
|
|
44
|
+
/** The running container instance (set during setup). */
|
|
45
|
+
private container;
|
|
46
|
+
/** The active exec stream (set during executeJob, used for abort). */
|
|
47
|
+
private execStream;
|
|
48
|
+
/** Whether the job failed (used in teardown for keepFailed). */
|
|
49
|
+
private jobFailed;
|
|
50
|
+
/** Container name for logging/debugging. */
|
|
51
|
+
private containerName;
|
|
52
|
+
constructor(options: ContainerSandboxOptions);
|
|
53
|
+
setup(options: SandboxSetupOptions): Promise<void>;
|
|
54
|
+
executeJob(options: JobExecutionOptions): Promise<JobExecutionResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Phase 1 of executeJob: create the docker exec, start it in hijack mode,
|
|
57
|
+
* demux the multiplexed stream into stdout / stderr passthroughs, capture
|
|
58
|
+
* stderr lines for crash diagnostics, and install the abort listener.
|
|
59
|
+
*/
|
|
60
|
+
private attachExecStream;
|
|
61
|
+
/**
|
|
62
|
+
* Phase 2 of executeJob: drive the readline IPC loop until job.complete,
|
|
63
|
+
* exec exit, or crash. Returns the final job status, accumulated step
|
|
64
|
+
* results, and any captured outputs.
|
|
65
|
+
*/
|
|
66
|
+
private awaitJobCompletion;
|
|
67
|
+
/**
|
|
68
|
+
* Dispatch one parsed RunnerToAgentMessage. Mutates `state`, `stepNames`,
|
|
69
|
+
* and `stepResults` in place; writes responses back through `stream` for
|
|
70
|
+
* the relay messages (event.emit / concurrency.report / agent.api.request).
|
|
71
|
+
*
|
|
72
|
+
* Returns `true` when the caller should resolve the awaitJobCompletion
|
|
73
|
+
* promise (only for `job.complete`); `false` otherwise.
|
|
74
|
+
*/
|
|
75
|
+
private dispatchRunnerMessage;
|
|
76
|
+
/**
|
|
77
|
+
* Phase 3 of executeJob: apply abort-signal short-circuit, set jobFailed,
|
|
78
|
+
* and assemble the final JobExecutionResult.
|
|
79
|
+
*/
|
|
80
|
+
private buildExecutionResult;
|
|
81
|
+
abort(): Promise<void>;
|
|
82
|
+
teardown(): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Send the execute request to the workflow runner via the exec's stdin.
|
|
85
|
+
*
|
|
86
|
+
* The runner in stdio mode reads from stdin. We write a single JSON object
|
|
87
|
+
* (the execute message) followed by a newline, then signal end of input.
|
|
88
|
+
*
|
|
89
|
+
* Reuses buildRequest() from fork-runner.ts to ensure consistent field
|
|
90
|
+
* mapping from JobDispatch to JobExecutionRequest.
|
|
91
|
+
*/
|
|
92
|
+
private sendExecuteRequest;
|
|
93
|
+
/**
|
|
94
|
+
* Handle abort: write abort message to stdin, wait for grace period,
|
|
95
|
+
* then kill the container if still running.
|
|
96
|
+
*/
|
|
97
|
+
private handleAbort;
|
|
98
|
+
}
|
|
99
|
+
export {};
|
|
100
|
+
//# sourceMappingURL=container-sandbox.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment sanitization for sandbox execution.
|
|
3
|
+
*
|
|
4
|
+
* Uses an explicit allowlist approach: only known-safe system variables are
|
|
5
|
+
* copied from the host process.env. Agent-internal credentials (KICI_*,
|
|
6
|
+
* DATABASE_URL, PLATFORM_TOKEN, WEBHOOK_SECRET, etc.) are NEVER included.
|
|
7
|
+
*
|
|
8
|
+
* 7-layer merge precedence (later overrides earlier):
|
|
9
|
+
* 1. Allowed system vars from process.env
|
|
10
|
+
* 2. Sandbox defaults (FORCE_COLOR=1, etc.)
|
|
11
|
+
* 3. KICI_* system vars (orchestrator-generated, from userEnv)
|
|
12
|
+
* 4. Org-level environment vars (from orchestrator via environmentVars)
|
|
13
|
+
* 5. Source-level environment overrides (merged into environmentVars by orchestrator)
|
|
14
|
+
* 6. Job env (from lock file env field, evaluated by orchestrator)
|
|
15
|
+
* 7. setEnv() calls (runtime -- applied at step execution, not here)
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Build a sanitized environment for sandbox execution.
|
|
19
|
+
*
|
|
20
|
+
* Constructs an environment from scratch using the 7-layer merge:
|
|
21
|
+
* 1. Explicitly allowed system variables from process.env
|
|
22
|
+
* 2. Sandbox default variables (FORCE_COLOR, etc.)
|
|
23
|
+
* 3. User-defined env vars (KICI_* system vars from orchestrator)
|
|
24
|
+
* 4-5. Environment vars (org-level + source overrides, pre-merged by orchestrator)
|
|
25
|
+
* 6. Job env (SDK-defined, evaluated by orchestrator)
|
|
26
|
+
* 7. setEnv() calls (runtime -- not handled here)
|
|
27
|
+
*
|
|
28
|
+
* Secrets are NOT injected into environment variables by this function.
|
|
29
|
+
* They flow through IPC to ctx.secrets and are only exposed via ctx.secrets.expose().
|
|
30
|
+
*
|
|
31
|
+
* Agent-internal credentials (KICI_ORCHESTRATOR_URL, DATABASE_URL,
|
|
32
|
+
* PLATFORM_TOKEN, WEBHOOK_SECRET, etc.) are never included because they
|
|
33
|
+
* are not in the allowlist.
|
|
34
|
+
*
|
|
35
|
+
* @param userEnv - Environment variables from workflow config and orchestrator
|
|
36
|
+
* @param options - Optional extended options for environment layers
|
|
37
|
+
* @returns A new Record with only safe environment variables
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildSanitizedEnv(userEnv: Record<string, string>, options?: {
|
|
40
|
+
environmentVars?: Record<string, string>;
|
|
41
|
+
jobEnv?: Record<string, string>;
|
|
42
|
+
}): Record<string, string>;
|
|
43
|
+
//# sourceMappingURL=env-sanitizer.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firecracker execution sandbox (defense-in-depth).
|
|
3
|
+
*
|
|
4
|
+
* The Firecracker backend already provides VM-level isolation (separate kernel,
|
|
5
|
+
* filesystem, network namespace). This sandbox adds defense-in-depth by running
|
|
6
|
+
* the workflow runner as a child process with sanitized environment inside the VM.
|
|
7
|
+
*
|
|
8
|
+
* This prevents customer workflow code from:
|
|
9
|
+
* - Accessing MMDS metadata (orchestrator URL, agent config)
|
|
10
|
+
* - Reading agent environment variables (KICI_*, DATABASE_URL, etc.)
|
|
11
|
+
* - Interfering with the agent process directly
|
|
12
|
+
*
|
|
13
|
+
* The fork mechanism is identical to BareMetalSandbox (sandbox=false) -- no
|
|
14
|
+
* bwrap needed since the VM itself provides PID/IPC/network/filesystem isolation.
|
|
15
|
+
*
|
|
16
|
+
* VM lifecycle (start/stop) is managed by the Firecracker scaler backend,
|
|
17
|
+
* NOT by this sandbox. The sandbox only manages the child process within the VM.
|
|
18
|
+
*/
|
|
19
|
+
import type { ExecutionSandbox, SandboxSetupOptions, JobExecutionOptions, JobExecutionResult } from './types.js';
|
|
20
|
+
/** Configuration options for FirecrackerSandbox. */
|
|
21
|
+
interface FirecrackerSandboxOptions {
|
|
22
|
+
/** Absolute path to the compiled workflow-runner.js inside the VM. */
|
|
23
|
+
runnerPath: string;
|
|
24
|
+
/** Pre-sanitized environment variables (system allowlist + user env). */
|
|
25
|
+
env: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Firecracker execution sandbox implementation.
|
|
29
|
+
*
|
|
30
|
+
* Thin defense-in-depth wrapper that forks the workflow runner with sanitized
|
|
31
|
+
* environment inside a Firecracker VM. The VM provides the real isolation;
|
|
32
|
+
* this sandbox ensures credential separation within the VM.
|
|
33
|
+
*/
|
|
34
|
+
export declare class FirecrackerSandbox implements ExecutionSandbox {
|
|
35
|
+
private readonly runnerPath;
|
|
36
|
+
private readonly env;
|
|
37
|
+
private runner;
|
|
38
|
+
private workDir;
|
|
39
|
+
constructor(options: FirecrackerSandboxOptions);
|
|
40
|
+
/**
|
|
41
|
+
* No-op: VM is already running, managed by the Firecracker scaler backend.
|
|
42
|
+
*/
|
|
43
|
+
setup(options: SandboxSetupOptions): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Execute a job by forking the workflow runner with sanitized environment.
|
|
46
|
+
*
|
|
47
|
+
* Identical to BareMetalSandbox (sandbox=false) -- no bwrap needed since
|
|
48
|
+
* the VM itself provides full isolation.
|
|
49
|
+
*/
|
|
50
|
+
executeJob(options: JobExecutionOptions): Promise<JobExecutionResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Abort the running job.
|
|
53
|
+
*
|
|
54
|
+
* Sends abort IPC message, then SIGTERM after 10s, SIGKILL after 15s.
|
|
55
|
+
*/
|
|
56
|
+
abort(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Kill child process if still running.
|
|
59
|
+
*
|
|
60
|
+
* VM lifecycle (shutdown) is managed by the scaler backend, not this sandbox.
|
|
61
|
+
*/
|
|
62
|
+
teardown(): Promise<void>;
|
|
63
|
+
}
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=firecracker-sandbox.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared fork-based execution logic used by BareMetalSandbox and FirecrackerSandbox.
|
|
3
|
+
*
|
|
4
|
+
* Both backends use child_process.fork() (or spawn via bwrap) with Node.js IPC
|
|
5
|
+
* channel for communication. This module extracts the common fork + IPC dispatch
|
|
6
|
+
* logic to avoid duplication.
|
|
7
|
+
*/
|
|
8
|
+
import { type ChildProcess } from 'node:child_process';
|
|
9
|
+
import type { JobDispatch } from '@kici-dev/engine';
|
|
10
|
+
import type { JobExecutionOptions, JobExecutionResult } from './types.js';
|
|
11
|
+
import type { JobExecutionRequest } from './ipc-protocol.js';
|
|
12
|
+
/** Options for creating a fork-based runner. */
|
|
13
|
+
interface ForkRunnerOptions {
|
|
14
|
+
/** Absolute path to the compiled workflow-runner.js. */
|
|
15
|
+
runnerPath: string;
|
|
16
|
+
/** Pre-sanitized base environment (system + user vars). */
|
|
17
|
+
env: Record<string, string>;
|
|
18
|
+
/** If true, wrap execution in bubblewrap for namespace isolation. */
|
|
19
|
+
useBwrap?: boolean;
|
|
20
|
+
/** Working directory for the child process. */
|
|
21
|
+
workDir?: string;
|
|
22
|
+
/**
|
|
23
|
+
* If true, enable network isolation via bwrap --unshare-net.
|
|
24
|
+
* Creates a network namespace with only loopback -- no external connectivity.
|
|
25
|
+
* Intentionally strict: bare-metal is for trusted environments only.
|
|
26
|
+
* Only effective when useBwrap is also true.
|
|
27
|
+
*/
|
|
28
|
+
networkIsolation?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Extra absolute host paths to bind read-only into the bwrap sandbox.
|
|
31
|
+
* Used by the bare-metal sandbox to expose `file://` clone source dirs
|
|
32
|
+
* (internal provider, dev/E2E) to the workflow runner so its `git clone`
|
|
33
|
+
* step can read from them. Ignored when useBwrap=false.
|
|
34
|
+
*/
|
|
35
|
+
extraReadOnlyBinds?: string[];
|
|
36
|
+
/**
|
|
37
|
+
* Agent-level maximum grace period in milliseconds.
|
|
38
|
+
* The effective grace period is Math.min(jobGracePeriod, maxGracePeriodMs).
|
|
39
|
+
* Defaults to 30_000 (30 seconds).
|
|
40
|
+
*/
|
|
41
|
+
maxGracePeriodMs?: number;
|
|
42
|
+
}
|
|
43
|
+
/** State of a running fork-based child process. */
|
|
44
|
+
export interface ForkRunnerHandle {
|
|
45
|
+
/** The child process instance. */
|
|
46
|
+
child: ChildProcess;
|
|
47
|
+
/** Promise that resolves when the job completes. */
|
|
48
|
+
result: Promise<JobExecutionResult>;
|
|
49
|
+
/** Abort the running job (SIGTERM -> SIGKILL). Legacy method -- prefer cancel(). */
|
|
50
|
+
abort: () => Promise<void>;
|
|
51
|
+
/** Kill the child process if still running. */
|
|
52
|
+
kill: () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Cancel the running job with two-level support.
|
|
55
|
+
*
|
|
56
|
+
* @param force If false: graceful cancel (SIGTERM, grace period, then SIGKILL).
|
|
57
|
+
* If true: force cancel (SIGKILL immediately, skip hooks).
|
|
58
|
+
* @param gracePeriodMs Grace period in ms before escalating to SIGKILL (graceful only).
|
|
59
|
+
* Capped by the agent's maxGracePeriodMs.
|
|
60
|
+
*/
|
|
61
|
+
cancel: (force: boolean, gracePeriodMs?: number) => void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Build a JobExecutionRequest from a JobDispatch.
|
|
65
|
+
*
|
|
66
|
+
* Maps orchestrator dispatch fields to the subset needed by the workflow runner.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildRequest(dispatch: JobDispatch, workDir: string): JobExecutionRequest;
|
|
69
|
+
/**
|
|
70
|
+
* Build bubblewrap (bwrap) arguments for namespace isolation.
|
|
71
|
+
*
|
|
72
|
+
* Creates a sandboxed filesystem view with:
|
|
73
|
+
* - Read-only bind mounts for system directories (/usr, /lib, /bin, etc.)
|
|
74
|
+
* - Read-only bind mount for Node.js binary
|
|
75
|
+
* - Writable bind mount for the workspace directory
|
|
76
|
+
* - Private /dev, /proc, /tmp
|
|
77
|
+
* - PID and IPC namespace isolation
|
|
78
|
+
* - Die-with-parent and new-session for process lifecycle safety
|
|
79
|
+
*
|
|
80
|
+
* When networkIsolation is true, --unshare-net creates a separate network namespace
|
|
81
|
+
* with only loopback (no external connectivity). This is intentionally strict:
|
|
82
|
+
* bare-metal mode is for trusted environments only, and full network isolation
|
|
83
|
+
* is simpler and more secure than selective blocking.
|
|
84
|
+
*/
|
|
85
|
+
export declare function buildBwrapArgs(workDir: string, nodeExecPath: string, networkIsolation?: boolean, runnerPath?: string, extraReadOnlyBinds?: string[]): string[];
|
|
86
|
+
/**
|
|
87
|
+
* Spawn the workflow runner as a child process with IPC channel.
|
|
88
|
+
*
|
|
89
|
+
* In non-bwrap mode: uses child_process.fork() which sets up a native IPC channel.
|
|
90
|
+
* In bwrap mode: uses child_process.spawn('bwrap', ...) with stdio IPC fd.
|
|
91
|
+
*/
|
|
92
|
+
export declare function createForkRunner(options: ForkRunnerOptions, execOptions: JobExecutionOptions): ForkRunnerHandle;
|
|
93
|
+
export {};
|
|
94
|
+
//# sourceMappingURL=fork-runner.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel export for execution sandbox types and implementations.
|
|
3
|
+
*
|
|
4
|
+
* Provides clean imports for the job runner:
|
|
5
|
+
* import { BareMetalSandbox, ContainerSandbox, buildSanitizedEnv } from './sandbox/index.js';
|
|
6
|
+
*/
|
|
7
|
+
export type { ExecutionSandbox, SandboxSetupOptions, JobExecutionOptions, JobExecutionResult, SandboxStepResult, } from './types.js';
|
|
8
|
+
export type { RunnerToAgentMessage, AgentToRunnerMessage, EventEmitRequest, EventEmitResponse, JobExecutionRequest, } from './ipc-protocol.js';
|
|
9
|
+
export { buildSanitizedEnv } from './env-sanitizer.js';
|
|
10
|
+
export { ALLOWED_SYSTEM_VARS, KICI_AGENT_ENV_PREFIX, AGENT_REQUIRED_KICI_VARS, } from '@kici-dev/engine';
|
|
11
|
+
export { BareMetalSandbox } from './bare-metal-sandbox.js';
|
|
12
|
+
export { FirecrackerSandbox } from './firecracker-sandbox.js';
|
|
13
|
+
export { ContainerSandbox } from './container-sandbox.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import type { SandboxStepResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Structured clone auth. Wire-compatible with `gitAuthSchema` on the
|
|
4
|
+
* orchestrator-agent protocol and `GitAuth` in `checkout/git-clone.ts`.
|
|
5
|
+
* Declared independently here so the sandbox IPC module has no runtime
|
|
6
|
+
* dependency on the engine protocol package.
|
|
7
|
+
*/
|
|
8
|
+
export interface GitAuthDispatch {
|
|
9
|
+
kind: 'basic' | 'ssh';
|
|
10
|
+
user?: string;
|
|
11
|
+
secret: string;
|
|
12
|
+
sshHostKeyPolicy?: 'accept-new' | 'pinned';
|
|
13
|
+
sshKnownHostsPem?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Workflow runner is initialized and ready to receive a job. */
|
|
16
|
+
interface ReadyMessage {
|
|
17
|
+
type: 'ready';
|
|
18
|
+
}
|
|
19
|
+
/** A step has started executing. */
|
|
20
|
+
interface StepStartMessage {
|
|
21
|
+
type: 'step.start';
|
|
22
|
+
stepIndex: number;
|
|
23
|
+
stepName: string;
|
|
24
|
+
/** Distinguishes regular steps from hook executions (e.g., 'hook:onCancel', 'hook:cleanup'). Defaults to 'step'. */
|
|
25
|
+
step_type?: string;
|
|
26
|
+
}
|
|
27
|
+
/** A step has completed (success or failure). */
|
|
28
|
+
interface StepCompleteMessage {
|
|
29
|
+
type: 'step.complete';
|
|
30
|
+
stepIndex: number;
|
|
31
|
+
status: 'success' | 'failed';
|
|
32
|
+
durationMs: number;
|
|
33
|
+
error?: {
|
|
34
|
+
message: string;
|
|
35
|
+
exitCode?: number;
|
|
36
|
+
signal?: string;
|
|
37
|
+
};
|
|
38
|
+
/** Step return value (outputs). Present on success when step returns non-void. */
|
|
39
|
+
outputs?: Record<string, unknown>;
|
|
40
|
+
/** Distinguishes regular steps from hook executions (e.g., 'hook:onCancel', 'hook:cleanup'). Defaults to 'step'. */
|
|
41
|
+
step_type?: string;
|
|
42
|
+
/** Secret key names accessed by this step via ctx.secrets.get() or ctx.secrets.expose(). Never contains values. */
|
|
43
|
+
secretsAccessed?: string[];
|
|
44
|
+
}
|
|
45
|
+
/** A single log line from step execution. */
|
|
46
|
+
interface LogLineMessage {
|
|
47
|
+
type: 'log.line';
|
|
48
|
+
stepIndex: number;
|
|
49
|
+
line: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Discriminator for {@link StepSecretMountMessage} -- distinguishes a bare
|
|
53
|
+
* `ctx.secrets.mountFile` call from a `ctx.secrets.exposeFile` call (the
|
|
54
|
+
* latter additionally sets an env var on the step's process).
|
|
55
|
+
*/
|
|
56
|
+
export type StepSecretMountKind = 'mountFile' | 'exposeFile';
|
|
57
|
+
/**
|
|
58
|
+
* Audit event emitted once per `ctx.secrets.mountFile` / `exposeFile` call.
|
|
59
|
+
* Carries only key names + the resulting path / env var -- never the file
|
|
60
|
+
* content. Persisted by the orchestrator alongside `secretsAccessed` so the
|
|
61
|
+
* dashboard can render the materialised-file audit trail.
|
|
62
|
+
*/
|
|
63
|
+
interface StepSecretMountMessage {
|
|
64
|
+
type: 'step.secret_mount';
|
|
65
|
+
stepIndex: number;
|
|
66
|
+
/** Source secret keys (in concatenation order). */
|
|
67
|
+
sources: string[];
|
|
68
|
+
/** Absolute path the file was materialised to inside the step sandbox. */
|
|
69
|
+
target: string;
|
|
70
|
+
/** Env var set when `kind === 'exposeFile'`; otherwise omitted. */
|
|
71
|
+
envVar?: string;
|
|
72
|
+
/** Discriminator between `mountFile` and `exposeFile`. */
|
|
73
|
+
kind: StepSecretMountKind;
|
|
74
|
+
}
|
|
75
|
+
/** The entire job has completed. */
|
|
76
|
+
interface JobCompleteMessage {
|
|
77
|
+
type: 'job.complete';
|
|
78
|
+
status: 'success' | 'failed';
|
|
79
|
+
stepResults: SandboxStepResult[];
|
|
80
|
+
/** Error message when the job failed before step execution (e.g. clone, deps, compile). */
|
|
81
|
+
error?: string;
|
|
82
|
+
/** Aggregated step outputs by step name. Present on success when steps produce outputs. */
|
|
83
|
+
outputs?: Record<string, Record<string, unknown>>;
|
|
84
|
+
/** Secret output values collected during step execution (plaintext -- encryption happens in the agent before WS send). */
|
|
85
|
+
secretOutputs?: Record<string, string>;
|
|
86
|
+
/** Names of sibling jobs dropped by DynamicJobFn re-evaluation drift. */
|
|
87
|
+
droppedJobs?: string[];
|
|
88
|
+
}
|
|
89
|
+
/** Request to emit a custom event from a workflow step (runner -> agent). */
|
|
90
|
+
export interface EventEmitRequest {
|
|
91
|
+
type: 'event.emit';
|
|
92
|
+
/** Unique ID for correlating the response back to the caller. */
|
|
93
|
+
requestId: string;
|
|
94
|
+
/** Custom event name (e.g. 'deploy-complete'). */
|
|
95
|
+
eventName: string;
|
|
96
|
+
/** Event payload (arbitrary JSON-serializable data). */
|
|
97
|
+
payload: Record<string, unknown>;
|
|
98
|
+
/** Optional targeting for cross-repo delivery. */
|
|
99
|
+
target?: {
|
|
100
|
+
repos?: string[];
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** Report evaluated concurrency group key (runner -> agent -> orchestrator). */
|
|
104
|
+
export interface ConcurrencyReportMessage {
|
|
105
|
+
type: 'concurrency.report';
|
|
106
|
+
/** Evaluated concurrency group key (e.g. 'deploy-main'). */
|
|
107
|
+
group: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Discriminated union of all messages sent from the workflow runner to the agent.
|
|
111
|
+
*
|
|
112
|
+
* The workflow runner sends these via:
|
|
113
|
+
* - Node.js IPC channel (`process.send()`) for bare-metal/Firecracker backends
|
|
114
|
+
* - stdout JSON-lines for container backend (`docker exec`)
|
|
115
|
+
*/
|
|
116
|
+
/** API request from runner to agent (relayed to orchestrator via WS). */
|
|
117
|
+
export interface AgentApiRequestIpc {
|
|
118
|
+
type: 'agent.api.request';
|
|
119
|
+
/** UUID for correlating the response. */
|
|
120
|
+
requestId: string;
|
|
121
|
+
/** Dot-namespaced method name (e.g., 'infrastructure.list'). */
|
|
122
|
+
method: string;
|
|
123
|
+
/** Method-specific parameters. */
|
|
124
|
+
params: Record<string, unknown>;
|
|
125
|
+
}
|
|
126
|
+
export type RunnerToAgentMessage = ReadyMessage | StepStartMessage | StepCompleteMessage | LogLineMessage | StepSecretMountMessage | JobCompleteMessage | EventEmitRequest | ConcurrencyReportMessage | AgentApiRequestIpc;
|
|
127
|
+
/** Instruct the workflow runner to execute a job. */
|
|
128
|
+
interface ExecuteMessage {
|
|
129
|
+
type: 'execute';
|
|
130
|
+
request: JobExecutionRequest;
|
|
131
|
+
}
|
|
132
|
+
/** Instruct the workflow runner to abort the current job. */
|
|
133
|
+
interface AbortMessage {
|
|
134
|
+
type: 'abort';
|
|
135
|
+
/** When true, force-cancel immediately (SIGKILL, skip hooks). When false, graceful cancel (run hooks). */
|
|
136
|
+
force?: boolean;
|
|
137
|
+
}
|
|
138
|
+
/** Response confirming event delivery (agent -> runner). */
|
|
139
|
+
export interface EventEmitResponse {
|
|
140
|
+
type: 'event.emit.response';
|
|
141
|
+
/** Correlates to the original EventEmitRequest.requestId. */
|
|
142
|
+
requestId: string;
|
|
143
|
+
/** Delivery ID assigned by the orchestrator (present on success). */
|
|
144
|
+
deliveryId?: string;
|
|
145
|
+
/** Error description (present on failure). */
|
|
146
|
+
error?: string;
|
|
147
|
+
}
|
|
148
|
+
/** Concurrency ack from orchestrator relayed to runner (agent -> runner). */
|
|
149
|
+
export interface ConcurrencyAckMessage {
|
|
150
|
+
type: 'concurrency.ack';
|
|
151
|
+
/** Action to take: proceed with execution, wait (release slot), or cancel the job. */
|
|
152
|
+
action: 'proceed' | 'wait' | 'cancel';
|
|
153
|
+
/** Optional reason for wait or cancel. */
|
|
154
|
+
reason?: string;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Discriminated union of all messages sent from the agent to the workflow runner.
|
|
158
|
+
*
|
|
159
|
+
* The agent sends these via:
|
|
160
|
+
* - Node.js IPC channel (`child.send()`) for bare-metal/Firecracker backends
|
|
161
|
+
* - stdin JSON-line for container backend (`docker exec`)
|
|
162
|
+
*/
|
|
163
|
+
/** API response from agent to runner (relayed from orchestrator via WS). */
|
|
164
|
+
export interface AgentApiResponseIpc {
|
|
165
|
+
type: 'agent.api.response';
|
|
166
|
+
/** Matches the original request's requestId. */
|
|
167
|
+
requestId: string;
|
|
168
|
+
/** Method result (present on success). */
|
|
169
|
+
result?: unknown;
|
|
170
|
+
/** Error description (present on failure). */
|
|
171
|
+
error?: string;
|
|
172
|
+
}
|
|
173
|
+
export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResponse | ConcurrencyAckMessage | AgentApiResponseIpc;
|
|
174
|
+
/**
|
|
175
|
+
* All data the workflow runner needs to execute a job inside the sandbox.
|
|
176
|
+
*
|
|
177
|
+
* Sent from the agent to the runner as part of the `execute` message.
|
|
178
|
+
* The runner uses this to clone, install deps, compile, and execute steps.
|
|
179
|
+
*/
|
|
180
|
+
export interface JobExecutionRequest {
|
|
181
|
+
/** Working directory inside the sandbox (e.g. /workspace). */
|
|
182
|
+
workDir: string;
|
|
183
|
+
/** Repository URL for git clone. */
|
|
184
|
+
repoUrl: string;
|
|
185
|
+
/** Git ref to checkout (branch or tag). */
|
|
186
|
+
ref: string;
|
|
187
|
+
/** Git commit SHA. */
|
|
188
|
+
sha: string;
|
|
189
|
+
/**
|
|
190
|
+
* Short-lived clone token (optional, for private repos).
|
|
191
|
+
*
|
|
192
|
+
* Deprecated in favour of `sourceAuth` / `workflowAuth` — retained as a
|
|
193
|
+
* back-compat field so same-provider GitHub App flows keep working while
|
|
194
|
+
* universal-git / cross-provider dispatches migrate to structured auth.
|
|
195
|
+
*/
|
|
196
|
+
token?: string;
|
|
197
|
+
/**
|
|
198
|
+
* Structured auth for the source repo clone (Phase 4). When set, the
|
|
199
|
+
* workflow runner uses this instead of `token`.
|
|
200
|
+
*/
|
|
201
|
+
sourceAuth?: GitAuthDispatch;
|
|
202
|
+
/**
|
|
203
|
+
* Structured auth for the workflow repo clone (global workflows only,
|
|
204
|
+
* Phase 4). Falls back to `sourceAuth` → `token` when absent.
|
|
205
|
+
*/
|
|
206
|
+
workflowAuth?: GitAuthDispatch;
|
|
207
|
+
/** URL to a pre-packed `.kici/` source tarball (skip clone if present). */
|
|
208
|
+
sourceTarUrl?: string;
|
|
209
|
+
/** SHA-256 hash of the source tarball bytes for integrity verification. */
|
|
210
|
+
sourceTarHash?: string;
|
|
211
|
+
/** URL to pre-built dependency tarball (skip install if present). */
|
|
212
|
+
depsUrl?: string;
|
|
213
|
+
/** SHA-256 hash of the dependency tarball for integrity verification. */
|
|
214
|
+
depsHash?: string;
|
|
215
|
+
/** Workflow name to execute. */
|
|
216
|
+
workflowName: string;
|
|
217
|
+
/** Job name within the workflow. */
|
|
218
|
+
jobName: string;
|
|
219
|
+
/** Runs-on label for the job. */
|
|
220
|
+
runsOn: string;
|
|
221
|
+
/** Secrets to merge into step environment (highest precedence). */
|
|
222
|
+
secrets?: Record<string, string>;
|
|
223
|
+
/** Namespaced secrets by context name for ctx.secrets['context-name'].KEY access. */
|
|
224
|
+
namespacedSecrets?: Record<string, Record<string, string>>;
|
|
225
|
+
/** Secret metadata from resolveForJobWithMeta (backend + scope per key). */
|
|
226
|
+
secretMeta?: Record<string, {
|
|
227
|
+
value: string;
|
|
228
|
+
backend: string;
|
|
229
|
+
scope: string;
|
|
230
|
+
}>;
|
|
231
|
+
/** Source file path for workflow compilation (relative to repo root). */
|
|
232
|
+
sourceFile?: string;
|
|
233
|
+
/** Content hash of the source file for cache key. */
|
|
234
|
+
contentHash?: string;
|
|
235
|
+
/** Resolved hash files for content-addressed caching. */
|
|
236
|
+
resolvedHashFiles?: string[];
|
|
237
|
+
/** Max log size per step in bytes (runner enforces truncation). */
|
|
238
|
+
maxLogSizeBytes?: number;
|
|
239
|
+
/** Default step timeout in milliseconds. */
|
|
240
|
+
defaultStepTimeoutMs?: number;
|
|
241
|
+
/** Container configuration passthrough (for container-aware steps). */
|
|
242
|
+
container?: Record<string, unknown>;
|
|
243
|
+
/** Webhook event payload for rule evaluation context. */
|
|
244
|
+
event?: Record<string, unknown>;
|
|
245
|
+
/** Git provider that originated the triggering event (e.g. 'github', 'forgejo'). */
|
|
246
|
+
provider?: string;
|
|
247
|
+
/** Whether to checkout the repo (default: true). */
|
|
248
|
+
checkout?: boolean;
|
|
249
|
+
/** Whether this job is part of a test run triggered by `kici test`. */
|
|
250
|
+
isTestRun?: boolean;
|
|
251
|
+
/** When true, skip git clone -- use overlay tarball as complete workspace. */
|
|
252
|
+
fullRepo?: boolean;
|
|
253
|
+
/** URL to download the encrypted overlay tarball (test runs with uncommitted changes). */
|
|
254
|
+
tarballUrl?: string;
|
|
255
|
+
/** Base64-encoded CLI ephemeral public key for overlay decryption (DER/SPKI). */
|
|
256
|
+
cliPublicKey?: string;
|
|
257
|
+
/** Base64-encoded orchestrator ephemeral private key for overlay decryption (DER/PKCS8). */
|
|
258
|
+
orchestratorPrivateKey?: string;
|
|
259
|
+
/** Base64-encoded X25519 public key for the run (for encrypting secret outputs). */
|
|
260
|
+
runPublicKey?: string;
|
|
261
|
+
/** Deployment environment name (resolved by orchestrator). */
|
|
262
|
+
environment?: string;
|
|
263
|
+
/** Environment variables from orchestrator (org-level + source overrides, layers 4-5). */
|
|
264
|
+
environmentVars?: Record<string, string>;
|
|
265
|
+
/** Job env from lock file env field (layer 6, evaluated by orchestrator). */
|
|
266
|
+
jobEnv?: Record<string, string>;
|
|
267
|
+
/** Whether this is a global workflow (dual-clone: workflow repo + source repo). */
|
|
268
|
+
isGlobalWorkflow?: boolean;
|
|
269
|
+
/** Clone URL for the workflow (registering) repo. Only set when isGlobalWorkflow is true. */
|
|
270
|
+
workflowRepoUrl?: string;
|
|
271
|
+
/** Git ref for the workflow repo. */
|
|
272
|
+
workflowRef?: string;
|
|
273
|
+
/** Git commit SHA for the workflow repo. */
|
|
274
|
+
workflowSha?: string;
|
|
275
|
+
/** Repository identifier for the workflow repo (e.g., "org/workflow-repo"). */
|
|
276
|
+
workflowRepoIdentifier?: string;
|
|
277
|
+
/** Whether the workflow has a concurrency group function to evaluate. */
|
|
278
|
+
hasConcurrencyGroup?: boolean;
|
|
279
|
+
/** Concurrency group evaluation timeout in milliseconds (default: 30000). */
|
|
280
|
+
concurrencyEvaluationTimeoutMs?: number;
|
|
281
|
+
/** Git branch for concurrency group context. */
|
|
282
|
+
branch?: string;
|
|
283
|
+
/** Plain outputs from upstream jobs (keyed by job name, then by step name). For ctx.jobOutputs(). */
|
|
284
|
+
upstreamJobOutputs?: Record<string, Record<string, unknown>>;
|
|
285
|
+
/** Resolved private npm registries for `npm install` auth (token bytes already filled). */
|
|
286
|
+
npmRegistries?: ReadonlyArray<{
|
|
287
|
+
url: string;
|
|
288
|
+
scope?: string;
|
|
289
|
+
alwaysAuth: boolean;
|
|
290
|
+
token: string;
|
|
291
|
+
}>;
|
|
292
|
+
/** Bare-name secrets to project as install-subprocess env vars. */
|
|
293
|
+
installEnvSecrets?: Record<string, string>;
|
|
294
|
+
/** Short job-scoped nonce — used as suffix on synthesized npm-token env vars. */
|
|
295
|
+
jobIdShort?: string;
|
|
296
|
+
/**
|
|
297
|
+
* Source of a dynamically generated job (from DynamicJobFn).
|
|
298
|
+
* When set, the workflow runner re-evaluates the DynamicJobFn to extract
|
|
299
|
+
* step functions instead of looking up the job in the static jobs array.
|
|
300
|
+
*/
|
|
301
|
+
dynamicSource?: {
|
|
302
|
+
/** Index of the DynamicJobFn in the workflow's jobs array. */
|
|
303
|
+
index: number;
|
|
304
|
+
/** Original event payload (passed to DynamicJobFn for re-evaluation). */
|
|
305
|
+
event: Record<string, unknown>;
|
|
306
|
+
/** Expected job names from the original eval (for determinism validation). */
|
|
307
|
+
expectedJobNames?: string[];
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
export {};
|
|
311
|
+
//# sourceMappingURL=ipc-protocol.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret value masking for log lines.
|
|
3
|
+
*
|
|
4
|
+
* Replaces all occurrences of registered secret values with '***' in log output.
|
|
5
|
+
* Used by the workflow runner to prevent secret leaks in IPC log messages.
|
|
6
|
+
*
|
|
7
|
+
* Performance: Builds a single combined regex from all secret values, so each
|
|
8
|
+
* log line is scanned in a single pass (not O(secrets * lines)).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Masks secret values in log lines.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* ```ts
|
|
15
|
+
* const masker = new LogMasker();
|
|
16
|
+
* masker.registerSecrets({ TOKEN: 'abc123', SHORT: 'ab' });
|
|
17
|
+
* masker.mask('Token is abc123'); // 'Token is ***'
|
|
18
|
+
* // 'ab' is NOT masked (< 3 chars)
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class LogMasker {
|
|
22
|
+
private pattern;
|
|
23
|
+
/**
|
|
24
|
+
* Register secret values to be masked in log output.
|
|
25
|
+
*
|
|
26
|
+
* Values shorter than 3 characters are skipped to avoid false positives.
|
|
27
|
+
* Base64-encoded variants of each qualifying secret are also registered,
|
|
28
|
+
* preventing leaks when secrets appear base64-encoded in logs (e.g.,
|
|
29
|
+
* Authorization: Basic headers, base64-encoded config values).
|
|
30
|
+
* Values are sorted by length descending so longer values are matched first
|
|
31
|
+
* (prevents partial masking when one secret is a substring of another).
|
|
32
|
+
*/
|
|
33
|
+
registerSecrets(secrets: Record<string, string>): void;
|
|
34
|
+
/**
|
|
35
|
+
* Mask all registered secret values in a log line.
|
|
36
|
+
*
|
|
37
|
+
* Returns the line unchanged if no secrets are registered.
|
|
38
|
+
*/
|
|
39
|
+
mask(line: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Returns true if any maskable secrets are registered.
|
|
42
|
+
*/
|
|
43
|
+
hasSecrets(): boolean;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=log-masker.d.ts.map
|