@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
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
1065
|
-
case "buildCommit": return "
|
|
1066
|
-
case "sdkVersion": return "0.1.
|
|
1067
|
-
case "sdkBundleHash": return "
|
|
1068
|
-
case "sharedVersion": return "0.1.
|
|
1069
|
-
case "sharedBundleHash": return "
|
|
1070
|
-
case "engineVersion": return "0.1.
|
|
1071
|
-
case "engineBundleHash": return "
|
|
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.
|
|
1756
|
-
AGENT_SDK_BUNDLE_HASH = "
|
|
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.
|
|
5673
|
-
const BUILD_COMMIT = "
|
|
5674
|
-
const SDK_VERSION = "0.1.
|
|
5675
|
-
const SDK_BUNDLE_HASH = "
|
|
5676
|
-
const SHARED_VERSION = "0.1.
|
|
5677
|
-
const SHARED_BUNDLE_HASH = "
|
|
5678
|
-
const ENGINE_VERSION = "0.1.
|
|
5679
|
-
const ENGINE_BUNDLE_HASH = "
|
|
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
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
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",
|