@kici-dev/agent 0.1.15 → 0.1.16
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/diagnostics/mini-bundle.d.ts +9 -0
- package/dist/execution/job-runner.d.ts +10 -1
- package/dist/execution/sandbox/index.d.ts +1 -1
- package/dist/execution/sandbox/ipc-protocol.d.ts +45 -2
- package/dist/execution/sandbox/step-loop.d.ts +24 -1
- package/dist/execution/sandbox/types.d.ts +10 -1
- package/dist/server.js +244 -39
- package/dist/workflow-runner.js +131 -4
- package/dist/ws/orchestrator-client.d.ts +38 -4
- package/package.json +7 -5
- package/sbom.spdx.json +1923 -148
package/dist/workflow-runner.js
CHANGED
|
@@ -8,7 +8,7 @@ import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:pa
|
|
|
8
8
|
import { $ } from "zx";
|
|
9
9
|
import { createLogger, deriveSharedSecret, initZx, normalizeLineEndings, sha256, sha256File, toErrorMessage } from "@kici-dev/shared";
|
|
10
10
|
import { CacheOutcome, CacheStepType, ExecutionJobStatus, ExecutionStepStatus, TimeoutReason } from "@kici-dev/engine";
|
|
11
|
-
import { buildKiciApi, createStepSecrets, evaluateRules, isDynamicJobFn, normalizeCacheSpecs, resolveJobOutputs, resolveStepOutputs, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "@kici-dev/sdk";
|
|
11
|
+
import { buildKiciApi, createStepSecrets, evaluateRules, isDynamicJobFn, normalizeCacheSpecs, normalizeRequireApproval, resolveJobOutputs, resolveStepOutputs, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "@kici-dev/sdk";
|
|
12
12
|
import { Readable, Transform } from "node:stream";
|
|
13
13
|
import { pipeline } from "node:stream/promises";
|
|
14
14
|
import { createGunzip } from "node:zlib";
|
|
@@ -1123,6 +1123,65 @@ async function evaluateStepRulesAndMaybeSkip(step, stepIndex, opts) {
|
|
|
1123
1123
|
};
|
|
1124
1124
|
}
|
|
1125
1125
|
/**
|
|
1126
|
+
* Manual approval gate for a step. When the step declares `requireApproval`
|
|
1127
|
+
* and the harness wired `awaitStepApproval`, block until the orchestrator
|
|
1128
|
+
* resolves the hold. Returns a failed `StepIterationOutcome` (breaking the
|
|
1129
|
+
* loop) on reject/expired; returns null when approved or when no gate applies.
|
|
1130
|
+
*/
|
|
1131
|
+
async function maybeGateStepApproval(step, stepIndex, opts) {
|
|
1132
|
+
if (step.requireApproval === void 0 || !opts.awaitStepApproval) return null;
|
|
1133
|
+
const normalized = normalizeRequireApproval(step.requireApproval);
|
|
1134
|
+
opts.sendIpc({
|
|
1135
|
+
type: "log.line",
|
|
1136
|
+
stepIndex,
|
|
1137
|
+
line: `[kici] Step '${step.name}' awaiting approval...`
|
|
1138
|
+
});
|
|
1139
|
+
const resolution = await opts.awaitStepApproval({
|
|
1140
|
+
stepIndex,
|
|
1141
|
+
stepName: step.name,
|
|
1142
|
+
clauses: normalized.clauses,
|
|
1143
|
+
reason: normalized.reason ?? `Approval required for step '${step.name}'`,
|
|
1144
|
+
timeoutSeconds: normalized.timeoutSeconds
|
|
1145
|
+
});
|
|
1146
|
+
if (resolution.outcome === "approved") {
|
|
1147
|
+
opts.sendIpc({
|
|
1148
|
+
type: "log.line",
|
|
1149
|
+
stepIndex,
|
|
1150
|
+
line: `[kici] Step '${step.name}' approved.`
|
|
1151
|
+
});
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
const why = resolution.outcome === "expired" ? "approval expired" : `approval rejected${resolution.reason ? `: ${resolution.reason}` : ""}`;
|
|
1155
|
+
opts.sendIpc({
|
|
1156
|
+
type: "step.start",
|
|
1157
|
+
stepIndex,
|
|
1158
|
+
stepName: step.name
|
|
1159
|
+
});
|
|
1160
|
+
opts.sendIpc({
|
|
1161
|
+
type: "step.complete",
|
|
1162
|
+
stepIndex,
|
|
1163
|
+
status: ExecutionStepStatus.enum.failed,
|
|
1164
|
+
durationMs: 0
|
|
1165
|
+
});
|
|
1166
|
+
opts.sendIpc({
|
|
1167
|
+
type: "log.line",
|
|
1168
|
+
stepIndex,
|
|
1169
|
+
line: `[kici] Step '${step.name}' ${why}.`
|
|
1170
|
+
});
|
|
1171
|
+
await opts.disposeStepResources?.();
|
|
1172
|
+
return {
|
|
1173
|
+
result: {
|
|
1174
|
+
name: step.name,
|
|
1175
|
+
stepIndex,
|
|
1176
|
+
status: ExecutionStepStatus.enum.failed,
|
|
1177
|
+
durationMs: 0,
|
|
1178
|
+
error: { message: `Step '${step.name}' ${why}` }
|
|
1179
|
+
},
|
|
1180
|
+
shouldBreak: true,
|
|
1181
|
+
failedStepName: step.name
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1126
1185
|
* Run a single observer hook (beforeStep / afterStep). Failures only emit a
|
|
1127
1186
|
* log line — they never change job status. Centralises the per-call boilerplate
|
|
1128
1187
|
* so the per-step body can stay flat.
|
|
@@ -1169,6 +1228,8 @@ async function runStepIteration(step, stepIndex, opts) {
|
|
|
1169
1228
|
shouldBreak: false
|
|
1170
1229
|
};
|
|
1171
1230
|
}
|
|
1231
|
+
const gate = await maybeGateStepApproval(step, stepIndex, opts);
|
|
1232
|
+
if (gate) return gate;
|
|
1172
1233
|
if (opts.jobHooks?.beforeStep) await runObserverHook({
|
|
1173
1234
|
hook: opts.jobHooks.beforeStep,
|
|
1174
1235
|
hookType: "beforeStep",
|
|
@@ -2179,8 +2240,8 @@ function logSubprocessStreams(e, tokens) {
|
|
|
2179
2240
|
* no Rolldown step at runtime. `@kici-dev/sdk` and host-repo deps resolve via
|
|
2180
2241
|
* Node's normal ESM lookup against `.kici/node_modules/`.
|
|
2181
2242
|
*/
|
|
2182
|
-
const AGENT_SDK_VERSION = "0.1.
|
|
2183
|
-
const AGENT_SDK_BUNDLE_HASH = "
|
|
2243
|
+
const AGENT_SDK_VERSION = "0.1.16";
|
|
2244
|
+
const AGENT_SDK_BUNDLE_HASH = "cd4b8e0d91efe578c96e7c746f89b44a3df8fcaebaec2185399168ef8eb4ebca";
|
|
2184
2245
|
/**
|
|
2185
2246
|
* Register the `@kici-dev/core/ts-loader-hook` oxc-transform ESM loader hook so
|
|
2186
2247
|
* subsequent dynamic `import()` calls for `.ts` / `.tsx` files transform on the
|
|
@@ -2873,6 +2934,68 @@ function waitForCacheResponse(requestId) {
|
|
|
2873
2934
|
});
|
|
2874
2935
|
}
|
|
2875
2936
|
/**
|
|
2937
|
+
* Pending promises for approval.resolved messages from the agent.
|
|
2938
|
+
* Key: requestId (correlates approval.request -> approval.resolved).
|
|
2939
|
+
*/
|
|
2940
|
+
const pendingApprovalResolutions = /* @__PURE__ */ new Map();
|
|
2941
|
+
/**
|
|
2942
|
+
* Hard ceiling on how long the runner blocks a step waiting for an approval
|
|
2943
|
+
* resolution. The orchestrator enforces the real (org-/SDK-configured) expiry
|
|
2944
|
+
* and sends `expired` when it lapses; this is a safety net well above any sane
|
|
2945
|
+
* approval window so the runner cannot hang forever if the resolution is lost.
|
|
2946
|
+
*/
|
|
2947
|
+
const APPROVAL_RESOLUTION_TIMEOUT_MS = 10080 * 60 * 1e3;
|
|
2948
|
+
/** Wait for an approval.resolved from the agent with the given requestId. */
|
|
2949
|
+
function waitForApprovalResolution(requestId) {
|
|
2950
|
+
return new Promise((resolve) => {
|
|
2951
|
+
const timer = setTimeout(() => {
|
|
2952
|
+
pendingApprovalResolutions.delete(requestId);
|
|
2953
|
+
resolve({
|
|
2954
|
+
type: "approval.resolved",
|
|
2955
|
+
requestId,
|
|
2956
|
+
outcome: "expired"
|
|
2957
|
+
});
|
|
2958
|
+
}, APPROVAL_RESOLUTION_TIMEOUT_MS);
|
|
2959
|
+
pendingApprovalResolutions.set(requestId, {
|
|
2960
|
+
resolve: (response) => {
|
|
2961
|
+
clearTimeout(timer);
|
|
2962
|
+
pendingApprovalResolutions.delete(requestId);
|
|
2963
|
+
resolve(response);
|
|
2964
|
+
},
|
|
2965
|
+
timer
|
|
2966
|
+
});
|
|
2967
|
+
});
|
|
2968
|
+
}
|
|
2969
|
+
/**
|
|
2970
|
+
* Build the `awaitStepApproval` callback the step loop uses to block on a
|
|
2971
|
+
* `requireApproval` step. Sends an `approval.request` IPC (relayed by the agent
|
|
2972
|
+
* over the WS as a `step.approval-request`) and awaits the matching
|
|
2973
|
+
* `approval.resolved`. A relay error is treated as a fail-closed reject.
|
|
2974
|
+
*/
|
|
2975
|
+
function buildAwaitStepApproval() {
|
|
2976
|
+
return async (req) => {
|
|
2977
|
+
const requestId = randomUUID();
|
|
2978
|
+
sendMessage({
|
|
2979
|
+
type: "approval.request",
|
|
2980
|
+
requestId,
|
|
2981
|
+
stepIndex: req.stepIndex,
|
|
2982
|
+
stepName: req.stepName,
|
|
2983
|
+
clauses: req.clauses,
|
|
2984
|
+
reason: req.reason,
|
|
2985
|
+
...req.timeoutSeconds !== void 0 && { timeoutSeconds: req.timeoutSeconds }
|
|
2986
|
+
});
|
|
2987
|
+
const resolution = await waitForApprovalResolution(requestId);
|
|
2988
|
+
if (resolution.error) return {
|
|
2989
|
+
outcome: "rejected",
|
|
2990
|
+
reason: resolution.error
|
|
2991
|
+
};
|
|
2992
|
+
return {
|
|
2993
|
+
outcome: resolution.outcome ?? "rejected",
|
|
2994
|
+
...resolution.reason !== void 0 && { reason: resolution.reason }
|
|
2995
|
+
};
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2876
2999
|
* Build the {@link CacheTransport} the sandbox-side cache engine uses to reach
|
|
2877
3000
|
* the orchestrator. Each method sends a `cache.request` IPC (relayed by the
|
|
2878
3001
|
* agent over the WS as a `cache.user.*` message) and awaits the matching
|
|
@@ -2985,6 +3108,9 @@ function dispatchAgentMessage(msg) {
|
|
|
2985
3108
|
} else if (msg.type === "cache.response") {
|
|
2986
3109
|
const pending = pendingCacheResponses.get(msg.requestId);
|
|
2987
3110
|
if (pending) pending.resolve(msg);
|
|
3111
|
+
} else if (msg.type === "approval.resolved") {
|
|
3112
|
+
const pending = pendingApprovalResolutions.get(msg.requestId);
|
|
3113
|
+
if (pending) pending.resolve(msg);
|
|
2988
3114
|
}
|
|
2989
3115
|
}
|
|
2990
3116
|
if (isForkMode) process.on("message", (msg) => {
|
|
@@ -4093,7 +4219,8 @@ async function main() {
|
|
|
4093
4219
|
if (disposeFn) await disposeFn();
|
|
4094
4220
|
},
|
|
4095
4221
|
beforeStepEnvFiles: stepEnvHooks.beforeStepEnvFiles,
|
|
4096
|
-
afterStepApplyEnvFiles: stepEnvHooks.afterStepApplyEnvFiles
|
|
4222
|
+
afterStepApplyEnvFiles: stepEnvHooks.afterStepApplyEnvFiles,
|
|
4223
|
+
awaitStepApproval: buildAwaitStepApproval()
|
|
4097
4224
|
});
|
|
4098
4225
|
jobDeadline.clear();
|
|
4099
4226
|
await maybeSaveJobCache(jobCacheSpecs, jobCacheRestore, cachePhaseDeps, !aborted && loopResult.status === ExecutionStepStatus.enum.success);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type AgentToOrchestratorMessage, type JobDispatch, type JobCancel } from '@kici-dev/engine';
|
|
2
|
-
import type { CacheRequestIpc, CacheResponseIpc } from '../execution/sandbox/index.js';
|
|
1
|
+
import { type AgentToOrchestratorMessage, type JobDispatch, type JobCancel, type FleetLogsRequest } from '@kici-dev/engine';
|
|
2
|
+
import type { CacheRequestIpc, CacheResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc } from '../execution/sandbox/index.js';
|
|
3
3
|
export type ConnectionState = 'disconnected' | 'connecting' | 'authenticating' | 'registering' | 'registered';
|
|
4
|
-
interface OrchestratorClientOptions {
|
|
4
|
+
export interface OrchestratorClientOptions {
|
|
5
5
|
/** WebSocket URL of the orchestrator. */
|
|
6
6
|
url: string;
|
|
7
7
|
/** Agent's unique identifier. */
|
|
@@ -35,6 +35,18 @@ interface OrchestratorClientOptions {
|
|
|
35
35
|
* doesn't have to read process.env directly.
|
|
36
36
|
*/
|
|
37
37
|
scalerManaged?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Supplies the inputs for the agent fleet mini-bundle on a fleet.logs.request.
|
|
40
|
+
* Returns the agent's resolved config (redacted inside the bundle assembler),
|
|
41
|
+
* the log directory, and the current Prometheus metrics text. Omitted in
|
|
42
|
+
* tests / contexts that don't participate in fleet collection — the handler
|
|
43
|
+
* then replies with an empty-config bundle.
|
|
44
|
+
*/
|
|
45
|
+
getFleetBundleInputs?: () => Promise<{
|
|
46
|
+
config: Record<string, unknown>;
|
|
47
|
+
logDir?: string;
|
|
48
|
+
metricsText?: string;
|
|
49
|
+
}>;
|
|
38
50
|
}
|
|
39
51
|
/**
|
|
40
52
|
* WebSocket client that connects the agent to the customer orchestrator.
|
|
@@ -70,6 +82,13 @@ export declare class OrchestratorClient {
|
|
|
70
82
|
private readonly pendingApiRequests;
|
|
71
83
|
/** Pending user-cache restore/save requests awaiting orchestrator response. */
|
|
72
84
|
private readonly pendingUserCacheRequests;
|
|
85
|
+
/**
|
|
86
|
+
* Pending step-approval requests awaiting the orchestrator's resolution.
|
|
87
|
+
* No client-side timeout: the orchestrator owns the (org-/SDK-configured)
|
|
88
|
+
* expiry and sends `step.approval-resolved: expired` when it lapses. The
|
|
89
|
+
* workflow-runner carries an outer safety-net timeout.
|
|
90
|
+
*/
|
|
91
|
+
private readonly pendingStepApprovals;
|
|
73
92
|
/** Pending concurrency report requests awaiting orchestrator ack. */
|
|
74
93
|
private readonly pendingConcurrencyRequests;
|
|
75
94
|
private readonly url;
|
|
@@ -83,6 +102,7 @@ export declare class OrchestratorClient {
|
|
|
83
102
|
private readonly getInFlightJobs?;
|
|
84
103
|
private readonly roles;
|
|
85
104
|
private readonly scalerManaged;
|
|
105
|
+
private readonly getFleetBundleInputs?;
|
|
86
106
|
/** Timestamp when the connection was lost, used for gap marker outage duration. */
|
|
87
107
|
private disconnectedAt;
|
|
88
108
|
/** Set to true when auth.failure is received. Prevents retrying with a bad token. */
|
|
@@ -180,6 +200,12 @@ export declare class OrchestratorClient {
|
|
|
180
200
|
deliveryId?: string;
|
|
181
201
|
error?: string;
|
|
182
202
|
}>;
|
|
203
|
+
/**
|
|
204
|
+
* Build this agent's fleet mini-bundle and stream it back to the orchestrator
|
|
205
|
+
* as ordered fleet.bundle.chunk frames (the WS frame cap forbids one frame).
|
|
206
|
+
* On failure, sends a single fleet.bundle.error. Public for unit testing.
|
|
207
|
+
*/
|
|
208
|
+
streamFleetBundle(req: FleetLogsRequest): Promise<void>;
|
|
183
209
|
/**
|
|
184
210
|
* Send a typed API request to the orchestrator and await the response.
|
|
185
211
|
*
|
|
@@ -202,6 +228,15 @@ export declare class OrchestratorClient {
|
|
|
202
228
|
* Times out after 30 seconds for the round-trip ops.
|
|
203
229
|
*/
|
|
204
230
|
requestUserCache(jobId: string, request: CacheRequestIpc): Promise<CacheResponseIpc>;
|
|
231
|
+
/**
|
|
232
|
+
* Relay a step-level approval request to the orchestrator. Sends a
|
|
233
|
+
* `step.approval-request` WS message and resolves with the orchestrator's
|
|
234
|
+
* `step.approval-resolved` mapped onto the IPC response shape. No client-side
|
|
235
|
+
* timeout — the orchestrator owns the approval expiry and replies with an
|
|
236
|
+
* `expired` outcome when it lapses. Rejects only on disconnect (the relay
|
|
237
|
+
* caller treats a rejection as a fail-closed reject).
|
|
238
|
+
*/
|
|
239
|
+
sendStepApproval(runId: string, jobId: string, request: StepApprovalRequestIpc): Promise<StepApprovalResolvedIpc>;
|
|
205
240
|
/**
|
|
206
241
|
* Send a job.context message to the orchestrator.
|
|
207
242
|
*
|
|
@@ -283,5 +318,4 @@ export declare class OrchestratorClient {
|
|
|
283
318
|
private scheduleReconnect;
|
|
284
319
|
private cancelReconnect;
|
|
285
320
|
}
|
|
286
|
-
export {};
|
|
287
321
|
//# sourceMappingURL=orchestrator-client.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kici-dev/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Customer-deployable agent for the KiCI CI/CD stack. Connects to an orchestrator, clones the workflow repo, executes steps, and streams logs back.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@hono/node-server": "^2.0.0",
|
|
57
|
+
"archiver": "^7.0.1",
|
|
57
58
|
"dockerode": "^5.0.0",
|
|
58
59
|
"hono": "^4.12.18",
|
|
59
60
|
"tar": "^7.5.13",
|
|
@@ -61,10 +62,10 @@
|
|
|
61
62
|
"ws": "^8.20.0",
|
|
62
63
|
"zod": "^4.3.6",
|
|
63
64
|
"zx": "^8.8.5",
|
|
64
|
-
"@kici-dev/core": "0.1.
|
|
65
|
-
"@kici-dev/
|
|
66
|
-
"@kici-dev/
|
|
67
|
-
"@kici-dev/
|
|
65
|
+
"@kici-dev/core": "0.1.16",
|
|
66
|
+
"@kici-dev/engine": "0.1.16",
|
|
67
|
+
"@kici-dev/shared": "0.1.16",
|
|
68
|
+
"@kici-dev/sdk": "0.1.16"
|
|
68
69
|
},
|
|
69
70
|
"kici": {
|
|
70
71
|
"metrics": {
|
|
@@ -73,6 +74,7 @@
|
|
|
73
74
|
}
|
|
74
75
|
},
|
|
75
76
|
"devDependencies": {
|
|
77
|
+
"@types/archiver": "^7.0.0",
|
|
76
78
|
"@types/dockerode": "^4.0.1"
|
|
77
79
|
},
|
|
78
80
|
"scripts": {
|