aglio-worker 0.9.2 → 0.11.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 +188 -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"];
@@ -28241,11 +28241,117 @@ var boardListMoveSchema = external_exports.object({
28241
28241
  // ../../packages/shared/src/password-reset.ts
28242
28242
  var PASSWORD_RESET_TTL_SECONDS = 60 * 60;
28243
28243
 
28244
+ // ../../packages/shared/src/card-open-mode.ts
28245
+ var CARD_OPEN_MODES = ["popup", "page"];
28246
+ var cardOpenModeSchema = external_exports.enum(CARD_OPEN_MODES);
28247
+
28244
28248
  // src/remote-docker.ts
28245
28249
  import { existsSync } from "node:fs";
28246
28250
  import { PassThrough } from "node:stream";
28247
- import { join, posix, relative, resolve } from "node:path";
28251
+ import { join as join2, posix, relative, resolve } from "node:path";
28248
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
28249
28355
  async function runnerCpuCount(client) {
28250
28356
  const info = await client.info().catch(() => null);
28251
28357
  return Math.max(1, Math.min(2, Math.floor(info?.NCPU ?? 1)));
@@ -28287,25 +28393,24 @@ function mapDockerWorkspace(input2) {
28287
28393
  throw new Error(`Path escapes the job workspace: ${input2.target}`);
28288
28394
  }
28289
28395
  return {
28290
- hostPath: resolve(join(input2.hostWorkspaceRoot, rel)),
28396
+ hostPath: resolve(join2(input2.hostWorkspaceRoot, rel)),
28291
28397
  containerPath: containerRel ? posix.join(CONTAINER_WORKSPACE, containerRel.split(/[\\/]+/).join("/")) : CONTAINER_WORKSPACE
28292
28398
  };
28293
28399
  }
28294
28400
  function sleep(ms) {
28295
28401
  return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
28296
28402
  }
28403
+ var defaultClients = /* @__PURE__ */ new Map();
28297
28404
  function defaultClient(socketPath) {
28298
- 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;
28299
28410
  }
28300
28411
  async function createDockerJobExecutor(input2) {
28301
28412
  const client = input2.client ?? defaultClient(input2.socketPath ?? "/var/run/docker.sock");
28302
- try {
28303
- await client.getImage(input2.image).inspect();
28304
- } catch {
28305
- throw new Error(
28306
- `Runner image ${input2.image} not found on this machine. Pull it with: docker pull ${input2.image}`
28307
- );
28308
- }
28413
+ await ensureDockerImage({ client, image: input2.image, log: input2.log });
28309
28414
  const { hostPath } = mapDockerWorkspace({
28310
28415
  workspaceRoot: input2.workspaceRoot,
28311
28416
  hostWorkspaceRoot: input2.hostWorkspaceRoot,
@@ -28417,13 +28522,35 @@ async function createDockerJobExecutor(input2) {
28417
28522
  }
28418
28523
  };
28419
28524
  }
28420
- async function createDockerLoginContainer(input2) {
28421
- 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");
28422
28527
  try {
28423
- await client.getImage(input2.image).inspect();
28424
- } catch {
28425
- 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;
28426
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
+ });
28427
28554
  const { hostPath } = mapDockerWorkspace({
28428
28555
  workspaceRoot: input2.workspaceRoot,
28429
28556
  hostWorkspaceRoot: input2.hostWorkspaceRoot,
@@ -28550,8 +28677,8 @@ async function verifyHostRuntime(input2, run = defaultRunner) {
28550
28677
  // src/job-runner.ts
28551
28678
  import { spawn } from "node:child_process";
28552
28679
  import { access, mkdir, readFile, stat } from "node:fs/promises";
28553
- import { homedir } from "node:os";
28554
- 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";
28555
28682
 
28556
28683
  // src/job-log-batcher.ts
28557
28684
  var DEFAULT_MAX_RETRIES = 3;
@@ -28758,7 +28885,7 @@ var JobAbortedError = class extends Error {
28758
28885
  }
28759
28886
  };
28760
28887
  function defaultWorkspaceRoot() {
28761
- return process.env.AGLIO_WORKER_WORKSPACE?.trim() || join2(homedir(), ".aglio", "workspaces");
28888
+ return process.env.AGLIO_WORKER_WORKSPACE?.trim() || join3(homedir2(), ".aglio", "workspaces");
28762
28889
  }
28763
28890
  function safeSegment(value) {
28764
28891
  return value.replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 120) || "job";
@@ -28778,7 +28905,7 @@ function inside(root, target) {
28778
28905
  async function executeRemoteJob(job, deps) {
28779
28906
  if (deps.signal?.aborted) throw new JobAbortedError();
28780
28907
  const root = deps.workspaceRoot ?? defaultWorkspaceRoot();
28781
- const workspace = join2(
28908
+ const workspace = join3(
28782
28909
  root,
28783
28910
  safeSegment(job.workspaceId || "workspace"),
28784
28911
  safeSegment(job.project?.id || "workspace"),
@@ -28887,7 +29014,7 @@ ${job.instructions.trim()}
28887
29014
  for (const step of job.steps) {
28888
29015
  if (deps.signal?.aborted) throw new JobAbortedError();
28889
29016
  if (leaseAbort.signal.aborted) throw new JobLeaseLostError("Lease was lost before the next step.");
28890
- const cwd = step.cwd ? inside(workspace, join2(workspace, step.cwd)) : workspace;
29017
+ const cwd = step.cwd ? inside(workspace, join3(workspace, step.cwd)) : workspace;
28891
29018
  write(`[aglio] step: ${step.name}
28892
29019
  $ ${step.command}
28893
29020
  `);
@@ -28946,8 +29073,8 @@ async function finish(deps, job, state, error62) {
28946
29073
  }
28947
29074
  async function checkoutRepos(job, workspace, write, signal) {
28948
29075
  for (const repo of job.project?.repos ?? []) {
28949
- const target = inside(workspace, join2(workspace, repo.name));
28950
- 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(
28951
29078
  () => true,
28952
29079
  () => false
28953
29080
  );
@@ -29029,7 +29156,7 @@ async function uploadJobArtifacts(job, workspace, deps, write) {
29029
29156
  `);
29030
29157
  continue;
29031
29158
  }
29032
- const path = inside(workspace, join2(workspace, artifact.name));
29159
+ const path = inside(workspace, join3(workspace, artifact.name));
29033
29160
  const info = await stat(path).catch(() => null);
29034
29161
  if (!info?.isFile()) {
29035
29162
  write(`[aglio] artifact missing: ${artifact.name}
@@ -29078,7 +29205,7 @@ import { dirname as dirname2 } from "node:path";
29078
29205
 
29079
29206
  // src/remote-card-run.ts
29080
29207
  import { mkdir as mkdir2, readFile as readFile2, rm as rm2, stat as stat3, writeFile } from "node:fs/promises";
29081
- 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";
29082
29209
 
29083
29210
  // ../../packages/agents/src/drivers.ts
29084
29211
  function withSystemPrompt(prompt, append) {
@@ -29732,13 +29859,13 @@ function credentialFiles(payload) {
29732
29859
 
29733
29860
  // src/remote-cleanup.ts
29734
29861
  import { access as access2, readdir, rm, stat as stat2 } from "node:fs/promises";
29735
- import { join as join3 } from "node:path";
29862
+ import { join as join4 } from "node:path";
29736
29863
  import Dockerode2 from "dockerode";
29737
29864
  var JOB_WORKSPACE_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
29738
29865
  var STALE_CONTAINER_RETENTION_SECONDS = 24 * 60 * 60;
29739
29866
  var SESSION_RESUME_MARKER = "resume-pending";
29740
29867
  function sessionResumeMarkerPath(workspaceDir) {
29741
- return join3(workspaceDir, ".aglio", SESSION_RESUME_MARKER);
29868
+ return join4(workspaceDir, ".aglio", SESSION_RESUME_MARKER);
29742
29869
  }
29743
29870
  async function workspaceHasResumeMarker(workspaceDir) {
29744
29871
  return access2(sessionResumeMarkerPath(workspaceDir)).then(
@@ -29761,16 +29888,16 @@ async function cleanupRemoteWorkspaces(input2) {
29761
29888
  const rootEntries = await readdir(input2.workspaceRoot, { withFileTypes: true }).catch(() => []);
29762
29889
  for (const workspace of rootEntries) {
29763
29890
  if (!workspace.isDirectory()) continue;
29764
- const workspaceDir = join3(input2.workspaceRoot, workspace.name);
29891
+ const workspaceDir = join4(input2.workspaceRoot, workspace.name);
29765
29892
  const projectEntries = await readdir(workspaceDir, { withFileTypes: true }).catch(() => []);
29766
29893
  for (const project of projectEntries) {
29767
29894
  if (!project.isDirectory()) continue;
29768
- const projectDir = join3(workspaceDir, project.name);
29895
+ const projectDir = join4(workspaceDir, project.name);
29769
29896
  const jobEntries = await readdir(projectDir, { withFileTypes: true }).catch(() => []);
29770
29897
  for (const job of jobEntries) {
29771
29898
  if (!job.isDirectory()) continue;
29772
29899
  scanned += 1;
29773
- const jobDir = join3(projectDir, job.name);
29900
+ const jobDir = join4(projectDir, job.name);
29774
29901
  const info = await stat2(jobDir).catch(() => null);
29775
29902
  if (!info) continue;
29776
29903
  if (!shouldRemoveWorkspace({
@@ -29932,12 +30059,12 @@ async function runRemoteCardJob(deps) {
29932
30059
  const lease = { jobId: job.id, leaseId: job.lease.id, token: job.lease.token };
29933
30060
  let context;
29934
30061
  try {
29935
- const workspaceReady = await stat3(join4(workspace, ".aglio")).then(
30062
+ const workspaceReady = await stat3(join5(workspace, ".aglio")).then(
29936
30063
  (info) => info.isDirectory(),
29937
30064
  () => false
29938
30065
  );
29939
30066
  context = await deps.api.fetchContext({ ...lease, workspaceRoot: runRoot, workspaceReady });
29940
- 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);
29941
30068
  } catch (error62) {
29942
30069
  return { state: "failed", error: `Could not load run context: ${errorText2(error62)}` };
29943
30070
  }
@@ -29946,9 +30073,9 @@ async function runRemoteCardJob(deps) {
29946
30073
  write("[aglio] waiting for a free run slot\n");
29947
30074
  return { state: "succeeded", error: null };
29948
30075
  }
29949
- const toolDir = join4(workspace, ".aglio", context.agent.driver);
29950
- const homeHost = join4(toolDir, "home");
29951
- 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");
29952
30079
  const mcpRelativePath = mcpConfigRelativePath(context.agent.driver);
29953
30080
  const mcpUrl = deps.mcp ? `${deps.mcp.baseUrl.replace(/\/+$/, "")}/mcp/${context.run.id}` : "";
29954
30081
  const extraServers = context.mcp.servers;
@@ -29996,7 +30123,7 @@ async function runRemoteCardJob(deps) {
29996
30123
  options: context.agent.options,
29997
30124
  sessionId: context.run.sessionId,
29998
30125
  resume: Boolean(context.run.sessionId),
29999
- mcpConfigPath: mcpRelativePath ? containerPath(runRoot, workspace, join4(toolDir, mcpRelativePath)) : "",
30126
+ mcpConfigPath: mcpRelativePath ? containerPath(runRoot, workspace, join5(toolDir, mcpRelativePath)) : "",
30000
30127
  piHome: containerPath(runRoot, workspace, toolDir),
30001
30128
  workingDirectory: repoPaths.length === 1 ? containerPath(runRoot, workspace, repoPaths[0]) : runRoot,
30002
30129
  systemPromptAppend: context.systemPromptAppend
@@ -30016,14 +30143,14 @@ async function runRemoteCardJob(deps) {
30016
30143
  deps.mcp?.stopRun(context.run.id);
30017
30144
  await rm2(homeHost, { recursive: true, force: true }).catch(() => void 0);
30018
30145
  await rm2(gitConfigPath, { force: true }).catch(() => void 0);
30019
- await rm2(join4(toolDir, "models.json"), { force: true }).catch(() => void 0);
30146
+ await rm2(join5(toolDir, "models.json"), { force: true }).catch(() => void 0);
30020
30147
  if (mcpRelativePath) {
30021
- await rm2(join4(toolDir, mcpRelativePath), { force: true }).catch(() => void 0);
30148
+ await rm2(join5(toolDir, mcpRelativePath), { force: true }).catch(() => void 0);
30022
30149
  }
30023
30150
  }
30024
30151
  async function prepareWorkspace() {
30025
30152
  await mkdir2(homeHost, { recursive: true, mode: 448 });
30026
- await mkdir2(join4(workspace, ".aglio", "attachments"), { recursive: true });
30153
+ await mkdir2(join5(workspace, ".aglio", "attachments"), { recursive: true });
30027
30154
  const files = credentialFiles(context.agent.credential.payload);
30028
30155
  if (context.agent.credential.authType === "session_bundle" && Object.keys(files).length === 0) {
30029
30156
  throw new Error(`Credential "${context.agent.credential.label}" has no session files.`);
@@ -30044,7 +30171,7 @@ async function runRemoteCardJob(deps) {
30044
30171
  throw new Error(`Credential "${context.agent.credential.label}" has no base URL.`);
30045
30172
  }
30046
30173
  await writeFile(
30047
- join4(toolDir, "models.json"),
30174
+ join5(toolDir, "models.json"),
30048
30175
  JSON.stringify(
30049
30176
  {
30050
30177
  providers: {
@@ -30063,7 +30190,7 @@ async function runRemoteCardJob(deps) {
30063
30190
  );
30064
30191
  }
30065
30192
  if (mcpRelativePath && mcpUrl) {
30066
- const hostConfigPath = join4(toolDir, mcpRelativePath);
30193
+ const hostConfigPath = join5(toolDir, mcpRelativePath);
30067
30194
  await mkdir2(dirname(hostConfigPath), { recursive: true });
30068
30195
  await writeFile(
30069
30196
  hostConfigPath,
@@ -30093,7 +30220,7 @@ async function runRemoteCardJob(deps) {
30093
30220
  try {
30094
30221
  const data = await deps.api.fetchAttachment({ ...lease, artifactId: attachment.id });
30095
30222
  await writeFile(
30096
- join4(workspace, ".aglio", "attachments", artifactFileName(attachment.id, attachment.label)),
30223
+ join5(workspace, ".aglio", "attachments", artifactFileName(attachment.id, attachment.label)),
30097
30224
  data
30098
30225
  );
30099
30226
  } catch {
@@ -30105,10 +30232,10 @@ async function runRemoteCardJob(deps) {
30105
30232
  async function cloneRepos() {
30106
30233
  const repoPaths = [];
30107
30234
  for (const repo of context.repos) {
30108
- const target = inside2(workspace, join4(workspace, repo.name));
30235
+ const target = inside2(workspace, join5(workspace, repo.name));
30109
30236
  const targetRun = containerPath(runRoot, workspace, target);
30110
30237
  repoPaths.push(target);
30111
- const hasGit = await readFile2(join4(target, ".git", "HEAD"), "utf8").then(
30238
+ const hasGit = await readFile2(join5(target, ".git", "HEAD"), "utf8").then(
30112
30239
  () => true,
30113
30240
  () => false
30114
30241
  );
@@ -30212,7 +30339,7 @@ async function runRemoteCardJob(deps) {
30212
30339
  };
30213
30340
  }
30214
30341
  async function pushBranch(repo, branch) {
30215
- const target = inside2(workspace, join4(workspace, repo.name));
30342
+ const target = inside2(workspace, join5(workspace, repo.name));
30216
30343
  const targetRun = containerPath(runRoot, workspace, target);
30217
30344
  write(`[aglio] pushing ${repo.name} ${branch}
30218
30345
  `);
@@ -30473,7 +30600,7 @@ async function runRemoteCardJob(deps) {
30473
30600
 
30474
30601
  // src/remote-provider-login.ts
30475
30602
  import { mkdir as mkdir3, readFile as readFile3, rm as rm3 } from "node:fs/promises";
30476
- import { join as join5, resolve as resolve4, sep } from "node:path";
30603
+ import { join as join6, resolve as resolve4, sep } from "node:path";
30477
30604
  var FINAL = ["completed", "failed", "cancelled", "expired"];
30478
30605
  var POLL_MS = 700;
30479
30606
  var KICKOFF_MS = 4e3;
@@ -30486,8 +30613,8 @@ function errorText3(error62) {
30486
30613
  async function runRemoteProviderLogin(input2) {
30487
30614
  let session = await input2.api.fetch();
30488
30615
  const method = session.command;
30489
- const root = join5(input2.workspace, "login");
30490
- const home = join5(root, "home");
30616
+ const root = join6(input2.workspace, "login");
30617
+ const home = join6(root, "home");
30491
30618
  const target = resolve4(home, session.bundlePath);
30492
30619
  if (!target.startsWith(resolve4(home) + sep)) {
30493
30620
  return { state: "failed", error: "Unsafe credential bundle path." };
@@ -31316,7 +31443,7 @@ async function runRemoteWorker(config2, deps = {}) {
31316
31443
  // src/remote-mcp.ts
31317
31444
  import { createServer as createServer2 } from "node:http";
31318
31445
  import { mkdir as mkdir5, readFile as readFile5, stat as stat4, writeFile as writeFile3 } from "node:fs/promises";
31319
- 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";
31320
31447
 
31321
31448
  // ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v3/helpers/util.js
31322
31449
  var util;
@@ -43024,7 +43151,7 @@ function fail(message) {
43024
43151
  function workspaceHostPath(workspace, input2) {
43025
43152
  const root = resolve5(workspace);
43026
43153
  const trimmed = input2.trim();
43027
- 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));
43028
43155
  const normalized = resolve5(mapped);
43029
43156
  const rel = relative4(root, normalized);
43030
43157
  if (rel.startsWith("..") || isAbsolute3(rel)) throw new Error("Artifact path escapes the workspace");
@@ -43134,9 +43261,9 @@ function createServerForRun(run) {
43134
43261
  try {
43135
43262
  const result = await run.callTool({ tool, input: input2 });
43136
43263
  if (run.kind !== "planning" && (tool === "question_ask" || tool === "spec_propose") && result && typeof result === "object" && ["awaiting_input", "pending"].includes(String(result.status ?? ""))) {
43137
- await mkdir5(join6(run.workspace, ".aglio"), { recursive: true }).catch(() => void 0);
43264
+ await mkdir5(join7(run.workspace, ".aglio"), { recursive: true }).catch(() => void 0);
43138
43265
  await writeFile3(
43139
- join6(run.workspace, ".aglio", SESSION_RESUME_MARKER),
43266
+ join7(run.workspace, ".aglio", SESSION_RESUME_MARKER),
43140
43267
  (/* @__PURE__ */ new Date()).toISOString(),
43141
43268
  "utf8"
43142
43269
  ).catch((error62) => run.log(`resume marker write failed: ${String(error62)}`));
@@ -43416,7 +43543,7 @@ async function main() {
43416
43543
  const mode = normalizeWorkerMode(process.env.AGLIO_WORKER_MODE?.trim() || "docker");
43417
43544
  const os = normalizeWorkerOs(process.env.AGLIO_WORKER_OS?.trim() || defaultOs());
43418
43545
  const labels = parseWorkerLabels(process.env.AGLIO_WORKER_LABELS ?? "");
43419
- 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");
43420
43547
  const workspaceRoot = process.env.AGLIO_WORKER_WORKSPACE?.trim() || defaultWorkspaceRoot();
43421
43548
  const hostWorkspaceRoot = process.env.AGLIO_WORKER_HOST_WORKSPACE?.trim() || workspaceRoot;
43422
43549
  const runnerImage = defaultRunnerImage();
@@ -43472,6 +43599,13 @@ async function main() {
43472
43599
  );
43473
43600
  const hostTools = await verifyHostRuntime({ mode, labels, platform: process.platform });
43474
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
+ }
43475
43609
  try {
43476
43610
  process.exitCode = await runRemoteWorker(config2, {
43477
43611
  signal: controller.signal,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aglio-worker",
3
- "version": "0.9.2",
3
+ "version": "0.11.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": {