aglio-worker 0.25.0 → 0.28.21

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.
Files changed (2) hide show
  1. package/agent.js +303 -43
  2. package/package.json +1 -1
package/agent.js CHANGED
@@ -27344,6 +27344,119 @@ function validateRoutingLabels(input2) {
27344
27344
  return labels;
27345
27345
  }
27346
27346
 
27347
+ // ../../packages/shared/src/worker-capacity.ts
27348
+ var WORKER_DEFAULT_CAPACITY = 1;
27349
+ var WORKER_MAX_CAPACITY = 8;
27350
+ var WORKER_MIN_CAPACITY = 1;
27351
+ var WORKER_CAPACITY_OPTIONS = Array.from(
27352
+ { length: WORKER_MAX_CAPACITY },
27353
+ (_, index) => index + 1
27354
+ );
27355
+ function workerCapacity(input2, fallback = WORKER_DEFAULT_CAPACITY) {
27356
+ const value = typeof input2 === "number" ? input2 : Number(input2);
27357
+ if (!Number.isFinite(value)) return fallback;
27358
+ return Math.min(WORKER_MAX_CAPACITY, Math.max(WORKER_MIN_CAPACITY, Math.trunc(value)));
27359
+ }
27360
+ function workerCapacityForMode(input2, mode) {
27361
+ return mode === "host" ? WORKER_MIN_CAPACITY : workerCapacity(input2);
27362
+ }
27363
+
27364
+ // ../../packages/shared/src/pool.ts
27365
+ var JOB_TARGETS = ["auto", "byow", "cloud"];
27366
+ var POOL_SIZES = ["small", "medium", "large", "xlarge"];
27367
+ var DEFAULT_POOL_SIZE = "small";
27368
+ var POOL_IDLE_TIMEOUT_MS = 5 * 6e4;
27369
+ var POOL_STALE_PROVISIONING_MS = 3 * 6e4;
27370
+ var POOL_SIZE_CONFIG = {
27371
+ small: {
27372
+ capacity: 2,
27373
+ cpuLimit: 2,
27374
+ memoryLimit: "2560m",
27375
+ instanceType: "c7i.xlarge",
27376
+ vcpu: 4,
27377
+ hourlyUsd: 0.1786,
27378
+ maxInstances: 4
27379
+ },
27380
+ medium: {
27381
+ capacity: 4,
27382
+ cpuLimit: 2,
27383
+ memoryLimit: "2560m",
27384
+ instanceType: "c7i.2xlarge",
27385
+ vcpu: 8,
27386
+ hourlyUsd: 0.3572,
27387
+ maxInstances: 2
27388
+ },
27389
+ large: {
27390
+ capacity: 8,
27391
+ cpuLimit: 2,
27392
+ memoryLimit: "2560m",
27393
+ instanceType: "c7i.4xlarge",
27394
+ vcpu: 16,
27395
+ hourlyUsd: 0.7144,
27396
+ // Documented only: the controller provisions small and medium machines.
27397
+ maxInstances: 0
27398
+ },
27399
+ xlarge: {
27400
+ capacity: 16,
27401
+ cpuLimit: 2,
27402
+ memoryLimit: "2560m",
27403
+ instanceType: "c7i.8xlarge",
27404
+ vcpu: 32,
27405
+ hourlyUsd: 1.4288,
27406
+ maxInstances: 0
27407
+ }
27408
+ };
27409
+ var POOL_REGION_LABEL = "region:us-east";
27410
+ function isPoolSize(value) {
27411
+ return typeof value === "string" && POOL_SIZES.includes(value);
27412
+ }
27413
+ function normalizePoolSize(value, fallback = DEFAULT_POOL_SIZE) {
27414
+ return isPoolSize(value) ? value : fallback;
27415
+ }
27416
+ function poolWorkerLabels(size) {
27417
+ return ["os:linux", "mode:docker", `size:${size}`, POOL_REGION_LABEL];
27418
+ }
27419
+ var POOL_HOST_RESERVE_BYTES = 1024 ** 3;
27420
+ var MEMORY_UNITS = {
27421
+ b: 1,
27422
+ k: 1024,
27423
+ m: 1024 ** 2,
27424
+ g: 1024 ** 3,
27425
+ t: 1024 ** 4
27426
+ };
27427
+ function parsePoolMemoryLimit(value) {
27428
+ const match = /^(\d+(?:\.\d+)?)\s*([bkmgt])?b?$/i.exec(value.trim());
27429
+ if (!match) return null;
27430
+ const unit = (match[2] ?? "").toLowerCase();
27431
+ const multiplier = unit === "" ? 1 : MEMORY_UNITS[unit];
27432
+ const amount = Number(match[1]);
27433
+ if (!multiplier || !Number.isFinite(amount) || amount <= 0) return null;
27434
+ return Math.round(amount * multiplier);
27435
+ }
27436
+ function poolBootCapCheck(input2) {
27437
+ const config2 = POOL_SIZE_CONFIG[input2.size];
27438
+ const capacity = workerCapacity(input2.capacity);
27439
+ const requiredCpu = capacity * config2.cpuLimit;
27440
+ const requiredMemoryBytes = capacity * (parsePoolMemoryLimit(config2.memoryLimit) ?? 0) + POOL_HOST_RESERVE_BYTES;
27441
+ const hostMemoryBytes = input2.hostMemoryBytes ?? null;
27442
+ const base = { requiredCpu, hostCpu: input2.hostCpu, requiredMemoryBytes, hostMemoryBytes };
27443
+ if (requiredCpu > input2.hostCpu) {
27444
+ return {
27445
+ ...base,
27446
+ ok: false,
27447
+ reason: `capacity ${capacity} x ${config2.cpuLimit} vCPU exceeds the host ${input2.hostCpu} vCPU`
27448
+ };
27449
+ }
27450
+ if (hostMemoryBytes != null && requiredMemoryBytes > hostMemoryBytes) {
27451
+ return {
27452
+ ...base,
27453
+ ok: false,
27454
+ reason: `capacity ${capacity} x ${config2.memoryLimit} plus the 1 GiB reserve (${Math.round(requiredMemoryBytes / 1024 ** 2)} MiB) exceeds the host ${Math.round(hostMemoryBytes / 1024 ** 2)} MiB`
27455
+ };
27456
+ }
27457
+ return { ...base, ok: true };
27458
+ }
27459
+
27347
27460
  // ../../packages/shared/src/schemas.ts
27348
27461
  var cardColumnSchema = external_exports.enum(CARD_COLUMNS);
27349
27462
  var runStatusSchema = external_exports.enum(RUN_STATUSES_FOR_SCHEMA);
@@ -27429,8 +27542,11 @@ var projectSettingsSchema = external_exports.object({
27429
27542
  /** Planning job labels. Empty means any online worker, so planning never waits on a build machine. */
27430
27543
  planningRequiredWorkerLabels: routingLabelsSchema.default([]),
27431
27544
  /** Undefined inherits the workspace switch. True or false overrides it. */
27432
- postBuildReply: external_exports.boolean().optional()
27545
+ postBuildReply: external_exports.boolean().optional(),
27546
+ /** Default pool size for cloud runs started from this project. Auto picks one at dispatch. */
27547
+ cloudRunnerSize: external_exports.enum(POOL_SIZES).default("small")
27433
27548
  });
27549
+ var jobTargetSchema = external_exports.enum(JOB_TARGETS);
27434
27550
  var repoConfigSchema = external_exports.object({
27435
27551
  id: external_exports.string(),
27436
27552
  name: external_exports.string(),
@@ -27564,23 +27680,6 @@ var MAX_SCAN_MINUTES = 8 * 24 * 60;
27564
27680
  // ../../packages/shared/src/workers.ts
27565
27681
  import { createHash, randomBytes } from "node:crypto";
27566
27682
 
27567
- // ../../packages/shared/src/worker-capacity.ts
27568
- var WORKER_DEFAULT_CAPACITY = 1;
27569
- var WORKER_MAX_CAPACITY = 8;
27570
- var WORKER_MIN_CAPACITY = 1;
27571
- var WORKER_CAPACITY_OPTIONS = Array.from(
27572
- { length: WORKER_MAX_CAPACITY },
27573
- (_, index) => index + 1
27574
- );
27575
- function workerCapacity(input2, fallback = WORKER_DEFAULT_CAPACITY) {
27576
- const value = typeof input2 === "number" ? input2 : Number(input2);
27577
- if (!Number.isFinite(value)) return fallback;
27578
- return Math.min(WORKER_MAX_CAPACITY, Math.max(WORKER_MIN_CAPACITY, Math.trunc(value)));
27579
- }
27580
- function workerCapacityForMode(input2, mode) {
27581
- return mode === "host" ? WORKER_MIN_CAPACITY : workerCapacity(input2);
27582
- }
27583
-
27584
27683
  // ../../packages/shared/src/job-contract.ts
27585
27684
  var WORKER_JOB_LOG_BATCH_BYTES = 64 * 1024;
27586
27685
  var WORKER_JOB_LOG_BATCH_MS = 1500;
@@ -28224,7 +28323,8 @@ var PLAN_CATALOG = {
28224
28323
  seats: 1,
28225
28324
  projects: 2,
28226
28325
  runsPerMonth: 50,
28227
- storageBytes: 500 * MEGABYTE
28326
+ storageBytes: 500 * MEGABYTE,
28327
+ poolSecondsPerMonth: 18e3
28228
28328
  }
28229
28329
  },
28230
28330
  team: {
@@ -28236,7 +28336,8 @@ var PLAN_CATALOG = {
28236
28336
  seats: 10,
28237
28337
  projects: 20,
28238
28338
  runsPerMonth: 2e3,
28239
- storageBytes: 20 * GIGABYTE
28339
+ storageBytes: 20 * GIGABYTE,
28340
+ poolSecondsPerMonth: 18e4
28240
28341
  }
28241
28342
  },
28242
28343
  enterprise: {
@@ -28248,7 +28349,8 @@ var PLAN_CATALOG = {
28248
28349
  seats: null,
28249
28350
  projects: null,
28250
28351
  runsPerMonth: null,
28251
- storageBytes: null
28352
+ storageBytes: null,
28353
+ poolSecondsPerMonth: null
28252
28354
  }
28253
28355
  }
28254
28356
  };
@@ -28277,6 +28379,124 @@ var cardOpenModeSchema = external_exports.enum(CARD_OPEN_MODES);
28277
28379
  var CARD_OPEN_SIZES = ["default", "large", "fullscreen"];
28278
28380
  var cardOpenSizeSchema = external_exports.enum(CARD_OPEN_SIZES);
28279
28381
 
28382
+ // src/pool-boot.ts
28383
+ import { createHash as createHash2, createHmac } from "node:crypto";
28384
+ import { cpus, totalmem } from "node:os";
28385
+ async function containerCredentials(fetchImpl) {
28386
+ const relative5 = process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI?.trim();
28387
+ const full = process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI?.trim();
28388
+ if (!relative5 && !full) return null;
28389
+ const response = await fetchImpl(relative5 ? `http://169.254.170.2${relative5}` : full);
28390
+ if (!response.ok) return null;
28391
+ const json3 = await response.json();
28392
+ if (!json3.AccessKeyId || !json3.SecretAccessKey) return null;
28393
+ return {
28394
+ accessKeyId: json3.AccessKeyId,
28395
+ secretAccessKey: json3.SecretAccessKey,
28396
+ sessionToken: json3.Token ?? null
28397
+ };
28398
+ }
28399
+ async function resolveAwsCredentials(fetchImpl = fetch) {
28400
+ const key = process.env.AWS_ACCESS_KEY_ID?.trim();
28401
+ const secret = process.env.AWS_SECRET_ACCESS_KEY?.trim();
28402
+ if (key && secret) {
28403
+ return {
28404
+ accessKeyId: key,
28405
+ secretAccessKey: secret,
28406
+ sessionToken: process.env.AWS_SESSION_TOKEN?.trim() || null
28407
+ };
28408
+ }
28409
+ return containerCredentials(fetchImpl);
28410
+ }
28411
+ function sha256(value) {
28412
+ return createHash2("sha256").update(value, "utf8").digest("hex");
28413
+ }
28414
+ function hmac(key, value) {
28415
+ return createHmac("sha256", key).update(value, "utf8").digest();
28416
+ }
28417
+ async function ssmCall(action, name, region, credentials, fetchImpl) {
28418
+ const host = `ssm.${region}.amazonaws.com`;
28419
+ const body = JSON.stringify(
28420
+ action === "GetParameter" ? { Name: name, WithDecryption: true } : { Name: name }
28421
+ );
28422
+ const now = /* @__PURE__ */ new Date();
28423
+ const long = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
28424
+ const short = long.slice(0, 8);
28425
+ const headers = {
28426
+ host,
28427
+ "content-type": "application/x-amz-json-1.1",
28428
+ "x-amz-date": long,
28429
+ "x-amz-target": `AmazonSSM.${action}`
28430
+ };
28431
+ if (credentials.sessionToken) headers["x-amz-security-token"] = credentials.sessionToken;
28432
+ const names = Object.keys(headers).map((h) => h.toLowerCase()).sort();
28433
+ const canonicalHeaders = names.map((h) => `${h}:${headers[h]}
28434
+ `).join("");
28435
+ const signedHeaders = names.join(";");
28436
+ const canonicalRequest = [
28437
+ "POST",
28438
+ "/",
28439
+ "",
28440
+ canonicalHeaders,
28441
+ signedHeaders,
28442
+ sha256(body)
28443
+ ].join("\n");
28444
+ const scope = `${short}/${region}/ssm/aws4_request`;
28445
+ const stringToSign = ["AWS4-HMAC-SHA256", long, scope, sha256(canonicalRequest)].join("\n");
28446
+ const signingKey = hmac(
28447
+ hmac(hmac(hmac(`AWS4${credentials.secretAccessKey}`, short), region), "ssm"),
28448
+ "aws4_request"
28449
+ );
28450
+ const signature = createHmac("sha256", signingKey).update(stringToSign, "utf8").digest("hex");
28451
+ headers.authorization = `AWS4-HMAC-SHA256 Credential=${credentials.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
28452
+ const response = await fetchImpl(`https://${host}/`, { method: "POST", headers, body });
28453
+ const text = await response.text();
28454
+ if (!response.ok) {
28455
+ throw new Error(`SSM ${action} failed ${response.status}: ${text.slice(0, 300)}`);
28456
+ }
28457
+ return text ? JSON.parse(text) : {};
28458
+ }
28459
+ async function readAndDeletePoolToken(input2) {
28460
+ const fetchImpl = input2.fetchImpl ?? fetch;
28461
+ const credentials = input2.credentials ?? await resolveAwsCredentials(fetchImpl);
28462
+ if (!credentials) throw new Error("Pool worker has no AWS credentials for SSM");
28463
+ const region = input2.region ?? process.env.AWS_REGION?.trim() ?? "us-east-1";
28464
+ const prefix = input2.prefix ?? process.env.AGLIO_POOL_SSM_PREFIX?.trim() ?? "/aglio/pool/workers";
28465
+ const name = `${prefix}/${input2.workerId}`;
28466
+ const response = await ssmCall("GetParameter", name, region, credentials, fetchImpl);
28467
+ const parameter = response.Parameter;
28468
+ const token = parameter?.Value;
28469
+ if (!token || !token.startsWith("aglio_wkr_")) {
28470
+ throw new Error("SSM parameter did not contain a worker token");
28471
+ }
28472
+ await ssmCall("DeleteParameter", name, region, credentials, fetchImpl).catch((error62) => {
28473
+ console.warn("[pool] could not delete the SSM token parameter", {
28474
+ error: error62 instanceof Error ? error62.message : String(error62)
28475
+ });
28476
+ });
28477
+ return token;
28478
+ }
28479
+ async function resolvePoolBoot(options) {
28480
+ const config2 = POOL_SIZE_CONFIG[options.size];
28481
+ const check2 = poolBootCapCheck({
28482
+ size: options.size,
28483
+ capacity: config2.capacity,
28484
+ hostCpu: cpus().length,
28485
+ hostMemoryBytes: totalmem()
28486
+ });
28487
+ if (!check2.ok) throw new Error(`Pool worker refuses to start: ${check2.reason}`);
28488
+ return {
28489
+ size: options.size,
28490
+ workerId: options.workerId,
28491
+ token: options.token,
28492
+ capacity: config2.capacity,
28493
+ labels: poolWorkerLabels(options.size),
28494
+ cpuLimit: config2.cpuLimit,
28495
+ memoryLimit: config2.memoryLimit,
28496
+ name: process.env.AGLIO_POOL_WORKER_NAME?.trim() || `pool-${options.size}-${options.workerId}`
28497
+ };
28498
+ }
28499
+
28280
28500
  // src/remote-docker.ts
28281
28501
  import { existsSync, readFileSync as readFileSync2 } from "node:fs";
28282
28502
  import { PassThrough } from "node:stream";
@@ -28386,7 +28606,7 @@ async function ensureDockerImage(input2) {
28386
28606
  // src/resources.ts
28387
28607
  var GIB = 1024 * 1024 * 1024;
28388
28608
  var AUTO_MEMORY_RATIO = 0.8;
28389
- var MEMORY_UNITS = {
28609
+ var MEMORY_UNITS2 = {
28390
28610
  "": 1,
28391
28611
  b: 1,
28392
28612
  k: 1024,
@@ -28419,7 +28639,7 @@ function parseCpuLimit(value, variable = "AGENT_CPU_LIMIT") {
28419
28639
  function parseMemoryLimit(value, variable = "AGENT_MEMORY_LIMIT") {
28420
28640
  const trimmed = value.trim().toLowerCase();
28421
28641
  const match = /^(\d+(?:\.\d+)?)\s*([bkmgt]?b?)$/.exec(trimmed);
28422
- const multiplier = match ? MEMORY_UNITS[match[2] ?? ""] : void 0;
28642
+ const multiplier = match ? MEMORY_UNITS2[match[2] ?? ""] : void 0;
28423
28643
  const amount = match ? Number(match[1]) : Number.NaN;
28424
28644
  if (!match || multiplier === void 0 || !Number.isFinite(amount)) {
28425
28645
  throw invalid(variable, value, "bytes or a Docker-style size such as 512m, 8g, 1073741824");
@@ -28433,6 +28653,11 @@ function parseMemoryLimit(value, variable = "AGENT_MEMORY_LIMIT") {
28433
28653
  function cpuToNanoCpus(cpu) {
28434
28654
  return Math.round(cpu * 1e9);
28435
28655
  }
28656
+ var NODE_HEAP_RATIO = 0.7;
28657
+ function nodeOptionsForMemory(memoryBytes) {
28658
+ const heapMb = Math.max(64, Math.floor(memoryBytes / 1024 ** 2 * NODE_HEAP_RATIO));
28659
+ return `--max-old-space-size=${heapMb}`;
28660
+ }
28436
28661
  function readAgentResourceOverrides(source = process.env) {
28437
28662
  const cpu = source.AGENT_CPU_LIMIT?.trim();
28438
28663
  const memory = source.AGENT_MEMORY_LIMIT?.trim();
@@ -28606,6 +28831,8 @@ async function createDockerJobExecutor(input2) {
28606
28831
  Cmd: ["sleep", "infinity"],
28607
28832
  WorkingDir: CONTAINER_WORKSPACE,
28608
28833
  Tty: false,
28834
+ // Heap follows the container cap, so a stale image default cannot overshoot it.
28835
+ Env: [`NODE_OPTIONS=${nodeOptionsForMemory(resources.memoryBytes)}`],
28609
28836
  Labels: { "aglio.remote_job": input2.jobId },
28610
28837
  HostConfig: hostConfig
28611
28838
  });
@@ -28727,13 +28954,18 @@ async function createDockerLoginContainer(input2) {
28727
28954
  if (error62.statusCode !== 404) throw error62;
28728
28955
  }
28729
28956
  const overrides = readAgentResourceOverrides();
28957
+ const memory = overrides.memory ?? 2 * 1024 * 1024 * 1024;
28730
28958
  const container = await client.createContainer({
28731
28959
  Image: input2.image,
28732
28960
  name,
28733
28961
  Cmd: ["sleep", "infinity"],
28734
28962
  WorkingDir: CONTAINER_WORKSPACE,
28735
28963
  Tty: false,
28736
- Env: Object.entries(input2.env ?? {}).map(([key, value]) => `${key}=${value}`),
28964
+ Env: Object.entries({
28965
+ ...input2.env ?? {},
28966
+ // Heap follows the container cap, so a stale image default cannot overshoot it.
28967
+ NODE_OPTIONS: nodeOptionsForMemory(memory)
28968
+ }).map(([key, value]) => `${key}=${value}`),
28737
28969
  Labels: { "aglio.remote_login": input2.jobId },
28738
28970
  HostConfig: {
28739
28971
  Binds: [`${hostPath}:${CONTAINER_WORKSPACE}`],
@@ -28741,7 +28973,7 @@ async function createDockerLoginContainer(input2) {
28741
28973
  ExtraHosts: input2.extraHosts ?? [],
28742
28974
  // Login keeps its small legacy default and never auto-detects, but an
28743
28975
  // explicit AGENT_CPU_LIMIT / AGENT_MEMORY_LIMIT override wins.
28744
- Memory: overrides.memory ?? 2 * 1024 * 1024 * 1024,
28976
+ Memory: memory,
28745
28977
  NanoCpus: overrides.cpu != null ? cpuToNanoCpus(overrides.cpu) : await loginCpuNanoCpus(client),
28746
28978
  PidsLimit: 512,
28747
28979
  AutoRemove: false,
@@ -30694,29 +30926,44 @@ async function runRemoteCardJob(deps) {
30694
30926
  }
30695
30927
  });
30696
30928
  }
30697
- async function runGit(cwd, args) {
30929
+ async function runGit(cwd, args, env = {}) {
30698
30930
  let output2 = "";
30699
- const code = await exec([`git ${args.map(shellArg2).join(" ")}`], cwd, await gitEnv(), {
30700
- log: false,
30701
- onOutput: (chunk) => {
30702
- output2 += chunk;
30931
+ const code = await exec(
30932
+ [`git ${args.map(shellArg2).join(" ")}`],
30933
+ cwd,
30934
+ Object.keys(env).length > 0 ? env : gitAuthEnv(),
30935
+ {
30936
+ log: false,
30937
+ onOutput: (chunk) => {
30938
+ output2 += chunk;
30939
+ }
30703
30940
  }
30704
- });
30941
+ );
30705
30942
  return { code, output: output2 };
30706
30943
  }
30707
30944
  async function repoGit(targetRun) {
30708
30945
  return (args) => runGit(workspace, ["-C", targetRun, ...args]);
30709
30946
  }
30710
30947
  async function git(args, cwd, options = {}) {
30711
- const { code } = await runGit(cwd, args);
30948
+ const { code } = await runGit(cwd, args, options.env);
30712
30949
  if (code !== 0 && !options.allowFailure) throw new Error(`git ${args[0]} failed with code ${code}`);
30713
30950
  return { code };
30714
30951
  }
30715
- async function gitEnv() {
30716
- return {
30952
+ function gitAuthEnv() {
30953
+ const env = {
30717
30954
  GIT_TERMINAL_PROMPT: "0",
30718
- GIT_CONFIG_NOSYSTEM: "1",
30719
- ...context.git.token ? { GIT_CONFIG_GLOBAL: containerPath(runRoot, workspace, gitConfigPath) } : {}
30955
+ GIT_CONFIG_NOSYSTEM: "1"
30956
+ };
30957
+ if (!context.git.token) return env;
30958
+ const encoded = Buffer.from(
30959
+ `${context.git.username || "x-access-token"}:${context.git.token}`,
30960
+ "utf8"
30961
+ ).toString("base64");
30962
+ return {
30963
+ ...env,
30964
+ GIT_CONFIG_COUNT: "1",
30965
+ GIT_CONFIG_KEY_0: "http.extraheader",
30966
+ GIT_CONFIG_VALUE_0: `AUTHORIZATION: basic ${encoded}`
30720
30967
  };
30721
30968
  }
30722
30969
  async function pushBranch(repo, branch) {
@@ -30781,7 +31028,7 @@ async function runRemoteCardJob(deps) {
30781
31028
  const code = await deps.runCommand({
30782
31029
  command: `git ${args.map(shellArg2).join(" ")}`,
30783
31030
  cwd: workspace,
30784
- env: await gitEnv(),
31031
+ env: gitAuthEnv(),
30785
31032
  signal: deps.signal,
30786
31033
  onOutput: (chunk) => {
30787
31034
  output2 += chunk.toString("utf8");
@@ -31769,7 +32016,7 @@ async function runRemoteWorker(config2, deps = {}) {
31769
32016
  ...input2,
31770
32017
  workspaceRoot: config2.workspaceRoot ?? input2.workspace,
31771
32018
  hostWorkspaceRoot: config2.hostWorkspaceRoot ?? config2.workspaceRoot ?? input2.workspace,
31772
- image: config2.runnerImage ?? "ghcr.io/h4kbas/aglio-runner:master",
32019
+ image: config2.runnerImage ?? "ghcr.io/h4kbas/aglio-runner:latest",
31773
32020
  socketPath: config2.dockerSocket,
31774
32021
  extraHosts: config2.extraHosts,
31775
32022
  api: workerProviderLoginApi(
@@ -43936,10 +44183,23 @@ function defaultRunnerImage() {
43936
44183
  }
43937
44184
  async function main() {
43938
44185
  const apiUrl = required2("AGLIO_API_URL");
43939
- const token = required2("AGLIO_WORKER_TOKEN");
44186
+ const poolMode = process.env.AGLIO_POOL === "1" || process.env.AGLIO_POOL === "true";
44187
+ const poolSize = poolMode ? normalizePoolSize(process.env.AGLIO_POOL_SIZE) : null;
44188
+ const poolWorkerId = poolMode ? required2("AGLIO_POOL_WORKER_ID") : null;
44189
+ const poolBoot = poolMode ? await resolvePoolBoot({
44190
+ workerId: poolWorkerId,
44191
+ size: poolSize,
44192
+ // Local stub only. Production reads the one-time token from SSM.
44193
+ token: process.env.AGLIO_POOL_TOKEN?.trim() || await readAndDeletePoolToken({ workerId: poolWorkerId })
44194
+ }) : null;
44195
+ if (poolBoot) {
44196
+ process.env.AGENT_CPU_LIMIT = String(poolBoot.cpuLimit);
44197
+ process.env.AGENT_MEMORY_LIMIT = poolBoot.memoryLimit;
44198
+ }
44199
+ const token = poolBoot ? poolBoot.token : required2("AGLIO_WORKER_TOKEN");
43940
44200
  const mode = normalizeWorkerMode(process.env.AGLIO_WORKER_MODE?.trim() || "docker");
43941
- const os = normalizeWorkerOs(process.env.AGLIO_WORKER_OS?.trim() || defaultOs());
43942
- const labels = parseWorkerLabels(process.env.AGLIO_WORKER_LABELS ?? "");
44201
+ const os = normalizeWorkerOs(poolBoot ? "linux" : process.env.AGLIO_WORKER_OS?.trim() || defaultOs());
44202
+ const labels = poolBoot ? poolBoot.labels : parseWorkerLabels(process.env.AGLIO_WORKER_LABELS ?? "");
43943
44203
  const stateFile = process.env.AGLIO_WORKER_STATE_FILE?.trim() || join8(homedir3(), ".aglio", "worker.json");
43944
44204
  const workspaceRoot = process.env.AGLIO_WORKER_WORKSPACE?.trim() || defaultWorkspaceRoot();
43945
44205
  const hostWorkspaceRoot = process.env.AGLIO_WORKER_HOST_WORKSPACE?.trim() || workspaceRoot;
@@ -43973,14 +44233,14 @@ async function main() {
43973
44233
  const config2 = {
43974
44234
  apiUrl,
43975
44235
  token,
43976
- name: process.env.AGLIO_WORKER_NAME?.trim() || hostname3(),
44236
+ name: poolBoot?.name ?? (process.env.AGLIO_WORKER_NAME?.trim() || hostname3()),
43977
44237
  os,
43978
44238
  mode,
43979
44239
  labels,
43980
44240
  version: process.env.AGLIO_WORKER_VERSION?.trim() || WORKER_MIN_VERSION,
43981
44241
  stateFile,
43982
44242
  // Simulators and Xcode share one signing context on a Mac host.
43983
- capacity: workerCapacityForMode(process.env.AGLIO_WORKER_CAPACITY ?? 1, mode),
44243
+ capacity: poolBoot?.capacity ?? workerCapacityForMode(process.env.AGLIO_WORKER_CAPACITY ?? 1, mode),
43984
44244
  claimTimeoutSeconds,
43985
44245
  workspaceRoot,
43986
44246
  hostWorkspaceRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aglio-worker",
3
- "version": "0.25.0",
3
+ "version": "0.28.21",
4
4
  "description": "Host machine agent for Aglio: claims jobs over the API and starts aglio-runner containers.",
5
5
  "type": "module",
6
6
  "bin": {