@kici-dev/agent 0.1.13 → 0.1.15

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 CHANGED
@@ -1 +1,13 @@
1
- TBD
1
+ # @kici-dev/agent
2
+
3
+ Customer-deployable agent for the KiCI CI/CD stack. Connects to an orchestrator, clones the workflow repository, executes steps, and streams logs back.
4
+
5
+ You normally don't install this package directly: agents are spawned by the orchestrator's auto-scaler or run from the published container image `quay.io/kici-dev/kici-agent`.
6
+
7
+ Part of [KiCI](https://kici.dev) — CI/CD workflows as TypeScript code: author them with full language power, dry-run them locally, and run them on your own infrastructure.
8
+
9
+ ## Links
10
+
11
+ - Documentation: <https://docs.kici.dev/operator/agent/getting-started/>
12
+ - Source: <https://github.com/kici-dev/kici-public/tree/main/packages/agent>
13
+ - License: AGPL-3.0-only
@@ -0,0 +1,62 @@
1
+ import type { CacheSpec, CacheRestoreResult } from '@kici-dev/sdk';
2
+ /** Override roots — exposed for tests so the home destination is sandboxable. */
3
+ export interface CacheRoots {
4
+ /** Home root override (defaults to `os.homedir()`). */
5
+ home?: string;
6
+ }
7
+ /**
8
+ * Resolve a cache path. `~`-prefixed -> home root; otherwise repo-root-relative.
9
+ * Rejects absolute paths and `..` escapes so a workflow cannot read or clobber
10
+ * files outside its tree / home.
11
+ */
12
+ export declare function resolveCachePath(workDir: string, p: string, roots?: CacheRoots): string;
13
+ /**
14
+ * Pack the spec's paths into a gzip tarball + its SHA-256.
15
+ *
16
+ * Each path is copied into a staging dir under its anchor prefix
17
+ * (`__repo__/<rel>` or `__home__/<rel>`), the staging dir is tarred (portable
18
+ * mode strips uid/gid/mtime), and the staging dir is removed. The resulting
19
+ * tarball self-describes which root each entry restores to.
20
+ */
21
+ export declare function packCachePaths(workDir: string, paths: string[], roots?: CacheRoots): Promise<{
22
+ tarball: Buffer;
23
+ hash: string;
24
+ }>;
25
+ /**
26
+ * Extract a cache tarball buffer, verifying its SHA-256 first, then move each
27
+ * anchored group into place (repo entries under `workDir`, home entries under
28
+ * the home root). Extracts into a scratch dir so a partial restore never
29
+ * leaves half-written paths in the live tree.
30
+ */
31
+ export declare function extractCacheTarball(tarball: Buffer, workDir: string, expectedHash: string, roots?: CacheRoots): Promise<void>;
32
+ /**
33
+ * Stream-download a presigned URL, verify its SHA-256 on the fly (mirrors
34
+ * dep-restore's response -> hash -> gunzip -> tar pipeline), then move the
35
+ * anchored groups into place. Extracts into a scratch dir so a failed download
36
+ * never half-writes the live tree.
37
+ */
38
+ export declare function downloadAndExtractCache(url: string, workDir: string, expectedHash: string, roots?: CacheRoots): Promise<void>;
39
+ /**
40
+ * Transport the cache engine uses to reach the orchestrator over IPC -> WS.
41
+ * Backed by the agent's request/response relay (added to the IPC protocol in a
42
+ * later wiring task).
43
+ */
44
+ export interface CacheTransport {
45
+ restore(key: string, restoreKeys?: string[]): Promise<{
46
+ hit: boolean;
47
+ matchedKey?: string;
48
+ downloadUrl?: string;
49
+ tarHash?: string;
50
+ }>;
51
+ beginSave(key: string): Promise<{
52
+ skip: boolean;
53
+ uploadUrl?: string;
54
+ }>;
55
+ completeSave(key: string, tarHash: string, sizeBytes: number): Promise<void>;
56
+ }
57
+ /** Build the imperative `ctx.cache` API bound to a workDir + transport. */
58
+ export declare function createCacheApi(workDir: string, transport: CacheTransport, roots?: CacheRoots): {
59
+ restore(spec: CacheSpec): Promise<CacheRestoreResult>;
60
+ save(spec: CacheSpec): Promise<void>;
61
+ };
62
+ //# sourceMappingURL=cache-engine.d.ts.map
@@ -0,0 +1,29 @@
1
+ import type { CacheSpec, CacheApi } from '@kici-dev/sdk';
2
+ import type { RunnerToAgentMessage } from '../sandbox/ipc-protocol.js';
3
+ /** The restore outcome remembered per spec key so the save phase can skip exact hits. */
4
+ export interface CacheRestoreOutcome {
5
+ hit: boolean;
6
+ matchedKey?: string;
7
+ }
8
+ export interface CachePhaseDeps {
9
+ /** Imperative cache API (the same one bound to `ctx.cache`). */
10
+ cache: CacheApi;
11
+ /** Emit a runner→agent IPC message (the masked send). */
12
+ sendIpc: (msg: RunnerToAgentMessage) => void;
13
+ /** Monotonic pseudo-step index allocator (continues after real steps + hooks). */
14
+ nextStepIndex: () => number;
15
+ }
16
+ /**
17
+ * Restore every spec, surfacing each as a `cache:restore` pseudo-step. Returns
18
+ * a map keyed by spec key recording whether the EXACT key hit (so the save
19
+ * phase can skip a redundant save of an entry that already exists).
20
+ */
21
+ export declare function restoreCacheSpecs(specs: CacheSpec[], deps: CachePhaseDeps): Promise<Map<string, CacheRestoreOutcome>>;
22
+ /**
23
+ * Save every spec whose EXACT key did not already hit on restore (immutable +
24
+ * no redundant save), surfacing each as a `cache:save` pseudo-step. A spec
25
+ * whose restore matched a different key via a `restoreKeys` prefix is still
26
+ * saved under its exact key.
27
+ */
28
+ export declare function saveCacheSpecs(specs: CacheSpec[], restoreResults: Map<string, CacheRestoreOutcome>, deps: CachePhaseDeps): Promise<void>;
29
+ //# sourceMappingURL=cache-phase.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * User-facing cache engine barrel (sandbox-side).
3
+ *
4
+ * Re-exports the pure pack/extract/checksum helpers and the imperative
5
+ * `ctx.cache` API factory plus its transport interface.
6
+ */
7
+ export { createCacheApi, packCachePaths, extractCacheTarball, downloadAndExtractCache, resolveCachePath, type CacheTransport, type CacheRoots, } from './cache-engine.js';
8
+ export { restoreCacheSpecs, saveCacheSpecs, type CachePhaseDeps, type CacheRestoreOutcome, } from './cache-phase.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -18,6 +18,17 @@
18
18
  import type { $ as Shell } from 'zx';
19
19
  import type { Job, Logger } from '@kici-dev/sdk';
20
20
  import type { LockJob } from '@kici-dev/engine';
21
+ /**
22
+ * Thrown when resolving a job's dynamic matrix fails (the matrix function threw
23
+ * or timed out, or returned an unsupported value). Lets the agent attribute the
24
+ * failure to the matrix_expansion init-failure category instead of the generic
25
+ * dynamic_eval bucket.
26
+ */
27
+ export declare class MatrixExpansionError extends Error {
28
+ readonly jobName: string;
29
+ readonly name = "MatrixExpansionError";
30
+ constructor(jobName: string, message: string);
31
+ }
21
32
  /** Maximum number of jobs a single DynamicJobFn can generate. */
22
33
  export declare const MAX_DYNAMIC_JOBS = 100;
23
34
  /**
@@ -27,7 +38,7 @@ export declare const MAX_DYNAMIC_JOBS = 100;
27
38
  * we thread it through the serializer rather than re-creating it.
28
39
  */
29
40
  export interface SerializerContext {
30
- /** Webhook event payload — passed as the first argument to env/environment/concurrencyGroup fns. */
41
+ /** Normalized event envelope — passed as the first argument to env/environment/concurrencyGroup fns. */
31
42
  event: Record<string, unknown>;
32
43
  /** zx shell — passed to dynamic matrix fns. */
33
44
  $: typeof Shell;
@@ -0,0 +1,64 @@
1
+ import type { $ as Shell } from 'zx';
2
+ import type { GenericInitConfig, CacheSpec } from '@kici-dev/sdk';
3
+ import { TimeoutReason } from '@kici-dev/engine';
4
+ import type { RunnerToAgentMessage } from '../sandbox/ipc-protocol.js';
5
+ /**
6
+ * Cache engine port (P2). Init reuses the same imperative cache API bound to
7
+ * `ctx.cache` (and used by the declarative job/step cache phase) — restore the
8
+ * spec's paths before the command, save them after on an exact-key miss. The
9
+ * shape mirrors `@kici-dev/sdk`'s `CacheApi` (`restore`/`save`).
10
+ */
11
+ export interface InitCachePort {
12
+ restore(spec: CacheSpec): Promise<{
13
+ hit: boolean;
14
+ matchedKey?: string;
15
+ }>;
16
+ save(spec: CacheSpec): Promise<void>;
17
+ }
18
+ /**
19
+ * Env-handoff port (P1). Mirrors the KICI_ENV / KICI_PATH file contract that
20
+ * steps use: before the command the agent allocates fresh KICI_ENV / KICI_PATH
21
+ * files and points the shell env at them (`beginCapture`); after a successful
22
+ * command it reads + parses those files into an EnvDelta and applies it through
23
+ * `applyEnvDelta` (operator-secret guard + PATH-prepend order), then truncates
24
+ * the files (`applyDelta`). P1 owns the file lifecycle and the parse; the init
25
+ * phase only sequences the two calls around the command.
26
+ */
27
+ export interface InitEnvPort {
28
+ /** Allocate fresh KICI_ENV/KICI_PATH files for this init; the shell's env points at them. */
29
+ beginCapture(): Promise<void>;
30
+ /** Read+parse the files, apply via applyEnvDelta (operator-secret guard + masking), then truncate. */
31
+ applyDelta(): Promise<void>;
32
+ }
33
+ export interface RunInitPhaseOptions {
34
+ /** Init specs to run, in order. Empty / undefined => no-op (ok:true). */
35
+ specs: GenericInitConfig[];
36
+ /**
37
+ * Returns the sandbox shell to run the i-th init command with.
38
+ * The workflow-runner builds a fresh zx$ shell (cwd=clone root, env=process.env,
39
+ * log->IPC) per init so each spec's `env` overlay + KICI_ENV/KICI_PATH files apply.
40
+ */
41
+ shellFor: (spec: GenericInitConfig, index: number) => typeof Shell;
42
+ /** Masked IPC sender (same as the step loop's maskedSend). */
43
+ sendIpc: (msg: RunnerToAgentMessage) => void;
44
+ /** stepIndex for init:0; subsequent specs use base+1, base+2, … (after user steps + hook indices). */
45
+ stepIndexBase: number;
46
+ /** Cache engine (P2). When a spec sets `cache`, restore before / save-on-miss after. */
47
+ cache?: InitCachePort;
48
+ /** Env-handoff port (P1). When set, capture before each init and apply the delta after success. */
49
+ env?: InitEnvPort;
50
+ }
51
+ export interface RunInitPhaseResult {
52
+ ok: boolean;
53
+ /** Index of the init spec that failed (when ok=false). */
54
+ failedInitIndex?: number;
55
+ /** Failure message (when ok=false). */
56
+ error?: string;
57
+ /** True when the failure was a wall-clock timeout (init exceeded `timeout`). */
58
+ timedOut?: boolean;
59
+ /** Distinct P3 timeout reason (`job_timeout`) when `timedOut` is true. */
60
+ reason?: TimeoutReason;
61
+ }
62
+ /** Run all init specs in order; stop + fail at the first non-zero / timeout. */
63
+ export declare function runInitPhase(opts: RunInitPhaseOptions): Promise<RunInitPhaseResult>;
64
+ //# sourceMappingURL=init-phase.d.ts.map
@@ -21,7 +21,7 @@ export interface InitResult {
21
21
  *
22
22
  * @param workflow - The extracted Workflow object
23
23
  * @param jobName - Name of the job whose dynamic fields to evaluate
24
- * @param event - Normalized webhook event data, passed as argument to dynamic functions
24
+ * @param event - Normalized event envelope — same shape every dynamic-function call site receives.
25
25
  * @param flags - Which fields are dynamic and need evaluation
26
26
  * @param timeoutMs - Timeout per dynamic function call (default 60_000ms)
27
27
  */
@@ -1,5 +1,6 @@
1
1
  import type { AgentToOrchestratorMessage, JobDispatch } from '@kici-dev/engine';
2
2
  import type { AppConfig } from '../config.js';
3
+ import type { CacheRequestIpc, CacheResponseIpc } from './sandbox/index.js';
3
4
  /**
4
5
  * Dependencies injected into JobRunner.
5
6
  */
@@ -81,6 +82,15 @@ export interface JobRunnerDeps {
81
82
  * Optional for backward compatibility.
82
83
  */
83
84
  sendApiRequest?: (method: string, params?: Record<string, unknown>) => Promise<unknown>;
85
+ /**
86
+ * Relay a user-facing cache request to the orchestrator and await the
87
+ * response. Translates the sandbox `cache.request` IPC into the matching
88
+ * `cache.user.restore.request` / `cache.user.save.request` /
89
+ * `cache.user.save.complete` WS message and returns the orchestrator's
90
+ * `cache.user.*.response` mapped back onto the IPC response shape.
91
+ * Optional for backward compatibility (callers that don't support the cache).
92
+ */
93
+ requestUserCache?: (jobId: string, request: CacheRequestIpc) => Promise<CacheResponseIpc>;
84
94
  }
85
95
  interface ActiveJob {
86
96
  abortController: AbortController;
@@ -114,6 +124,7 @@ export declare class JobRunner {
114
124
  private readonly _sendRunEvent;
115
125
  private readonly _sendConcurrencyReport;
116
126
  private readonly _sendApiRequest?;
127
+ private readonly _requestUserCache?;
117
128
  /** Tracks running jobs for concurrency and cancellation */
118
129
  readonly activeJobs: Map<string, ActiveJob>;
119
130
  /** Active sandbox for the current job (used for abort). */
@@ -240,6 +251,16 @@ export declare class JobRunner {
240
251
  * Emit a run.event message to the orchestrator for infrastructure lifecycle tracking.
241
252
  */
242
253
  private emitRunEvent;
254
+ /**
255
+ * Emit a `cache.restore` / `cache.save` run event for a cache pseudo-step.
256
+ *
257
+ * The cache phase tags its `step.complete` IPC with a {@link CacheStepType}
258
+ * `step_type` and a `data.cacheOutcome` ({@link CacheOutcome}); when one of
259
+ * those terminal pseudo-step statuses arrives here, mirror it onto the run
260
+ * timeline as a `run.event` so hit/miss/saved/skipped/error is recorded for
261
+ * the dashboard. A no-op for regular steps and hooks.
262
+ */
263
+ private maybeEmitCacheRunEvent;
243
264
  /**
244
265
  * Create a LogStreamer for a synthetic step (build, evaluate, etc.).
245
266
  */
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Shared environment-delta applier.
3
+ *
4
+ * This is the single code path behind the JS API (ctx.setEnv / ctx.addPath) and
5
+ * the shell-side KICI_ENV / KICI_PATH file contract. Routing both through one
6
+ * helper guarantees they share the operator-secret guard and the PATH-prepend
7
+ * ordering. The generic init phase applies its captured delta through the same
8
+ * function so the init command and a step export env identically.
9
+ */
10
+ /** A parsed environment delta to apply to process.env. */
11
+ export interface EnvDelta {
12
+ /** KEY -> value pairs to set (last-write-wins). */
13
+ env: Record<string, string>;
14
+ /** Directories to prepend to PATH, in order (first entry ends up first on PATH). */
15
+ pathPrepends: string[];
16
+ }
17
+ /** Outcome of applying a delta -- used for logging / tests. */
18
+ export interface ApplyEnvDeltaResult {
19
+ /** Keys that were applied to the target env. */
20
+ appliedKeys: string[];
21
+ /** Keys that were rejected because they collide with an operator secret. */
22
+ rejectedKeys: string[];
23
+ /** Directories prepended to PATH (in the order they ended up on PATH). */
24
+ appliedPaths: string[];
25
+ }
26
+ /** Options controlling how a delta is applied. */
27
+ export interface ApplyEnvDeltaOptions {
28
+ /** Keys that must never be overridden (operator-injected secrets + reserved ctx keys). */
29
+ operatorSecretKeys: Set<string>;
30
+ /** Environment object to mutate. Defaults to process.env. */
31
+ target?: NodeJS.ProcessEnv;
32
+ /** Invoked once per rejected key (e.g. to emit a masked log warning). */
33
+ onReject?: (key: string) => void;
34
+ }
35
+ /**
36
+ * Apply an environment delta to `target` (defaults to process.env), honoring the
37
+ * operator-secret guard.
38
+ *
39
+ * - env keys present in `operatorSecretKeys` are rejected (never override an
40
+ * operator secret); `onReject` fires once per rejected key.
41
+ * - pathPrepends are applied so the FIRST array entry ends up FIRST on PATH.
42
+ */
43
+ export declare function applyEnvDelta(delta: EnvDelta, options: ApplyEnvDeltaOptions): ApplyEnvDeltaResult;
44
+ //# sourceMappingURL=env-delta.d.ts.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * KICI_ENV / KICI_PATH temp-file contract.
3
+ *
4
+ * Before each step the agent points the KICI_ENV and KICI_PATH env vars at fresh
5
+ * temp files. A step's shell commands append `KEY=value` lines to $KICI_ENV and
6
+ * one directory per line to $KICI_PATH. After the step the agent parses both
7
+ * files into an EnvDelta and feeds it through applyEnvDelta() -- the same path
8
+ * the JS API (ctx.setEnv / ctx.addPath) uses -- then truncates the files for the
9
+ * next step.
10
+ *
11
+ * Format v1: single-line `KEY=value` for env (no embedded newlines); one
12
+ * directory per line for path. Blank lines and lines without `=` are ignored.
13
+ */
14
+ import type { EnvDelta } from './env-delta.js';
15
+ /** The pair of temp files the step's shell appends to. */
16
+ export interface EnvFiles {
17
+ /** Path of the file steps append `KEY=value` lines to (KICI_ENV). */
18
+ envFile: string;
19
+ /** Path of the file steps append one directory per line to (KICI_PATH). */
20
+ pathFile: string;
21
+ }
22
+ /**
23
+ * Parse `KEY=value` lines into a record. Blank lines, lines without `=`, and
24
+ * lines with an empty key are ignored. The split is on the first `=` only, so a
25
+ * value may contain `=`. The key is trimmed; the value is taken verbatim after
26
+ * the first `=`. Last assignment to a key wins.
27
+ */
28
+ export declare function parseEnvFileContent(content: string): Record<string, string>;
29
+ /** Parse one trimmed directory per non-blank line, preserving order. */
30
+ export declare function parsePathFileContent(content: string): string[];
31
+ /** Create fresh, empty env + path files inside a private temp dir under `baseDir`. */
32
+ export declare function createEnvFiles(baseDir: string): Promise<EnvFiles>;
33
+ /** Convenience: create env files under the OS temp dir. */
34
+ export declare function createEnvFilesInTmp(): Promise<EnvFiles>;
35
+ /** Read + parse both files into an EnvDelta. Missing/empty files yield an empty delta. */
36
+ export declare function readEnvDelta(files: EnvFiles): Promise<EnvDelta>;
37
+ /** Truncate both files to empty so the next step starts clean. */
38
+ export declare function truncateEnvFiles(files: EnvFiles): Promise<void>;
39
+ //# sourceMappingURL=env-file.d.ts.map
@@ -5,7 +5,7 @@
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, JobExecutionRequest, } from './ipc-protocol.js';
8
+ export type { RunnerToAgentMessage, AgentToRunnerMessage, EventEmitRequest, EventEmitResponse, CacheRequestIpc, CacheResponseIpc, 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';
@@ -41,6 +41,12 @@ interface StepCompleteMessage {
41
41
  step_type?: string;
42
42
  /** Secret key names accessed by this step via ctx.secrets.get() or ctx.secrets.expose(). Never contains values. */
43
43
  secretsAccessed?: string[];
44
+ /**
45
+ * Structured per-step metadata forwarded to the orchestrator's `step.status`
46
+ * `data` field (and persisted for the dashboard timeline). The cache
47
+ * pseudo-steps carry `{ cacheOutcome, key, matchedKey?, bytes? }` here.
48
+ */
49
+ data?: Record<string, unknown>;
44
50
  }
45
51
  /** A single log line from step execution. */
46
52
  interface LogLineMessage {
@@ -123,7 +129,37 @@ export interface AgentApiRequestIpc {
123
129
  /** Method-specific parameters. */
124
130
  params: Record<string, unknown>;
125
131
  }
126
- export type RunnerToAgentMessage = ReadyMessage | StepStartMessage | StepCompleteMessage | LogLineMessage | StepSecretMountMessage | JobCompleteMessage | EventEmitRequest | ConcurrencyReportMessage | AgentApiRequestIpc;
132
+ /**
133
+ * Operation requested by a user-facing cache IPC message.
134
+ *
135
+ * - `restore` — look up a cache entry (exact key + prefix fallbacks).
136
+ * - `beginSave` — request a presigned PUT (declined when the immutable key exists).
137
+ * - `completeSave` — confirm the upload so the orchestrator commits temp -> final.
138
+ */
139
+ export type CacheRequestOp = 'restore' | 'beginSave' | 'completeSave';
140
+ /**
141
+ * Request a user-facing cache operation (runner -> agent). The sandbox runner
142
+ * can't open a WS, so it sends this IPC; the agent relays it to the
143
+ * orchestrator over the WS as a `cache.user.*` message and pipes the response
144
+ * back as a {@link CacheResponseIpc}. Mirrors the {@link AgentApiRequestIpc}
145
+ * relay pattern.
146
+ */
147
+ export interface CacheRequestIpc {
148
+ type: 'cache.request';
149
+ /** UUID for correlating the response. */
150
+ requestId: string;
151
+ /** Which cache operation to perform. */
152
+ op: CacheRequestOp;
153
+ /** Exact cache key (all ops). */
154
+ key: string;
155
+ /** Ordered prefix fallbacks (newest matching entry wins). `restore` only. */
156
+ restoreKeys?: string[];
157
+ /** SHA-256 of the uploaded tarball bytes. `completeSave` only. */
158
+ tarHash?: string;
159
+ /** Tarball size in bytes (drives quota accounting). `completeSave` only. */
160
+ sizeBytes?: number;
161
+ }
162
+ export type RunnerToAgentMessage = ReadyMessage | StepStartMessage | StepCompleteMessage | LogLineMessage | StepSecretMountMessage | JobCompleteMessage | EventEmitRequest | ConcurrencyReportMessage | AgentApiRequestIpc | CacheRequestIpc;
127
163
  /** Instruct the workflow runner to execute a job. */
128
164
  interface ExecuteMessage {
129
165
  type: 'execute';
@@ -170,7 +206,34 @@ export interface AgentApiResponseIpc {
170
206
  /** Error description (present on failure). */
171
207
  error?: string;
172
208
  }
173
- export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResponse | ConcurrencyAckMessage | AgentApiResponseIpc;
209
+ /**
210
+ * Response to a {@link CacheRequestIpc} (agent -> runner). Relayed from the
211
+ * orchestrator's `cache.user.*.response` WS message. Carries the union of the
212
+ * restore-response (`hit` / `matchedKey` / `downloadUrl` / `tarHash`) and
213
+ * save-response (`skip` / `uploadUrl`) fields; `completeSave` resolves with an
214
+ * empty (no-field) response. `error` is set when the relay or orchestrator
215
+ * failed.
216
+ */
217
+ export interface CacheResponseIpc {
218
+ type: 'cache.response';
219
+ /** Matches the original request's requestId. */
220
+ requestId: string;
221
+ /** Restore: true when an entry matched (exact or prefix). */
222
+ hit?: boolean;
223
+ /** Restore: full key that matched (exact key or matched prefix entry's key). */
224
+ matchedKey?: string;
225
+ /** Restore: presigned GET URL for the matched tarball (present only on hit). */
226
+ downloadUrl?: string;
227
+ /** Restore: SHA-256 of the tarball bytes for download integrity verification. */
228
+ tarHash?: string;
229
+ /** Save: true when the immutable key already exists (upload skipped). */
230
+ skip?: boolean;
231
+ /** Save: presigned PUT URL to the temp object (absent when `skip`). */
232
+ uploadUrl?: string;
233
+ /** Error description (present when the relay or orchestrator failed). */
234
+ error?: string;
235
+ }
236
+ export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResponse | ConcurrencyAckMessage | AgentApiResponseIpc | CacheResponseIpc;
174
237
  /**
175
238
  * All data the workflow runner needs to execute a job inside the sandbox.
176
239
  *
@@ -238,9 +301,11 @@ export interface JobExecutionRequest {
238
301
  maxLogSizeBytes?: number;
239
302
  /** Default step timeout in milliseconds. */
240
303
  defaultStepTimeoutMs?: number;
304
+ /** Total job wall-clock timeout in milliseconds (init + all steps + hooks). When set, the runner aborts the job on breach and reports TimeoutReason.job_timeout. From the lock job's `timeout`. */
305
+ jobTimeoutMs?: number;
241
306
  /** Container configuration passthrough (for container-aware steps). */
242
307
  container?: Record<string, unknown>;
243
- /** Webhook event payload for rule evaluation context. */
308
+ /** Normalized event envelope (type/action/targetBranch/payload…) for rule evaluation and step context. */
244
309
  event?: Record<string, unknown>;
245
310
  /** Git provider that originated the triggering event (e.g. 'github', 'forgejo'). */
246
311
  provider?: string;
@@ -0,0 +1,13 @@
1
+ import { TimeoutReason } from '@kici-dev/engine';
2
+ /** Handle returned by armJobDeadline; call clear() to cancel the timer. */
3
+ export interface JobDeadlineHandle {
4
+ clear(): void;
5
+ }
6
+ /**
7
+ * Arm a job-level wall-clock deadline. When `timeoutMs` is set and elapses
8
+ * before clear() is called, invokes `onTimeout` with the distinct
9
+ * `job_timeout` reason and the configured budget. A no-op when `timeoutMs`
10
+ * is undefined (no job-level cap configured).
11
+ */
12
+ export declare function armJobDeadline(timeoutMs: number | undefined, onTimeout: (reason: TimeoutReason, timeoutMs: number) => void): JobDeadlineHandle;
13
+ //# sourceMappingURL=job-deadline.d.ts.map
@@ -8,6 +8,7 @@
8
8
  import type { Step, StepContext, HookInput, OutputsMap, StepSecretMountRecord } from '@kici-dev/sdk';
9
9
  import type { RunnerToAgentMessage } from './ipc-protocol.js';
10
10
  import type { SandboxStepResult } from './types.js';
11
+ import { type CachePhaseDeps } from '../cache/index.js';
11
12
  /** Job-level hooks passed to the step loop. */
12
13
  export interface JobHooks {
13
14
  beforeStep?: HookInput;
@@ -31,8 +32,24 @@ interface StepLoopOptions {
31
32
  env: Record<string, string | undefined>;
32
33
  /** Job-level hooks. */
33
34
  jobHooks?: JobHooks;
35
+ /**
36
+ * Declarative cache phase dependencies (cache API + IPC + pseudo-step index
37
+ * allocator). When set, each step's own `cache` specs are restored before the
38
+ * step's `run` and saved after (on an exact-key miss), surfacing as
39
+ * `cache:restore` / `cache:save` pseudo-steps. Absent ⇒ no step-level cache.
40
+ */
41
+ cachePhaseDeps?: CachePhaseDeps;
34
42
  /** Abort check callback. Returns true if job was aborted. */
35
43
  isAborted?: () => boolean;
44
+ /**
45
+ * Aborted when the job-level wall-clock deadline (the lock job's `timeout`)
46
+ * is breached. Threaded into each step's run race so an in-flight step is
47
+ * interrupted immediately on breach — the between-steps `isAborted()` check
48
+ * alone cannot unwind a single long-running step that has no per-step
49
+ * `timeout`. When the signal fires, the step rejects with a job_timeout
50
+ * error and the loop stops.
51
+ */
52
+ jobDeadlineSignal?: AbortSignal;
36
53
  /** Job start time (epoch ms) for outcome metadata duration. */
37
54
  startTime?: number;
38
55
  /**
@@ -54,6 +71,18 @@ interface StepLoopOptions {
54
71
  * orchestrator can persist the audit trail alongside `secretsAccessed`.
55
72
  */
56
73
  getSecretMountRecords?: () => StepSecretMountRecord[];
74
+ /**
75
+ * Before a step's run function executes, point KICI_ENV / KICI_PATH at fresh
76
+ * temp files for this step. Invoked once per executed step (NOT for rule-skipped
77
+ * steps). The workflow-runner owns the file lifecycle.
78
+ */
79
+ beforeStepEnvFiles?: () => Promise<void>;
80
+ /**
81
+ * After a step's run function completes (success OR failure), read the
82
+ * KICI_ENV / KICI_PATH files, apply the delta via applyEnvDelta, and truncate
83
+ * them for the next step. Never throws -- errors are logged by the wired impl.
84
+ */
85
+ afterStepApplyEnvFiles?: () => Promise<void>;
57
86
  }
58
87
  /** Result of the step execution loop. */
59
88
  interface StepLoopResult {
@@ -1,5 +1,5 @@
1
1
  import type { JobDispatch } from '@kici-dev/engine';
2
- import type { EventEmitRequest, EventEmitResponse, ConcurrencyReportMessage, ConcurrencyAckMessage } from './ipc-protocol.js';
2
+ import type { EventEmitRequest, EventEmitResponse, ConcurrencyReportMessage, ConcurrencyAckMessage, CacheRequestIpc, CacheResponseIpc } from './ipc-protocol.js';
3
3
  /**
4
4
  * Common interface for all execution sandbox backends.
5
5
  *
@@ -79,6 +79,16 @@ export interface JobExecutionOptions {
79
79
  * Optional for backward compatibility (callers that don't support the agent API).
80
80
  */
81
81
  onApiRequest?: (method: string, params: Record<string, unknown>) => Promise<unknown>;
82
+ /**
83
+ * Callback for relaying a user-facing cache request from the sandbox to the
84
+ * orchestrator. The sandbox runner sends `cache.request` IPC; the agent wraps
85
+ * it in the matching `cache.user.*` WS message and forwards to the
86
+ * orchestrator. Returns the orchestrator's response (or an error response).
87
+ *
88
+ * Optional so backends / harnesses that don't thread the cache through keep
89
+ * working — the runner falls back to a "not configured" cache response.
90
+ */
91
+ onCacheRequest?: (request: CacheRequestIpc) => Promise<CacheResponseIpc>;
82
92
  /**
83
93
  * Callback fired once per `ctx.secrets.mountFile` / `exposeFile` call the
84
94
  * workflow runner performs. Carries only key names + the resulting path /
@@ -13,5 +13,13 @@
13
13
  * This file is compiled alongside the agent by rolldown (existing build), but
14
14
  * runs as a SEPARATE process spawned by the sandbox backend.
15
15
  */
16
- export {};
16
+ import type { Job, GenericInitConfig } from '@kici-dev/sdk';
17
+ /** Raw provider webhook body for ctx.rawPayload — nested in the envelope. */
18
+ export declare function rawPayloadFromEvent(event: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
19
+ /**
20
+ * Normalize `Job.init` (config | config[] | false | undefined) to an ordered
21
+ * array of init specs. `false` is an explicit opt-out and `undefined` (no
22
+ * config) both resolve to an empty list — the init phase is then a no-op.
23
+ */
24
+ export declare function resolveInitSpecs(job: Job | undefined): GenericInitConfig[];
17
25
  //# sourceMappingURL=workflow-runner.d.ts.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Startup garbage collection for this agent's own temp-directory families.
3
+ *
4
+ * Job workdirs (`kici-<6 random chars>`, see job-runner.ts) and isolated
5
+ * pnpm stores (`kici-pnpm-store-*`, see dep-installer.ts) clean themselves
6
+ * up in `finally` blocks — but a hard process death (SIGKILL, OOM kill)
7
+ * skips those, and on a long-lived bare-metal agent the leftovers then
8
+ * accumulate forever. Collecting anything older than a day at startup is
9
+ * safe on shared hosts: no job lives remotely that long (job timeouts are
10
+ * minutes), so a concurrent agent's in-flight dirs are never eligible.
11
+ */
12
+ /**
13
+ * Collect this agent's stale temp dirs. `base` is overridable for tests;
14
+ * production callers use the default temp root. Never throws.
15
+ */
16
+ export declare function gcStaleAgentTmpDirs(base?: string): Promise<string[]>;
17
+ //# sourceMappingURL=tmp-gc.d.ts.map
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Workflow module loading: transforms `.ts` workflow files on import via the
3
- * shared oxc-transform ESM loader hook. Customer workflow code is imported
3
+ * `@kici-dev/core/ts-loader-hook` oxc-transform ESM loader hook. Customer
4
+ * workflow code is imported
4
5
  * directly from the cloned / extracted source tree — no intermediate bundle,
5
6
  * no Rolldown step at runtime. `@kici-dev/sdk` and host-repo deps resolve via
6
7
  * Node's normal ESM lookup against `.kici/node_modules/`.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { loadConfig, type AppConfig } from './config.js';
2
2
  export { installDeps, type InstallDepsOptions } from './execution/dep-installer.js';
3
3
  export { findLocalProtocolDeps, assertResolvableDeps, formatUnresolvableDepError, kiciHasLocalProtocolDeps, LocalDepProtocol, type LocalProtocolDep, } from './execution/validate-kici-deps.js';
4
+ export { createCacheApi, packCachePaths, extractCacheTarball, downloadAndExtractCache, resolveCachePath, type CacheTransport, type CacheRoots, } from './execution/cache/index.js';
4
5
  //# sourceMappingURL=index.d.ts.map