aglio-worker 0.10.0 → 0.12.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.
Files changed (2) hide show
  1. package/agent.js +184 -54
  2. package/package.json +1 -1
package/agent.js CHANGED
@@ -7302,8 +7302,8 @@ var require_content_type = __commonJS({
7302
7302
  });
7303
7303
 
7304
7304
  // src/remote-agent.ts
7305
- import { homedir as homedir2, hostname as hostname3 } from "node:os";
7306
- import { join as join7 } from "node:path";
7305
+ import { homedir as homedir3, hostname as hostname3 } from "node:os";
7306
+ import { join as join8 } from "node:path";
7307
7307
 
7308
7308
  // ../../packages/shared/src/enums.ts
7309
7309
  var CARD_COLUMNS = ["backlog", "todo", "doing", "review", "done"];
@@ -28248,8 +28248,110 @@ var cardOpenModeSchema = external_exports.enum(CARD_OPEN_MODES);
28248
28248
  // src/remote-docker.ts
28249
28249
  import { existsSync } from "node:fs";
28250
28250
  import { PassThrough } from "node:stream";
28251
- import { join, posix, relative, resolve } from "node:path";
28251
+ import { join as join2, posix, relative, resolve } from "node:path";
28252
28252
  import Dockerode from "dockerode";
28253
+
28254
+ // src/docker-images.ts
28255
+ import { readFileSync } from "node:fs";
28256
+ import { homedir } from "node:os";
28257
+ import { join } from "node:path";
28258
+ function isRegistryImage(image) {
28259
+ return image.includes("/");
28260
+ }
28261
+ function registryHost(image) {
28262
+ const first = image.split("/")[0] ?? "";
28263
+ if (first.includes(".") || first.includes(":") || first === "localhost") return first;
28264
+ return "https://index.docker.io/v1/";
28265
+ }
28266
+ function dockerAuthFor(image) {
28267
+ const configDir = process.env.DOCKER_CONFIG || join(homedir(), ".docker");
28268
+ let config2;
28269
+ try {
28270
+ config2 = JSON.parse(readFileSync(join(configDir, "config.json"), "utf8"));
28271
+ } catch {
28272
+ return void 0;
28273
+ }
28274
+ const host = registryHost(image);
28275
+ const entry = config2.auths?.[host] ?? config2.auths?.[`https://${host}`] ?? config2.auths?.[`http://${host}`];
28276
+ if (!entry) return void 0;
28277
+ if (entry.auth) {
28278
+ const decoded = Buffer.from(entry.auth, "base64").toString("utf8");
28279
+ const separator = decoded.indexOf(":");
28280
+ if (separator > 0) {
28281
+ return {
28282
+ username: decoded.slice(0, separator),
28283
+ password: decoded.slice(separator + 1)
28284
+ };
28285
+ }
28286
+ }
28287
+ if (entry.username && entry.password) {
28288
+ return { username: entry.username, password: entry.password };
28289
+ }
28290
+ if (entry.identitytoken) return { identitytoken: entry.identitytoken };
28291
+ if (entry.registrytoken) return { registrytoken: entry.registrytoken };
28292
+ return void 0;
28293
+ }
28294
+ var inFlight = /* @__PURE__ */ new WeakMap();
28295
+ async function pullDockerImage(client, image) {
28296
+ if (!isRegistryImage(image)) {
28297
+ throw new Error(
28298
+ `Runner image ${image} is not registry-qualified. Build it with: pnpm runner:build`
28299
+ );
28300
+ }
28301
+ let pending = inFlight.get(client);
28302
+ if (!pending) {
28303
+ pending = /* @__PURE__ */ new Map();
28304
+ inFlight.set(client, pending);
28305
+ }
28306
+ const existing = pending.get(image);
28307
+ if (existing) return existing;
28308
+ const pull = (async () => {
28309
+ const stream = await client.pull(image, { authconfig: dockerAuthFor(image) });
28310
+ await new Promise((resolve6, reject) => {
28311
+ stream.on("data", () => void 0);
28312
+ stream.on("end", () => resolve6());
28313
+ stream.on("error", (error62) => reject(error62));
28314
+ });
28315
+ })();
28316
+ pending.set(image, pull);
28317
+ try {
28318
+ await pull;
28319
+ } finally {
28320
+ pending.delete(image);
28321
+ }
28322
+ }
28323
+ async function ensureDockerImage(input2) {
28324
+ try {
28325
+ await input2.client.getImage(input2.image).inspect();
28326
+ return "present";
28327
+ } catch {
28328
+ }
28329
+ if (!isRegistryImage(input2.image)) {
28330
+ throw new Error(
28331
+ `Runner image ${input2.image} not found on this machine. Build it with: pnpm runner:build`
28332
+ );
28333
+ }
28334
+ input2.log?.("runner image missing, pulling", { image: input2.image });
28335
+ try {
28336
+ await pullDockerImage(input2.client, input2.image);
28337
+ } catch (error62) {
28338
+ const reason = error62 instanceof Error ? error62.message : String(error62);
28339
+ throw new Error(
28340
+ `Runner image ${input2.image} not found and could not be pulled: ${reason}. Check registry access or docker login, then retry: docker pull ${input2.image}`
28341
+ );
28342
+ }
28343
+ try {
28344
+ await input2.client.getImage(input2.image).inspect();
28345
+ } catch {
28346
+ throw new Error(
28347
+ `Runner image ${input2.image} pull finished but the image is still missing locally. Retry: docker pull ${input2.image}`
28348
+ );
28349
+ }
28350
+ input2.log?.("runner image pulled", { image: input2.image });
28351
+ return "pulled";
28352
+ }
28353
+
28354
+ // src/remote-docker.ts
28253
28355
  async function runnerCpuCount(client) {
28254
28356
  const info = await client.info().catch(() => null);
28255
28357
  return Math.max(1, Math.min(2, Math.floor(info?.NCPU ?? 1)));
@@ -28291,25 +28393,24 @@ function mapDockerWorkspace(input2) {
28291
28393
  throw new Error(`Path escapes the job workspace: ${input2.target}`);
28292
28394
  }
28293
28395
  return {
28294
- hostPath: resolve(join(input2.hostWorkspaceRoot, rel)),
28396
+ hostPath: resolve(join2(input2.hostWorkspaceRoot, rel)),
28295
28397
  containerPath: containerRel ? posix.join(CONTAINER_WORKSPACE, containerRel.split(/[\\/]+/).join("/")) : CONTAINER_WORKSPACE
28296
28398
  };
28297
28399
  }
28298
28400
  function sleep(ms) {
28299
28401
  return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
28300
28402
  }
28403
+ var defaultClients = /* @__PURE__ */ new Map();
28301
28404
  function defaultClient(socketPath) {
28302
- return new Dockerode({ socketPath });
28405
+ const existing = defaultClients.get(socketPath);
28406
+ if (existing) return existing;
28407
+ const client = new Dockerode({ socketPath });
28408
+ defaultClients.set(socketPath, client);
28409
+ return client;
28303
28410
  }
28304
28411
  async function createDockerJobExecutor(input2) {
28305
28412
  const client = input2.client ?? defaultClient(input2.socketPath ?? "/var/run/docker.sock");
28306
- try {
28307
- await client.getImage(input2.image).inspect();
28308
- } catch {
28309
- throw new Error(
28310
- `Runner image ${input2.image} not found on this machine. Pull it with: docker pull ${input2.image}`
28311
- );
28312
- }
28413
+ await ensureDockerImage({ client, image: input2.image, log: input2.log });
28313
28414
  const { hostPath } = mapDockerWorkspace({
28314
28415
  workspaceRoot: input2.workspaceRoot,
28315
28416
  hostWorkspaceRoot: input2.hostWorkspaceRoot,
@@ -28421,13 +28522,35 @@ async function createDockerJobExecutor(input2) {
28421
28522
  }
28422
28523
  };
28423
28524
  }
28424
- async function createDockerLoginContainer(input2) {
28425
- const client = new Dockerode({ socketPath: input2.socketPath ?? "/var/run/docker.sock" });
28525
+ async function prefetchRunnerImage(input2) {
28526
+ const client = input2.client ?? defaultClient(input2.socketPath ?? "/var/run/docker.sock");
28426
28527
  try {
28427
- await client.getImage(input2.image).inspect();
28428
- } catch {
28429
- throw new Error(`Runner image ${input2.image} not found on this machine.`);
28528
+ if (isRegistryImage(input2.image)) {
28529
+ await pullDockerImage(client, input2.image);
28530
+ input2.log?.("runner image refreshed", { image: input2.image });
28531
+ return true;
28532
+ }
28533
+ const result = await ensureDockerImage({
28534
+ client,
28535
+ image: input2.image,
28536
+ log: input2.log
28537
+ });
28538
+ if (result === "present") input2.log?.("runner image ready", { image: input2.image });
28539
+ return true;
28540
+ } catch (error62) {
28541
+ input2.log?.("runner image unavailable", {
28542
+ image: input2.image,
28543
+ error: error62 instanceof Error ? error62.message : String(error62)
28544
+ });
28545
+ return false;
28430
28546
  }
28547
+ }
28548
+ async function createDockerLoginContainer(input2) {
28549
+ const client = new Dockerode({ socketPath: input2.socketPath ?? "/var/run/docker.sock" });
28550
+ await ensureDockerImage({
28551
+ client,
28552
+ image: input2.image
28553
+ });
28431
28554
  const { hostPath } = mapDockerWorkspace({
28432
28555
  workspaceRoot: input2.workspaceRoot,
28433
28556
  hostWorkspaceRoot: input2.hostWorkspaceRoot,
@@ -28554,8 +28677,8 @@ async function verifyHostRuntime(input2, run = defaultRunner) {
28554
28677
  // src/job-runner.ts
28555
28678
  import { spawn } from "node:child_process";
28556
28679
  import { access, mkdir, readFile, stat } from "node:fs/promises";
28557
- import { homedir } from "node:os";
28558
- import { isAbsolute, join as join2, relative as relative2, resolve as resolve2 } from "node:path";
28680
+ import { homedir as homedir2 } from "node:os";
28681
+ import { isAbsolute, join as join3, relative as relative2, resolve as resolve2 } from "node:path";
28559
28682
 
28560
28683
  // src/job-log-batcher.ts
28561
28684
  var DEFAULT_MAX_RETRIES = 3;
@@ -28762,7 +28885,7 @@ var JobAbortedError = class extends Error {
28762
28885
  }
28763
28886
  };
28764
28887
  function defaultWorkspaceRoot() {
28765
- return process.env.AGLIO_WORKER_WORKSPACE?.trim() || join2(homedir(), ".aglio", "workspaces");
28888
+ return process.env.AGLIO_WORKER_WORKSPACE?.trim() || join3(homedir2(), ".aglio", "workspaces");
28766
28889
  }
28767
28890
  function safeSegment(value) {
28768
28891
  return value.replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 120) || "job";
@@ -28782,7 +28905,7 @@ function inside(root, target) {
28782
28905
  async function executeRemoteJob(job, deps) {
28783
28906
  if (deps.signal?.aborted) throw new JobAbortedError();
28784
28907
  const root = deps.workspaceRoot ?? defaultWorkspaceRoot();
28785
- const workspace = join2(
28908
+ const workspace = join3(
28786
28909
  root,
28787
28910
  safeSegment(job.workspaceId || "workspace"),
28788
28911
  safeSegment(job.project?.id || "workspace"),
@@ -28891,7 +29014,7 @@ ${job.instructions.trim()}
28891
29014
  for (const step of job.steps) {
28892
29015
  if (deps.signal?.aborted) throw new JobAbortedError();
28893
29016
  if (leaseAbort.signal.aborted) throw new JobLeaseLostError("Lease was lost before the next step.");
28894
- const cwd = step.cwd ? inside(workspace, join2(workspace, step.cwd)) : workspace;
29017
+ const cwd = step.cwd ? inside(workspace, join3(workspace, step.cwd)) : workspace;
28895
29018
  write(`[aglio] step: ${step.name}
28896
29019
  $ ${step.command}
28897
29020
  `);
@@ -28950,8 +29073,8 @@ async function finish(deps, job, state, error62) {
28950
29073
  }
28951
29074
  async function checkoutRepos(job, workspace, write, signal) {
28952
29075
  for (const repo of job.project?.repos ?? []) {
28953
- const target = inside(workspace, join2(workspace, repo.name));
28954
- const hasGit = await access(join2(target, ".git")).then(
29076
+ const target = inside(workspace, join3(workspace, repo.name));
29077
+ const hasGit = await access(join3(target, ".git")).then(
28955
29078
  () => true,
28956
29079
  () => false
28957
29080
  );
@@ -29033,7 +29156,7 @@ async function uploadJobArtifacts(job, workspace, deps, write) {
29033
29156
  `);
29034
29157
  continue;
29035
29158
  }
29036
- const path = inside(workspace, join2(workspace, artifact.name));
29159
+ const path = inside(workspace, join3(workspace, artifact.name));
29037
29160
  const info = await stat(path).catch(() => null);
29038
29161
  if (!info?.isFile()) {
29039
29162
  write(`[aglio] artifact missing: ${artifact.name}
@@ -29082,7 +29205,7 @@ import { dirname as dirname2 } from "node:path";
29082
29205
 
29083
29206
  // src/remote-card-run.ts
29084
29207
  import { mkdir as mkdir2, readFile as readFile2, rm as rm2, stat as stat3, writeFile } from "node:fs/promises";
29085
- import { dirname, isAbsolute as isAbsolute2, join as join4, relative as relative3, resolve as resolve3 } from "node:path";
29208
+ import { dirname, isAbsolute as isAbsolute2, join as join5, relative as relative3, resolve as resolve3 } from "node:path";
29086
29209
 
29087
29210
  // ../../packages/agents/src/drivers.ts
29088
29211
  function withSystemPrompt(prompt, append) {
@@ -29736,13 +29859,13 @@ function credentialFiles(payload) {
29736
29859
 
29737
29860
  // src/remote-cleanup.ts
29738
29861
  import { access as access2, readdir, rm, stat as stat2 } from "node:fs/promises";
29739
- import { join as join3 } from "node:path";
29862
+ import { join as join4 } from "node:path";
29740
29863
  import Dockerode2 from "dockerode";
29741
29864
  var JOB_WORKSPACE_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
29742
29865
  var STALE_CONTAINER_RETENTION_SECONDS = 24 * 60 * 60;
29743
29866
  var SESSION_RESUME_MARKER = "resume-pending";
29744
29867
  function sessionResumeMarkerPath(workspaceDir) {
29745
- return join3(workspaceDir, ".aglio", SESSION_RESUME_MARKER);
29868
+ return join4(workspaceDir, ".aglio", SESSION_RESUME_MARKER);
29746
29869
  }
29747
29870
  async function workspaceHasResumeMarker(workspaceDir) {
29748
29871
  return access2(sessionResumeMarkerPath(workspaceDir)).then(
@@ -29765,16 +29888,16 @@ async function cleanupRemoteWorkspaces(input2) {
29765
29888
  const rootEntries = await readdir(input2.workspaceRoot, { withFileTypes: true }).catch(() => []);
29766
29889
  for (const workspace of rootEntries) {
29767
29890
  if (!workspace.isDirectory()) continue;
29768
- const workspaceDir = join3(input2.workspaceRoot, workspace.name);
29891
+ const workspaceDir = join4(input2.workspaceRoot, workspace.name);
29769
29892
  const projectEntries = await readdir(workspaceDir, { withFileTypes: true }).catch(() => []);
29770
29893
  for (const project of projectEntries) {
29771
29894
  if (!project.isDirectory()) continue;
29772
- const projectDir = join3(workspaceDir, project.name);
29895
+ const projectDir = join4(workspaceDir, project.name);
29773
29896
  const jobEntries = await readdir(projectDir, { withFileTypes: true }).catch(() => []);
29774
29897
  for (const job of jobEntries) {
29775
29898
  if (!job.isDirectory()) continue;
29776
29899
  scanned += 1;
29777
- const jobDir = join3(projectDir, job.name);
29900
+ const jobDir = join4(projectDir, job.name);
29778
29901
  const info = await stat2(jobDir).catch(() => null);
29779
29902
  if (!info) continue;
29780
29903
  if (!shouldRemoveWorkspace({
@@ -29936,12 +30059,12 @@ async function runRemoteCardJob(deps) {
29936
30059
  const lease = { jobId: job.id, leaseId: job.lease.id, token: job.lease.token };
29937
30060
  let context;
29938
30061
  try {
29939
- const workspaceReady = await stat3(join4(workspace, ".aglio")).then(
30062
+ const workspaceReady = await stat3(join5(workspace, ".aglio")).then(
29940
30063
  (info) => info.isDirectory(),
29941
30064
  () => false
29942
30065
  );
29943
30066
  context = await deps.api.fetchContext({ ...lease, workspaceRoot: runRoot, workspaceReady });
29944
- await rm2(join4(workspace, ".aglio", SESSION_RESUME_MARKER), { force: true }).catch(() => void 0);
30067
+ await rm2(join5(workspace, ".aglio", SESSION_RESUME_MARKER), { force: true }).catch(() => void 0);
29945
30068
  } catch (error62) {
29946
30069
  return { state: "failed", error: `Could not load run context: ${errorText2(error62)}` };
29947
30070
  }
@@ -29950,9 +30073,9 @@ async function runRemoteCardJob(deps) {
29950
30073
  write("[aglio] waiting for a free run slot\n");
29951
30074
  return { state: "succeeded", error: null };
29952
30075
  }
29953
- const toolDir = join4(workspace, ".aglio", context.agent.driver);
29954
- const homeHost = join4(toolDir, "home");
29955
- const gitConfigPath = join4(workspace, ".aglio", "gitconfig");
30076
+ const toolDir = join5(workspace, ".aglio", context.agent.driver);
30077
+ const homeHost = join5(toolDir, "home");
30078
+ const gitConfigPath = join5(workspace, ".aglio", "gitconfig");
29956
30079
  const mcpRelativePath = mcpConfigRelativePath(context.agent.driver);
29957
30080
  const mcpUrl = deps.mcp ? `${deps.mcp.baseUrl.replace(/\/+$/, "")}/mcp/${context.run.id}` : "";
29958
30081
  const extraServers = context.mcp.servers;
@@ -30000,7 +30123,7 @@ async function runRemoteCardJob(deps) {
30000
30123
  options: context.agent.options,
30001
30124
  sessionId: context.run.sessionId,
30002
30125
  resume: Boolean(context.run.sessionId),
30003
- mcpConfigPath: mcpRelativePath ? containerPath(runRoot, workspace, join4(toolDir, mcpRelativePath)) : "",
30126
+ mcpConfigPath: mcpRelativePath ? containerPath(runRoot, workspace, join5(toolDir, mcpRelativePath)) : "",
30004
30127
  piHome: containerPath(runRoot, workspace, toolDir),
30005
30128
  workingDirectory: repoPaths.length === 1 ? containerPath(runRoot, workspace, repoPaths[0]) : runRoot,
30006
30129
  systemPromptAppend: context.systemPromptAppend
@@ -30020,14 +30143,14 @@ async function runRemoteCardJob(deps) {
30020
30143
  deps.mcp?.stopRun(context.run.id);
30021
30144
  await rm2(homeHost, { recursive: true, force: true }).catch(() => void 0);
30022
30145
  await rm2(gitConfigPath, { force: true }).catch(() => void 0);
30023
- await rm2(join4(toolDir, "models.json"), { force: true }).catch(() => void 0);
30146
+ await rm2(join5(toolDir, "models.json"), { force: true }).catch(() => void 0);
30024
30147
  if (mcpRelativePath) {
30025
- await rm2(join4(toolDir, mcpRelativePath), { force: true }).catch(() => void 0);
30148
+ await rm2(join5(toolDir, mcpRelativePath), { force: true }).catch(() => void 0);
30026
30149
  }
30027
30150
  }
30028
30151
  async function prepareWorkspace() {
30029
30152
  await mkdir2(homeHost, { recursive: true, mode: 448 });
30030
- await mkdir2(join4(workspace, ".aglio", "attachments"), { recursive: true });
30153
+ await mkdir2(join5(workspace, ".aglio", "attachments"), { recursive: true });
30031
30154
  const files = credentialFiles(context.agent.credential.payload);
30032
30155
  if (context.agent.credential.authType === "session_bundle" && Object.keys(files).length === 0) {
30033
30156
  throw new Error(`Credential "${context.agent.credential.label}" has no session files.`);
@@ -30048,7 +30171,7 @@ async function runRemoteCardJob(deps) {
30048
30171
  throw new Error(`Credential "${context.agent.credential.label}" has no base URL.`);
30049
30172
  }
30050
30173
  await writeFile(
30051
- join4(toolDir, "models.json"),
30174
+ join5(toolDir, "models.json"),
30052
30175
  JSON.stringify(
30053
30176
  {
30054
30177
  providers: {
@@ -30067,7 +30190,7 @@ async function runRemoteCardJob(deps) {
30067
30190
  );
30068
30191
  }
30069
30192
  if (mcpRelativePath && mcpUrl) {
30070
- const hostConfigPath = join4(toolDir, mcpRelativePath);
30193
+ const hostConfigPath = join5(toolDir, mcpRelativePath);
30071
30194
  await mkdir2(dirname(hostConfigPath), { recursive: true });
30072
30195
  await writeFile(
30073
30196
  hostConfigPath,
@@ -30097,7 +30220,7 @@ async function runRemoteCardJob(deps) {
30097
30220
  try {
30098
30221
  const data = await deps.api.fetchAttachment({ ...lease, artifactId: attachment.id });
30099
30222
  await writeFile(
30100
- join4(workspace, ".aglio", "attachments", artifactFileName(attachment.id, attachment.label)),
30223
+ join5(workspace, ".aglio", "attachments", artifactFileName(attachment.id, attachment.label)),
30101
30224
  data
30102
30225
  );
30103
30226
  } catch {
@@ -30109,10 +30232,10 @@ async function runRemoteCardJob(deps) {
30109
30232
  async function cloneRepos() {
30110
30233
  const repoPaths = [];
30111
30234
  for (const repo of context.repos) {
30112
- const target = inside2(workspace, join4(workspace, repo.name));
30235
+ const target = inside2(workspace, join5(workspace, repo.name));
30113
30236
  const targetRun = containerPath(runRoot, workspace, target);
30114
30237
  repoPaths.push(target);
30115
- const hasGit = await readFile2(join4(target, ".git", "HEAD"), "utf8").then(
30238
+ const hasGit = await readFile2(join5(target, ".git", "HEAD"), "utf8").then(
30116
30239
  () => true,
30117
30240
  () => false
30118
30241
  );
@@ -30216,7 +30339,7 @@ async function runRemoteCardJob(deps) {
30216
30339
  };
30217
30340
  }
30218
30341
  async function pushBranch(repo, branch) {
30219
- const target = inside2(workspace, join4(workspace, repo.name));
30342
+ const target = inside2(workspace, join5(workspace, repo.name));
30220
30343
  const targetRun = containerPath(runRoot, workspace, target);
30221
30344
  write(`[aglio] pushing ${repo.name} ${branch}
30222
30345
  `);
@@ -30477,7 +30600,7 @@ async function runRemoteCardJob(deps) {
30477
30600
 
30478
30601
  // src/remote-provider-login.ts
30479
30602
  import { mkdir as mkdir3, readFile as readFile3, rm as rm3 } from "node:fs/promises";
30480
- import { join as join5, resolve as resolve4, sep } from "node:path";
30603
+ import { join as join6, resolve as resolve4, sep } from "node:path";
30481
30604
  var FINAL = ["completed", "failed", "cancelled", "expired"];
30482
30605
  var POLL_MS = 700;
30483
30606
  var KICKOFF_MS = 4e3;
@@ -30490,8 +30613,8 @@ function errorText3(error62) {
30490
30613
  async function runRemoteProviderLogin(input2) {
30491
30614
  let session = await input2.api.fetch();
30492
30615
  const method = session.command;
30493
- const root = join5(input2.workspace, "login");
30494
- const home = join5(root, "home");
30616
+ const root = join6(input2.workspace, "login");
30617
+ const home = join6(root, "home");
30495
30618
  const target = resolve4(home, session.bundlePath);
30496
30619
  if (!target.startsWith(resolve4(home) + sep)) {
30497
30620
  return { state: "failed", error: "Unsafe credential bundle path." };
@@ -31320,7 +31443,7 @@ async function runRemoteWorker(config2, deps = {}) {
31320
31443
  // src/remote-mcp.ts
31321
31444
  import { createServer as createServer2 } from "node:http";
31322
31445
  import { mkdir as mkdir5, readFile as readFile5, stat as stat4, writeFile as writeFile3 } from "node:fs/promises";
31323
- import { basename, isAbsolute as isAbsolute3, join as join6, relative as relative4, resolve as resolve5 } from "node:path";
31446
+ import { basename, isAbsolute as isAbsolute3, join as join7, relative as relative4, resolve as resolve5 } from "node:path";
31324
31447
 
31325
31448
  // ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v3/helpers/util.js
31326
31449
  var util;
@@ -43028,7 +43151,7 @@ function fail(message) {
43028
43151
  function workspaceHostPath(workspace, input2) {
43029
43152
  const root = resolve5(workspace);
43030
43153
  const trimmed = input2.trim();
43031
- const mapped = !trimmed.startsWith("/") ? join6(root, trimmed) : trimmed === root || trimmed.startsWith(`${root}/`) ? trimmed : join6(root, relative4("/workspace", trimmed));
43154
+ const mapped = !trimmed.startsWith("/") ? join7(root, trimmed) : trimmed === root || trimmed.startsWith(`${root}/`) ? trimmed : join7(root, relative4("/workspace", trimmed));
43032
43155
  const normalized = resolve5(mapped);
43033
43156
  const rel = relative4(root, normalized);
43034
43157
  if (rel.startsWith("..") || isAbsolute3(rel)) throw new Error("Artifact path escapes the workspace");
@@ -43138,9 +43261,9 @@ function createServerForRun(run) {
43138
43261
  try {
43139
43262
  const result = await run.callTool({ tool, input: input2 });
43140
43263
  if (run.kind !== "planning" && (tool === "question_ask" || tool === "spec_propose") && result && typeof result === "object" && ["awaiting_input", "pending"].includes(String(result.status ?? ""))) {
43141
- await mkdir5(join6(run.workspace, ".aglio"), { recursive: true }).catch(() => void 0);
43264
+ await mkdir5(join7(run.workspace, ".aglio"), { recursive: true }).catch(() => void 0);
43142
43265
  await writeFile3(
43143
- join6(run.workspace, ".aglio", SESSION_RESUME_MARKER),
43266
+ join7(run.workspace, ".aglio", SESSION_RESUME_MARKER),
43144
43267
  (/* @__PURE__ */ new Date()).toISOString(),
43145
43268
  "utf8"
43146
43269
  ).catch((error62) => run.log(`resume marker write failed: ${String(error62)}`));
@@ -43420,7 +43543,7 @@ async function main() {
43420
43543
  const mode = normalizeWorkerMode(process.env.AGLIO_WORKER_MODE?.trim() || "docker");
43421
43544
  const os = normalizeWorkerOs(process.env.AGLIO_WORKER_OS?.trim() || defaultOs());
43422
43545
  const labels = parseWorkerLabels(process.env.AGLIO_WORKER_LABELS ?? "");
43423
- const stateFile = process.env.AGLIO_WORKER_STATE_FILE?.trim() || join7(homedir2(), ".aglio", "worker.json");
43546
+ const stateFile = process.env.AGLIO_WORKER_STATE_FILE?.trim() || join8(homedir3(), ".aglio", "worker.json");
43424
43547
  const workspaceRoot = process.env.AGLIO_WORKER_WORKSPACE?.trim() || defaultWorkspaceRoot();
43425
43548
  const hostWorkspaceRoot = process.env.AGLIO_WORKER_HOST_WORKSPACE?.trim() || workspaceRoot;
43426
43549
  const runnerImage = defaultRunnerImage();
@@ -43476,6 +43599,13 @@ async function main() {
43476
43599
  );
43477
43600
  const hostTools = await verifyHostRuntime({ mode, labels, platform: process.platform });
43478
43601
  if (hostTools.length > 0) console.log(`[worker-agent] host tools ${hostTools.join(" | ")}`);
43602
+ if (mode === "docker") {
43603
+ void prefetchRunnerImage({
43604
+ image: runnerImage,
43605
+ socketPath: dockerSocket,
43606
+ log: (message, extra) => console.log(`[worker-agent] ${message}`, extra ?? "")
43607
+ });
43608
+ }
43479
43609
  try {
43480
43610
  process.exitCode = await runRemoteWorker(config2, {
43481
43611
  signal: controller.signal,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aglio-worker",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
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": {