@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.
@@ -0,0 +1,9 @@
1
+ export interface AgentMiniBundleOptions {
2
+ agentId: string;
3
+ logDir?: string;
4
+ logWindowHours: number;
5
+ config: Record<string, unknown>;
6
+ metricsText?: string;
7
+ }
8
+ export declare function buildAgentMiniBundle(opts: AgentMiniBundleOptions): Promise<Buffer>;
9
+ //# sourceMappingURL=mini-bundle.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import type { AgentToOrchestratorMessage, JobDispatch } from '@kici-dev/engine';
2
2
  import type { AppConfig } from '../config.js';
3
- import type { CacheRequestIpc, CacheResponseIpc } from './sandbox/index.js';
3
+ import type { CacheRequestIpc, CacheResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc } from './sandbox/index.js';
4
4
  /**
5
5
  * Dependencies injected into JobRunner.
6
6
  */
@@ -91,6 +91,14 @@ export interface JobRunnerDeps {
91
91
  * Optional for backward compatibility (callers that don't support the cache).
92
92
  */
93
93
  requestUserCache?: (jobId: string, request: CacheRequestIpc) => Promise<CacheResponseIpc>;
94
+ /**
95
+ * Relay a step-level approval request to the orchestrator and await the
96
+ * resolution. Translates the sandbox `approval.request` IPC into a
97
+ * `step.approval-request` WS message and returns the orchestrator's
98
+ * `step.approval-resolved` mapped onto the IPC response shape. Optional for
99
+ * backward compatibility (callers that don't support approvals).
100
+ */
101
+ sendStepApproval?: (runId: string, jobId: string, request: StepApprovalRequestIpc) => Promise<StepApprovalResolvedIpc>;
94
102
  }
95
103
  interface ActiveJob {
96
104
  abortController: AbortController;
@@ -125,6 +133,7 @@ export declare class JobRunner {
125
133
  private readonly _sendConcurrencyReport;
126
134
  private readonly _sendApiRequest?;
127
135
  private readonly _requestUserCache?;
136
+ private readonly _sendStepApproval?;
128
137
  /** Tracks running jobs for concurrency and cancellation */
129
138
  readonly activeJobs: Map<string, ActiveJob>;
130
139
  /** Active sandbox for the current job (used for abort). */
@@ -5,7 +5,7 @@
5
5
  * import { BareMetalSandbox, ContainerSandbox, buildSanitizedEnv } from './sandbox/index.js';
6
6
  */
7
7
  export type { ExecutionSandbox, SandboxSetupOptions, JobExecutionOptions, JobExecutionResult, SandboxStepResult, } from './types.js';
8
- export type { RunnerToAgentMessage, AgentToRunnerMessage, EventEmitRequest, EventEmitResponse, CacheRequestIpc, CacheResponseIpc, JobExecutionRequest, } from './ipc-protocol.js';
8
+ export type { RunnerToAgentMessage, AgentToRunnerMessage, EventEmitRequest, EventEmitResponse, CacheRequestIpc, CacheResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc, JobExecutionRequest, } from './ipc-protocol.js';
9
9
  export { buildSanitizedEnv } from './env-sanitizer.js';
10
10
  export { ALLOWED_SYSTEM_VARS, KICI_AGENT_ENV_PREFIX, AGENT_REQUIRED_KICI_VARS, } from '@kici-dev/engine';
11
11
  export { BareMetalSandbox } from './bare-metal-sandbox.js';
@@ -159,7 +159,33 @@ export interface CacheRequestIpc {
159
159
  /** Tarball size in bytes (drives quota accounting). `completeSave` only. */
160
160
  sizeBytes?: number;
161
161
  }
162
- export type RunnerToAgentMessage = ReadyMessage | StepStartMessage | StepCompleteMessage | LogLineMessage | StepSecretMountMessage | JobCompleteMessage | EventEmitRequest | ConcurrencyReportMessage | AgentApiRequestIpc | CacheRequestIpc;
162
+ /**
163
+ * Request a step-level approval hold (runner -> agent). The sandbox runner
164
+ * blocks the step loop before a `requireApproval` step; the agent relays this
165
+ * as a `step.approval-request` WS message and pipes the orchestrator's
166
+ * resolution back as a {@link StepApprovalResolvedIpc}. Mirrors the
167
+ * {@link CacheRequestIpc} relay pattern.
168
+ */
169
+ export interface StepApprovalRequestIpc {
170
+ type: 'approval.request';
171
+ /** UUID for correlating the response. */
172
+ requestId: string;
173
+ /** Step index within the job. */
174
+ stepIndex: number;
175
+ /** Step name (for the hold reason / logs). */
176
+ stepName: string;
177
+ /** AND-list of approver clauses (empty = any approval-capable member). */
178
+ clauses: Array<{
179
+ team: string;
180
+ } | {
181
+ user: string;
182
+ }>;
183
+ /** Human label for the gate. */
184
+ reason: string;
185
+ /** Per-gate timeout override (seconds) from the SDK `requireApproval.timeout`. */
186
+ timeoutSeconds?: number;
187
+ }
188
+ export type RunnerToAgentMessage = ReadyMessage | StepStartMessage | StepCompleteMessage | LogLineMessage | StepSecretMountMessage | JobCompleteMessage | EventEmitRequest | ConcurrencyReportMessage | AgentApiRequestIpc | CacheRequestIpc | StepApprovalRequestIpc;
163
189
  /** Instruct the workflow runner to execute a job. */
164
190
  interface ExecuteMessage {
165
191
  type: 'execute';
@@ -233,7 +259,24 @@ export interface CacheResponseIpc {
233
259
  /** Error description (present when the relay or orchestrator failed). */
234
260
  error?: string;
235
261
  }
236
- export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResponse | ConcurrencyAckMessage | AgentApiResponseIpc | CacheResponseIpc;
262
+ /**
263
+ * Resolution of a step-level approval hold (agent -> runner). Relayed from the
264
+ * orchestrator's `step.approval-resolved` WS message. On `approved` the runner
265
+ * runs the step; on `rejected`/`expired` it fails the job. `error` is set when
266
+ * the relay itself failed (treated as a fail-closed reject by the runner).
267
+ */
268
+ export interface StepApprovalResolvedIpc {
269
+ type: 'approval.resolved';
270
+ /** Matches the original request's requestId. */
271
+ requestId: string;
272
+ /** Outcome of the hold. */
273
+ outcome?: 'approved' | 'rejected' | 'expired';
274
+ /** Optional human reason (e.g. the reject reason). */
275
+ reason?: string;
276
+ /** Error description (present when the relay or orchestrator failed). */
277
+ error?: string;
278
+ }
279
+ export type AgentToRunnerMessage = ExecuteMessage | AbortMessage | EventEmitResponse | ConcurrencyAckMessage | AgentApiResponseIpc | CacheResponseIpc | StepApprovalResolvedIpc;
237
280
  /**
238
281
  * All data the workflow runner needs to execute a job inside the sandbox.
239
282
  *
@@ -19,7 +19,7 @@ export interface JobHooks {
19
19
  cleanup?: HookInput;
20
20
  }
21
21
  /** Options for the step execution loop. */
22
- interface StepLoopOptions {
22
+ export interface StepLoopOptions {
23
23
  steps: Step[];
24
24
  /** Factory that creates a StepContext for a given step index and name. */
25
25
  createStepContext: (stepIndex: number, stepName: string) => StepContext;
@@ -83,6 +83,29 @@ interface StepLoopOptions {
83
83
  * them for the next step. Never throws -- errors are logged by the wired impl.
84
84
  */
85
85
  afterStepApplyEnvFiles?: () => Promise<void>;
86
+ /**
87
+ * Block a `requireApproval` step pending an orchestrator-side approval hold.
88
+ * The runner sends the normalized requirement and awaits the resolution; the
89
+ * agent keeps job heartbeats flowing during the wait so the agent isn't
90
+ * reaped. Absent ⇒ approvals are not gated (CT / unit harnesses) and steps
91
+ * run unconditionally.
92
+ */
93
+ awaitStepApproval?: (req: {
94
+ stepIndex: number;
95
+ stepName: string;
96
+ clauses: Array<{
97
+ team: string;
98
+ } | {
99
+ user: string;
100
+ }>;
101
+ reason: string;
102
+ timeoutSeconds?: number;
103
+ }) => Promise<StepApprovalResolution>;
104
+ }
105
+ /** Outcome of an awaited step-level approval hold. */
106
+ export interface StepApprovalResolution {
107
+ outcome: 'approved' | 'rejected' | 'expired';
108
+ reason?: string;
86
109
  }
87
110
  /** Result of the step execution loop. */
88
111
  interface StepLoopResult {
@@ -1,5 +1,5 @@
1
1
  import type { JobDispatch } from '@kici-dev/engine';
2
- import type { EventEmitRequest, EventEmitResponse, ConcurrencyReportMessage, ConcurrencyAckMessage, CacheRequestIpc, CacheResponseIpc } from './ipc-protocol.js';
2
+ import type { EventEmitRequest, EventEmitResponse, ConcurrencyReportMessage, ConcurrencyAckMessage, CacheRequestIpc, CacheResponseIpc, StepApprovalRequestIpc, StepApprovalResolvedIpc } from './ipc-protocol.js';
3
3
  /**
4
4
  * Common interface for all execution sandbox backends.
5
5
  *
@@ -89,6 +89,15 @@ export interface JobExecutionOptions {
89
89
  * working — the runner falls back to a "not configured" cache response.
90
90
  */
91
91
  onCacheRequest?: (request: CacheRequestIpc) => Promise<CacheResponseIpc>;
92
+ /**
93
+ * Callback for relaying a step-level approval request from the sandbox to the
94
+ * orchestrator. The sandbox runner sends `approval.request` IPC; the agent
95
+ * wraps it in a `step.approval-request` WS message and forwards to the
96
+ * orchestrator, awaiting the `step.approval-resolved` response which it pipes
97
+ * back as `approval.resolved`. Optional so harnesses that don't thread
98
+ * approvals keep working — the runner falls back to a fail-closed reject.
99
+ */
100
+ onApprovalRequest?: (request: StepApprovalRequestIpc) => Promise<StepApprovalResolvedIpc>;
92
101
  /**
93
102
  * Callback fired once per `ctx.secrets.mountFile` / `exposeFile` call the
94
103
  * workflow runner performs. Carries only key names + the resulting path /
package/dist/server.js CHANGED
@@ -3,17 +3,19 @@ import { dirname as __cjs_dirname } from "node:path";
3
3
  __cjs_dirname(__cjs_fileURLToPath(import.meta.url));
4
4
  import { register } from "node:module";
5
5
  import crypto$1, { createCipheriv, createHash, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes, randomUUID } from "node:crypto";
6
+ import * as os$1 from "node:os";
6
7
  import os, { hostname, tmpdir } from "node:os";
7
8
  import { PassThrough, Readable, Transform, Writable } from "node:stream";
8
9
  import { serve } from "@hono/node-server";
9
10
  import { Hono } from "hono";
10
11
  import winston from "winston";
11
- import { RingBuffer, createHealthRoutes, createLogger, createMeter, createMetricsRoutes, deriveSharedSecret, getPrometheusExporter, getReconnectDelay, getRequestContext, guardStartup, initTelemetry, logger, normalizeLineEndings, requestContext, setServiceName, setupGracefulShutdown, sha256, sha256File, toErrorMessage, validateRequiredTools } from "@kici-dev/shared";
12
+ import { RingBuffer, addLogsToArchive, chunkBuffer, createHealthRoutes, createLogger, createMeter, createMetricsRoutes, deriveSharedSecret, getPrometheusExporter, getReconnectDelay, getRequestContext, guardStartup, initTelemetry, logger, normalizeLineEndings, redactConfig, requestContext, setServiceName, setupGracefulShutdown, sha256, sha256File, toErrorMessage, validateRequiredTools } from "@kici-dev/shared";
12
13
  import { z } from "zod";
13
14
  import { LOGGER_ENV_VARS, defineEnv, validateUnknownKiciVars } from "@kici-dev/shared/env";
14
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, deriveOsArchLabels, heartbeatSchema, hostLabel, mergeAutoLabels, orchestratorToAgentMessageSchema, resolveRoleLabels, validateNoReservedLabels } from "@kici-dev/engine";
15
16
  import { execFile, execFileSync, execSync, fork, spawn } from "node:child_process";
16
17
  import WebSocket from "ws";
18
+ import archiver from "archiver";
17
19
  import { AsyncLocalStorage } from "node:async_hooks";
18
20
  import { format, promisify } from "node:util";
19
21
  import { existsSync } from "node:fs";
@@ -177,6 +179,46 @@ function agentClientConnectionOptions(config) {
177
179
  };
178
180
  }
179
181
  //#endregion
182
+ //#region src/diagnostics/mini-bundle.ts
183
+ /**
184
+ * Agent fleet mini-bundle assembler.
185
+ *
186
+ * Builds an in-memory ZIP of the agent's recent logs, system info, redacted
187
+ * config, and current Prometheus metrics text, streamed to the orchestrator on
188
+ * a fleet.logs.request. No diagnostics runner exists agent-side, so this is a
189
+ * lean subset of the orchestrator's createDebugBundle.
190
+ */
191
+ async function buildAgentMiniBundle(opts) {
192
+ const archive = archiver("zip", { zlib: { level: 6 } });
193
+ const chunks = [];
194
+ archive.on("data", (d) => chunks.push(d));
195
+ const done = new Promise((resolve, reject) => {
196
+ archive.on("end", resolve);
197
+ archive.on("error", reject);
198
+ });
199
+ archive.append(JSON.stringify({
200
+ kind: "agent",
201
+ agentId: opts.agentId,
202
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
203
+ }, null, 2), { name: "manifest.json" });
204
+ archive.append(JSON.stringify(redactConfig(opts.config), null, 2), { name: "config/config.json" });
205
+ archive.append(JSON.stringify({
206
+ hostname: os$1.hostname(),
207
+ platform: process.platform,
208
+ arch: process.arch,
209
+ nodeVersion: process.version,
210
+ cpus: os$1.cpus().length,
211
+ totalmem: os$1.totalmem(),
212
+ freemem: os$1.freemem(),
213
+ uptime: os$1.uptime()
214
+ }, null, 2), { name: "system/info.json" });
215
+ if (opts.metricsText) archive.append(opts.metricsText, { name: "system/metrics.txt" });
216
+ if (opts.logDir) await addLogsToArchive(archive, opts.logDir, opts.logWindowHours);
217
+ await archive.finalize();
218
+ await done;
219
+ return Buffer.concat(chunks);
220
+ }
221
+ //#endregion
180
222
  //#region src/ws/event-buffer.ts
181
223
  /**
182
224
  * In-memory buffer for agent-to-orchestrator messages during disconnection.
@@ -243,6 +285,13 @@ var OrchestratorClient = class OrchestratorClient {
243
285
  pendingApiRequests = /* @__PURE__ */ new Map();
244
286
  /** Pending user-cache restore/save requests awaiting orchestrator response. */
245
287
  pendingUserCacheRequests = /* @__PURE__ */ new Map();
288
+ /**
289
+ * Pending step-approval requests awaiting the orchestrator's resolution.
290
+ * No client-side timeout: the orchestrator owns the (org-/SDK-configured)
291
+ * expiry and sends `step.approval-resolved: expired` when it lapses. The
292
+ * workflow-runner carries an outer safety-net timeout.
293
+ */
294
+ pendingStepApprovals = /* @__PURE__ */ new Map();
246
295
  /** Pending concurrency report requests awaiting orchestrator ack. */
247
296
  pendingConcurrencyRequests = /* @__PURE__ */ new Map();
248
297
  url;
@@ -256,6 +305,7 @@ var OrchestratorClient = class OrchestratorClient {
256
305
  getInFlightJobs;
257
306
  roles;
258
307
  scalerManaged;
308
+ getFleetBundleInputs;
259
309
  /** Timestamp when the connection was lost, used for gap marker outage duration. */
260
310
  disconnectedAt = null;
261
311
  /** Set to true when auth.failure is received. Prevents retrying with a bad token. */
@@ -282,6 +332,7 @@ var OrchestratorClient = class OrchestratorClient {
282
332
  this.getInFlightJobs = options.getInFlightJobs;
283
333
  this.roles = options.roles;
284
334
  this.scalerManaged = options.scalerManaged ?? false;
335
+ this.getFleetBundleInputs = options.getFleetBundleInputs;
285
336
  this.eventBuffer = new EventBuffer({ maxSize: options.maxBufferSize ?? 5e3 });
286
337
  this.logBuffer = new LogBuffer({ maxLines: options.maxLogBufferLines ?? 1e4 });
287
338
  }
@@ -453,6 +504,36 @@ var OrchestratorClient = class OrchestratorClient {
453
504
  });
454
505
  }
455
506
  /**
507
+ * Build this agent's fleet mini-bundle and stream it back to the orchestrator
508
+ * as ordered fleet.bundle.chunk frames (the WS frame cap forbids one frame).
509
+ * On failure, sends a single fleet.bundle.error. Public for unit testing.
510
+ */
511
+ async streamFleetBundle(req) {
512
+ try {
513
+ const inputs = await this.getFleetBundleInputs?.() ?? { config: {} };
514
+ const buf = await buildAgentMiniBundle({
515
+ agentId: this.agentId,
516
+ logDir: inputs.logDir,
517
+ logWindowHours: req.logWindowHours,
518
+ config: inputs.config,
519
+ metricsText: inputs.metricsText
520
+ });
521
+ for (const f of chunkBuffer(buf)) this.sendDirect({
522
+ type: "fleet.bundle.chunk",
523
+ requestId: req.requestId,
524
+ seq: f.seq,
525
+ isLast: f.isLast,
526
+ dataB64: f.dataB64
527
+ });
528
+ } catch (err) {
529
+ this.sendDirect({
530
+ type: "fleet.bundle.error",
531
+ requestId: req.requestId,
532
+ message: toErrorMessage(err)
533
+ });
534
+ }
535
+ }
536
+ /**
456
537
  * Send a typed API request to the orchestrator and await the response.
457
538
  *
458
539
  * This is the transport layer for the agent private API. The SDK's typed
@@ -547,6 +628,37 @@ var OrchestratorClient = class OrchestratorClient {
547
628
  });
548
629
  }
549
630
  /**
631
+ * Relay a step-level approval request to the orchestrator. Sends a
632
+ * `step.approval-request` WS message and resolves with the orchestrator's
633
+ * `step.approval-resolved` mapped onto the IPC response shape. No client-side
634
+ * timeout — the orchestrator owns the approval expiry and replies with an
635
+ * `expired` outcome when it lapses. Rejects only on disconnect (the relay
636
+ * caller treats a rejection as a fail-closed reject).
637
+ */
638
+ async sendStepApproval(runId, jobId, request) {
639
+ const messageId = randomUUID();
640
+ return new Promise((resolve, reject) => {
641
+ this.pendingStepApprovals.set(messageId, {
642
+ resolve: (response) => resolve({
643
+ ...response,
644
+ requestId: request.requestId
645
+ }),
646
+ reject
647
+ });
648
+ this.sendDirect({
649
+ type: "step.approval-request",
650
+ messageId,
651
+ runId,
652
+ jobId,
653
+ stepIndex: request.stepIndex,
654
+ stepName: request.stepName,
655
+ clauses: request.clauses,
656
+ reason: request.reason,
657
+ ...request.timeoutSeconds !== void 0 && { timeoutSeconds: request.timeoutSeconds }
658
+ });
659
+ });
660
+ }
661
+ /**
550
662
  * Send a job.context message to the orchestrator.
551
663
  *
552
664
  * Conveys execution environment details (runtime, sandbox type, env vars)
@@ -684,6 +796,8 @@ var OrchestratorClient = class OrchestratorClient {
684
796
  this.pendingUserCacheRequests.clear();
685
797
  for (const [_id, pending] of this.pendingConcurrencyRequests) pending.reject(/* @__PURE__ */ new Error("WebSocket disconnected"));
686
798
  this.pendingConcurrencyRequests.clear();
799
+ for (const [_id, pending] of this.pendingStepApprovals) pending.reject(/* @__PURE__ */ new Error("WebSocket disconnected"));
800
+ this.pendingStepApprovals.clear();
687
801
  if (!this.intentionalDisconnect) this.scheduleReconnect();
688
802
  });
689
803
  this.ws.on("error", (err) => {
@@ -814,6 +928,33 @@ var OrchestratorClient = class OrchestratorClient {
814
928
  }
815
929
  break;
816
930
  }
931
+ case "step.approval-resolved": {
932
+ logger$11.info("Step approval resolved", {
933
+ requestId: msg.requestId,
934
+ runId: msg.runId,
935
+ jobId: msg.jobId,
936
+ stepIndex: msg.stepIndex,
937
+ outcome: msg.outcome
938
+ });
939
+ const pending = this.pendingStepApprovals.get(msg.requestId);
940
+ if (pending) {
941
+ this.pendingStepApprovals.delete(msg.requestId);
942
+ pending.resolve({
943
+ type: "approval.resolved",
944
+ requestId: msg.requestId,
945
+ outcome: msg.outcome,
946
+ ...msg.reason !== void 0 && { reason: msg.reason }
947
+ });
948
+ }
949
+ break;
950
+ }
951
+ case "fleet.logs.request":
952
+ logger$11.info("Fleet log collection requested", {
953
+ requestId: msg.requestId,
954
+ logWindowHours: msg.logWindowHours
955
+ });
956
+ this.streamFleetBundle(msg);
957
+ break;
817
958
  }
818
959
  return;
819
960
  }
@@ -1061,14 +1202,14 @@ var init_console_capture = __esmMin((() => {
1061
1202
  init_console_capture();
1062
1203
  function safe(name, fallback = "unknown") {
1063
1204
  switch (name) {
1064
- case "version": return "0.1.15";
1065
- case "buildCommit": return "831f6a763";
1066
- case "sdkVersion": return "0.1.15";
1067
- case "sdkBundleHash": return "cee24b67bbb4a1d2a770e4063dd37a9af33409faeb5ce32f936703b4431316dd";
1068
- case "sharedVersion": return "0.1.15";
1069
- case "sharedBundleHash": return "e82a8b68a7d72698674352158c5de4c0d10824c61bfa7166cc8f846044b8b5e4";
1070
- case "engineVersion": return "0.1.15";
1071
- case "engineBundleHash": return "032d6b28b3d32bcbea80ba80a52934b017ee2c86a97fcaf8fe8db0d86d72a7f7";
1205
+ case "version": return "0.1.16";
1206
+ case "buildCommit": return "7d97bb32c";
1207
+ case "sdkVersion": return "0.1.16";
1208
+ case "sdkBundleHash": return "cd4b8e0d91efe578c96e7c746f89b44a3df8fcaebaec2185399168ef8eb4ebca";
1209
+ case "sharedVersion": return "0.1.16";
1210
+ case "sharedBundleHash": return "c58b1596e92c8423ef83958e86894cb150315d8b91578a0080f1c645000e93e8";
1211
+ case "engineVersion": return "0.1.16";
1212
+ case "engineBundleHash": return "a611c0017d08faa9c5aa4fd97c3dd6259f53f3cca248bd2b369ad5d30c394847";
1072
1213
  default: return fallback;
1073
1214
  }
1074
1215
  }
@@ -1752,8 +1893,8 @@ async function extractStepsFromDynamicJob(workflow, dynamicIndex, jobName, event
1752
1893
  }
1753
1894
  var AGENT_SDK_VERSION, AGENT_SDK_BUNDLE_HASH, hookRegistered;
1754
1895
  var init_workflow_loader = __esmMin((() => {
1755
- AGENT_SDK_VERSION = "0.1.15";
1756
- AGENT_SDK_BUNDLE_HASH = "cee24b67bbb4a1d2a770e4063dd37a9af33409faeb5ce32f936703b4431316dd";
1896
+ AGENT_SDK_VERSION = "0.1.16";
1897
+ AGENT_SDK_BUNDLE_HASH = "cd4b8e0d91efe578c96e7c746f89b44a3df8fcaebaec2185399168ef8eb4ebca";
1757
1898
  hookRegistered = false;
1758
1899
  }));
1759
1900
  //#endregion
@@ -3821,6 +3962,24 @@ function relayCacheRequest$1(msg, ctx) {
3821
3962
  error: toErrorMessage(err)
3822
3963
  }));
3823
3964
  }
3965
+ /** Relay `approval.request` and pipe the orchestrator's resolution (or a
3966
+ * fail-closed reject when the callback isn't wired or the relay throws) back
3967
+ * into the sandbox runner. */
3968
+ function relayApprovalRequest$1(msg, ctx) {
3969
+ if (!ctx.execOptions.onApprovalRequest) {
3970
+ safeSendToChild(ctx.child, {
3971
+ type: "approval.resolved",
3972
+ requestId: msg.requestId,
3973
+ error: "Approvals not available in this agent configuration"
3974
+ });
3975
+ return;
3976
+ }
3977
+ ctx.execOptions.onApprovalRequest(msg).then((response) => safeSendToChild(ctx.child, response), (err) => safeSendToChild(ctx.child, {
3978
+ type: "approval.resolved",
3979
+ requestId: msg.requestId,
3980
+ error: toErrorMessage(err)
3981
+ }));
3982
+ }
3824
3983
  /** Resolve the result promise for `job.complete` IPC messages. Encrypts
3825
3984
  * secret outputs (when a runPublicKey is available) and overrides status to
3826
3985
  * `cancelled` if a cancel was already in flight. */
@@ -3893,6 +4052,9 @@ function relayChildIpcMessage(msg, dispatch, ctx) {
3893
4052
  case "cache.request":
3894
4053
  relayCacheRequest$1(msg, ctx);
3895
4054
  return;
4055
+ case "approval.request":
4056
+ relayApprovalRequest$1(msg, ctx);
4057
+ return;
3896
4058
  case "job.complete":
3897
4059
  handleJobComplete(msg, dispatch, ctx);
3898
4060
  return;
@@ -4314,6 +4476,32 @@ function relayCacheRequest(stream, options, cacheMsg) {
4314
4476
  }));
4315
4477
  }
4316
4478
  /**
4479
+ * Relay approval.request from the container runner to the orchestrator via
4480
+ * options.onApprovalRequest, then write the resolution back through `stream`.
4481
+ * If the agent doesn't expose an approval relay (or it throws), write a
4482
+ * fail-closed reject so the runner doesn't hang.
4483
+ */
4484
+ function relayApprovalRequest(stream, options, approvalMsg) {
4485
+ const writeResponse = (response) => {
4486
+ try {
4487
+ stream.write(JSON.stringify(response) + "\n");
4488
+ } catch {}
4489
+ };
4490
+ if (!options.onApprovalRequest) {
4491
+ writeResponse({
4492
+ type: "approval.resolved",
4493
+ requestId: approvalMsg.requestId,
4494
+ error: "Approvals not available in this agent configuration"
4495
+ });
4496
+ return;
4497
+ }
4498
+ options.onApprovalRequest(approvalMsg).then((response) => writeResponse(response), (err) => writeResponse({
4499
+ type: "approval.resolved",
4500
+ requestId: approvalMsg.requestId,
4501
+ error: toErrorMessage(err)
4502
+ }));
4503
+ }
4504
+ /**
4317
4505
  * Apply a job.complete message to the mutable runner state: capture status,
4318
4506
  * merge any bulk-reported step results, propagate plain outputs, and encrypt
4319
4507
  * secret outputs if a run public key is available.
@@ -4570,6 +4758,9 @@ var init_container_sandbox = __esmMin((() => {
4570
4758
  case "cache.request":
4571
4759
  relayCacheRequest(stream, options, msg);
4572
4760
  return false;
4761
+ case "approval.request":
4762
+ relayApprovalRequest(stream, options, msg);
4763
+ return false;
4573
4764
  case "job.complete":
4574
4765
  applyJobComplete(msg, stepResults, state, options);
4575
4766
  return true;
@@ -4740,6 +4931,7 @@ var init_job_runner = __esmMin((() => {
4740
4931
  _sendConcurrencyReport;
4741
4932
  _sendApiRequest;
4742
4933
  _requestUserCache;
4934
+ _sendStepApproval;
4743
4935
  /** Tracks running jobs for concurrency and cancellation */
4744
4936
  activeJobs = /* @__PURE__ */ new Map();
4745
4937
  /** Active sandbox for the current job (used for abort). */
@@ -4758,6 +4950,7 @@ var init_job_runner = __esmMin((() => {
4758
4950
  this._sendConcurrencyReport = deps.sendConcurrencyReport;
4759
4951
  this._sendApiRequest = deps.sendApiRequest;
4760
4952
  this._requestUserCache = deps.requestUserCache;
4953
+ this._sendStepApproval = deps.sendStepApproval;
4761
4954
  }
4762
4955
  /**
4763
4956
  * Execute a dispatched job through its full lifecycle.
@@ -4997,6 +5190,7 @@ var init_job_runner = __esmMin((() => {
4997
5190
  },
4998
5191
  onApiRequest: this._sendApiRequest ? async (method, params) => this._sendApiRequest(method, params) : void 0,
4999
5192
  onCacheRequest: this._requestUserCache ? async (request) => this._requestUserCache(jobId, request) : void 0,
5193
+ onApprovalRequest: this._sendStepApproval ? async (request) => this._sendStepApproval(dispatch.runId, dispatch.jobId, request) : void 0,
5000
5194
  onSecretMount: (event) => {
5001
5195
  this.emitRunEvent(runId, "step.secret_mount", {
5002
5196
  jobId,
@@ -5669,14 +5863,14 @@ var init_job_runner = __esmMin((() => {
5669
5863
  */
5670
5864
  init_console_capture();
5671
5865
  init_npm_resolver();
5672
- const AGENT_VERSION = "0.1.15";
5673
- const BUILD_COMMIT = "831f6a763";
5674
- const SDK_VERSION = "0.1.15";
5675
- const SDK_BUNDLE_HASH = "cee24b67bbb4a1d2a770e4063dd37a9af33409faeb5ce32f936703b4431316dd";
5676
- const SHARED_VERSION = "0.1.15";
5677
- const SHARED_BUNDLE_HASH = "e82a8b68a7d72698674352158c5de4c0d10824c61bfa7166cc8f846044b8b5e4";
5678
- const ENGINE_VERSION = "0.1.15";
5679
- const ENGINE_BUNDLE_HASH = "032d6b28b3d32bcbea80ba80a52934b017ee2c86a97fcaf8fe8db0d86d72a7f7";
5866
+ const AGENT_VERSION = "0.1.16";
5867
+ const BUILD_COMMIT = "7d97bb32c";
5868
+ const SDK_VERSION = "0.1.16";
5869
+ const SDK_BUNDLE_HASH = "cd4b8e0d91efe578c96e7c746f89b44a3df8fcaebaec2185399168ef8eb4ebca";
5870
+ const SHARED_VERSION = "0.1.16";
5871
+ const SHARED_BUNDLE_HASH = "c58b1596e92c8423ef83958e86894cb150315d8b91578a0080f1c645000e93e8";
5872
+ const ENGINE_VERSION = "0.1.16";
5873
+ const ENGINE_BUNDLE_HASH = "a611c0017d08faa9c5aa4fd97c3dd6259f53f3cca248bd2b369ad5d30c394847";
5680
5874
  initTelemetry({
5681
5875
  serviceName: "kici-agent",
5682
5876
  otlpEndpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT
@@ -5685,6 +5879,26 @@ const { connectionStatus, jobsActive, jobsTotal } = await Promise.resolve().then
5685
5879
  const { JobRunner } = await Promise.resolve().then(() => (init_job_runner(), job_runner_exports));
5686
5880
  setServiceName("agent");
5687
5881
  const logger$1 = createLogger({ prefix: "agent" });
5882
+ /**
5883
+ * Serialize the agent's current Prometheus metrics to text. The OTel
5884
+ * PrometheusExporter exposes no direct serialize method, so the metrics are
5885
+ * piped through its request handler with a mock ServerResponse. Returns an
5886
+ * empty string when no exporter is configured. Shared by the /metrics health
5887
+ * route and the fleet mini-bundle.
5888
+ */
5889
+ async function serializeAgentMetrics() {
5890
+ const exporter = getPrometheusExporter();
5891
+ if (!exporter) return "";
5892
+ return new Promise((resolve) => {
5893
+ exporter.getMetricsRequestHandler({}, {
5894
+ statusCode: 200,
5895
+ setHeader: () => {},
5896
+ end: (data) => {
5897
+ resolve(typeof data === "string" ? data : data ? data.toString() : "");
5898
+ }
5899
+ });
5900
+ });
5901
+ }
5688
5902
  installConsoleCapture();
5689
5903
  await guardStartup(logger$1, async () => {
5690
5904
  const config = loadConfig();
@@ -5736,7 +5950,8 @@ await guardStartup(logger$1, async () => {
5736
5950
  sendRunEvent: (runId, eventType, opts) => client.sendRunEvent(runId, eventType, opts),
5737
5951
  sendConcurrencyReport: (runId, jobId, group) => client.sendConcurrencyReport(runId, jobId, group),
5738
5952
  sendApiRequest: (method, params) => client.sendApiRequest(method, params ?? {}),
5739
- requestUserCache: (jobId, request) => client.requestUserCache(jobId, request)
5953
+ requestUserCache: (jobId, request) => client.requestUserCache(jobId, request),
5954
+ sendStepApproval: (runId, jobId, request) => client.sendStepApproval(runId, jobId, request)
5740
5955
  });
5741
5956
  /** Build and send an agent.status message with dynamic OS metadata. */
5742
5957
  function sendAgentStatus() {
@@ -5752,6 +5967,11 @@ await guardStartup(logger$1, async () => {
5752
5967
  }
5753
5968
  client = new OrchestratorClient({
5754
5969
  ...agentClientConnectionOptions(config),
5970
+ getFleetBundleInputs: async () => ({
5971
+ config,
5972
+ logDir: process.env.KICI_LOG_DIR,
5973
+ metricsText: await serializeAgentMetrics()
5974
+ }),
5755
5975
  onJobDispatch: (dispatch) => {
5756
5976
  const reqId = dispatch.requestId ?? randomUUID();
5757
5977
  requestContext.run({
@@ -5899,25 +6119,10 @@ await guardStartup(logger$1, async () => {
5899
6119
  metricsReporter.start();
5900
6120
  const app = new Hono();
5901
6121
  const healthRoutes = createHealthRoutes$1({
5902
- getMetrics: async () => {
5903
- const exporter = getPrometheusExporter();
5904
- if (!exporter) return {
5905
- contentType: "text/plain",
5906
- body: ""
5907
- };
5908
- return new Promise((resolve) => {
5909
- exporter.getMetricsRequestHandler({}, {
5910
- statusCode: 200,
5911
- setHeader: () => {},
5912
- end: (data) => {
5913
- resolve({
5914
- contentType: "text/plain",
5915
- body: typeof data === "string" ? data : data ? data.toString() : ""
5916
- });
5917
- }
5918
- });
5919
- });
5920
- },
6122
+ getMetrics: async () => ({
6123
+ contentType: "text/plain",
6124
+ body: await serializeAgentMetrics()
6125
+ }),
5921
6126
  getStatus: () => ({
5922
6127
  agentId: config.agentId,
5923
6128
  connected: client.state === "registered",