@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,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-side encryption for cross-job secret outputs.
|
|
3
|
+
*
|
|
4
|
+
* When a workflow step calls ctx.setSecretOutput(), the plaintext values flow
|
|
5
|
+
* through IPC from the sandbox runner to the agent process. Before sending them
|
|
6
|
+
* over the WebSocket to the orchestrator, the agent encrypts each value using
|
|
7
|
+
* X25519 ECDH + HKDF + AES-256-GCM.
|
|
8
|
+
*
|
|
9
|
+
* Protocol:
|
|
10
|
+
* 1. Generate a fresh ephemeral X25519 key pair (shared across all outputs in one call)
|
|
11
|
+
* 2. ECDH: sharedSecret = agentPrivateKey x runPublicKey
|
|
12
|
+
* 3. HKDF(sha256, sharedSecret, salt='', info='kici-run-secret-outputs', 32) -> AES key
|
|
13
|
+
* 4. AES-256-GCM encrypt each value independently (unique IV per value)
|
|
14
|
+
* 5. Pack as IV (12B) || AuthTag (16B) || Ciphertext, base64 encode
|
|
15
|
+
*
|
|
16
|
+
* The orchestrator decrypts using the run's private key and the agent's public key
|
|
17
|
+
* (via the same ECDH derivation from the other side).
|
|
18
|
+
*/
|
|
19
|
+
/** Encrypted envelope for a single secret output value. */
|
|
20
|
+
export interface EncryptedSecretOutput {
|
|
21
|
+
/** Base64-encoded agent ephemeral X25519 public key (DER SPKI). */
|
|
22
|
+
agentPublicKey: string;
|
|
23
|
+
/** Base64-encoded encrypted value: IV (12B) || AuthTag (16B) || Ciphertext. */
|
|
24
|
+
encrypted: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Encrypt secret output values for transport to the orchestrator.
|
|
28
|
+
*
|
|
29
|
+
* Generates a single ephemeral X25519 key pair per call (shared across all outputs
|
|
30
|
+
* in this batch -- one ECDH derivation, unique IV per value).
|
|
31
|
+
*
|
|
32
|
+
* @param outputs - Plaintext secret output key-value pairs
|
|
33
|
+
* @param runPublicKeyBase64 - Base64-encoded run X25519 public key (DER SPKI)
|
|
34
|
+
* @returns Map of key -> encrypted envelope
|
|
35
|
+
*/
|
|
36
|
+
export declare function encryptSecretOutputs(outputs: Record<string, string>, runPublicKeyBase64: string): Record<string, EncryptedSecretOutput>;
|
|
37
|
+
//# sourceMappingURL=secret-encryption.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret merging utilities for the workflow runner.
|
|
3
|
+
*
|
|
4
|
+
* Separated from workflow-runner.ts to allow unit testing without
|
|
5
|
+
* triggering the runner's top-level side effects (process handlers, main()).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Merge orchestrator-level secrets with auto-flattened context keys.
|
|
9
|
+
*
|
|
10
|
+
* Precedence (last wins):
|
|
11
|
+
* 1. Orchestrator-level secrets (lowest)
|
|
12
|
+
* 2. Context-flattened keys in declaration order (each context's keys overlay previous)
|
|
13
|
+
*
|
|
14
|
+
* This means: context-flattened keys override orchestrator-level secrets,
|
|
15
|
+
* and for collisions between contexts, last declared context wins.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildMergedFlatSecrets(orchestratorSecrets: Record<string, string>, namespacedSecrets: Record<string, Record<string, string>>): Record<string, string>;
|
|
18
|
+
//# sourceMappingURL=secret-merge.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step loop -- extracted step execution logic with hook integration and step rules.
|
|
3
|
+
*
|
|
4
|
+
* This module contains the core step execution loop that the workflow-runner calls.
|
|
5
|
+
* Extracted for testability: the workflow-runner's main() handles IPC, clone, deps,
|
|
6
|
+
* module loading, and calls this loop for step execution with hooks.
|
|
7
|
+
*/
|
|
8
|
+
import type { Step, StepContext, HookInput, OutputsMap, StepSecretMountRecord } from '@kici-dev/sdk';
|
|
9
|
+
import type { RunnerToAgentMessage } from './ipc-protocol.js';
|
|
10
|
+
import type { SandboxStepResult } from './types.js';
|
|
11
|
+
/** Job-level hooks passed to the step loop. */
|
|
12
|
+
export interface JobHooks {
|
|
13
|
+
beforeStep?: HookInput;
|
|
14
|
+
afterStep?: HookInput;
|
|
15
|
+
onSuccess?: HookInput;
|
|
16
|
+
onFailure?: HookInput;
|
|
17
|
+
onCancel?: HookInput;
|
|
18
|
+
cleanup?: HookInput;
|
|
19
|
+
}
|
|
20
|
+
/** Options for the step execution loop. */
|
|
21
|
+
interface StepLoopOptions {
|
|
22
|
+
steps: Step[];
|
|
23
|
+
/** Factory that creates a StepContext for a given step index and name. */
|
|
24
|
+
createStepContext: (stepIndex: number, stepName: string) => StepContext;
|
|
25
|
+
sendIpc: (msg: RunnerToAgentMessage) => void;
|
|
26
|
+
defaultTimeoutMs: number;
|
|
27
|
+
outputsMap: OutputsMap;
|
|
28
|
+
/** Event payload for rule context. */
|
|
29
|
+
event: Record<string, unknown>;
|
|
30
|
+
/** Environment variables for rule context. */
|
|
31
|
+
env: Record<string, string | undefined>;
|
|
32
|
+
/** Job-level hooks. */
|
|
33
|
+
jobHooks?: JobHooks;
|
|
34
|
+
/** Abort check callback. Returns true if job was aborted. */
|
|
35
|
+
isAborted?: () => boolean;
|
|
36
|
+
/** Job start time (epoch ms) for outcome metadata duration. */
|
|
37
|
+
startTime?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the secret key names accessed by the most recently created step context.
|
|
40
|
+
* Called after each step completes to include in step.complete IPC messages.
|
|
41
|
+
*/
|
|
42
|
+
getSecretsAccessLog?: () => string[];
|
|
43
|
+
/**
|
|
44
|
+
* Tear down per-step state created by the most recent `createStepContext`
|
|
45
|
+
* call. Invoked from the step-loop's `finally` after the step completes
|
|
46
|
+
* (success, failure, rule-skip, or timeout) so resources like the
|
|
47
|
+
* `ctx.secrets.mountFile` tmpdir get removed even on the failure paths.
|
|
48
|
+
* Never throws -- errors are logged by the wired implementation.
|
|
49
|
+
*/
|
|
50
|
+
disposeStepResources?: () => Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Returns the IPC `step.secret_mount` records collected by the most
|
|
53
|
+
* recently created step context. Emitted on step completion so the
|
|
54
|
+
* orchestrator can persist the audit trail alongside `secretsAccessed`.
|
|
55
|
+
*/
|
|
56
|
+
getSecretMountRecords?: () => StepSecretMountRecord[];
|
|
57
|
+
}
|
|
58
|
+
/** Result of the step execution loop. */
|
|
59
|
+
interface StepLoopResult {
|
|
60
|
+
status: 'success' | 'failed' | 'aborted';
|
|
61
|
+
stepResults: SandboxStepResult[];
|
|
62
|
+
failureReason?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Execute the step loop with hook integration and step-level rule evaluation.
|
|
66
|
+
*
|
|
67
|
+
* Hook execution order:
|
|
68
|
+
* - beforeStep -> step -> afterStep (per step)
|
|
69
|
+
* - onSuccess or onFailure (after all steps)
|
|
70
|
+
* - cleanup (always, after onSuccess/onFailure)
|
|
71
|
+
*
|
|
72
|
+
* Hooks are observers: beforeStep/afterStep failures do NOT affect step execution.
|
|
73
|
+
* Only completion hooks (onSuccess/onFailure/cleanup) can change job status to failed.
|
|
74
|
+
*/
|
|
75
|
+
export declare function executeStepLoop(opts: StepLoopOptions): Promise<StepLoopResult>;
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=step-loop.d.ts.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { JobDispatch } from '@kici-dev/engine';
|
|
2
|
+
import type { EventEmitRequest, EventEmitResponse, ConcurrencyReportMessage, ConcurrencyAckMessage } from './ipc-protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* Common interface for all execution sandbox backends.
|
|
5
|
+
*
|
|
6
|
+
* All three backends (container, bare-metal, Firecracker) implement this
|
|
7
|
+
* interface to provide isolated code execution with a consistent lifecycle:
|
|
8
|
+
* setup -> executeJob -> teardown (with abort available at any time)
|
|
9
|
+
*
|
|
10
|
+
* The sandbox isolates customer code from agent-internal credentials and
|
|
11
|
+
* resources. The agent process never loads or executes customer code directly.
|
|
12
|
+
*/
|
|
13
|
+
export interface ExecutionSandbox {
|
|
14
|
+
/**
|
|
15
|
+
* Prepare the sandbox environment.
|
|
16
|
+
*
|
|
17
|
+
* - Container: create + start disposable container
|
|
18
|
+
* - Bare-metal: validate bwrap availability
|
|
19
|
+
* - Firecracker: no-op (VM already running, managed by scaler)
|
|
20
|
+
*/
|
|
21
|
+
setup(options: SandboxSetupOptions): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Execute the full job lifecycle inside the sandbox.
|
|
24
|
+
*
|
|
25
|
+
* Handles: clone, dependency install, compile, step execution.
|
|
26
|
+
* Returns step results via callbacks as they complete.
|
|
27
|
+
*/
|
|
28
|
+
executeJob(options: JobExecutionOptions): Promise<JobExecutionResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Abort a running job.
|
|
31
|
+
*
|
|
32
|
+
* Sends SIGTERM to the sandbox process, waits a grace period (~10s),
|
|
33
|
+
* then sends SIGKILL if the process has not exited.
|
|
34
|
+
*/
|
|
35
|
+
abort(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Tear down the sandbox environment.
|
|
38
|
+
*
|
|
39
|
+
* - Container: docker rm -f
|
|
40
|
+
* - Bare-metal: process cleanup
|
|
41
|
+
* - Firecracker: no-op (VM lifecycle managed by scaler)
|
|
42
|
+
*/
|
|
43
|
+
teardown(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
/** Options for preparing the sandbox environment. */
|
|
46
|
+
export interface SandboxSetupOptions {
|
|
47
|
+
/** Container image (container backend only, e.g. 'node:20-alpine'). */
|
|
48
|
+
image?: string;
|
|
49
|
+
/** Working directory for the job on the host. */
|
|
50
|
+
workDir: string;
|
|
51
|
+
/** Sanitized environment variables (user env + secrets, NO agent credentials). */
|
|
52
|
+
env: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
/** Options for executing a job inside the sandbox. */
|
|
55
|
+
export interface JobExecutionOptions {
|
|
56
|
+
/** Dispatch data from the orchestrator (repo URL, ref, sha, token, etc.). */
|
|
57
|
+
dispatch: JobDispatch;
|
|
58
|
+
/** Callback for real-time step status updates (start, success, failed). */
|
|
59
|
+
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;
|
|
62
|
+
/** Abort signal for cancellation. */
|
|
63
|
+
signal: AbortSignal;
|
|
64
|
+
/**
|
|
65
|
+
* Callback for relaying event.emit requests from the sandbox to the orchestrator.
|
|
66
|
+
* The sandbox runner sends event.emit IPC messages; the agent wraps them in WS
|
|
67
|
+
* protocol and forwards to the orchestrator. Returns the orchestrator's response.
|
|
68
|
+
*/
|
|
69
|
+
onEventEmit: (request: EventEmitRequest) => Promise<EventEmitResponse>;
|
|
70
|
+
/**
|
|
71
|
+
* Callback for relaying concurrency.report from the sandbox to the orchestrator.
|
|
72
|
+
* Returns the orchestrator's ack (proceed/wait/cancel).
|
|
73
|
+
*/
|
|
74
|
+
onConcurrencyReport: (report: ConcurrencyReportMessage) => Promise<ConcurrencyAckMessage>;
|
|
75
|
+
/**
|
|
76
|
+
* Callback for relaying agent.api.request from the sandbox to the orchestrator.
|
|
77
|
+
* Returns the orchestrator's response (result or error).
|
|
78
|
+
*
|
|
79
|
+
* Optional for backward compatibility (callers that don't support the agent API).
|
|
80
|
+
*/
|
|
81
|
+
onApiRequest?: (method: string, params: Record<string, unknown>) => Promise<unknown>;
|
|
82
|
+
/**
|
|
83
|
+
* Callback fired once per `ctx.secrets.mountFile` / `exposeFile` call the
|
|
84
|
+
* workflow runner performs. Carries only key names + the resulting path /
|
|
85
|
+
* env var -- never the file content. Optional so backends that don't yet
|
|
86
|
+
* thread the event through (CT / unit-style harnesses) keep working.
|
|
87
|
+
*/
|
|
88
|
+
onSecretMount?: (event: {
|
|
89
|
+
stepIndex: number;
|
|
90
|
+
sources: string[];
|
|
91
|
+
target: string;
|
|
92
|
+
envVar?: string;
|
|
93
|
+
kind: 'mountFile' | 'exposeFile';
|
|
94
|
+
}) => void;
|
|
95
|
+
}
|
|
96
|
+
/** Aggregated result of a job execution. */
|
|
97
|
+
export interface JobExecutionResult {
|
|
98
|
+
/** Overall job status. */
|
|
99
|
+
status: 'success' | 'failed' | 'cancelled';
|
|
100
|
+
/** Per-step results in execution order. */
|
|
101
|
+
stepResults: SandboxStepResult[];
|
|
102
|
+
/** Total job duration in milliseconds. */
|
|
103
|
+
durationMs: number;
|
|
104
|
+
/** Error message when the runner process crashed (no job.complete received). */
|
|
105
|
+
error?: string;
|
|
106
|
+
/** Aggregated step outputs by step name (present on success when steps return values). */
|
|
107
|
+
outputs?: Record<string, Record<string, unknown>>;
|
|
108
|
+
/** Encrypted secret outputs (present on success when steps called ctx.setSecretOutput). */
|
|
109
|
+
secretOutputs?: Record<string, {
|
|
110
|
+
agentPublicKey: string;
|
|
111
|
+
encrypted: string;
|
|
112
|
+
}>;
|
|
113
|
+
/** Names of sibling jobs dropped by DynamicJobFn re-evaluation drift. */
|
|
114
|
+
droppedJobs?: string[];
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Result of a single step execution within the sandbox.
|
|
118
|
+
*
|
|
119
|
+
* Self-contained step result type for the sandbox execution model.
|
|
120
|
+
*/
|
|
121
|
+
export interface SandboxStepResult {
|
|
122
|
+
/** Step name from the workflow definition. */
|
|
123
|
+
name: string;
|
|
124
|
+
/** Zero-based index of the step within the job. */
|
|
125
|
+
stepIndex: number;
|
|
126
|
+
/** Step execution status. */
|
|
127
|
+
status: 'success' | 'failed' | 'skipped';
|
|
128
|
+
/** Step duration in milliseconds. */
|
|
129
|
+
durationMs: number;
|
|
130
|
+
/** Error details when status is 'failed'. */
|
|
131
|
+
error?: {
|
|
132
|
+
/** Human-readable error message. */
|
|
133
|
+
message: string;
|
|
134
|
+
/** Process exit code (non-zero on failure). */
|
|
135
|
+
exitCode?: number;
|
|
136
|
+
/** Signal that terminated the process (e.g. 'SIGTERM', 'SIGKILL'). */
|
|
137
|
+
signal?: string;
|
|
138
|
+
};
|
|
139
|
+
/** Step return value (outputs). Present on success when step returns non-void. */
|
|
140
|
+
outputs?: Record<string, unknown>;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Runner -- standalone entry point that runs INSIDE the sandbox.
|
|
3
|
+
*
|
|
4
|
+
* This is the code that actually executes customer workflows in isolation.
|
|
5
|
+
* The agent process never loads or executes customer code -- only this runner does.
|
|
6
|
+
*
|
|
7
|
+
* Supports two IPC modes:
|
|
8
|
+
* - Fork IPC: Used by bare-metal (bwrap) and Firecracker backends. Messages go
|
|
9
|
+
* via Node.js IPC channel (process.send / process.on('message')).
|
|
10
|
+
* - Stdio IPC: Used by container backend (docker exec). JSON-line messages flow
|
|
11
|
+
* bidirectionally: agent writes to container stdin, runner writes to stdout.
|
|
12
|
+
*
|
|
13
|
+
* This file is compiled alongside the agent by rolldown (existing build), but
|
|
14
|
+
* runs as a SEPARATE process spawned by the sandbox backend.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=workflow-runner.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.kici/` source tarball creation for build agents.
|
|
3
|
+
*
|
|
4
|
+
* After cloning a customer repo, packs the `.kici/` directory (excluding
|
|
5
|
+
* `node_modules/` — that lives in its own cached tarball per `dep-packer.ts`)
|
|
6
|
+
* into a deterministic gzip tarball. The tarball bytes are hashed for
|
|
7
|
+
* integrity verification on the execution-job side.
|
|
8
|
+
*
|
|
9
|
+
* The artifact is content-addressed by the compiler's `contentHash` (hash of
|
|
10
|
+
* the raw workflow entry + asset digest), not by the tarball bytes — so the
|
|
11
|
+
* orchestrator's cache lookup keys unchanged while the stored bytes switch
|
|
12
|
+
* from a Rolldown bundle to a raw-source tarball.
|
|
13
|
+
*/
|
|
14
|
+
export declare function packKiciSource(workDir: string): Promise<{
|
|
15
|
+
tarball: Buffer;
|
|
16
|
+
hash: string;
|
|
17
|
+
}>;
|
|
18
|
+
//# sourceMappingURL=source-packer.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.kici/` source tarball restoration for execution agents.
|
|
3
|
+
*
|
|
4
|
+
* Downloads a pre-built `.kici/` source tarball from the orchestrator's cache
|
|
5
|
+
* and extracts it into `workDir/` so the workflow entry point becomes
|
|
6
|
+
* importable. Mirrors the shape of `dep-restore.ts` but without the streaming
|
|
7
|
+
* optimization — source tarballs are tiny (kilobytes, not the hundreds of
|
|
8
|
+
* megabytes a `node_modules/` tarball carries).
|
|
9
|
+
*
|
|
10
|
+
* Note on integrity: `dispatch.sourceTarHash` is the workflow `contentHash`
|
|
11
|
+
* (computed over the raw source per `workflow-loader.ts::computeContentHash`),
|
|
12
|
+
* not the SHA-256 of the tarball bytes. The shared S3 cache key is derived
|
|
13
|
+
* from that same contentHash, so a signed GET URL from the orchestrator
|
|
14
|
+
* already establishes provenance for restored tarballs. Every
|
|
15
|
+
* `loadWorkflowSource` call site — build, init, and dynamic eval — passes
|
|
16
|
+
* the dispatched `contentHash` (and `resolvedHashFiles` when present) so
|
|
17
|
+
* the lock-vs-source drift gate fires at each author-TS load site, not
|
|
18
|
+
* only the build phase. That closes the corner cases where init or eval
|
|
19
|
+
* runs without a preceding build (cache infrastructure unavailable, or a
|
|
20
|
+
* build job that failed but left dynamic dispatch in flight).
|
|
21
|
+
*/
|
|
22
|
+
export declare function restoreSource(workDir: string, sourceTarUrl: string): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=source-restore.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared timeout utility for wrapping async operations with a deadline.
|
|
3
|
+
*
|
|
4
|
+
* Used by init-runner (dynamic field evaluation) and dynamic job function evaluation.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Execute a function with a timeout using Promise.race.
|
|
8
|
+
* Throws if the function does not resolve within the given timeout.
|
|
9
|
+
*/
|
|
10
|
+
export declare function withTimeout<T>(fn: () => T | Promise<T>, timeoutMs: number, label: string): Promise<T>;
|
|
11
|
+
//# sourceMappingURL=timeout-util.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow module loading: transforms `.ts` workflow files on import via the
|
|
3
|
+
* shared oxc-transform ESM loader hook. Customer workflow code is imported
|
|
4
|
+
* directly from the cloned / extracted source tree — no intermediate bundle,
|
|
5
|
+
* no Rolldown step at runtime. `@kici-dev/sdk` and host-repo deps resolve via
|
|
6
|
+
* Node's normal ESM lookup against `.kici/node_modules/`.
|
|
7
|
+
*/
|
|
8
|
+
import type { Workflow, StepInput, DynamicJobFn } from '@kici-dev/sdk';
|
|
9
|
+
/**
|
|
10
|
+
* Compile schema version — must match `@kici-dev/compiler` lockfile/hasher.ts.
|
|
11
|
+
* Mixed into the content hash so compilation-approach changes produce different
|
|
12
|
+
* hashes. Bumped 3 → 4 when the artifact model switched from a Rolldown-bundled
|
|
13
|
+
* `.compiled.mjs` to a raw-source tarball consumed by the oxc-transform ESM
|
|
14
|
+
* loader hook. Bumped 4 → 5 when the hash input started normalizing line
|
|
15
|
+
* endings (CRLF → LF) so a Windows agent's checked-out CRLF source matches a
|
|
16
|
+
* Linux compiler's LF source.
|
|
17
|
+
*/
|
|
18
|
+
export declare const COMPILE_SCHEMA_VERSION = 5;
|
|
19
|
+
export declare function ensureLoaderHookRegistered(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Load a workflow module by dynamic-importing its source file.
|
|
22
|
+
*
|
|
23
|
+
* Registers the oxc-transform loader hook (idempotent), then dynamic-imports
|
|
24
|
+
* the `.ts` file. Transitive imports resolve against the workspace's
|
|
25
|
+
* `node_modules/` the same way any `tsx`-style runner would — so host-repo
|
|
26
|
+
* helpers and `@kici-dev/sdk` Just Work.
|
|
27
|
+
*
|
|
28
|
+
* When `expectedContentHash` is provided, verifies the raw source matches
|
|
29
|
+
* the hash in the lock file. Drift between source and lock file produces a
|
|
30
|
+
* descriptive error that surfaces the baked agent SDK fingerprint (useful
|
|
31
|
+
* when debugging "is the agent running a stale build?").
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadWorkflowSource(workDir: string, sourceFile: string, expectedContentHash?: string, resolvedHashFiles?: string[]): Promise<{
|
|
34
|
+
module: Record<string, unknown>;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Extract a workflow by name from a module's exports.
|
|
38
|
+
*
|
|
39
|
+
* Searches:
|
|
40
|
+
* 1. Default export (single Workflow or array of Workflows)
|
|
41
|
+
* 2. Named exports
|
|
42
|
+
*/
|
|
43
|
+
export declare function extractWorkflow(module: Record<string, unknown>, workflowName: string): Workflow;
|
|
44
|
+
/**
|
|
45
|
+
* Extract a dynamic job function from a workflow by index.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractDynamicJobFn(workflow: Workflow, index: number): DynamicJobFn;
|
|
48
|
+
/**
|
|
49
|
+
* Extract steps from a static job within a workflow.
|
|
50
|
+
*/
|
|
51
|
+
export declare function extractSteps(workflow: Workflow, jobName: string): readonly StepInput[];
|
|
52
|
+
/**
|
|
53
|
+
* Extract steps from a job generated by a DynamicJobFn.
|
|
54
|
+
*
|
|
55
|
+
* Re-evaluates the DynamicJobFn to get the generated Job[] array, then finds
|
|
56
|
+
* the job by name and returns its steps. This is necessary because
|
|
57
|
+
* DynamicJobFn-generated jobs' step functions are closures that can only be
|
|
58
|
+
* obtained by calling the DynamicJobFn again.
|
|
59
|
+
*
|
|
60
|
+
* The function must be deterministic: given the same event context, it should
|
|
61
|
+
* return the same jobs with the same step functions. When `expectedJobNames`
|
|
62
|
+
* is provided, the re-evaluated output is compared against the original eval.
|
|
63
|
+
* A sibling mismatch logs a warning; a missing target job throws a clear
|
|
64
|
+
* determinism error.
|
|
65
|
+
*/
|
|
66
|
+
export declare function extractStepsFromDynamicJob(workflow: Workflow, dynamicIndex: number, jobName: string, event: Record<string, unknown>, env: Record<string, string | undefined>, apiTransport?: (method: string, params?: Record<string, unknown>) => Promise<unknown>, expectedJobNames?: string[]): Promise<{
|
|
67
|
+
steps: readonly StepInput[];
|
|
68
|
+
droppedJobs: string[];
|
|
69
|
+
}>;
|
|
70
|
+
//# sourceMappingURL=workflow-loader.d.ts.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { fileURLToPath as __cjs_fileURLToPath } from "node:url";
|
|
2
|
+
import { dirname as __cjs_dirname } from "node:path";
|
|
3
|
+
__cjs_dirname(__cjs_fileURLToPath(import.meta.url));
|
|
4
|
+
import "node:module";
|
|
5
|
+
import { hostname } from "node:os";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/shared/env";
|
|
9
|
+
import { KNOWN_ROLES, validateNoReservedLabels } from "@kici-dev/engine";
|
|
10
|
+
import.meta.url;
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/config.ts
|
|
13
|
+
/** Execution mode for the agent's sandbox backend. Mirrors the runtime enum. */
|
|
14
|
+
const ExecutionMode = z.enum([
|
|
15
|
+
"container",
|
|
16
|
+
"bare-metal",
|
|
17
|
+
"firecracker"
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Env-var definition for the agent. Exported so the docs generator and the
|
|
21
|
+
* deploy-stg pre-validator can inspect / re-parse without round-tripping
|
|
22
|
+
* through process.env.
|
|
23
|
+
*/
|
|
24
|
+
const envDef = defineEnv({
|
|
25
|
+
service: "agent",
|
|
26
|
+
schema: z.object({
|
|
27
|
+
orchestratorUrl: z.string().url().min(1, "KICI_ORCHESTRATOR_URL is required"),
|
|
28
|
+
agentId: z.string().optional(),
|
|
29
|
+
labels: z.string().default("").transform((s) => s.split(",").filter(Boolean)),
|
|
30
|
+
roles: z.string().optional().transform((s) => {
|
|
31
|
+
if (s === void 0) return void 0;
|
|
32
|
+
if (s === "") return [];
|
|
33
|
+
return s.split(",").filter(Boolean);
|
|
34
|
+
}).refine((roles) => {
|
|
35
|
+
if (roles === void 0) return true;
|
|
36
|
+
const validValues = [...KNOWN_ROLES, "all"];
|
|
37
|
+
return roles.every((r) => validValues.includes(r));
|
|
38
|
+
}, { message: `KICI_ROLES must contain only: ${[...KNOWN_ROLES, "all"].join(", ")}` }).transform((roles) => {
|
|
39
|
+
if (roles === void 0) return void 0;
|
|
40
|
+
if (roles.includes("all")) return void 0;
|
|
41
|
+
if (roles.length === 0) return [];
|
|
42
|
+
return roles.filter((r) => r !== "all");
|
|
43
|
+
}),
|
|
44
|
+
port: z.coerce.number().default(8080),
|
|
45
|
+
logLevel: z.enum([
|
|
46
|
+
"debug",
|
|
47
|
+
"info",
|
|
48
|
+
"warn",
|
|
49
|
+
"error"
|
|
50
|
+
]).default("info"),
|
|
51
|
+
agentToken: z.string().optional(),
|
|
52
|
+
githubToken: z.string().optional(),
|
|
53
|
+
maxLogSizeBytes: z.coerce.number().default(10 * 1024 * 1024),
|
|
54
|
+
defaultStepTimeoutMs: z.coerce.number().default(1800 * 1e3),
|
|
55
|
+
dockerKeepFailed: z.string().default("false").transform((s) => s === "true"),
|
|
56
|
+
jobHeartbeatIntervalMs: z.coerce.number().default(6e4),
|
|
57
|
+
backpressureMode: z.enum(["pause", "drop"]).default("pause"),
|
|
58
|
+
sandbox: z.string().default("false").transform((s) => s === "true"),
|
|
59
|
+
sandboxNetwork: z.enum(["isolated", "host"]).default("isolated"),
|
|
60
|
+
scalerManaged: z.string().optional().transform((s) => s === "1"),
|
|
61
|
+
scalerIdleTimeoutMs: z.coerce.number().default(5e3),
|
|
62
|
+
scalerPendingDispatchTimeoutMs: z.coerce.number().default(6e4),
|
|
63
|
+
executionMode: ExecutionMode.optional(),
|
|
64
|
+
otelExporterOtlpEndpoint: z.string().optional(),
|
|
65
|
+
concurrencyWaitTimeoutMs: z.coerce.number().int().min(1e3).default(36e5)
|
|
66
|
+
}),
|
|
67
|
+
envMap: {
|
|
68
|
+
orchestratorUrl: "KICI_ORCHESTRATOR_URL",
|
|
69
|
+
agentId: "KICI_AGENT_ID",
|
|
70
|
+
labels: "KICI_LABELS",
|
|
71
|
+
roles: "KICI_ROLES",
|
|
72
|
+
port: "KICI_PORT",
|
|
73
|
+
logLevel: "KICI_LOG_LEVEL",
|
|
74
|
+
agentToken: "KICI_AGENT_TOKEN",
|
|
75
|
+
githubToken: "KICI_GITHUB_TOKEN",
|
|
76
|
+
maxLogSizeBytes: "KICI_MAX_LOG_SIZE_BYTES",
|
|
77
|
+
defaultStepTimeoutMs: "KICI_DEFAULT_STEP_TIMEOUT_MS",
|
|
78
|
+
dockerKeepFailed: "KICI_DOCKER_KEEP_FAILED",
|
|
79
|
+
jobHeartbeatIntervalMs: "KICI_JOB_HEARTBEAT_INTERVAL_MS",
|
|
80
|
+
backpressureMode: "KICI_BACKPRESSURE_MODE",
|
|
81
|
+
sandbox: "KICI_SANDBOX",
|
|
82
|
+
sandboxNetwork: "KICI_SANDBOX_NETWORK",
|
|
83
|
+
scalerManaged: "KICI_SCALER_MANAGED",
|
|
84
|
+
scalerIdleTimeoutMs: "KICI_SCALER_IDLE_TIMEOUT",
|
|
85
|
+
scalerPendingDispatchTimeoutMs: "KICI_SCALER_PENDING_DISPATCH_TIMEOUT",
|
|
86
|
+
executionMode: "KICI_EXECUTION_MODE",
|
|
87
|
+
otelExporterOtlpEndpoint: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
88
|
+
concurrencyWaitTimeoutMs: "KICI_CONCURRENCY_WAIT_TIMEOUT_MS"
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
/**
|
|
92
|
+
* Load and validate agent configuration from environment variables.
|
|
93
|
+
*
|
|
94
|
+
* Maps env vars with KICI_ prefix:
|
|
95
|
+
* - KICI_ORCHESTRATOR_URL (required)
|
|
96
|
+
* - KICI_AGENT_ID (optional, auto-generated from hostname-uuid8)
|
|
97
|
+
* - KICI_LABELS (comma-separated, e.g. "linux,docker"). Labels with 'kici-' prefix are reserved.
|
|
98
|
+
* - KICI_ROLES (comma-separated agent roles, e.g. "builder,init-runner". undefined=all, empty=execution-only)
|
|
99
|
+
* - KICI_PORT (default: 8080)
|
|
100
|
+
* - KICI_LOG_LEVEL (default: info)
|
|
101
|
+
* - KICI_AGENT_TOKEN (optional, kat_ prefixed PSK for orchestrator authentication)
|
|
102
|
+
* - KICI_GITHUB_TOKEN (optional)
|
|
103
|
+
* - KICI_MAX_LOG_SIZE_BYTES (default: 10MB)
|
|
104
|
+
* - KICI_DEFAULT_STEP_TIMEOUT_MS (default: 30 min)
|
|
105
|
+
* - KICI_DOCKER_KEEP_FAILED (default: false)
|
|
106
|
+
* - KICI_JOB_HEARTBEAT_INTERVAL_MS (default: 60000)
|
|
107
|
+
* - KICI_BACKPRESSURE_MODE (default: pause, options: pause | drop)
|
|
108
|
+
* - KICI_SANDBOX (default: false) — enable bubblewrap (bwrap) namespace isolation for bare-metal execution
|
|
109
|
+
* - KICI_SANDBOX_NETWORK (default: isolated, options: isolated | host) — when sandbox=true, controls bwrap network namespace
|
|
110
|
+
* - KICI_SCALER_MANAGED (set to "1" by the orchestrator's auto-scaler — agent self-shuts down on idle)
|
|
111
|
+
* - KICI_SCALER_IDLE_TIMEOUT (ms, default 5000) — how long a scaler-managed agent waits before shutdown after going idle
|
|
112
|
+
* - KICI_SCALER_PENDING_DISPATCH_TIMEOUT (ms, default 60000) — extended idle window when register.ack signals a queued bound job
|
|
113
|
+
* - KICI_EXECUTION_MODE (optional, options: container | bare-metal | firecracker) — override the runner's mode-pick logic
|
|
114
|
+
* - KICI_CONCURRENCY_WAIT_TIMEOUT_MS (default: 3_600_000) — workflow-runner timeout when long-polling for a slot-release follow-up `concurrency.ack`
|
|
115
|
+
*/
|
|
116
|
+
function loadConfig() {
|
|
117
|
+
const data = envDef.parse();
|
|
118
|
+
if (!data.scalerManaged) validateNoReservedLabels(data.labels, "KICI_LABELS");
|
|
119
|
+
validateUnknownKiciVars([...envDef.listKnownEnvVars(), ...LOGGER_ENV_VARS]);
|
|
120
|
+
return {
|
|
121
|
+
...data,
|
|
122
|
+
agentId: data.agentId ?? `${hostname()}-${randomUUID().slice(0, 8)}`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
export { loadConfig };
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic metrics reporter that collects OTel metrics and pushes them
|
|
3
|
+
* to the orchestrator via WebSocket as `agent.metrics` messages.
|
|
4
|
+
*
|
|
5
|
+
* The orchestrator aggregates these metrics from all agents and exposes
|
|
6
|
+
* them on its `/metrics` endpoint for Prometheus scraping.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentMetrics } from '@kici-dev/engine';
|
|
9
|
+
export declare class MetricsReporter {
|
|
10
|
+
private timer?;
|
|
11
|
+
private readonly agentId;
|
|
12
|
+
private readonly send;
|
|
13
|
+
private readonly intervalMs;
|
|
14
|
+
constructor(opts: {
|
|
15
|
+
agentId: string;
|
|
16
|
+
send: (msg: AgentMetrics) => void;
|
|
17
|
+
intervalMs?: number;
|
|
18
|
+
});
|
|
19
|
+
/** Start periodic metric collection and push. */
|
|
20
|
+
start(): void;
|
|
21
|
+
/** Stop the periodic reporter. */
|
|
22
|
+
stop(): void;
|
|
23
|
+
/** Collect current metrics and send as agent.metrics message. */
|
|
24
|
+
collectAndSend(): Promise<void>;
|
|
25
|
+
/** Collect a snapshot of all current OTel metrics. */
|
|
26
|
+
collectSnapshot(): Promise<AgentMetrics['metrics']>;
|
|
27
|
+
/** Map OTel DataPointType to wire format type string. */
|
|
28
|
+
private mapDataPointType;
|
|
29
|
+
/** Extract string labels from OTel Attributes. */
|
|
30
|
+
private extractLabels;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=metrics-reporter.d.ts.map
|