@kici-dev/agent 0.1.23 → 0.1.25

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/server.js CHANGED
@@ -15,7 +15,7 @@ import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/s
15
15
  import { ALLOWED_SYSTEM_VARS, CacheRunEventType, CacheStepType, ExecutionJobStatus, ExecutionStepStatus, InitFailureCategory, KNOWN_ROLES, PROTOCOL_VERSION, SANDBOX_DEFAULT_VARS, WS_CLOSE_AGENT_AUTH_FAILED, WS_MAX_PAYLOAD_BYTES, applyIncludeExclude, deriveOsArchLabels, expandMatrix, heartbeatSchema, hostLabel, mergeAutoLabels, orchestratorToAgentMessageSchema, parseHostPropertyAssignments, resolveRoleLabels, resolveWhenToRunOn, validateNoReservedLabels } from "@kici-dev/engine";
16
16
  import { execFile, execFileSync, execSync, fork, spawn } from "node:child_process";
17
17
  import WebSocket from "ws";
18
- import * as fs$2 from "node:fs";
18
+ import * as fs$1 from "node:fs";
19
19
  import fs, { existsSync } from "node:fs";
20
20
  import { ZipArchive } from "archiver";
21
21
  import path, { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -23,9 +23,9 @@ import { fileURLToPath, pathToFileURL } from "node:url";
23
23
  import { AsyncLocalStorage } from "node:async_hooks";
24
24
  import { format, promisify } from "node:util";
25
25
  import { gcStaleTmpDirs } from "@kici-dev/core/tmp-gc";
26
- import fs$1, { access, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
26
+ import fsPromises, { 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";
@@ -36,7 +36,14 @@ import { PNPM_IGNORE_BUILD_GATE_ARG, PackageManager, YarnFlavor, detectPackageMa
36
36
  import { parse, stringify } from "yaml";
37
37
  import { createInterface } from "node:readline";
38
38
  var __defProp = Object.defineProperty;
39
- var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
39
+ var __esmMin = (fn, res, err) => () => {
40
+ if (err) throw err[0];
41
+ try {
42
+ return fn && (res = fn(fn = 0)), res;
43
+ } catch (e) {
44
+ throw err = [e], e;
45
+ }
46
+ };
40
47
  var __exportAll = (all, no_symbols) => {
41
48
  let target = {};
42
49
  for (var name in all) __defProp(target, name, {
@@ -46,7 +53,6 @@ var __exportAll = (all, no_symbols) => {
46
53
  if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
47
54
  return target;
48
55
  };
49
- import.meta.url;
50
56
  //#endregion
51
57
  //#region src/config.ts
52
58
  /** Execution mode for the agent's sandbox backend. Mirrors the runtime enum. */
@@ -222,7 +228,7 @@ async function buildAgentMiniBundle(opts) {
222
228
  uptime: os$1.uptime()
223
229
  }, null, 2), { name: "system/info.json" });
224
230
  if (opts.metricsText) archive.append(opts.metricsText, { name: "system/metrics.txt" });
225
- if (opts.logDir && fs$2.existsSync(opts.logDir)) await addLogsToArchive(archive, opts.logDir, opts.logWindowHours);
231
+ if (opts.logDir && fs$1.existsSync(opts.logDir)) await addLogsToArchive(archive, opts.logDir, opts.logWindowHours);
226
232
  await archive.finalize();
227
233
  await done;
228
234
  return Buffer.concat(chunks);
@@ -722,6 +728,13 @@ var OrchestratorClient = class OrchestratorClient {
722
728
  requestId: request.requestId
723
729
  };
724
730
  }
731
+ if (request.op === "defer") {
732
+ this.sendProvenanceUploadDefer(jobId, request);
733
+ return {
734
+ type: "provenance.response",
735
+ requestId: request.requestId
736
+ };
737
+ }
725
738
  const uploadUrl = await this.requestProvenanceUploadUrl(jobId, request.subjectDigest);
726
739
  return {
727
740
  type: "provenance.response",
@@ -730,6 +743,25 @@ var OrchestratorClient = class OrchestratorClient {
730
743
  };
731
744
  }
732
745
  /**
746
+ * Capture a frozen, DSSE-signed attestation for later minting (transient
747
+ * mint-failure path). Fire-and-forget: the orchestrator persists it into the
748
+ * deferred-attestation outbox and the job stays green.
749
+ */
750
+ sendProvenanceUploadDefer(jobId, request) {
751
+ this.sendDirect({
752
+ type: "provenance.upload.defer",
753
+ messageId: randomUUID(),
754
+ jobId,
755
+ subjectName: request.subjectName,
756
+ subjectDigest: request.subjectDigest,
757
+ audience: request.audience,
758
+ mediaType: request.mediaType,
759
+ statementHash: request.statementHash,
760
+ dsseEnvelope: request.dsseEnvelope,
761
+ publicKey: request.publicKey
762
+ });
763
+ }
764
+ /**
733
765
  * Relay a step-level approval request to the orchestrator. Sends a
734
766
  * `step.approval-request` WS message and resolves with the orchestrator's
735
767
  * `step.approval-resolved` mapped onto the IPC response shape. No client-side
@@ -1310,14 +1342,14 @@ var init_console_capture = __esmMin((() => {
1310
1342
  init_console_capture();
1311
1343
  function safe(name, fallback = "unknown") {
1312
1344
  switch (name) {
1313
- case "version": return "0.1.23";
1314
- case "buildCommit": return "4465935bb";
1315
- case "sdkVersion": return "0.1.23";
1316
- case "sdkBundleHash": return "3a83610d2d122b9f9b0f924b225a47050d0b35f00a8f3256a3e15f3ff3bf7ddc";
1317
- case "sharedVersion": return "0.1.23";
1318
- case "sharedBundleHash": return "991385c024392c395d3eb8a68946ef8ed3fcba96f2652f21c3164a54eafa1b1b";
1319
- case "engineVersion": return "0.1.23";
1320
- case "engineBundleHash": return "4a0566c709180a6a8744ff3281640e596b8e661ddceb81327cd37d232d9768d5";
1345
+ case "version": return "0.1.25";
1346
+ case "buildCommit": return "15e8e4155";
1347
+ case "sdkVersion": return "0.1.25";
1348
+ case "sdkBundleHash": return "e06bd1acf349ba365bdb357f3d865e0cbb4ad4604a0270b707a681a4f440954e";
1349
+ case "sharedVersion": return "0.1.25";
1350
+ case "sharedBundleHash": return "2394db0d8560b2cebf220c0e2d8993c75083aaf70a5917c35099b24feb3a22ba";
1351
+ case "engineVersion": return "0.1.25";
1352
+ case "engineBundleHash": return "c3320e812b8593d692f3fbf5eafe83507270028f7a1174c607881105dd702654";
1321
1353
  default: return fallback;
1322
1354
  }
1323
1355
  }
@@ -1951,7 +1983,7 @@ async function buildAssetDigestFromResolvedPaths(workDir, resolvedPaths) {
1951
1983
  for (const rel of resolvedPaths) {
1952
1984
  const abs = path.join(workDir, rel);
1953
1985
  try {
1954
- const content = await fs$1.readFile(abs, "utf-8");
1986
+ const content = await fsPromises.readFile(abs, "utf-8");
1955
1987
  parts.push(`${rel}\n${content}`);
1956
1988
  } catch {
1957
1989
  parts.push(`${rel}\n`);
@@ -1976,7 +2008,7 @@ async function loadWorkflowSource(workDir, sourceFile, expectedContentHash, reso
1976
2008
  ensureLoaderHookRegistered();
1977
2009
  const filePath = path.join(workDir, sourceFile);
1978
2010
  if (expectedContentHash) {
1979
- const rawSource = await fs$1.readFile(filePath, "utf-8");
2011
+ const rawSource = await fsPromises.readFile(filePath, "utf-8");
1980
2012
  let assetDigest;
1981
2013
  if (resolvedHashFiles?.length) assetDigest = await buildAssetDigestFromResolvedPaths(workDir, resolvedHashFiles);
1982
2014
  const actualHash = computeContentHash(rawSource, assetDigest);
@@ -2080,8 +2112,8 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
2080
2112
  }
2081
2113
  var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
2082
2114
  var init_workflow_loader = __esmMin((() => {
2083
- AGENT_SDK_VERSION = "0.1.23";
2084
- AGENT_SDK_BUNDLE_HASH = "3a83610d2d122b9f9b0f924b225a47050d0b35f00a8f3256a3e15f3ff3bf7ddc";
2115
+ AGENT_SDK_VERSION = "0.1.25";
2116
+ AGENT_SDK_BUNDLE_HASH = "e06bd1acf349ba365bdb357f3d865e0cbb4ad4604a0270b707a681a4f440954e";
2085
2117
  hookRegistered = false;
2086
2118
  }));
2087
2119
  //#endregion
@@ -2247,24 +2279,24 @@ function resolveOrchestratorUrl(url) {
2247
2279
  * have nothing to race; the defensive `rm` covers re-runs.
2248
2280
  */
2249
2281
  async function moveScratchIntoRepo(scratchDir, workDir) {
2250
- for (const child of await fs$1.readdir(scratchDir)) if (child === ".kici") {
2282
+ for (const child of await fsPromises.readdir(scratchDir)) if (child === ".kici") {
2251
2283
  const kiciScratch = join(scratchDir, ".kici");
2252
- for (const sub of await fs$1.readdir(kiciScratch)) await moveInto(join(kiciScratch, sub), join(workDir, ".kici", sub));
2284
+ for (const sub of await fsPromises.readdir(kiciScratch)) await moveInto(join(kiciScratch, sub), join(workDir, ".kici", sub));
2253
2285
  } else await moveInto(join(scratchDir, child), join(workDir, child));
2254
2286
  }
2255
2287
  /** Move `src` to `dest`, creating the parent and clearing any stale dest. */
2256
2288
  async function moveInto(src, dest) {
2257
2289
  await mkdir(dirname(dest), { recursive: true });
2258
- await fs$1.rm(dest, {
2290
+ await fsPromises.rm(dest, {
2259
2291
  recursive: true,
2260
2292
  force: true
2261
2293
  });
2262
- await fs$1.rename(src, dest);
2294
+ await fsPromises.rename(src, dest);
2263
2295
  }
2264
2296
  /** Best-effort cleanup of a settled scratch dir; logs and continues on failure. */
2265
2297
  async function cleanupScratch(scratchDir) {
2266
2298
  try {
2267
- await fs$1.rm(scratchDir, {
2299
+ await fsPromises.rm(scratchDir, {
2268
2300
  recursive: true,
2269
2301
  force: true
2270
2302
  });
@@ -2298,7 +2330,7 @@ async function restoreDeps(workDir, depsUrl, depsHash) {
2298
2330
  const kiciDir = join(workDir, ".kici");
2299
2331
  if (depsUrl.startsWith("file://")) {
2300
2332
  const localPath = fileURLToPath(depsUrl);
2301
- const data = await fs$1.readFile(localPath);
2333
+ const data = await fsPromises.readFile(localPath);
2302
2334
  if (depsHash) {
2303
2335
  const actualHash = computeHash(data);
2304
2336
  if (actualHash !== depsHash) throw new Error(`Dep tarball hash mismatch: expected ${depsHash}, got ${actualHash}`);
@@ -2457,7 +2489,7 @@ async function restoreSource(workDir, sourceTarUrl) {
2457
2489
  let data;
2458
2490
  if (sourceTarUrl.startsWith("file://")) {
2459
2491
  const localPath = fileURLToPath(sourceTarUrl);
2460
- data = await fs$1.readFile(localPath);
2492
+ data = await fsPromises.readFile(localPath);
2461
2493
  } else if (sourceTarUrl.startsWith("http://") || sourceTarUrl.startsWith("https://")) data = await downloadUrl(sourceTarUrl);
2462
2494
  else throw new Error(`Unsupported source tarball URL scheme: ${sourceTarUrl}`);
2463
2495
  await extractSourceTarball(data, workDir);
@@ -2548,9 +2580,16 @@ async function evaluateDynamicFields(workflow, jobName, event, flags, timeoutMs
2548
2580
  if (job.include || job.exclude) combos = applyIncludeExclude(combos, job.include, job.exclude);
2549
2581
  result.matrixValues = combos;
2550
2582
  }
2551
- if (flags.dynamicEnvironment && typeof job.environment === "function") {
2552
- const value = await withTimeout(() => job.environment(event), timeoutMs, `dynamicEnvironment for job '${jobName}'`);
2553
- if (value !== void 0 && value !== null) result.environmentName = value;
2583
+ if (flags.dynamicEnvironment) {
2584
+ const envRefs = job.environments ?? (job.environment !== void 0 ? [job.environment] : void 0);
2585
+ if (envRefs && envRefs.length > 0) {
2586
+ const names = [];
2587
+ for (const ref of envRefs) if (typeof ref === "function") {
2588
+ const value = await withTimeout(() => ref(event), timeoutMs, `dynamicEnvironment for job '${jobName}'`);
2589
+ if (value !== void 0 && value !== null) names.push(value);
2590
+ } else if (typeof ref === "string") names.push(ref);
2591
+ if (names.length > 0) result.environmentNames = names;
2592
+ }
2554
2593
  }
2555
2594
  if (flags.dynamicEnv && typeof job.env === "function") {
2556
2595
  const value = await withTimeout(() => job.env(event), timeoutMs, `dynamicEnv for job '${jobName}'`);
@@ -2658,37 +2697,53 @@ async function sshPush(reach, privateKey, localBytes, remotePath, opts = {}, dep
2658
2697
  /**
2659
2698
  * Start a per-call ephemeral ssh-agent, load the key via stdin (never a file),
2660
2699
  * run `body` with `SSH_AUTH_SOCK` in env, and kill the agent in `finally`.
2700
+ *
2701
+ * The agent is bound to a socket inside a private `kici-bootstrap-ssh-*`
2702
+ * directory (`ssh-agent -a <dir>/agent.sock`) rather than the default
2703
+ * `/tmp/ssh-XXXX`. `ssh-agent` daemonizes into its own session, so it does NOT
2704
+ * die with this process — a SIGKILL of the agent (routine when an ephemeral
2705
+ * bring-up runner is torn down) skips the `finally` and orphans the daemon.
2706
+ * The namespaced socket path is how `kici-leak-sweep` reaps such orphans
2707
+ * precisely: it can distinguish a KiCI bring-up agent from an operator's login
2708
+ * agent, which a bare `/tmp/ssh-XXXX` socket cannot. The private dir is removed
2709
+ * in an outer `finally` so the normal path leaves nothing behind.
2661
2710
  */
2662
2711
  async function withEphemeralAgent(privateKey, spawnFn, body) {
2663
2712
  const baseEnv = { ...process.env };
2664
- const start = await spawnFn("ssh-agent", ["-s"], { env: baseEnv });
2665
- if (start.exitCode !== 0) throw new Error(`ssh-agent start failed: exit ${start.exitCode}\n${start.stderr}`);
2666
- const sock = parseAgentSocket(start.stdout);
2667
- const pid = parseAgentPid(start.stdout);
2668
- const agentEnv = {
2669
- ...baseEnv,
2670
- SSH_AUTH_SOCK: sock,
2671
- ...pid ? { SSH_AGENT_PID: pid } : {},
2672
- SSH_ASKPASS: "/bin/false",
2673
- DISPLAY: ""
2674
- };
2713
+ const agentDir = await mkdtemp(join(tmpdir(), "kici-bootstrap-ssh-"));
2714
+ const sock = join(agentDir, "agent.sock");
2675
2715
  try {
2676
- const add = await spawnFn("ssh-add", ["-"], {
2677
- env: agentEnv,
2678
- stdin: privateKey.endsWith("\n") ? privateKey : `${privateKey}\n`
2679
- });
2680
- if (add.exitCode !== 0) throw new Error(`ssh-add failed: exit ${add.exitCode}\n${add.stderr}`);
2681
- return await body(agentEnv);
2716
+ const start = await spawnFn("ssh-agent", [
2717
+ "-a",
2718
+ sock,
2719
+ "-s"
2720
+ ], { env: baseEnv });
2721
+ if (start.exitCode !== 0) throw new Error(`ssh-agent start failed: exit ${start.exitCode}\n${start.stderr}`);
2722
+ const pid = parseAgentPid(start.stdout);
2723
+ const agentEnv = {
2724
+ ...baseEnv,
2725
+ SSH_AUTH_SOCK: sock,
2726
+ ...pid ? { SSH_AGENT_PID: pid } : {},
2727
+ SSH_ASKPASS: "/bin/false",
2728
+ DISPLAY: ""
2729
+ };
2730
+ try {
2731
+ const add = await spawnFn("ssh-add", ["-"], {
2732
+ env: agentEnv,
2733
+ stdin: privateKey.endsWith("\n") ? privateKey : `${privateKey}\n`
2734
+ });
2735
+ if (add.exitCode !== 0) throw new Error(`ssh-add failed: exit ${add.exitCode}\n${add.stderr}`);
2736
+ return await body(agentEnv);
2737
+ } finally {
2738
+ await spawnFn("ssh-agent", ["-k"], { env: agentEnv }).catch(() => {});
2739
+ }
2682
2740
  } finally {
2683
- await spawnFn("ssh-agent", ["-k"], { env: agentEnv }).catch(() => {});
2741
+ await rm(agentDir, {
2742
+ recursive: true,
2743
+ force: true
2744
+ }).catch(() => {});
2684
2745
  }
2685
2746
  }
2686
- /** Extract `SSH_AUTH_SOCK=<path>;` from `ssh-agent -s` output. */
2687
- function parseAgentSocket(out) {
2688
- const m = out.match(/SSH_AUTH_SOCK=([^;\n]+)/);
2689
- if (!m) throw new Error("ssh-agent -s did not emit SSH_AUTH_SOCK");
2690
- return m[1];
2691
- }
2692
2747
  /** Extract `SSH_AGENT_PID=<n>;` from `ssh-agent -s` output (best-effort). */
2693
2748
  function parseAgentPid(out) {
2694
2749
  return out.match(/SSH_AGENT_PID=([^;\n]+)/)?.[1];
@@ -2857,11 +2912,22 @@ async function serializeJobsToLock(jobs, ctx, staticNames, allowedGroups) {
2857
2912
  }
2858
2913
  async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups) {
2859
2914
  const { include: runsOn, exclude: excludeLabels } = normalizeRunsOnToMatchers(job.runsOn, `generated job '${job.name}' runsOn`);
2860
- let resolvedEnvironment;
2861
- if (typeof job.environment === "function") {
2862
- const value = await withTimeout(() => job.environment(ctx.event), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic environment for generated job '${job.name}'`);
2863
- if (value !== void 0 && value !== null) resolvedEnvironment = value;
2864
- } else if (typeof job.environment === "string") resolvedEnvironment = job.environment;
2915
+ const envRefs = job.environments ?? (job.environment !== void 0 ? [job.environment] : void 0);
2916
+ let resolvedEnvironments;
2917
+ if (envRefs !== void 0 && envRefs.length > 0) {
2918
+ const resolved = [];
2919
+ for (const ref of envRefs) if (typeof ref === "function") {
2920
+ const value = await withTimeout(() => ref(ctx.event), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic environment for generated job '${job.name}'`);
2921
+ if (value !== void 0 && value !== null) resolved.push({
2922
+ value,
2923
+ dynamic: false
2924
+ });
2925
+ } else if (typeof ref === "string") resolved.push({
2926
+ value: ref,
2927
+ dynamic: false
2928
+ });
2929
+ if (resolved.length > 0) resolvedEnvironments = resolved;
2930
+ }
2865
2931
  let resolvedEnv;
2866
2932
  if (typeof job.env === "function") {
2867
2933
  const value = await withTimeout(() => job.env(ctx.event), DYNAMIC_FIELD_TIMEOUT_MS, `dynamic env for generated job '${job.name}'`);
@@ -2887,7 +2953,7 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
2887
2953
  ...job.include ? { include: job.include } : {},
2888
2954
  ...job.exclude ? { exclude: job.exclude } : {},
2889
2955
  ...job.description ? { description: job.description } : {},
2890
- ...resolvedEnvironment !== void 0 ? { environment: resolvedEnvironment } : {},
2956
+ ...resolvedEnvironments !== void 0 ? { environments: resolvedEnvironments } : {},
2891
2957
  ...resolvedEnv !== void 0 ? { env: resolvedEnv } : {},
2892
2958
  ...resolvedConcurrencyGroup !== void 0 ? { concurrencyGroup: resolvedConcurrencyGroup } : {}
2893
2959
  };
@@ -2903,7 +2969,7 @@ async function serializeJob(job, generatedNames, ctx, staticNames, allowedGroups
2903
2969
  */
2904
2970
  function resolveNeeds(needs, generatedNames, staticNames, allowedGroups) {
2905
2971
  if (!needs || needs.length === 0) return [];
2906
- const allNames = new Set([...generatedNames, ...staticNames]);
2972
+ const allNames = /* @__PURE__ */ new Set([...generatedNames, ...staticNames]);
2907
2973
  return needs.map((dep) => {
2908
2974
  if (typeof dep === "string") {
2909
2975
  if (!allNames.has(dep)) throw new Error(`Job dependency '${dep}' not found in workflow jobs (checked: ${generatedNames.size} generated, ${staticNames.size} static)`);
@@ -2945,26 +3011,41 @@ function resolveNeeds(needs, generatedNames, staticNames, allowedGroups) {
2945
3011
  * are loaded from the workflow bundle at execution time.
2946
3012
  */
2947
3013
  function serializeSteps(steps) {
2948
- return steps.map((stepOrFn, index) => {
2949
- if (typeof stepOrFn === "function") return {
2950
- name: `step-${index}`,
2951
- hasOutputs: false
2952
- };
2953
- const step = stepOrFn;
2954
- return {
2955
- name: step.name || `step-${index}`,
2956
- hasOutputs: !!step.outputs,
2957
- ...step.continueOnError ? { continueOnError: true } : {},
2958
- ...step.timeout ? { timeout: step.timeout } : {},
2959
- ...step.retry ? { retry: {
2960
- maxAttempts: step.retry.maxAttempts,
2961
- delayMs: step.retry.delayMs,
2962
- backoff: step.retry.backoff,
2963
- maxDelayMs: step.retry.maxDelayMs
2964
- } } : {}
2965
- };
3014
+ let flatIndex = 0;
3015
+ return steps.map((entry) => {
3016
+ if (isParallelGroup(entry)) {
3017
+ const children = entry.steps.map((child) => serializeSequentialStep(child, flatIndex++));
3018
+ return {
3019
+ kind: "parallel",
3020
+ name: entry.name ?? `parallel-${children[0]?.name ?? "group"}`,
3021
+ failFast: entry.failFast,
3022
+ ...entry.maxParallel !== void 0 ? { maxParallel: entry.maxParallel } : {},
3023
+ children
3024
+ };
3025
+ }
3026
+ return serializeSequentialStep(entry, flatIndex++);
2966
3027
  });
2967
3028
  }
3029
+ /** Serialize one sequential step (or bare function) to a flat `LockStep`. */
3030
+ function serializeSequentialStep(stepOrFn, index) {
3031
+ if (typeof stepOrFn === "function") return {
3032
+ name: `step-${index}`,
3033
+ hasOutputs: false
3034
+ };
3035
+ const step = stepOrFn;
3036
+ return {
3037
+ name: step.name || `step-${index}`,
3038
+ hasOutputs: !!step.outputs,
3039
+ ...step.continueOnError ? { continueOnError: true } : {},
3040
+ ...step.timeout ? { timeout: step.timeout } : {},
3041
+ ...step.retry ? { retry: {
3042
+ maxAttempts: step.retry.maxAttempts,
3043
+ delayMs: step.retry.delayMs,
3044
+ backoff: step.retry.backoff,
3045
+ maxDelayMs: step.retry.maxDelayMs
3046
+ } } : {}
3047
+ };
3048
+ }
2968
3049
  /**
2969
3050
  * Serialize matrix configuration. Static array/object matrices are embedded as-is;
2970
3051
  * dynamic matrix functions are invoked against the eval context (mirroring the
@@ -3285,7 +3366,7 @@ function decryptBuffer(encrypted, aesKey) {
3285
3366
  */
3286
3367
  async function applyOverlay(config) {
3287
3368
  const { tarballUrl, cliPublicKey, orchestratorPrivateKey, repoDir } = config;
3288
- const tmpDir = await fs$1.mkdtemp(path.join(os.tmpdir(), "kici-overlay-"));
3369
+ const tmpDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), "kici-overlay-"));
3289
3370
  try {
3290
3371
  logger$7.info("Downloading overlay tarball", { url: tarballUrl.replace(/\?.*$/, "?[redacted]") });
3291
3372
  let encryptedData;
@@ -3299,7 +3380,7 @@ async function applyOverlay(config) {
3299
3380
  const decryptedData = decryptBuffer(encryptedData, aesKey);
3300
3381
  logger$7.info("Extracting overlay tarball", { size: decryptedData.length });
3301
3382
  const extractDir = path.join(tmpDir, "extracted");
3302
- await fs$1.mkdir(extractDir, { recursive: true });
3383
+ await fsPromises.mkdir(extractDir, { recursive: true });
3303
3384
  try {
3304
3385
  const readable = Readable.from(decryptedData);
3305
3386
  await new Promise((resolve, reject) => {
@@ -3314,7 +3395,7 @@ async function applyOverlay(config) {
3314
3395
  const manifestPath = path.join(extractDir, ".kici-overlay-tmp", "manifest.json");
3315
3396
  let manifestContent;
3316
3397
  try {
3317
- manifestContent = await fs$1.readFile(manifestPath, "utf-8");
3398
+ manifestContent = await fsPromises.readFile(manifestPath, "utf-8");
3318
3399
  } catch {
3319
3400
  throw new Error("Overlay manifest not found: expected .kici-overlay-tmp/manifest.json in tarball");
3320
3401
  }
@@ -3335,15 +3416,15 @@ async function applyOverlay(config) {
3335
3416
  for (const file of checksumFiles) {
3336
3417
  const srcPath = path.join(extractDir, file);
3337
3418
  const destPath = path.join(repoDir, file);
3338
- await fs$1.mkdir(path.dirname(destPath), { recursive: true });
3339
- await fs$1.copyFile(srcPath, destPath);
3419
+ await fsPromises.mkdir(path.dirname(destPath), { recursive: true });
3420
+ await fsPromises.copyFile(srcPath, destPath);
3340
3421
  filesApplied++;
3341
3422
  }
3342
3423
  let filesDeleted = 0;
3343
3424
  for (const file of manifest.deletions) {
3344
3425
  const targetPath = path.join(repoDir, file);
3345
3426
  try {
3346
- await fs$1.unlink(targetPath);
3427
+ await fsPromises.unlink(targetPath);
3347
3428
  filesDeleted++;
3348
3429
  } catch {
3349
3430
  logger$7.debug("Deletion target not found, skipping", { file });
@@ -3359,7 +3440,7 @@ async function applyOverlay(config) {
3359
3440
  verified: true
3360
3441
  };
3361
3442
  } finally {
3362
- await fs$1.rm(tmpDir, {
3443
+ await fsPromises.rm(tmpDir, {
3363
3444
  recursive: true,
3364
3445
  force: true
3365
3446
  }).catch(() => {});
@@ -4883,10 +4964,17 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
4883
4964
  case "log.line":
4884
4965
  ctx.execOptions.onLogLine(msg.stepIndex, msg.line);
4885
4966
  return;
4886
- case "step.start":
4967
+ case "step.start": {
4887
4968
  ctx.stepNames.set(msg.stepIndex, msg.stepName);
4888
- ctx.execOptions.onStepStatus(msg.stepIndex, msg.stepName, ExecutionStepStatus.enum.running);
4969
+ const startState = msg.state === "pending" ? ExecutionStepStatus.enum.pending : ExecutionStepStatus.enum.running;
4970
+ const startData = {
4971
+ ...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
4972
+ ...msg.groupId && { groupId: msg.groupId }
4973
+ };
4974
+ if (Object.keys(startData).length > 0) ctx.execOptions.onStepStatus(msg.stepIndex, msg.stepName, startState, startData);
4975
+ else ctx.execOptions.onStepStatus(msg.stepIndex, msg.stepName, startState);
4889
4976
  return;
4977
+ }
4890
4978
  case "step.complete":
4891
4979
  ctx.execOptions.onStepStatus(msg.stepIndex, ctx.stepNames.get(msg.stepIndex) ?? "", msg.status, {
4892
4980
  durationMs: msg.durationMs,
@@ -4896,6 +4984,8 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
4896
4984
  ...msg.checkOutcome !== void 0 && { checkOutcome: msg.checkOutcome },
4897
4985
  ...msg.driftSummary !== void 0 && { driftSummary: msg.driftSummary },
4898
4986
  ...msg.drift !== void 0 && { drift: msg.drift },
4987
+ ...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
4988
+ ...msg.groupId && { groupId: msg.groupId },
4899
4989
  ...msg.data && msg.data
4900
4990
  });
4901
4991
  return;
@@ -5609,10 +5699,17 @@ var init_container_sandbox = __esmMin((() => {
5609
5699
  case "ready":
5610
5700
  this.sendExecuteRequest(stream, options);
5611
5701
  return false;
5612
- case "step.start":
5702
+ case "step.start": {
5613
5703
  stepNames.set(msg.stepIndex, msg.stepName);
5614
- options.onStepStatus(msg.stepIndex, msg.stepName, ExecutionStepStatus.enum.running);
5704
+ const startState = msg.state === "pending" ? ExecutionStepStatus.enum.pending : ExecutionStepStatus.enum.running;
5705
+ const startData = {
5706
+ ...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
5707
+ ...msg.groupId && { groupId: msg.groupId }
5708
+ };
5709
+ if (Object.keys(startData).length > 0) options.onStepStatus(msg.stepIndex, msg.stepName, startState, startData);
5710
+ else options.onStepStatus(msg.stepIndex, msg.stepName, startState);
5615
5711
  return false;
5712
+ }
5616
5713
  case "step.complete": {
5617
5714
  const name = stepNames.get(msg.stepIndex) ?? `step-${msg.stepIndex}`;
5618
5715
  options.onStepStatus(msg.stepIndex, name, msg.status, {
@@ -5623,6 +5720,8 @@ var init_container_sandbox = __esmMin((() => {
5623
5720
  ...msg.checkOutcome !== void 0 && { checkOutcome: msg.checkOutcome },
5624
5721
  ...msg.driftSummary !== void 0 && { driftSummary: msg.driftSummary },
5625
5722
  ...msg.drift !== void 0 && { drift: msg.drift },
5723
+ ...msg.concurrencyKind && { concurrencyKind: msg.concurrencyKind },
5724
+ ...msg.groupId && { groupId: msg.groupId },
5626
5725
  ...msg.data && msg.data
5627
5726
  });
5628
5727
  stepResults.push({
@@ -5763,7 +5862,7 @@ var job_runner_exports = /* @__PURE__ */ __exportAll({
5763
5862
  */
5764
5863
  async function fileExists(p) {
5765
5864
  try {
5766
- await fs$1.access(p);
5865
+ await fsPromises.access(p);
5767
5866
  return true;
5768
5867
  } catch {
5769
5868
  return false;
@@ -5879,11 +5978,11 @@ var init_job_runner = __esmMin((() => {
5879
5978
  async execute(dispatch) {
5880
5979
  const { runId: _runId, jobId, jobConfig: _jobConfig } = dispatch;
5881
5980
  const abortController = new AbortController();
5882
- const workDir = await fs$1.mkdtemp(join(tmpdir(), "kici-"));
5981
+ const workDir = await fsPromises.mkdtemp(join(tmpdir(), "kici-"));
5883
5982
  const completionPromise = this.runJob(dispatch, workDir, abortController).finally(async () => {
5884
5983
  this.activeJobs.delete(jobId);
5885
5984
  this.activeSandbox = null;
5886
- await fs$1.rm(workDir, {
5985
+ await fsPromises.rm(workDir, {
5887
5986
  recursive: true,
5888
5987
  force: true
5889
5988
  }).catch(() => {});
@@ -6494,7 +6593,7 @@ var init_job_runner = __esmMin((() => {
6494
6593
  });
6495
6594
  logger$2.info("Init job completed successfully", {
6496
6595
  jobId,
6497
- hasEnvironment: initResult.environmentName !== void 0,
6596
+ hasEnvironment: initResult.environmentNames !== void 0,
6498
6597
  hasEnv: initResult.env !== void 0,
6499
6598
  hasConcurrencyGroup: initResult.concurrencyGroup !== void 0
6500
6599
  });
@@ -6801,7 +6900,9 @@ var init_job_runner = __esmMin((() => {
6801
6900
  */
6802
6901
  sendStepStatus(dispatch, stepIndex, stepName, state, data, logBytesStreamed) {
6803
6902
  const secretsAccessed = data?.secretsAccessed;
6804
- const { secretsAccessed: _, ...restData } = data ?? {};
6903
+ const concurrencyKind = data?.concurrencyKind;
6904
+ const groupId = data?.groupId;
6905
+ const { secretsAccessed: _s, concurrencyKind: _c, groupId: _g, ...restData } = data ?? {};
6805
6906
  const hasRestData = Object.keys(restData).length > 0;
6806
6907
  this.sendDirect({
6807
6908
  type: "step.status",
@@ -6814,6 +6915,8 @@ var init_job_runner = __esmMin((() => {
6814
6915
  timestamp: Date.now(),
6815
6916
  ...hasRestData && { data: restData },
6816
6917
  ...secretsAccessed !== void 0 && { secretsAccessed },
6918
+ ...concurrencyKind !== void 0 && { concurrencyKind },
6919
+ ...groupId !== void 0 && { groupId },
6817
6920
  ...logBytesStreamed !== void 0 && { logBytesStreamed }
6818
6921
  });
6819
6922
  }
@@ -6841,14 +6944,14 @@ var init_job_runner = __esmMin((() => {
6841
6944
  */
6842
6945
  init_console_capture();
6843
6946
  init_npm_resolver();
6844
- const AGENT_VERSION = "0.1.23";
6845
- const BUILD_COMMIT = "4465935bb";
6846
- const SDK_VERSION = "0.1.23";
6847
- const SDK_BUNDLE_HASH = "3a83610d2d122b9f9b0f924b225a47050d0b35f00a8f3256a3e15f3ff3bf7ddc";
6848
- const SHARED_VERSION = "0.1.23";
6849
- const SHARED_BUNDLE_HASH = "991385c024392c395d3eb8a68946ef8ed3fcba96f2652f21c3164a54eafa1b1b";
6850
- const ENGINE_VERSION = "0.1.23";
6851
- const ENGINE_BUNDLE_HASH = "4a0566c709180a6a8744ff3281640e596b8e661ddceb81327cd37d232d9768d5";
6947
+ const AGENT_VERSION = "0.1.25";
6948
+ const BUILD_COMMIT = "15e8e4155";
6949
+ const SDK_VERSION = "0.1.25";
6950
+ const SDK_BUNDLE_HASH = "e06bd1acf349ba365bdb357f3d865e0cbb4ad4604a0270b707a681a4f440954e";
6951
+ const SHARED_VERSION = "0.1.25";
6952
+ const SHARED_BUNDLE_HASH = "2394db0d8560b2cebf220c0e2d8993c75083aaf70a5917c35099b24feb3a22ba";
6953
+ const ENGINE_VERSION = "0.1.25";
6954
+ const ENGINE_BUNDLE_HASH = "c3320e812b8593d692f3fbf5eafe83507270028f7a1174c607881105dd702654";
6852
6955
  initTelemetry({
6853
6956
  serviceName: "kici-agent",
6854
6957
  otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT