@kici-dev/agent 0.4.0 → 0.5.0

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/index.js CHANGED
@@ -748,7 +748,7 @@ function isAbsoluteRel(rel) {
748
748
  * env vars — the install runs with `--ignore-scripts` whenever a private
749
749
  * registry is configured.
750
750
  */
751
- const logger$2 = createLogger({ prefix: "dep-installer" });
751
+ const logger$3 = createLogger({ prefix: "dep-installer" });
752
752
  const execFileAsync = promisify(execFile);
753
753
  /** Install subprocess timeout (10 min) and stdout/stderr buffer (128 MiB). */
754
754
  const INSTALL_TIMEOUT_MS = 6e5;
@@ -793,7 +793,7 @@ async function installDeps(kiciDir, opts = {}) {
793
793
  const repoRoot = opts.repoRoot ?? dirname(kiciDir);
794
794
  const packageManager = await detectKiciPackageManager(repoRoot, kiciDir);
795
795
  const yarnFlavor = packageManager === PackageManager.Yarn ? await detectKiciYarnFlavor(repoRoot, kiciDir) : YarnFlavor.Classic;
796
- logger$2.info("Installing deps inline", {
796
+ logger$3.info("Installing deps inline", {
797
797
  packageManager,
798
798
  yarnFlavor,
799
799
  dir: kiciDir
@@ -851,7 +851,7 @@ async function installDeps(kiciDir, opts = {}) {
851
851
  if (packageManager === PackageManager.Yarn) await buildYarnWorkspaceClosure(repoRoot, kiciDir, yarnFlavor);
852
852
  const durationMs = Date.now() - startTime;
853
853
  process.stderr.write(`[dep-installer:trace] install complete: ${durationMs}ms\n`);
854
- logger$2.info("Deps installed inline", {
854
+ logger$3.info("Deps installed inline", {
855
855
  packageManager,
856
856
  durationMs
857
857
  });
@@ -1143,10 +1143,25 @@ var init_dep_restore = __esmMin((() => {
1143
1143
  * dep-restore.ts and workflow-loader.ts.
1144
1144
  */
1145
1145
  var download_exports = /* @__PURE__ */ __exportAll({
1146
+ UPLOAD_MAX_RETRIES: () => 2,
1146
1147
  downloadUrl: () => downloadUrl,
1147
1148
  uploadToPresignedUrl: () => uploadToPresignedUrl
1148
1149
  });
1149
1150
  /**
1151
+ * Whether a failed upload attempt is worth repeating.
1152
+ *
1153
+ * A transport failure (connection refused, reset, DNS) never reached a
1154
+ * responder, and 5xx / 429 are the object-storage overload signals AWS
1155
+ * documents as retry-with-backoff (S3 answers `SlowDown` with 503). Every other
1156
+ * status is a decision the server will repeat: a 403 from an expired or
1157
+ * malformed signature, a 400 from a malformed request. Retrying those burns the
1158
+ * ceiling without a chance of success and delays the real error.
1159
+ */
1160
+ function isRetryableUploadFailure(err) {
1161
+ if (!(err instanceof PresignedUploadHttpError)) return true;
1162
+ return err.statusCode >= 500 || err.statusCode === 429;
1163
+ }
1164
+ /**
1150
1165
  * Download content from an HTTP/HTTPS URL.
1151
1166
  *
1152
1167
  * Includes a 5-minute timeout to prevent the agent from hanging indefinitely
@@ -1170,21 +1185,10 @@ function downloadUrl(url) {
1170
1185
  }).on("error", reject);
1171
1186
  });
1172
1187
  }
1173
- /**
1174
- * Upload a buffer to a pre-signed S3 URL via HTTP PUT.
1175
- *
1176
- * Used for direct-to-S3 uploads of bundles and dep tarballs. Localhost /
1177
- * 127.0.0.1 URLs are rewritten via `resolveOrchestratorUrl` so the
1178
- * filesystem cache backend's signed URLs work from container agents that
1179
- * can't reach the orchestrator's host loopback directly.
1180
- *
1181
- * @param url - The pre-signed URL to upload to
1182
- * @param data - The buffer to upload
1183
- */
1184
- function uploadToPresignedUrl(url, data) {
1188
+ /** One PUT of the whole buffer. Rejects with {@link PresignedUploadHttpError} on a non-2xx. */
1189
+ function putOnce(resolvedUrl, data, timeoutMs) {
1185
1190
  return new Promise((resolve, reject) => {
1186
- const resolved = resolveOrchestratorUrl(url);
1187
- const parsed = new URL(resolved);
1191
+ const parsed = new URL(resolvedUrl);
1188
1192
  const req = (parsed.protocol === "https:" ? https : http).request({
1189
1193
  hostname: parsed.hostname,
1190
1194
  port: parsed.port,
@@ -1193,7 +1197,7 @@ function uploadToPresignedUrl(url, data) {
1193
1197
  headers: { "Content-Length": data.length }
1194
1198
  }, (res) => {
1195
1199
  if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
1196
- reject(/* @__PURE__ */ new Error(`HTTP ${res.statusCode} uploading to pre-signed URL`));
1200
+ reject(new PresignedUploadHttpError(res.statusCode));
1197
1201
  res.resume();
1198
1202
  return;
1199
1203
  }
@@ -1201,14 +1205,80 @@ function uploadToPresignedUrl(url, data) {
1201
1205
  res.on("end", () => resolve());
1202
1206
  res.on("error", reject);
1203
1207
  });
1208
+ req.setTimeout(timeoutMs, () => {
1209
+ req.destroy(/* @__PURE__ */ new Error(`Pre-signed upload timed out after ${timeoutMs}ms`));
1210
+ });
1204
1211
  req.on("error", reject);
1205
1212
  req.end(data);
1206
1213
  });
1207
1214
  }
1208
- var DOWNLOAD_TIMEOUT_MS$1;
1215
+ /**
1216
+ * Upload a buffer to a pre-signed S3 URL via HTTP PUT, retrying a transient
1217
+ * failure.
1218
+ *
1219
+ * Used for direct-to-S3 uploads of bundles and dep tarballs. Localhost /
1220
+ * 127.0.0.1 URLs are rewritten via `resolveOrchestratorUrl` so the
1221
+ * filesystem cache backend's signed URLs work from container agents that
1222
+ * can't reach the orchestrator's host loopback directly.
1223
+ *
1224
+ * **Why retrying is safe here.** A pre-signed PUT writes one whole object at a
1225
+ * single key: there is no multipart session, no append, and no
1226
+ * server-generated identity, so a repeat attempt writes the same bytes to the
1227
+ * same key and the last write wins. S3 also only makes an object visible once
1228
+ * the body has been received in full, so an attempt that died mid-body left
1229
+ * nothing behind. A retry therefore cannot double-write or produce a torn
1230
+ * object — which is why every AWS SDK retries PUTs by default.
1231
+ *
1232
+ * Only a failure that can plausibly differ next time is repeated — see
1233
+ * {@link isRetryableUploadFailure}.
1234
+ *
1235
+ * @param url - The pre-signed URL to upload to
1236
+ * @param data - The buffer to upload
1237
+ * @param opts.baseDelayMs - Backoff before the first retry (doubles thereafter)
1238
+ * @param opts.timeoutMs - Per-attempt socket-inactivity timeout (see
1239
+ * {@link UPLOAD_TIMEOUT_MS}); an override exists so a test can drive the
1240
+ * stall path without waiting out the production budget.
1241
+ */
1242
+ async function uploadToPresignedUrl(url, data, opts) {
1243
+ const resolved = resolveOrchestratorUrl(url);
1244
+ const baseDelayMs = opts?.baseDelayMs ?? UPLOAD_RETRY_BASE_DELAY_MS;
1245
+ const timeoutMs = opts?.timeoutMs ?? UPLOAD_TIMEOUT_MS;
1246
+ let lastError;
1247
+ for (let attempt = 0; attempt <= 2; attempt++) {
1248
+ if (attempt > 0) {
1249
+ const delayMs = baseDelayMs * 2 ** (attempt - 1);
1250
+ logger$1.warn("Retrying pre-signed upload", {
1251
+ attempt,
1252
+ delayMs,
1253
+ error: lastError?.message
1254
+ });
1255
+ await new Promise((r) => setTimeout(r, delayMs));
1256
+ }
1257
+ try {
1258
+ await putOnce(resolved, data, timeoutMs);
1259
+ return;
1260
+ } catch (err) {
1261
+ lastError = err instanceof Error ? err : new Error(String(err));
1262
+ if (!isRetryableUploadFailure(lastError)) throw lastError;
1263
+ }
1264
+ }
1265
+ throw new Error(`Pre-signed upload failed after 3 attempts: ${lastError?.message}`);
1266
+ }
1267
+ var logger$1, DOWNLOAD_TIMEOUT_MS$1, UPLOAD_TIMEOUT_MS, UPLOAD_RETRY_BASE_DELAY_MS, PresignedUploadHttpError;
1209
1268
  var init_download = __esmMin((() => {
1210
1269
  init_dep_restore();
1270
+ logger$1 = createLogger({ prefix: "agent:download" });
1211
1271
  DOWNLOAD_TIMEOUT_MS$1 = 300 * 1e3;
1272
+ UPLOAD_TIMEOUT_MS = 300 * 1e3;
1273
+ UPLOAD_RETRY_BASE_DELAY_MS = 500;
1274
+ PresignedUploadHttpError = class extends Error {
1275
+ statusCode;
1276
+ constructor(statusCode) {
1277
+ super(`HTTP ${statusCode} uploading to pre-signed URL`);
1278
+ this.statusCode = statusCode;
1279
+ this.name = "PresignedUploadHttpError";
1280
+ }
1281
+ };
1212
1282
  }));
1213
1283
  //#endregion
1214
1284
  //#region src/execution/cache/cache-engine.ts
@@ -2,40 +2,40 @@
2
2
  * Total completed jobs.
3
3
  * Labels:
4
4
  * - status: success | failed | cancelled
5
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
5
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
6
6
  */
7
7
  export declare const jobsTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
8
8
  /**
9
9
  * Currently running jobs.
10
10
  * Labels:
11
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
11
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
12
12
  */
13
13
  export declare const jobsActive: import("@opentelemetry/api").UpDownCounter<import("@opentelemetry/api").Attributes>;
14
14
  /**
15
15
  * Total completed steps.
16
16
  * Labels:
17
17
  * - status: success | failed | skipped
18
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
18
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
19
19
  */
20
20
  export declare const stepsTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
21
21
  /**
22
22
  * Step execution duration in seconds.
23
23
  * Advisory boundaries cover sub-second steps through 30-minute long-running steps.
24
24
  * Labels:
25
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
25
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
26
26
  */
27
27
  export declare const stepDurationSeconds: import("@opentelemetry/api").Histogram<import("@opentelemetry/api").Attributes>;
28
28
  /**
29
29
  * Git clone duration in seconds.
30
30
  * Advisory boundaries cover fast shallow clones through large repo clones.
31
31
  * Labels:
32
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
32
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
33
33
  */
34
34
  export declare const cloneDurationSeconds: import("@opentelemetry/api").Histogram<import("@opentelemetry/api").Attributes>;
35
35
  /**
36
36
  * Total log bytes streamed back to orchestrator.
37
37
  * Labels:
38
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
38
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
39
39
  */
40
40
  export declare const logBytesTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
41
41
  /**
@@ -51,7 +51,7 @@ export declare const logBytesTotal: import("@opentelemetry/api").Counter<import(
51
51
  * Labels:
52
52
  * - mode: `pause` (producer paused, no data loss) | `drop` (lines
53
53
  * discarded with a `[N lines dropped due to backpressure]` marker)
54
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
54
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
55
55
  */
56
56
  export declare const logBackpressureEventsTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
57
57
  /**
@@ -64,7 +64,7 @@ export declare const logBackpressureEventsTotal: import("@opentelemetry/api").Co
64
64
  * producer.
65
65
  *
66
66
  * Labels:
67
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
67
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
68
68
  */
69
69
  export declare const logLinesDroppedTotal: import("@opentelemetry/api").Counter<import("@opentelemetry/api").Attributes>;
70
70
  /**
@@ -81,7 +81,7 @@ export declare const logLinesDroppedTotal: import("@opentelemetry/api").Counter<
81
81
  *
82
82
  * Labels:
83
83
  * - mode: `pause` | `drop`
84
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
84
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
85
85
  */
86
86
  export declare const logBackpressureActive: import("@opentelemetry/api").UpDownCounter<import("@opentelemetry/api").Attributes>;
87
87
  /**
@@ -89,7 +89,7 @@ export declare const logBackpressureActive: import("@opentelemetry/api").UpDownC
89
89
  * Use add(1) for connected, add(-1) for disconnected.
90
90
  *
91
91
  * Labels:
92
- * - scaler: injected by orch-side AgentMetricsAggregator (Phase 5b); `stateful` for static agents, backend name (`container` / `firecracker` / `bare-metal`) for scaler-managed
92
+ * - scaler: injected by orch-side AgentMetricsAggregator; `stateful` for static agents, backend type (`container` / `firecracker` / `bare-metal`) for scaler-managed
93
93
  */
94
94
  export declare const connectionStatus: import("@opentelemetry/api").UpDownCounter<import("@opentelemetry/api").Attributes>;
95
95
  //# sourceMappingURL=prometheus.d.ts.map