@kici-dev/agent 0.1.20 → 0.1.22
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/config.d.ts +6 -0
- package/dist/execution/job-runner.d.ts +10 -0
- package/dist/execution/reboot.d.ts +26 -0
- package/dist/execution/sandbox/ipc-protocol.d.ts +30 -3
- package/dist/execution/sandbox/step-loop.d.ts +33 -5
- package/dist/execution/sandbox/workflow-runner.d.ts +11 -0
- package/dist/index.js +7 -2
- package/dist/server.js +180 -59
- package/dist/workflow-runner.js +224 -47
- package/dist/ws/orchestrator-client.d.ts +7 -0
- package/package.json +5 -5
- package/sbom.spdx.json +71 -122
package/dist/workflow-runner.js
CHANGED
|
@@ -7,8 +7,8 @@ import os, { homedir, tmpdir } from "node:os";
|
|
|
7
7
|
import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
8
8
|
import { $ } from "zx";
|
|
9
9
|
import { createLogger, deriveSharedSecret, initZx, normalizeLineEndings, sha256, sha256File, toErrorMessage } from "@kici-dev/shared";
|
|
10
|
-
import { CacheOutcome, CacheStepType, ExecutionJobStatus, ExecutionStepStatus, TimeoutReason } from "@kici-dev/engine";
|
|
11
|
-
import { buildKiciApi, createStepSecrets, evaluateRules, isDynamicJobFn,
|
|
10
|
+
import { CacheOutcome, CacheStepType, CheckMode, CheckStepOutcome, ExecutionJobStatus, ExecutionStepStatus, TimeoutReason } from "@kici-dev/engine";
|
|
11
|
+
import { buildKiciApi, buildNeedsContext, createStepSecrets, evaluateRules, isDynamicJobFn, normalizeApproval, normalizeCacheSpecs, provenanceSubjectIsPath, resolveJobOutputs, resolveStepOutputs, setJobOutputsMap, setStepOutputsMap, setStepRefMap } from "@kici-dev/sdk";
|
|
12
12
|
import { OIDC_TOKEN_REQUEST_METHOD } from "@kici-dev/engine/protocol/messages/oidc-token-relay";
|
|
13
13
|
import { sha256File as sha256File$1 } from "@kici-dev/core";
|
|
14
14
|
import { calculateJwkThumbprint, decodeJwt, exportJWK, generateKeyPair } from "jose";
|
|
@@ -22,6 +22,7 @@ import { pipeline } from "node:stream/promises";
|
|
|
22
22
|
import { createGunzip } from "node:zlib";
|
|
23
23
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
24
24
|
import { c, x } from "tar";
|
|
25
|
+
import { runIdempotentStep } from "@kici-dev/core/idempotency";
|
|
25
26
|
import { execFile } from "node:child_process";
|
|
26
27
|
import { promisify } from "node:util";
|
|
27
28
|
import { PNPM_IGNORE_BUILD_GATE_ARG, PackageManager, YarnFlavor, detectPackageManagerFromManifests, detectYarnFlavor } from "@kici-dev/shared/package-manager";
|
|
@@ -1116,12 +1117,92 @@ function createRuleContext(event, changedFiles = [], env = {}) {
|
|
|
1116
1117
|
}
|
|
1117
1118
|
//#endregion
|
|
1118
1119
|
//#region src/execution/sandbox/step-loop.ts
|
|
1120
|
+
/** Result of a rejected drift gate: the run was declined by a reviewer. */
|
|
1121
|
+
var DriftGateRejectedError = class extends Error {
|
|
1122
|
+
constructor(reason) {
|
|
1123
|
+
super(reason ? `approval rejected: ${reason}` : "approval rejected");
|
|
1124
|
+
this.name = "DriftGateRejectedError";
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
/**
|
|
1128
|
+
* Run one step honoring the run-level {@link CheckMode}, reusing the
|
|
1129
|
+
* `runIdempotentStep` primitive for checked steps (never hand-rolled branching).
|
|
1130
|
+
*
|
|
1131
|
+
* - Plain step (no `check`): in apply mode, runs as today; in any check mode it
|
|
1132
|
+
* is skipped with `no_check` (a side-effecting step can't be safely previewed).
|
|
1133
|
+
* - Checked step: adapted into an `IdempotentStep` and driven by the primitive
|
|
1134
|
+
* with `dryRun` set in check mode (so `apply`/`run` never fires). On drift the
|
|
1135
|
+
* summary is emitted as a log line. A `approval: { when: 'drift' }` step in
|
|
1136
|
+
* apply mode passes a `confirm` callback that round-trips a payload-bearing
|
|
1137
|
+
* step-approval; on reject the gate throws (fail-stop). Any other apply-mode
|
|
1138
|
+
* step uses `yes: true`.
|
|
1139
|
+
*/
|
|
1140
|
+
async function runStepWithCheckMode(step, stepIndex, ctx, checkMode, sendFn, opts) {
|
|
1141
|
+
if (!step.check) {
|
|
1142
|
+
if (checkMode !== CheckMode.enum.apply) return {
|
|
1143
|
+
checkOutcome: CheckStepOutcome.enum.no_check,
|
|
1144
|
+
status: "skipped",
|
|
1145
|
+
outputs: void 0
|
|
1146
|
+
};
|
|
1147
|
+
return {
|
|
1148
|
+
status: "success",
|
|
1149
|
+
outputs: await step.run(ctx)
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
let lastDrift = null;
|
|
1153
|
+
const adapted = {
|
|
1154
|
+
name: step.name,
|
|
1155
|
+
check: async () => {
|
|
1156
|
+
lastDrift = await step.check(ctx);
|
|
1157
|
+
return lastDrift;
|
|
1158
|
+
},
|
|
1159
|
+
summarize: step.summarize,
|
|
1160
|
+
apply: (drift) => step.run(ctx, drift),
|
|
1161
|
+
whenInSync: step.whenInSync ? () => step.whenInSync(ctx) : void 0
|
|
1162
|
+
};
|
|
1163
|
+
const driftGate = step.approval !== void 0 && normalizeApproval(step.approval).when === "drift" && checkMode === CheckMode.enum.apply && opts.awaitStepApprovalWithPayload !== void 0;
|
|
1164
|
+
const res = await runIdempotentStep(adapted, {
|
|
1165
|
+
dryRun: checkMode !== CheckMode.enum.apply,
|
|
1166
|
+
...driftGate ? { confirm: async () => {
|
|
1167
|
+
const norm = normalizeApproval(step.approval);
|
|
1168
|
+
const summaryMarkdown = step.summarize(lastDrift);
|
|
1169
|
+
const resolution = await opts.awaitStepApprovalWithPayload({
|
|
1170
|
+
stepIndex,
|
|
1171
|
+
stepName: step.name,
|
|
1172
|
+
clauses: norm.clauses,
|
|
1173
|
+
reason: norm.reason ?? `Approval required for drift in '${step.name}'`,
|
|
1174
|
+
...norm.timeoutSeconds !== void 0 && { timeoutSeconds: norm.timeoutSeconds },
|
|
1175
|
+
payload: {
|
|
1176
|
+
summaryMarkdown,
|
|
1177
|
+
drift: lastDrift
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
if (resolution.outcome === "approved") return true;
|
|
1181
|
+
throw new DriftGateRejectedError(resolution.outcome === "expired" ? "approval expired" : resolution.reason);
|
|
1182
|
+
} } : { yes: true },
|
|
1183
|
+
log: (line) => sendFn({
|
|
1184
|
+
type: "log.line",
|
|
1185
|
+
stepIndex,
|
|
1186
|
+
line
|
|
1187
|
+
})
|
|
1188
|
+
});
|
|
1189
|
+
const driftSummary = res.drift != null ? step.summarize(res.drift) : void 0;
|
|
1190
|
+
const status = res.outcome === CheckStepOutcome.enum.applied ? "success" : "skipped";
|
|
1191
|
+
const mappedStatus = res.outcome === CheckStepOutcome.enum["dry-run"] ? "success" : status;
|
|
1192
|
+
return {
|
|
1193
|
+
checkOutcome: res.outcome,
|
|
1194
|
+
status: mappedStatus,
|
|
1195
|
+
outputs: res.result,
|
|
1196
|
+
...driftSummary !== void 0 && { driftSummary },
|
|
1197
|
+
...res.drift != null && { drift: res.drift }
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1119
1200
|
/**
|
|
1120
1201
|
* Execute a single step with timeout enforcement.
|
|
1121
1202
|
*
|
|
1122
1203
|
* Timeout pattern using Promise.race + AbortController, with IPC status reporting.
|
|
1123
1204
|
*/
|
|
1124
|
-
async function executeStepInLoop(step, stepIndex, ctx, timeoutMs, sendFn, outputsMap, getSecretsAccessLog, getSecretMountRecords, jobDeadlineSignal) {
|
|
1205
|
+
async function executeStepInLoop(step, stepIndex, ctx, timeoutMs, sendFn, outputsMap, getSecretsAccessLog, getSecretMountRecords, jobDeadlineSignal, checkMode, opts) {
|
|
1125
1206
|
sendFn({
|
|
1126
1207
|
type: "step.start",
|
|
1127
1208
|
stepIndex,
|
|
@@ -1131,8 +1212,8 @@ async function executeStepInLoop(step, stepIndex, ctx, timeoutMs, sendFn, output
|
|
|
1131
1212
|
const abortController = new AbortController();
|
|
1132
1213
|
const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);
|
|
1133
1214
|
try {
|
|
1134
|
-
const
|
|
1135
|
-
step
|
|
1215
|
+
const phase = await Promise.race([
|
|
1216
|
+
runStepWithCheckMode(step, stepIndex, ctx, checkMode, sendFn, opts),
|
|
1136
1217
|
new Promise((_, reject) => {
|
|
1137
1218
|
abortController.signal.addEventListener("abort", () => {
|
|
1138
1219
|
reject(/* @__PURE__ */ new Error(`Step '${step.name}' timed out after ${timeoutMs}ms`));
|
|
@@ -1151,22 +1232,26 @@ async function executeStepInLoop(step, stepIndex, ctx, timeoutMs, sendFn, output
|
|
|
1151
1232
|
]);
|
|
1152
1233
|
clearTimeout(timeoutId);
|
|
1153
1234
|
const durationMs = Date.now() - startTime;
|
|
1154
|
-
const outputsPayload =
|
|
1235
|
+
const outputsPayload = phase.outputs != null ? phase.outputs : void 0;
|
|
1155
1236
|
if (outputsPayload) outputsMap.set(step.name, outputsPayload);
|
|
1156
1237
|
const secretsAccessed = getSecretsAccessLog?.();
|
|
1157
1238
|
emitSecretMountEvents(getSecretMountRecords?.(), stepIndex, sendFn);
|
|
1239
|
+
const stepStatus = phase.status === "skipped" ? ExecutionStepStatus.enum.skipped : ExecutionStepStatus.enum.success;
|
|
1158
1240
|
sendFn({
|
|
1159
1241
|
type: "step.complete",
|
|
1160
1242
|
stepIndex,
|
|
1161
|
-
status:
|
|
1243
|
+
status: stepStatus,
|
|
1162
1244
|
durationMs,
|
|
1163
1245
|
...outputsPayload && { outputs: outputsPayload },
|
|
1164
|
-
...secretsAccessed !== void 0 && { secretsAccessed }
|
|
1246
|
+
...secretsAccessed !== void 0 && { secretsAccessed },
|
|
1247
|
+
...phase.checkOutcome !== void 0 && { checkOutcome: phase.checkOutcome },
|
|
1248
|
+
...phase.driftSummary !== void 0 && { driftSummary: phase.driftSummary },
|
|
1249
|
+
...phase.drift !== void 0 && { drift: phase.drift }
|
|
1165
1250
|
});
|
|
1166
1251
|
return {
|
|
1167
1252
|
name: step.name,
|
|
1168
1253
|
stepIndex,
|
|
1169
|
-
status:
|
|
1254
|
+
status: stepStatus,
|
|
1170
1255
|
durationMs,
|
|
1171
1256
|
...outputsPayload && { outputs: outputsPayload }
|
|
1172
1257
|
};
|
|
@@ -1258,14 +1343,17 @@ async function evaluateStepRulesAndMaybeSkip(step, stepIndex, opts) {
|
|
|
1258
1343
|
};
|
|
1259
1344
|
}
|
|
1260
1345
|
/**
|
|
1261
|
-
*
|
|
1262
|
-
* and the harness wired `awaitStepApproval`, block until the
|
|
1263
|
-
* resolves the hold.
|
|
1264
|
-
*
|
|
1346
|
+
* Pre-step manual approval gate. When the step declares `approval` with
|
|
1347
|
+
* `when: 'always'` and the harness wired `awaitStepApproval`, block until the
|
|
1348
|
+
* orchestrator resolves the hold. A `when: 'drift'` gate is NOT handled here —
|
|
1349
|
+
* it fires mid-execution inside `runStepWithCheckMode` once `check()` returns
|
|
1350
|
+
* drift. Returns a failed `StepIterationOutcome` (breaking the loop) on
|
|
1351
|
+
* reject/expired; returns null when approved or when no gate applies.
|
|
1265
1352
|
*/
|
|
1266
1353
|
async function maybeGateStepApproval(step, stepIndex, opts) {
|
|
1267
|
-
if (step.
|
|
1268
|
-
const normalized =
|
|
1354
|
+
if (step.approval === void 0 || !opts.awaitStepApproval) return null;
|
|
1355
|
+
const normalized = normalizeApproval(step.approval);
|
|
1356
|
+
if (normalized.when === "drift") return null;
|
|
1269
1357
|
opts.sendIpc({
|
|
1270
1358
|
type: "log.line",
|
|
1271
1359
|
stepIndex,
|
|
@@ -1381,7 +1469,7 @@ async function runStepIteration(step, stepIndex, opts) {
|
|
|
1381
1469
|
const timeoutMs = step.timeout ?? opts.defaultTimeoutMs;
|
|
1382
1470
|
let result;
|
|
1383
1471
|
try {
|
|
1384
|
-
result = await executeStepInLoop(step, stepIndex, ctx, timeoutMs, opts.sendIpc, opts.outputsMap, opts.getSecretsAccessLog, opts.getSecretMountRecords, opts.jobDeadlineSignal);
|
|
1472
|
+
result = await executeStepInLoop(step, stepIndex, ctx, timeoutMs, opts.sendIpc, opts.outputsMap, opts.getSecretsAccessLog, opts.getSecretMountRecords, opts.jobDeadlineSignal, opts.checkMode ?? CheckMode.enum.apply, opts);
|
|
1385
1473
|
} finally {
|
|
1386
1474
|
await opts.afterStepApplyEnvFiles?.();
|
|
1387
1475
|
}
|
|
@@ -3023,8 +3111,8 @@ function logSubprocessStreams(e, tokens) {
|
|
|
3023
3111
|
* no Rolldown step at runtime. `@kici-dev/sdk` and host-repo deps resolve via
|
|
3024
3112
|
* Node's normal ESM lookup against `.kici/node_modules/`.
|
|
3025
3113
|
*/
|
|
3026
|
-
const AGENT_SDK_VERSION = "0.1.
|
|
3027
|
-
const AGENT_SDK_BUNDLE_HASH = "
|
|
3114
|
+
const AGENT_SDK_VERSION = "0.1.22";
|
|
3115
|
+
const AGENT_SDK_BUNDLE_HASH = "4b26a42978f77e29db110d47523ec947d3d673c3109fc48141c1a62814bde7a1";
|
|
3028
3116
|
/**
|
|
3029
3117
|
* Register the `@kici-dev/core/ts-loader-hook` oxc-transform ESM loader hook so
|
|
3030
3118
|
* subsequent dynamic `import()` calls for `.ts` / `.tsx` files transform on the
|
|
@@ -3386,7 +3474,7 @@ async function applyOverlay(config) {
|
|
|
3386
3474
|
*/
|
|
3387
3475
|
init_download();
|
|
3388
3476
|
init_dep_restore();
|
|
3389
|
-
const AGENT_VERSION = "0.1.
|
|
3477
|
+
const AGENT_VERSION = "0.1.22";
|
|
3390
3478
|
process.on("uncaughtException", (err) => {
|
|
3391
3479
|
process.stderr.write(`[workflow-runner] UNCAUGHT EXCEPTION: ${err.message}\n`);
|
|
3392
3480
|
if (err.stack) process.stderr.write(`[workflow-runner] Stack: ${err.stack}\n`);
|
|
@@ -3779,35 +3867,49 @@ function waitForApprovalResolution(requestId) {
|
|
|
3779
3867
|
});
|
|
3780
3868
|
}
|
|
3781
3869
|
/**
|
|
3782
|
-
*
|
|
3783
|
-
* `
|
|
3784
|
-
*
|
|
3785
|
-
*
|
|
3870
|
+
* Send a step-approval `approval.request` IPC (relayed by the agent over the WS
|
|
3871
|
+
* as a `step.approval-request`), await the matching `approval.resolved`, and map
|
|
3872
|
+
* it onto a `StepApprovalResolution`. A relay error is treated as a fail-closed
|
|
3873
|
+
* reject. Shared by the `when: 'always'` and `when: 'drift'` callbacks; the
|
|
3874
|
+
* latter carries a drift `payload`.
|
|
3786
3875
|
*/
|
|
3787
|
-
function
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
};
|
|
3876
|
+
async function requestStepApproval(req) {
|
|
3877
|
+
const requestId = randomUUID();
|
|
3878
|
+
sendMessage({
|
|
3879
|
+
type: "approval.request",
|
|
3880
|
+
requestId,
|
|
3881
|
+
stepIndex: req.stepIndex,
|
|
3882
|
+
stepName: req.stepName,
|
|
3883
|
+
clauses: req.clauses,
|
|
3884
|
+
reason: req.reason,
|
|
3885
|
+
...req.timeoutSeconds !== void 0 && { timeoutSeconds: req.timeoutSeconds },
|
|
3886
|
+
...req.payload !== void 0 && { payload: req.payload }
|
|
3887
|
+
});
|
|
3888
|
+
const resolution = await waitForApprovalResolution(requestId);
|
|
3889
|
+
if (resolution.error) return {
|
|
3890
|
+
outcome: "rejected",
|
|
3891
|
+
reason: resolution.error
|
|
3892
|
+
};
|
|
3893
|
+
return {
|
|
3894
|
+
outcome: resolution.outcome ?? "rejected",
|
|
3895
|
+
...resolution.reason !== void 0 && { reason: resolution.reason }
|
|
3808
3896
|
};
|
|
3809
3897
|
}
|
|
3810
3898
|
/**
|
|
3899
|
+
* Build the `awaitStepApproval` callback the step loop uses to block on an
|
|
3900
|
+
* `approval` step (`when: 'always'`).
|
|
3901
|
+
*/
|
|
3902
|
+
function buildAwaitStepApproval() {
|
|
3903
|
+
return (req) => requestStepApproval(req);
|
|
3904
|
+
}
|
|
3905
|
+
/**
|
|
3906
|
+
* Build the `awaitStepApprovalWithPayload` callback the step loop uses to block
|
|
3907
|
+
* on a `when: 'drift'` step mid-execution, carrying the computed drift payload.
|
|
3908
|
+
*/
|
|
3909
|
+
function buildAwaitStepApprovalWithPayload() {
|
|
3910
|
+
return (req) => requestStepApproval(req);
|
|
3911
|
+
}
|
|
3912
|
+
/**
|
|
3811
3913
|
* Build the {@link CacheTransport} the sandbox-side cache engine uses to reach
|
|
3812
3914
|
* the orchestrator. Each method sends a `cache.request` IPC (relayed by the
|
|
3813
3915
|
* agent over the WS as a `cache.user.*` message) and awaits the matching
|
|
@@ -4040,6 +4142,75 @@ function createIpcLogger(stepIndex, stepName, sendFn) {
|
|
|
4040
4142
|
}
|
|
4041
4143
|
};
|
|
4042
4144
|
}
|
|
4145
|
+
/** A lock needs entry maps to a base name (single/matrix/host) or a group name. */
|
|
4146
|
+
function needBaseName(need) {
|
|
4147
|
+
if (typeof need === "string") return {
|
|
4148
|
+
kind: "job",
|
|
4149
|
+
key: need
|
|
4150
|
+
};
|
|
4151
|
+
if (need && typeof need === "object") {
|
|
4152
|
+
if ("group" in need) return {
|
|
4153
|
+
kind: "group",
|
|
4154
|
+
key: need.group
|
|
4155
|
+
};
|
|
4156
|
+
if ("name" in need) return {
|
|
4157
|
+
kind: "job",
|
|
4158
|
+
key: need.name
|
|
4159
|
+
};
|
|
4160
|
+
}
|
|
4161
|
+
return null;
|
|
4162
|
+
}
|
|
4163
|
+
/**
|
|
4164
|
+
* Build `ctx.needs` for a job's steps from the dispatch envelope. Reconstructs
|
|
4165
|
+
* an {@link UpstreamSnapshot} from `upstreamJobOutputs` (flat per single job;
|
|
4166
|
+
* `byMatrix` / `byHost` envelopes per fan-out) + `upstreamJobStatuses` (keyed by
|
|
4167
|
+
* each upstream job/child name), then resolves the job's declared needs into the
|
|
4168
|
+
* `{ result, status }` / ordered-array shape via the shared SDK builder. Returns
|
|
4169
|
+
* undefined when the job declares no needs.
|
|
4170
|
+
*/
|
|
4171
|
+
function buildStepNeedsContext(declaredNeeds, upstreamJobOutputs, upstreamJobStatuses) {
|
|
4172
|
+
if (!declaredNeeds || declaredNeeds.length === 0) return void 0;
|
|
4173
|
+
const statuses = upstreamJobStatuses ?? {};
|
|
4174
|
+
const jobs = {};
|
|
4175
|
+
const groups = {};
|
|
4176
|
+
const snapStatuses = {};
|
|
4177
|
+
const resolvedNeeds = [];
|
|
4178
|
+
for (const need of declaredNeeds) {
|
|
4179
|
+
const base = needBaseName(need);
|
|
4180
|
+
if (!base) continue;
|
|
4181
|
+
const childNames = Object.keys(statuses).filter((n) => n.startsWith(`${base.key} (`));
|
|
4182
|
+
if (base.kind === "group" || childNames.length > 0) {
|
|
4183
|
+
groups[base.key] = [...childNames].sort();
|
|
4184
|
+
const envelope = upstreamJobOutputs?.[base.key];
|
|
4185
|
+
const bySuffix = envelopeChildOutputs(envelope);
|
|
4186
|
+
for (const child of childNames) {
|
|
4187
|
+
jobs[child] = bySuffix[child.slice(base.key.length + 2, -1)] ?? {};
|
|
4188
|
+
snapStatuses[child] = statuses[child];
|
|
4189
|
+
}
|
|
4190
|
+
resolvedNeeds.push({ group: base.key });
|
|
4191
|
+
} else {
|
|
4192
|
+
jobs[base.key] = upstreamJobOutputs?.[base.key] ?? {};
|
|
4193
|
+
if (statuses[base.key]) snapStatuses[base.key] = statuses[base.key];
|
|
4194
|
+
resolvedNeeds.push(base.key);
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
return buildNeedsContext({
|
|
4198
|
+
jobs,
|
|
4199
|
+
groups,
|
|
4200
|
+
statuses: snapStatuses
|
|
4201
|
+
}, resolvedNeeds);
|
|
4202
|
+
}
|
|
4203
|
+
/**
|
|
4204
|
+
* Extract per-child output records from a fan-out outputs envelope, keyed by the
|
|
4205
|
+
* combination suffix (matrix `byMatrix`) or hostname (`runsOnAll` `byHost`).
|
|
4206
|
+
* Returns an empty map for a non-envelope value.
|
|
4207
|
+
*/
|
|
4208
|
+
function envelopeChildOutputs(envelope) {
|
|
4209
|
+
if (!envelope) return {};
|
|
4210
|
+
const byMatrix = envelope.byMatrix;
|
|
4211
|
+
const byHost = envelope.byHost;
|
|
4212
|
+
return byMatrix ?? byHost ?? {};
|
|
4213
|
+
}
|
|
4043
4214
|
/**
|
|
4044
4215
|
* Build StepSecrets from the job execution request, wired with the per-step
|
|
4045
4216
|
* file-mount host (used by `ctx.secrets.mountFile` / `exposeFile`).
|
|
@@ -4235,7 +4406,11 @@ function createSandboxStepContext(workDir, stepIndex, stepName, request, maskedS
|
|
|
4235
4406
|
...request.provider && { provider: request.provider },
|
|
4236
4407
|
...request.matrixValues && { matrix: request.matrixValues },
|
|
4237
4408
|
...request.host && { host: request.host },
|
|
4238
|
-
...request.agent && { agent: request.agent }
|
|
4409
|
+
...request.agent && { agent: request.agent },
|
|
4410
|
+
...(() => {
|
|
4411
|
+
const needs = buildStepNeedsContext(request.jobNeeds, request.upstreamJobOutputs, request.upstreamJobStatuses);
|
|
4412
|
+
return needs ? { needs } : {};
|
|
4413
|
+
})()
|
|
4239
4414
|
};
|
|
4240
4415
|
}
|
|
4241
4416
|
/** Raw provider webhook body for ctx.rawPayload — nested in the envelope. */
|
|
@@ -5099,6 +5274,7 @@ async function main() {
|
|
|
5099
5274
|
const jobStartTime = Date.now();
|
|
5100
5275
|
const loopResult = await executeStepLoop({
|
|
5101
5276
|
steps: normalizedSteps,
|
|
5277
|
+
checkMode: request.checkMode,
|
|
5102
5278
|
createStepContext: createStepCtxWithCapture,
|
|
5103
5279
|
sendIpc: maskedSend,
|
|
5104
5280
|
defaultTimeoutMs,
|
|
@@ -5126,7 +5302,8 @@ async function main() {
|
|
|
5126
5302
|
},
|
|
5127
5303
|
beforeStepEnvFiles: stepEnvHooks.beforeStepEnvFiles,
|
|
5128
5304
|
afterStepApplyEnvFiles: stepEnvHooks.afterStepApplyEnvFiles,
|
|
5129
|
-
awaitStepApproval: buildAwaitStepApproval()
|
|
5305
|
+
awaitStepApproval: buildAwaitStepApproval(),
|
|
5306
|
+
awaitStepApprovalWithPayload: buildAwaitStepApprovalWithPayload()
|
|
5130
5307
|
});
|
|
5131
5308
|
jobDeadline.clear();
|
|
5132
5309
|
await maybeSaveJobCache(jobCacheSpecs, jobCacheRestore, cachePhaseDeps, !aborted && loopResult.status === ExecutionStepStatus.enum.success);
|
|
@@ -5212,6 +5389,6 @@ main().catch((error) => {
|
|
|
5212
5389
|
setTimeout(() => process.exit(1), 100);
|
|
5213
5390
|
});
|
|
5214
5391
|
//#endregion
|
|
5215
|
-
export { createSandboxStepContext, rawPayloadFromEvent };
|
|
5392
|
+
export { buildStepNeedsContext, createSandboxStepContext, rawPayloadFromEvent };
|
|
5216
5393
|
|
|
5217
5394
|
//# sourceMappingURL=workflow-runner.js.map
|
|
@@ -8,6 +8,12 @@ export interface OrchestratorClientOptions {
|
|
|
8
8
|
agentId: string;
|
|
9
9
|
/** Agent's label set for job routing. */
|
|
10
10
|
labels: string[];
|
|
11
|
+
/**
|
|
12
|
+
* Agent-reported typed host-vars (the `KICI_PROPERTIES` bag). Reported at
|
|
13
|
+
* registration and shallow-merged into the orchestrator's host roster.
|
|
14
|
+
* Omitted / empty ⇒ no properties reported.
|
|
15
|
+
*/
|
|
16
|
+
properties?: Record<string, string | number | boolean>;
|
|
11
17
|
/** Callback invoked when a job.dispatch message is received. */
|
|
12
18
|
onJobDispatch: (dispatch: JobDispatch) => void;
|
|
13
19
|
/** Callback invoked when a job.cancel message is received. */
|
|
@@ -94,6 +100,7 @@ export declare class OrchestratorClient {
|
|
|
94
100
|
private readonly url;
|
|
95
101
|
private readonly agentId;
|
|
96
102
|
private readonly labels;
|
|
103
|
+
private readonly properties;
|
|
97
104
|
private readonly onJobDispatch;
|
|
98
105
|
private readonly onJobCancel;
|
|
99
106
|
private readonly token?;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kici-dev/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
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",
|
|
@@ -64,10 +64,10 @@
|
|
|
64
64
|
"yaml": "^2.9.0",
|
|
65
65
|
"zod": "^4.4.3",
|
|
66
66
|
"zx": "^8.8.5",
|
|
67
|
-
"@kici-dev/core": "0.1.
|
|
68
|
-
"@kici-dev/engine": "0.1.
|
|
69
|
-
"@kici-dev/sdk": "0.1.
|
|
70
|
-
"@kici-dev/shared": "0.1.
|
|
67
|
+
"@kici-dev/core": "0.1.22",
|
|
68
|
+
"@kici-dev/engine": "0.1.22",
|
|
69
|
+
"@kici-dev/sdk": "0.1.22",
|
|
70
|
+
"@kici-dev/shared": "0.1.22"
|
|
71
71
|
},
|
|
72
72
|
"kici": {
|
|
73
73
|
"metrics": {
|