@kici-dev/agent 0.1.23 → 0.1.24
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/dist/execution/cache/cache-phase.d.ts +21 -4
- package/dist/execution/cache/index.d.ts +1 -1
- package/dist/execution/init-runner.d.ts +2 -1
- package/dist/execution/sandbox/capture-context.d.ts +11 -0
- package/dist/execution/sandbox/ipc-protocol.d.ts +17 -3
- package/dist/execution/sandbox/parallel-scheduler.d.ts +34 -0
- package/dist/execution/sandbox/step-loop.d.ts +91 -16
- package/dist/execution/sandbox/step-task-registry.d.ts +26 -0
- package/dist/execution/sandbox/types.d.ts +2 -2
- package/dist/execution/sandbox/workflow-runner.d.ts +22 -1
- package/dist/server.js +107 -52
- package/dist/workflow-runner.js +436 -121
- package/package.json +6 -6
- package/sbom.spdx.json +68 -68
|
@@ -10,20 +10,37 @@ export interface CachePhaseDeps {
|
|
|
10
10
|
cache: CacheApi;
|
|
11
11
|
/** Emit a runner→agent IPC message (the masked send). */
|
|
12
12
|
sendIpc: (msg: RunnerToAgentMessage) => void;
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Allocate the next cache pseudo-step index for `ownerStepIndex` (the real
|
|
15
|
+
* step the cache op belongs to, or {@link JOB_CACHE_OWNER} for job-level
|
|
16
|
+
* cache). Each owner draws from a disjoint block above every real-step and
|
|
17
|
+
* hook index, so two concurrently-running steps' cache pseudo-steps never
|
|
18
|
+
* collide.
|
|
19
|
+
*/
|
|
20
|
+
nextStepIndex: (ownerStepIndex: number) => number;
|
|
15
21
|
}
|
|
22
|
+
/** Owner sentinel for job-level (not step-scoped) cache restore/save. */
|
|
23
|
+
export declare const JOB_CACHE_OWNER = -1;
|
|
24
|
+
/**
|
|
25
|
+
* Build the cache pseudo-step index allocator. Each owner (a real step index, or
|
|
26
|
+
* {@link JOB_CACHE_OWNER}) gets its own disjoint block of {@link CACHE_INDEX_BLOCK}
|
|
27
|
+
* indices, all above every real-step and hook index (`stepCount * 3 + 100`). A
|
|
28
|
+
* step's two-or-more cache pseudo-steps are a pure function of its own owner
|
|
29
|
+
* index, so concurrent children never collide. Under sequential execution the
|
|
30
|
+
* emitted indices stay above all real/hook indices exactly as before.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createCacheStepIndexAllocator(stepCount: number): (ownerStepIndex: number) => number;
|
|
16
33
|
/**
|
|
17
34
|
* Restore every spec, surfacing each as a `cache:restore` pseudo-step. Returns
|
|
18
35
|
* a map keyed by spec key recording whether the EXACT key hit (so the save
|
|
19
36
|
* phase can skip a redundant save of an entry that already exists).
|
|
20
37
|
*/
|
|
21
|
-
export declare function restoreCacheSpecs(specs: CacheSpec[], deps: CachePhaseDeps): Promise<Map<string, CacheRestoreOutcome>>;
|
|
38
|
+
export declare function restoreCacheSpecs(specs: CacheSpec[], deps: CachePhaseDeps, ownerStepIndex: number): Promise<Map<string, CacheRestoreOutcome>>;
|
|
22
39
|
/**
|
|
23
40
|
* Save every spec whose EXACT key did not already hit on restore (immutable +
|
|
24
41
|
* no redundant save), surfacing each as a `cache:save` pseudo-step. A spec
|
|
25
42
|
* whose restore matched a different key via a `restoreKeys` prefix is still
|
|
26
43
|
* saved under its exact key.
|
|
27
44
|
*/
|
|
28
|
-
export declare function saveCacheSpecs(specs: CacheSpec[], restoreResults: Map<string, CacheRestoreOutcome>, deps: CachePhaseDeps): Promise<void>;
|
|
45
|
+
export declare function saveCacheSpecs(specs: CacheSpec[], restoreResults: Map<string, CacheRestoreOutcome>, deps: CachePhaseDeps, ownerStepIndex: number): Promise<void>;
|
|
29
46
|
//# sourceMappingURL=cache-phase.d.ts.map
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* `ctx.cache` API factory plus its transport interface.
|
|
6
6
|
*/
|
|
7
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';
|
|
8
|
+
export { restoreCacheSpecs, saveCacheSpecs, createCacheStepIndexAllocator, JOB_CACHE_OWNER, type CachePhaseDeps, type CacheRestoreOutcome, } from './cache-phase.js';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -5,7 +5,8 @@ import { type MatrixValues } from '@kici-dev/engine';
|
|
|
5
5
|
* Only fields that were flagged as dynamic and successfully resolved are set.
|
|
6
6
|
*/
|
|
7
7
|
export interface InitResult {
|
|
8
|
-
|
|
8
|
+
/** Resolved bound-environment names, in merge order (one per `environments` element). */
|
|
9
|
+
environmentNames?: string[];
|
|
9
10
|
env?: Record<string, string>;
|
|
10
11
|
concurrencyGroup?: string;
|
|
11
12
|
/**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `fn` with `stepIndex` as the active console-capture attribution for the
|
|
3
|
+
* duration of its async execution (including everything it awaits).
|
|
4
|
+
*/
|
|
5
|
+
export declare function runInStepCapture<T>(stepIndex: number, fn: () => Promise<T>): Promise<T>;
|
|
6
|
+
/**
|
|
7
|
+
* The step index whose run is currently on the async stack, or `-1` when no
|
|
8
|
+
* capture scope is active (workflow-level / between-steps output).
|
|
9
|
+
*/
|
|
10
|
+
export declare function currentCaptureStepIndex(): number;
|
|
11
|
+
//# sourceMappingURL=capture-context.d.ts.map
|
|
@@ -24,13 +24,27 @@ interface StepStartMessage {
|
|
|
24
24
|
stepName: string;
|
|
25
25
|
/** Distinguishes regular steps from hook executions (e.g., 'hook:onCancel', 'hook:cleanup'). Defaults to 'step'. */
|
|
26
26
|
step_type?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Initial state for the step. Defaults to `running`. A parallel-group child
|
|
29
|
+
* queued behind `maxParallel` is announced as `pending` before it acquires a
|
|
30
|
+
* slot; it later emits a second `step.start` with `running` when it launches.
|
|
31
|
+
*/
|
|
32
|
+
state?: 'running' | 'pending';
|
|
33
|
+
/** Step concurrency role; absent means an ordinary sequential step. */
|
|
34
|
+
concurrencyKind?: string;
|
|
35
|
+
/** Parallel-group correlation id shared by a group's children (e.g. `g0`). */
|
|
36
|
+
groupId?: string;
|
|
27
37
|
}
|
|
28
|
-
/** A step has completed (success, failure,
|
|
38
|
+
/** A step has completed (success, failure, check-mode skip, or fail-fast cancel). */
|
|
29
39
|
interface StepCompleteMessage {
|
|
30
40
|
type: 'step.complete';
|
|
31
41
|
stepIndex: number;
|
|
32
|
-
status: 'success' | 'failed' | 'skipped';
|
|
42
|
+
status: 'success' | 'failed' | 'skipped' | 'cancelled';
|
|
33
43
|
durationMs: number;
|
|
44
|
+
/** Step concurrency role; absent means an ordinary sequential step. */
|
|
45
|
+
concurrencyKind?: string;
|
|
46
|
+
/** Parallel-group correlation id shared by a group's children (e.g. `g0`). */
|
|
47
|
+
groupId?: string;
|
|
34
48
|
error?: {
|
|
35
49
|
message: string;
|
|
36
50
|
exitCode?: number;
|
|
@@ -454,7 +468,7 @@ export interface JobExecutionRequest {
|
|
|
454
468
|
provider?: string;
|
|
455
469
|
/** Whether to checkout the repo (default: true). */
|
|
456
470
|
checkout?: boolean;
|
|
457
|
-
/** Whether this job is part of a
|
|
471
|
+
/** Whether this job is part of a developer-initiated run triggered by `kici run`. */
|
|
458
472
|
isTestRun?: boolean;
|
|
459
473
|
/**
|
|
460
474
|
* Run mode for idempotent steps (`apply` | `check` | `check-fail-on-drift`).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concurrency-aware scheduler for `parallel()` step groups.
|
|
3
|
+
*
|
|
4
|
+
* A parallel group's children each run as their own observable step (own logs,
|
|
5
|
+
* status, timing, retry, cache, hooks — all task-scoped by the Phase 0 per-task
|
|
6
|
+
* isolation) through the same `runStepIteration` machinery the sequential loop
|
|
7
|
+
* uses. Children launch behind a `maxParallel` window (queued children report
|
|
8
|
+
* `pending`); the group joins at a barrier. On the first non-`continueOnError`
|
|
9
|
+
* child failure when `failFast`, every in-flight sibling's per-task abort
|
|
10
|
+
* controller is fired so its step race rejects and it is reported `cancelled`
|
|
11
|
+
* (which is NOT a failure).
|
|
12
|
+
*/
|
|
13
|
+
import { type StepLoopOptions, type StepNode } from './step-loop.js';
|
|
14
|
+
import type { SandboxStepResult } from './types.js';
|
|
15
|
+
type ParallelNode = Extract<StepNode, {
|
|
16
|
+
kind: 'parallel';
|
|
17
|
+
}>;
|
|
18
|
+
/** Outcome of running one parallel group. */
|
|
19
|
+
export interface ParallelGroupOutcome {
|
|
20
|
+
/** True when at least one non-`continueOnError` child failed. */
|
|
21
|
+
failed: boolean;
|
|
22
|
+
/** Name of the first failing child (drives the job failure reason). */
|
|
23
|
+
failedStepName?: string;
|
|
24
|
+
/** Per-child results, in child array order. */
|
|
25
|
+
results: SandboxStepResult[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run a parallel group: launch children with a bounded-concurrency window, join
|
|
29
|
+
* at a barrier, and fail-fast-cancel in-flight siblings on the first hard
|
|
30
|
+
* failure.
|
|
31
|
+
*/
|
|
32
|
+
export declare function runParallelGroup(node: ParallelNode, opts: StepLoopOptions): Promise<ParallelGroupOutcome>;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=parallel-scheduler.d.ts.map
|
|
@@ -10,6 +10,36 @@ import { CheckMode } from '@kici-dev/engine';
|
|
|
10
10
|
import type { RunnerToAgentMessage } from './ipc-protocol.js';
|
|
11
11
|
import type { SandboxStepResult } from './types.js';
|
|
12
12
|
import { type CachePhaseDeps } from '../cache/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* Thrown inside the step race when a step's own per-task abort controller fires
|
|
15
|
+
* (parallel fail-fast cancels an in-flight sibling). Distinguished from a
|
|
16
|
+
* timeout/job-deadline reject so the loop reports the step as `cancelled`
|
|
17
|
+
* (which is NOT a failure) rather than `failed`.
|
|
18
|
+
*/
|
|
19
|
+
export declare class StepCancelledError extends Error {
|
|
20
|
+
readonly name = "StepCancelledError";
|
|
21
|
+
constructor(stepName: string);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A node in the concurrency-aware step walk. A `sequential` node is one ordinary
|
|
25
|
+
* step; a `parallel` node is a `parallel()` group whose children each carry their
|
|
26
|
+
* own flat `stepIndex` (the group wrapper consumes no index).
|
|
27
|
+
*/
|
|
28
|
+
export type StepNode = {
|
|
29
|
+
kind: 'sequential';
|
|
30
|
+
step: Step;
|
|
31
|
+
stepIndex: number;
|
|
32
|
+
} | {
|
|
33
|
+
kind: 'parallel';
|
|
34
|
+
groupId: string;
|
|
35
|
+
name: string;
|
|
36
|
+
failFast: boolean;
|
|
37
|
+
maxParallel?: number;
|
|
38
|
+
children: {
|
|
39
|
+
step: Step;
|
|
40
|
+
stepIndex: number;
|
|
41
|
+
}[];
|
|
42
|
+
};
|
|
13
43
|
/** Job-level hooks passed to the step loop. */
|
|
14
44
|
export interface JobHooks {
|
|
15
45
|
beforeStep?: HookInput;
|
|
@@ -21,7 +51,32 @@ export interface JobHooks {
|
|
|
21
51
|
}
|
|
22
52
|
/** Options for the step execution loop. */
|
|
23
53
|
export interface StepLoopOptions {
|
|
54
|
+
/**
|
|
55
|
+
* Flat list of every executable step (sequential steps + parallel-group
|
|
56
|
+
* children inlined in flat-stepIndex order). `steps.length` is the flat step
|
|
57
|
+
* count used to derive hook pseudo-indices. The structural walk order (which
|
|
58
|
+
* entries are grouped) is carried separately by `stepNodes`.
|
|
59
|
+
*/
|
|
24
60
|
steps: Step[];
|
|
61
|
+
/**
|
|
62
|
+
* Structural walk order: sequential steps and parallel groups in array order.
|
|
63
|
+
* When present, the loop walks these nodes (dispatching parallel groups to the
|
|
64
|
+
* concurrency-aware scheduler); when absent, it walks `steps` sequentially with
|
|
65
|
+
* the array index as the stepIndex (unit-harness back-compat).
|
|
66
|
+
*/
|
|
67
|
+
stepNodes?: StepNode[];
|
|
68
|
+
/**
|
|
69
|
+
* Abort the per-task controller for `stepIndex` (parallel fail-fast). Wired to
|
|
70
|
+
* the workflow-runner's `stepAbortControllers` map so an aborted sibling's
|
|
71
|
+
* `ctx.signal` fires and its step race rejects with {@link StepCancelledError}.
|
|
72
|
+
*/
|
|
73
|
+
abortStep?: (stepIndex: number) => void;
|
|
74
|
+
/**
|
|
75
|
+
* Returns the per-task abort signal for `stepIndex` (the same controller
|
|
76
|
+
* `abortStep` triggers). The step race watches it so a fail-fast abort
|
|
77
|
+
* interrupts an in-flight step even if its body ignores `ctx.signal`.
|
|
78
|
+
*/
|
|
79
|
+
getStepAbortSignal?: (stepIndex: number) => AbortSignal | undefined;
|
|
25
80
|
/**
|
|
26
81
|
* Run mode for idempotent steps. `apply` (default) converges; `check` /
|
|
27
82
|
* `check-fail-on-drift` preview drift and never invoke a checked step's apply.
|
|
@@ -29,6 +84,12 @@ export interface StepLoopOptions {
|
|
|
29
84
|
checkMode?: CheckMode;
|
|
30
85
|
/** Factory that creates a StepContext for a given step index and name. */
|
|
31
86
|
createStepContext: (stepIndex: number, stepName: string) => StepContext;
|
|
87
|
+
/**
|
|
88
|
+
* Run `fn` inside the step's console-capture scope so any console output it
|
|
89
|
+
* (or its hooks) produces attributes to `stepIndex`. Defaults to calling `fn`
|
|
90
|
+
* directly when absent (unit harnesses without capture wiring).
|
|
91
|
+
*/
|
|
92
|
+
runWithStepCapture?: <T>(stepIndex: number, fn: () => Promise<T>) => Promise<T>;
|
|
32
93
|
sendIpc: (msg: RunnerToAgentMessage) => void;
|
|
33
94
|
defaultTimeoutMs: number;
|
|
34
95
|
outputsMap: OutputsMap;
|
|
@@ -63,36 +124,38 @@ export interface StepLoopOptions {
|
|
|
63
124
|
/** Job start time (epoch ms) for outcome metadata duration. */
|
|
64
125
|
startTime?: number;
|
|
65
126
|
/**
|
|
66
|
-
* Returns the secret key names accessed by the
|
|
67
|
-
* Called after each step completes to include in step.complete
|
|
127
|
+
* Returns the secret key names accessed by the step context created for
|
|
128
|
+
* `stepIndex`. Called after each step completes to include in step.complete
|
|
129
|
+
* IPC messages.
|
|
68
130
|
*/
|
|
69
|
-
getSecretsAccessLog?: () => string[];
|
|
131
|
+
getSecretsAccessLog?: (stepIndex: number) => string[];
|
|
70
132
|
/**
|
|
71
|
-
* Tear down per-step state created by the
|
|
72
|
-
*
|
|
133
|
+
* Tear down per-step state created by the `createStepContext` call for
|
|
134
|
+
* `stepIndex`. Invoked from the step-loop's `finally` after the step completes
|
|
73
135
|
* (success, failure, rule-skip, or timeout) so resources like the
|
|
74
136
|
* `ctx.secrets.mountFile` tmpdir get removed even on the failure paths.
|
|
75
137
|
* Never throws -- errors are logged by the wired implementation.
|
|
76
138
|
*/
|
|
77
|
-
disposeStepResources?: () => Promise<void>;
|
|
139
|
+
disposeStepResources?: (stepIndex: number) => Promise<void>;
|
|
78
140
|
/**
|
|
79
|
-
* Returns the IPC `step.secret_mount` records collected by the
|
|
80
|
-
*
|
|
81
|
-
*
|
|
141
|
+
* Returns the IPC `step.secret_mount` records collected by the step context
|
|
142
|
+
* created for `stepIndex`. Emitted on step completion so the orchestrator can
|
|
143
|
+
* persist the audit trail alongside `secretsAccessed`.
|
|
82
144
|
*/
|
|
83
|
-
getSecretMountRecords?: () => StepSecretMountRecord[];
|
|
145
|
+
getSecretMountRecords?: (stepIndex: number) => StepSecretMountRecord[];
|
|
84
146
|
/**
|
|
85
147
|
* Before a step's run function executes, point KICI_ENV / KICI_PATH at fresh
|
|
86
|
-
* temp files for this step
|
|
148
|
+
* temp files for this step (keyed by `stepIndex` so concurrent steps never
|
|
149
|
+
* share a delta file). Invoked once per executed step (NOT for rule-skipped
|
|
87
150
|
* steps). The workflow-runner owns the file lifecycle.
|
|
88
151
|
*/
|
|
89
|
-
beforeStepEnvFiles?: () => Promise<void>;
|
|
152
|
+
beforeStepEnvFiles?: (stepIndex: number) => Promise<void>;
|
|
90
153
|
/**
|
|
91
|
-
* After a step's run function completes (success OR failure), read
|
|
92
|
-
* KICI_ENV / KICI_PATH files, apply the delta via applyEnvDelta, and
|
|
93
|
-
* them
|
|
154
|
+
* After a step's run function completes (success OR failure), read this
|
|
155
|
+
* step's KICI_ENV / KICI_PATH files, apply the delta via applyEnvDelta, and
|
|
156
|
+
* release them. Never throws -- errors are logged by the wired impl.
|
|
94
157
|
*/
|
|
95
|
-
afterStepApplyEnvFiles?: () => Promise<void>;
|
|
158
|
+
afterStepApplyEnvFiles?: (stepIndex: number) => Promise<void>;
|
|
96
159
|
/**
|
|
97
160
|
* Block an `approval` step (`when: 'always'`) pending an orchestrator-side
|
|
98
161
|
* approval hold. The runner sends the normalized requirement and awaits the
|
|
@@ -145,6 +208,18 @@ interface StepLoopResult {
|
|
|
145
208
|
stepResults: SandboxStepResult[];
|
|
146
209
|
failureReason?: string;
|
|
147
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Per-step iteration outcome returned by `runStepIteration`.
|
|
213
|
+
*/
|
|
214
|
+
export interface StepIterationOutcome {
|
|
215
|
+
/** The result to append to the running stepResults list. */
|
|
216
|
+
result: SandboxStepResult;
|
|
217
|
+
/** When true, the loop must break (failed step without continueOnError). */
|
|
218
|
+
shouldBreak: boolean;
|
|
219
|
+
/** Set when the step failed; carried into completion-hook outcome metadata. */
|
|
220
|
+
failedStepName?: string;
|
|
221
|
+
}
|
|
222
|
+
export declare function runStepIteration(step: Step, stepIndex: number, opts: StepLoopOptions): Promise<StepIterationOutcome>;
|
|
148
223
|
/**
|
|
149
224
|
* Execute the step loop with hook integration and step-level rule evaluation.
|
|
150
225
|
*
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StepSecretMountRecord, TrackedStepSecrets } from '@kici-dev/sdk';
|
|
2
|
+
/** Per-step secrets handle + its teardown closure, keyed by step index. */
|
|
3
|
+
export interface StepTaskSlot {
|
|
4
|
+
secrets: TrackedStepSecrets;
|
|
5
|
+
dispose: () => Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Per-task replacement for the runner's former `currentStepSecrets` /
|
|
9
|
+
* `currentStepDispose` single-slots.
|
|
10
|
+
*
|
|
11
|
+
* The runner used to remember only the *most recent* step's secrets handle and
|
|
12
|
+
* dispose closure; the access-log / mount-record / dispose reader callbacks read
|
|
13
|
+
* that single slot. Under sequential execution that is correct (one step at a
|
|
14
|
+
* time), but two concurrently-running steps would clobber each other's
|
|
15
|
+
* secrets-audit trail. Keying every slot by the step's index keeps each step's
|
|
16
|
+
* audit trail and teardown isolated — sequential behavior is identical, Phase 1
|
|
17
|
+
* concurrency is correct.
|
|
18
|
+
*/
|
|
19
|
+
export declare class StepTaskRegistry {
|
|
20
|
+
#private;
|
|
21
|
+
set(stepIndex: number, slot: StepTaskSlot): void;
|
|
22
|
+
getAccessLog(stepIndex: number): string[];
|
|
23
|
+
getMountRecords(stepIndex: number): StepSecretMountRecord[];
|
|
24
|
+
dispose(stepIndex: number): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=step-task-registry.d.ts.map
|
|
@@ -150,8 +150,8 @@ export interface SandboxStepResult {
|
|
|
150
150
|
name: string;
|
|
151
151
|
/** Zero-based index of the step within the job. */
|
|
152
152
|
stepIndex: number;
|
|
153
|
-
/** Step execution status. */
|
|
154
|
-
status: 'success' | 'failed' | 'skipped';
|
|
153
|
+
/** Step execution status. `cancelled` = a parallel sibling fail-fast cancel. */
|
|
154
|
+
status: 'success' | 'failed' | 'skipped' | 'cancelled';
|
|
155
155
|
/** Step duration in milliseconds. */
|
|
156
156
|
durationMs: number;
|
|
157
157
|
/** Error details when status is 'failed'. */
|
|
@@ -35,7 +35,7 @@ export declare function buildStepNeedsContext(declaredNeeds: readonly unknown[]
|
|
|
35
35
|
* NOT serialized across the process boundary. This means zx $ runs natively
|
|
36
36
|
* inside this process with full shell access.
|
|
37
37
|
*/
|
|
38
|
-
export declare function createSandboxStepContext(workDir: string, stepIndex: number, stepName: string, request: JobExecutionRequest, maskedSendFn: (msg: RunnerToAgentMessage) => void, outputsMap: OutputsMap, refMap: StepRefMap, operatorSecretKeys: Set<string>, secretOutputs: Map<string, string>, jobOutputsMap: OutputsMap, secrets: TrackedStepSecrets, masker: LogMasker): StepContext;
|
|
38
|
+
export declare function createSandboxStepContext(workDir: string, stepIndex: number, stepName: string, request: JobExecutionRequest, maskedSendFn: (msg: RunnerToAgentMessage) => void, outputsMap: OutputsMap, refMap: StepRefMap, operatorSecretKeys: Set<string>, secretOutputs: Map<string, string>, jobOutputsMap: OutputsMap, secrets: TrackedStepSecrets, masker: LogMasker, signal: AbortSignal): StepContext;
|
|
39
39
|
/**
|
|
40
40
|
* Derive the fan-out position (`ctx.fanout`) from a dispatch request. Returns
|
|
41
41
|
* `undefined` for a non-fan-out job (no `fanoutTotal`), so `ctx.fanout` is only
|
|
@@ -44,4 +44,25 @@ export declare function createSandboxStepContext(workDir: string, stepIndex: num
|
|
|
44
44
|
export declare function deriveFanout(request: JobExecutionRequest): FanoutPosition | undefined;
|
|
45
45
|
/** Raw provider webhook body for ctx.rawPayload — nested in the envelope. */
|
|
46
46
|
export declare function rawPayloadFromEvent(event: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Build the step loop's KICI_ENV/KICI_PATH callbacks with a per-step delta-file
|
|
49
|
+
* pair (keyed by step index). `beforeStepEnvFiles(stepIndex)` lazily creates the
|
|
50
|
+
* step's pair and points the runner's process.env at it (each step's zx $
|
|
51
|
+
* snapshots process.env at context creation, which happens AFTER this
|
|
52
|
+
* before-hook, so the shell sees them; the pre-fork env allowlist does not
|
|
53
|
+
* re-filter runtime-set vars). `afterStepApplyEnvFiles(stepIndex)` applies that
|
|
54
|
+
* step's delta and releases the pair.
|
|
55
|
+
*
|
|
56
|
+
* Env-isolation contract: under sequential execution this is identical to a
|
|
57
|
+
* single shared pair truncated between steps — each step still sees only its own
|
|
58
|
+
* delta. The pair is now per-step so two concurrently-running steps cannot
|
|
59
|
+
* corrupt each other's delta file. `process.env.KICI_ENV` / `process.env.KICI_PATH`
|
|
60
|
+
* remain process-global, so Phase 1 forbids `setEnv` / `addPath` / `$KICI_ENV`
|
|
61
|
+
* writes inside `parallel()` children (compile-time validation); Phase 0 only
|
|
62
|
+
* makes the file pair per-task.
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildStepEnvFileHooks(operatorSecretKeys: Set<string>, maskedSend: (msg: RunnerToAgentMessage) => void): {
|
|
65
|
+
beforeStepEnvFiles: (stepIndex: number) => Promise<void>;
|
|
66
|
+
afterStepApplyEnvFiles: (stepIndex: number) => Promise<void>;
|
|
67
|
+
};
|
|
47
68
|
//# sourceMappingURL=workflow-runner.d.ts.map
|
package/dist/server.js
CHANGED
|
@@ -25,7 +25,7 @@ import { format, promisify } from "node:util";
|
|
|
25
25
|
import { gcStaleTmpDirs } from "@kici-dev/core/tmp-gc";
|
|
26
26
|
import fs$1, { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
|
|
27
27
|
import Docker from "dockerode";
|
|
28
|
-
import { buildKiciApi, buildNeedsContext, isDynamicGroupRef, isDynamicJobFn, isStaticArray, isStaticObject } from "@kici-dev/sdk";
|
|
28
|
+
import { buildKiciApi, buildNeedsContext, isDynamicGroupRef, isDynamicJobFn, isParallelGroup, isStaticArray, isStaticObject } from "@kici-dev/sdk";
|
|
29
29
|
import { c, x } from "tar";
|
|
30
30
|
import https from "node:https";
|
|
31
31
|
import http from "node:http";
|
|
@@ -1310,14 +1310,14 @@ var init_console_capture = __esmMin((() => {
|
|
|
1310
1310
|
init_console_capture();
|
|
1311
1311
|
function safe(name, fallback = "unknown") {
|
|
1312
1312
|
switch (name) {
|
|
1313
|
-
case "version": return "0.1.
|
|
1314
|
-
case "buildCommit": return "
|
|
1315
|
-
case "sdkVersion": return "0.1.
|
|
1316
|
-
case "sdkBundleHash": return "
|
|
1317
|
-
case "sharedVersion": return "0.1.
|
|
1318
|
-
case "sharedBundleHash": return "
|
|
1319
|
-
case "engineVersion": return "0.1.
|
|
1320
|
-
case "engineBundleHash": return "
|
|
1313
|
+
case "version": return "0.1.24";
|
|
1314
|
+
case "buildCommit": return "73592f67f";
|
|
1315
|
+
case "sdkVersion": return "0.1.24";
|
|
1316
|
+
case "sdkBundleHash": return "031712cf8cd02f483365113aad7e946da982110d544b177eb05edcc0d4421253";
|
|
1317
|
+
case "sharedVersion": return "0.1.24";
|
|
1318
|
+
case "sharedBundleHash": return "b977224129c767c4851458a795fa470264b2fc51255baf06cf14640e29e2f44c";
|
|
1319
|
+
case "engineVersion": return "0.1.24";
|
|
1320
|
+
case "engineBundleHash": return "734acca885cd70eed07a1a9426b08c04c07a9bf99484c18100d3d797b3eb8f39";
|
|
1321
1321
|
default: return fallback;
|
|
1322
1322
|
}
|
|
1323
1323
|
}
|
|
@@ -2080,8 +2080,8 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
|
|
|
2080
2080
|
}
|
|
2081
2081
|
var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
|
|
2082
2082
|
var init_workflow_loader = __esmMin((() => {
|
|
2083
|
-
AGENT_SDK_VERSION = "0.1.
|
|
2084
|
-
AGENT_SDK_BUNDLE_HASH = "
|
|
2083
|
+
AGENT_SDK_VERSION = "0.1.24";
|
|
2084
|
+
AGENT_SDK_BUNDLE_HASH = "031712cf8cd02f483365113aad7e946da982110d544b177eb05edcc0d4421253";
|
|
2085
2085
|
hookRegistered = false;
|
|
2086
2086
|
}));
|
|
2087
2087
|
//#endregion
|
|
@@ -2548,9 +2548,16 @@ async function evaluateDynamicFields(workflow, jobName, event, flags, timeoutMs
|
|
|
2548
2548
|
if (job.include || job.exclude) combos = applyIncludeExclude(combos, job.include, job.exclude);
|
|
2549
2549
|
result.matrixValues = combos;
|
|
2550
2550
|
}
|
|
2551
|
-
if (flags.dynamicEnvironment
|
|
2552
|
-
const
|
|
2553
|
-
if (
|
|
2551
|
+
if (flags.dynamicEnvironment) {
|
|
2552
|
+
const envRefs = job.environments ?? (job.environment !== void 0 ? [job.environment] : void 0);
|
|
2553
|
+
if (envRefs && envRefs.length > 0) {
|
|
2554
|
+
const names = [];
|
|
2555
|
+
for (const ref of envRefs) if (typeof ref === "function") {
|
|
2556
|
+
const value = await withTimeout(() => ref(event), timeoutMs, `dynamicEnvironment for job '${jobName}'`);
|
|
2557
|
+
if (value !== void 0 && value !== null) names.push(value);
|
|
2558
|
+
} else if (typeof ref === "string") names.push(ref);
|
|
2559
|
+
if (names.length > 0) result.environmentNames = names;
|
|
2560
|
+
}
|
|
2554
2561
|
}
|
|
2555
2562
|
if (flags.dynamicEnv && typeof job.env === "function") {
|
|
2556
2563
|
const value = await withTimeout(() => job.env(event), timeoutMs, `dynamicEnv for job '${jobName}'`);
|
|
@@ -2857,11 +2864,22 @@ async function serializeJobsToLock(jobs, ctx, staticNames, allowedGroups) {
|
|
|
2857
2864
|
}
|
|
2858
2865
|
async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups) {
|
|
2859
2866
|
const { include: runsOn, exclude: excludeLabels } = normalizeRunsOnToMatchers(job.runsOn, `generated job '${job.name}' runsOn`);
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2867
|
+
const envRefs = job.environments ?? (job.environment !== void 0 ? [job.environment] : void 0);
|
|
2868
|
+
let resolvedEnvironments;
|
|
2869
|
+
if (envRefs !== void 0 && envRefs.length > 0) {
|
|
2870
|
+
const resolved = [];
|
|
2871
|
+
for (const ref of envRefs) if (typeof ref === "function") {
|
|
2872
|
+
const value = await withTimeout(() => ref(ctx.event), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic environment for generated job '${job.name}'`);
|
|
2873
|
+
if (value !== void 0 && value !== null) resolved.push({
|
|
2874
|
+
value,
|
|
2875
|
+
dynamic: false
|
|
2876
|
+
});
|
|
2877
|
+
} else if (typeof ref === "string") resolved.push({
|
|
2878
|
+
value: ref,
|
|
2879
|
+
dynamic: false
|
|
2880
|
+
});
|
|
2881
|
+
if (resolved.length > 0) resolvedEnvironments = resolved;
|
|
2882
|
+
}
|
|
2865
2883
|
let resolvedEnv;
|
|
2866
2884
|
if (typeof job.env === "function") {
|
|
2867
2885
|
const value = await withTimeout(() => job.env(ctx.event), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic env for generated job '${job.name}'`);
|
|
@@ -2887,7 +2905,7 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
|
|
|
2887
2905
|
...job.include ? { include: job.include } : {},
|
|
2888
2906
|
...job.exclude ? { exclude: job.exclude } : {},
|
|
2889
2907
|
...job.description ? { description: job.description } : {},
|
|
2890
|
-
...
|
|
2908
|
+
...resolvedEnvironments !== void 0 ? { environments: resolvedEnvironments } : {},
|
|
2891
2909
|
...resolvedEnv !== void 0 ? { env: resolvedEnv } : {},
|
|
2892
2910
|
...resolvedConcurrencyGroup !== void 0 ? { concurrencyGroup: resolvedConcurrencyGroup } : {}
|
|
2893
2911
|
};
|
|
@@ -2945,26 +2963,41 @@ function resolveNeeds(needs, generatedNames, staticNames, allowedGroups) {
|
|
|
2945
2963
|
* are loaded from the workflow bundle at execution time.
|
|
2946
2964
|
*/
|
|
2947
2965
|
function serializeSteps(steps) {
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
delayMs: step.retry.delayMs,
|
|
2962
|
-
backoff: step.retry.backoff,
|
|
2963
|
-
maxDelayMs: step.retry.maxDelayMs
|
|
2964
|
-
} } : {}
|
|
2965
|
-
};
|
|
2966
|
+
let flatIndex = 0;
|
|
2967
|
+
return steps.map((entry) => {
|
|
2968
|
+
if (isParallelGroup(entry)) {
|
|
2969
|
+
const children = entry.steps.map((child) => serializeSequentialStep(child, flatIndex++));
|
|
2970
|
+
return {
|
|
2971
|
+
kind: "parallel",
|
|
2972
|
+
name: entry.name ?? `parallel-${children[0]?.name ?? "group"}`,
|
|
2973
|
+
failFast: entry.failFast,
|
|
2974
|
+
...entry.maxParallel !== void 0 ? { maxParallel: entry.maxParallel } : {},
|
|
2975
|
+
children
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
return serializeSequentialStep(entry, flatIndex++);
|
|
2966
2979
|
});
|
|
2967
2980
|
}
|
|
2981
|
+
/** Serialize one sequential step (or bare function) to a flat `LockStep`. */
|
|
2982
|
+
function serializeSequentialStep(stepOrFn, index) {
|
|
2983
|
+
if (typeof stepOrFn === "function") return {
|
|
2984
|
+
name: `step-${index}`,
|
|
2985
|
+
hasOutputs: false
|
|
2986
|
+
};
|
|
2987
|
+
const step = stepOrFn;
|
|
2988
|
+
return {
|
|
2989
|
+
name: step.name || `step-${index}`,
|
|
2990
|
+
hasOutputs: !!step.outputs,
|
|
2991
|
+
...step.continueOnError ? { continueOnError: true } : {},
|
|
2992
|
+
...step.timeout ? { timeout: step.timeout } : {},
|
|
2993
|
+
...step.retry ? { retry: {
|
|
2994
|
+
maxAttempts: step.retry.maxAttempts,
|
|
2995
|
+
delayMs: step.retry.delayMs,
|
|
2996
|
+
backoff: step.retry.backoff,
|
|
2997
|
+
maxDelayMs: step.retry.maxDelayMs
|
|
2998
|
+
} } : {}
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
2968
3001
|
/**
|
|
2969
3002
|
* Serialize matrix configuration. Static array/object matrices are embedded as-is;
|
|
2970
3003
|
* dynamic matrix functions are invoked against the eval context (mirroring the
|
|
@@ -4883,10 +4916,17 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
|
|
|
4883
4916
|
case "log.line":
|
|
4884
4917
|
ctx.execOptions.onLogLine(msg.stepIndex, msg.line);
|
|
4885
4918
|
return;
|
|
4886
|
-
case "step.start":
|
|
4919
|
+
case "step.start": {
|
|
4887
4920
|
ctx.stepNames.set(msg.stepIndex, msg.stepName);
|
|
4888
|
-
|
|
4921
|
+
const startState = msg.state === "pending" ? ExecutionStepStatus.enum.pending : ExecutionStepStatus.enum.running;
|
|
4922
|
+
const startData = {
|
|
4923
|
+
...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
|
|
4924
|
+
...msg.groupId && { groupId: msg.groupId }
|
|
4925
|
+
};
|
|
4926
|
+
if (Object.keys(startData).length > 0) ctx.execOptions.onStepStatus(msg.stepIndex, msg.stepName, startState, startData);
|
|
4927
|
+
else ctx.execOptions.onStepStatus(msg.stepIndex, msg.stepName, startState);
|
|
4889
4928
|
return;
|
|
4929
|
+
}
|
|
4890
4930
|
case "step.complete":
|
|
4891
4931
|
ctx.execOptions.onStepStatus(msg.stepIndex, ctx.stepNames.get(msg.stepIndex) ?? "", msg.status, {
|
|
4892
4932
|
durationMs: msg.durationMs,
|
|
@@ -4896,6 +4936,8 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
|
|
|
4896
4936
|
...msg.checkOutcome !== void 0 && { checkOutcome: msg.checkOutcome },
|
|
4897
4937
|
...msg.driftSummary !== void 0 && { driftSummary: msg.driftSummary },
|
|
4898
4938
|
...msg.drift !== void 0 && { drift: msg.drift },
|
|
4939
|
+
...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
|
|
4940
|
+
...msg.groupId && { groupId: msg.groupId },
|
|
4899
4941
|
...msg.data && msg.data
|
|
4900
4942
|
});
|
|
4901
4943
|
return;
|
|
@@ -5609,10 +5651,17 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
5609
5651
|
case "ready":
|
|
5610
5652
|
this.sendExecuteRequest(stream, options);
|
|
5611
5653
|
return false;
|
|
5612
|
-
case "step.start":
|
|
5654
|
+
case "step.start": {
|
|
5613
5655
|
stepNames.set(msg.stepIndex, msg.stepName);
|
|
5614
|
-
|
|
5656
|
+
const startState = msg.state === "pending" ? ExecutionStepStatus.enum.pending : ExecutionStepStatus.enum.running;
|
|
5657
|
+
const startData = {
|
|
5658
|
+
...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
|
|
5659
|
+
...msg.groupId && { groupId: msg.groupId }
|
|
5660
|
+
};
|
|
5661
|
+
if (Object.keys(startData).length > 0) options.onStepStatus(msg.stepIndex, msg.stepName, startState, startData);
|
|
5662
|
+
else options.onStepStatus(msg.stepIndex, msg.stepName, startState);
|
|
5615
5663
|
return false;
|
|
5664
|
+
}
|
|
5616
5665
|
case "step.complete": {
|
|
5617
5666
|
const name = stepNames.get(msg.stepIndex) ?? `step-${msg.stepIndex}`;
|
|
5618
5667
|
options.onStepStatus(msg.stepIndex, name, msg.status, {
|
|
@@ -5623,6 +5672,8 @@ var init_container_sandbox = __esmMin((() => {
|
|
|
5623
5672
|
...msg.checkOutcome !== void 0 && { checkOutcome: msg.checkOutcome },
|
|
5624
5673
|
...msg.driftSummary !== void 0 && { driftSummary: msg.driftSummary },
|
|
5625
5674
|
...msg.drift !== void 0 && { drift: msg.drift },
|
|
5675
|
+
...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
|
|
5676
|
+
...msg.groupId && { groupId: msg.groupId },
|
|
5626
5677
|
...msg.data && msg.data
|
|
5627
5678
|
});
|
|
5628
5679
|
stepResults.push({
|
|
@@ -6494,7 +6545,7 @@ var init_job_runner = __esmMin((() => {
|
|
|
6494
6545
|
});
|
|
6495
6546
|
logger$2.info("Init job completed successfully", {
|
|
6496
6547
|
jobId,
|
|
6497
|
-
hasEnvironment: initResult.
|
|
6548
|
+
hasEnvironment: initResult.environmentNames !== void 0,
|
|
6498
6549
|
hasEnv: initResult.env !== void 0,
|
|
6499
6550
|
hasConcurrencyGroup: initResult.concurrencyGroup !== void 0
|
|
6500
6551
|
});
|
|
@@ -6801,7 +6852,9 @@ var init_job_runner = __esmMin((() => {
|
|
|
6801
6852
|
*/
|
|
6802
6853
|
sendStepStatus(dispatch, stepIndex, stepName, state, data, logBytesStreamed) {
|
|
6803
6854
|
const secretsAccessed = data?.secretsAccessed;
|
|
6804
|
-
const
|
|
6855
|
+
const concurrencyKind = data?.concurrencyKind;
|
|
6856
|
+
const groupId = data?.groupId;
|
|
6857
|
+
const { secretsAccessed: _s, concurrencyKind: _c, groupId: _g, ...restData } = data ?? {};
|
|
6805
6858
|
const hasRestData = Object.keys(restData).length > 0;
|
|
6806
6859
|
this.sendDirect({
|
|
6807
6860
|
type: "step.status",
|
|
@@ -6814,6 +6867,8 @@ var init_job_runner = __esmMin((() => {
|
|
|
6814
6867
|
timestamp: Date.now(),
|
|
6815
6868
|
...hasRestData && { data: restData },
|
|
6816
6869
|
...secretsAccessed !== void 0 && { secretsAccessed },
|
|
6870
|
+
...concurrencyKind !== void 0 && { concurrencyKind },
|
|
6871
|
+
...groupId !== void 0 && { groupId },
|
|
6817
6872
|
...logBytesStreamed !== void 0 && { logBytesStreamed }
|
|
6818
6873
|
});
|
|
6819
6874
|
}
|
|
@@ -6841,14 +6896,14 @@ var init_job_runner = __esmMin((() => {
|
|
|
6841
6896
|
*/
|
|
6842
6897
|
init_console_capture();
|
|
6843
6898
|
init_npm_resolver();
|
|
6844
|
-
const AGENT_VERSION = "0.1.
|
|
6845
|
-
const BUILD_COMMIT = "
|
|
6846
|
-
const SDK_VERSION = "0.1.
|
|
6847
|
-
const SDK_BUNDLE_HASH = "
|
|
6848
|
-
const SHARED_VERSION = "0.1.
|
|
6849
|
-
const SHARED_BUNDLE_HASH = "
|
|
6850
|
-
const ENGINE_VERSION = "0.1.
|
|
6851
|
-
const ENGINE_BUNDLE_HASH = "
|
|
6899
|
+
const AGENT_VERSION = "0.1.24";
|
|
6900
|
+
const BUILD_COMMIT = "73592f67f";
|
|
6901
|
+
const SDK_VERSION = "0.1.24";
|
|
6902
|
+
const SDK_BUNDLE_HASH = "031712cf8cd02f483365113aad7e946da982110d544b177eb05edcc0d4421253";
|
|
6903
|
+
const SHARED_VERSION = "0.1.24";
|
|
6904
|
+
const SHARED_BUNDLE_HASH = "b977224129c767c4851458a795fa470264b2fc51255baf06cf14640e29e2f44c";
|
|
6905
|
+
const ENGINE_VERSION = "0.1.24";
|
|
6906
|
+
const ENGINE_BUNDLE_HASH = "734acca885cd70eed07a1a9426b08c04c07a9bf99484c18100d3d797b3eb8f39";
|
|
6852
6907
|
initTelemetry({
|
|
6853
6908
|
serviceName: "kici-agent",
|
|
6854
6909
|
otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|