@madarco/agentbox 0.7.0 → 0.9.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 (77) hide show
  1. package/dist/_cloud-attach-ZXBCNWJX.js +13 -0
  2. package/dist/{chunk-NW5NYTQM.js → chunk-BXQMIEHC.js} +459 -110
  3. package/dist/chunk-BXQMIEHC.js.map +1 -0
  4. package/dist/{chunk-UK72UQ5U.js → chunk-G3H2L3O2.js} +55 -4
  5. package/dist/chunk-G3H2L3O2.js.map +1 -0
  6. package/dist/{chunk-7KOEFGN2.js → chunk-GU5LW4B5.js} +385 -31
  7. package/dist/chunk-GU5LW4B5.js.map +1 -0
  8. package/dist/chunk-KL36BRN4.js +455 -0
  9. package/dist/chunk-KL36BRN4.js.map +1 -0
  10. package/dist/{chunk-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
  11. package/dist/chunk-LEV3KICD.js.map +1 -0
  12. package/dist/chunk-MTVI44DW.js +662 -0
  13. package/dist/chunk-MTVI44DW.js.map +1 -0
  14. package/dist/{chunk-NAVL4R34.js → chunk-NCJP5MTN.js} +1281 -556
  15. package/dist/chunk-NCJP5MTN.js.map +1 -0
  16. package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
  17. package/dist/{dist-ETCFRVPA.js → dist-32EZBYG4.js} +50 -20
  18. package/dist/{dist-R67WMLCF.js → dist-CX5CGVEB.js} +120 -10
  19. package/dist/dist-CX5CGVEB.js.map +1 -0
  20. package/dist/{dist-QZGJIBT5.js → dist-GDHP34ZK.js} +141 -75
  21. package/dist/dist-GDHP34ZK.js.map +1 -0
  22. package/dist/dist-XML54CNB.js +849 -0
  23. package/dist/dist-XML54CNB.js.map +1 -0
  24. package/dist/index.js +3881 -867
  25. package/dist/index.js.map +1 -1
  26. package/dist/prepared-state-CL4CWXQA-H5THETIM.js +18 -0
  27. package/dist/prepared-state-CL4CWXQA-H5THETIM.js.map +1 -0
  28. package/package.json +7 -5
  29. package/runtime/daytona/custom-system-CLAUDE.md +39 -0
  30. package/runtime/docker/Dockerfile.box +22 -0
  31. package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +1 -1
  32. package/runtime/docker/packages/ctl/dist/bin.cjs +1214 -98
  33. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
  34. package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
  35. package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
  36. package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
  37. package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
  38. package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
  39. package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
  40. package/runtime/hetzner/agentbox-codex-hooks.json +66 -35
  41. package/runtime/hetzner/agentbox-setup-skill.md +1 -1
  42. package/runtime/hetzner/agentbox-vnc-start +15 -1
  43. package/runtime/hetzner/claude-managed-settings.json +62 -1
  44. package/runtime/hetzner/ctl.cjs +1214 -98
  45. package/runtime/hetzner/custom-system-CLAUDE.md +26 -14
  46. package/runtime/hetzner/gh-shim +263 -0
  47. package/runtime/hetzner/git-shim +131 -0
  48. package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
  49. package/runtime/hetzner/scripts/install-box.sh +11 -2
  50. package/runtime/relay/bin.cjs +1146 -63
  51. package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
  52. package/runtime/vercel/agentbox-codex-hooks.json +68 -0
  53. package/runtime/vercel/agentbox-open +28 -0
  54. package/runtime/vercel/agentbox-setup-skill.md +196 -0
  55. package/runtime/vercel/agentbox-vnc-start +91 -0
  56. package/runtime/vercel/claude-managed-settings.json +115 -0
  57. package/runtime/vercel/ctl.cjs +23466 -0
  58. package/runtime/vercel/custom-system-CLAUDE.md +50 -0
  59. package/runtime/vercel/gh-shim +263 -0
  60. package/runtime/vercel/git-shim +131 -0
  61. package/runtime/vercel/scripts/provision.sh +274 -0
  62. package/share/agentbox-setup/SKILL.md +1 -1
  63. package/share/host-skills/agentbox/SKILL.md +29 -0
  64. package/share/host-skills/agentbox-info/SKILL.md +211 -0
  65. package/share/host-skills/codex/agentbox.md +35 -0
  66. package/share/host-skills/opencode/agentbox.md +26 -0
  67. package/dist/_cloud-attach-DMVH6GWO.js +0 -12
  68. package/dist/chunk-7KOEFGN2.js.map +0 -1
  69. package/dist/chunk-NAVL4R34.js.map +0 -1
  70. package/dist/chunk-NW5NYTQM.js.map +0 -1
  71. package/dist/chunk-UK72UQ5U.js.map +0 -1
  72. package/dist/chunk-V5KZGB5V.js.map +0 -1
  73. package/dist/dist-QZGJIBT5.js.map +0 -1
  74. package/dist/dist-R67WMLCF.js.map +0 -1
  75. /package/dist/{_cloud-attach-DMVH6GWO.js.map → _cloud-attach-ZXBCNWJX.js.map} +0 -0
  76. /package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
  77. /package/dist/{dist-ETCFRVPA.js.map → dist-32EZBYG4.js.map} +0 -0
@@ -20,7 +20,7 @@ import {
20
20
  } from "./chunk-I24B6AXR.js";
21
21
  import {
22
22
  createCloudProvider
23
- } from "./chunk-NW5NYTQM.js";
23
+ } from "./chunk-BXQMIEHC.js";
24
24
  import {
25
25
  stageClaudeCredentialsForUpload,
26
26
  stageClaudeStaticForUpload,
@@ -28,33 +28,35 @@ import {
28
28
  stageCodexStaticForUpload,
29
29
  stageOpencodeCredentialsForUpload,
30
30
  stageOpencodeStaticForUpload
31
- } from "./chunk-NAVL4R34.js";
32
- import "./chunk-UK72UQ5U.js";
31
+ } from "./chunk-NCJP5MTN.js";
32
+ import {
33
+ computeContextSha256,
34
+ preparedStatePathFor,
35
+ readCliStamp,
36
+ readPreparedStateRaw,
37
+ writePreparedStateRaw
38
+ } from "./chunk-KL36BRN4.js";
39
+ import "./chunk-G3H2L3O2.js";
33
40
 
34
41
  // ../../packages/sandbox-hetzner/dist/index.js
35
- import { existsSync as existsSync4 } from "fs";
42
+ import { existsSync as existsSync3 } from "fs";
36
43
  import { rm as rm2, rename, mkdir as mkdir3 } from "fs/promises";
37
44
  import { join as join4 } from "path";
38
45
  import { execa as execa4 } from "execa";
39
46
  import { resolve as resolvePath } from "path";
40
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
41
- import { homedir } from "os";
42
- import { dirname, resolve } from "path";
43
- import { readFile as readFile2 } from "fs/promises";
44
47
  import { join as join2 } from "path";
45
- import { createHash } from "crypto";
46
- import { existsSync as existsSync2 } from "fs";
47
- import { dirname as dirname2, resolve as resolve2 } from "path";
48
+ import { existsSync } from "fs";
49
+ import { dirname, resolve } from "path";
48
50
  import { fileURLToPath } from "url";
49
51
  import { mkdir, readFile } from "fs/promises";
50
- import { dirname as dirname3, join, resolve as resolve3 } from "path";
52
+ import { dirname as dirname2, join, resolve as resolve2 } from "path";
51
53
  import { execa } from "execa";
52
54
  import { execa as execa2 } from "execa";
53
- import { existsSync as existsSync3 } from "fs";
55
+ import { existsSync as existsSync2 } from "fs";
54
56
  import { mkdir as mkdir2, rm } from "fs/promises";
55
57
  import { createServer } from "net";
56
- import { homedir as homedir2 } from "os";
57
- import { dirname as dirname4, join as join3, resolve as resolve4 } from "path";
58
+ import { homedir } from "os";
59
+ import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
58
60
  import { execa as execa3 } from "execa";
59
61
  function generatePrepareCloudInit(opts) {
60
62
  const pubkey = opts.sshPubkey.trim();
@@ -145,51 +147,56 @@ async function pollUntil(label, check, opts = {}) {
145
147
  interval = Math.min(interval * 2, max);
146
148
  }
147
149
  }
148
- var SCHEMA = 1;
150
+ var SCHEMA = 2;
149
151
  function preparedStatePath() {
150
- return resolve(homedir(), ".agentbox", "hetzner-prepared.json");
152
+ return preparedStatePathFor("hetzner");
151
153
  }
152
154
  function readPreparedState() {
153
- const path = preparedStatePath();
154
- if (!existsSync(path)) return { schema: SCHEMA, projects: {} };
155
- try {
156
- const raw = readFileSync(path, "utf8");
157
- const parsed = JSON.parse(raw);
158
- if (parsed.schema !== SCHEMA) {
159
- return { schema: SCHEMA, projects: {} };
160
- }
161
- return {
162
- schema: SCHEMA,
163
- base: parsed.base,
164
- projects: parsed.projects ?? {}
165
- };
166
- } catch {
167
- return { schema: SCHEMA, projects: {} };
155
+ const raw = readPreparedStateRaw("hetzner");
156
+ if (raw === null || typeof raw !== "object") return { schema: SCHEMA };
157
+ const parsed = raw;
158
+ if (parsed.schema === 1) {
159
+ const v1 = parsed;
160
+ return migrateFromV1(v1);
168
161
  }
162
+ if (parsed.schema !== SCHEMA) {
163
+ return { schema: SCHEMA };
164
+ }
165
+ return {
166
+ schema: SCHEMA,
167
+ base: parsed.base
168
+ };
169
+ }
170
+ function migrateFromV1(v1) {
171
+ const base = v1.base ? {
172
+ imageId: v1.base.imageId,
173
+ description: v1.base.description,
174
+ createdAt: v1.base.createdAt,
175
+ contextSha256: v1.base.installScriptSha256
176
+ } : void 0;
177
+ return {
178
+ schema: SCHEMA,
179
+ base
180
+ };
169
181
  }
170
182
  function writePreparedState(state) {
171
- const path = preparedStatePath();
172
- mkdirSync(dirname(path), { recursive: true });
173
- const body = JSON.stringify(state, null, 2) + "\n";
174
- const tmp = `${path}.tmp`;
175
- writeFileSync(tmp, body, { mode: 384 });
176
- renameSync(tmp, path);
183
+ writePreparedStateRaw("hetzner", state);
177
184
  }
178
185
  function updatePreparedState(mutate) {
179
186
  const s = readPreparedState();
180
187
  mutate(s);
181
188
  writePreparedState(s);
182
189
  }
183
- var SELF = dirname2(fileURLToPath(import.meta.url));
190
+ var SELF = dirname(fileURLToPath(import.meta.url));
184
191
  function findStagedCliRuntimeRoot() {
185
192
  const candidates = [
186
- resolve2(SELF, "..", "runtime"),
193
+ resolve(SELF, "..", "runtime"),
187
194
  // <cliRoot>/dist/.. → <cliRoot> then /runtime
188
- resolve2(SELF, "..", "..", "runtime")
195
+ resolve(SELF, "..", "..", "runtime")
189
196
  // chunk-NNNN.js at <cliRoot>/dist/<sub>/.. → <cliRoot>/runtime
190
197
  ];
191
198
  for (const c of candidates) {
192
- if (existsSync2(resolve2(c, "hetzner", "scripts", "install-box.sh"))) return c;
199
+ if (existsSync(resolve(c, "hetzner", "scripts", "install-box.sh"))) return c;
193
200
  }
194
201
  return void 0;
195
202
  }
@@ -200,6 +207,8 @@ var RUNTIME_ASSETS = [
200
207
  { name: "agentbox-dockerd-start", remoteBasename: "agentbox-dockerd-start", remoteMode: 493 },
201
208
  { name: "agentbox-checkpoint-cleanup", remoteBasename: "agentbox-checkpoint-cleanup", remoteMode: 493 },
202
209
  { name: "agentbox-open", remoteBasename: "agentbox-open", remoteMode: 493 },
210
+ { name: "gh-shim", remoteBasename: "agentbox-gh-shim", remoteMode: 493 },
211
+ { name: "git-shim", remoteBasename: "agentbox-git-shim", remoteMode: 493 },
203
212
  { name: "custom-system-CLAUDE.md", remoteBasename: "agentbox-custom-CLAUDE.md", remoteMode: 420 },
204
213
  { name: "claude-managed-settings.json", remoteBasename: "agentbox-managed-settings.json", remoteMode: 420 },
205
214
  { name: "agentbox-codex-hooks.json", remoteBasename: "agentbox-codex-hooks.json", remoteMode: 420 },
@@ -215,7 +224,9 @@ function candidatesFor(name, opts = {}) {
215
224
  "agentbox-dockerd-start": ["packages/sandbox-docker/scripts/agentbox-dockerd-start"],
216
225
  "agentbox-checkpoint-cleanup": ["packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup"],
217
226
  "agentbox-open": ["packages/sandbox-docker/scripts/agentbox-open"],
218
- "custom-system-CLAUDE.md": ["packages/sandbox-docker/scripts/custom-system-CLAUDE.md"],
227
+ "gh-shim": ["packages/sandbox-docker/scripts/gh-shim"],
228
+ "git-shim": ["packages/sandbox-docker/scripts/git-shim"],
229
+ "custom-system-CLAUDE.md": ["packages/sandbox-hetzner/scripts/custom-system-CLAUDE.md"],
219
230
  "claude-managed-settings.json": ["packages/sandbox-docker/scripts/claude-managed-settings.json"],
220
231
  "agentbox-codex-hooks.json": ["packages/sandbox-docker/scripts/agentbox-codex-hooks.json"],
221
232
  "agentbox-setup-skill.md": ["apps/cli/share/agentbox-setup/SKILL.md"]
@@ -227,16 +238,18 @@ function candidatesFor(name, opts = {}) {
227
238
  "agentbox-dockerd-start": ["hetzner/agentbox-dockerd-start", "docker/packages/sandbox-docker/scripts/agentbox-dockerd-start"],
228
239
  "agentbox-checkpoint-cleanup": ["hetzner/agentbox-checkpoint-cleanup", "docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup"],
229
240
  "agentbox-open": ["hetzner/agentbox-open", "docker/packages/sandbox-docker/scripts/agentbox-open"],
230
- "custom-system-CLAUDE.md": ["hetzner/custom-system-CLAUDE.md", "docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md"],
241
+ "gh-shim": ["hetzner/gh-shim", "docker/packages/sandbox-docker/scripts/gh-shim"],
242
+ "git-shim": ["hetzner/git-shim", "docker/packages/sandbox-docker/scripts/git-shim"],
243
+ "custom-system-CLAUDE.md": ["hetzner/custom-system-CLAUDE.md"],
231
244
  "claude-managed-settings.json": ["hetzner/claude-managed-settings.json", "docker/packages/sandbox-docker/scripts/claude-managed-settings.json"],
232
245
  "agentbox-codex-hooks.json": ["hetzner/agentbox-codex-hooks.json", "docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json"],
233
246
  "agentbox-setup-skill.md": ["hetzner/agentbox-setup-skill.md", "docker/apps/cli/share/agentbox-setup/SKILL.md"]
234
247
  };
235
248
  const out = [];
236
249
  if (cliRoot) {
237
- for (const rel of cliRelative[name] ?? []) out.push(resolve2(cliRoot, rel));
250
+ for (const rel of cliRelative[name] ?? []) out.push(resolve(cliRoot, rel));
238
251
  }
239
- for (const rel of monorepoRelative[name] ?? []) out.push(resolve2(monorepo, rel));
252
+ for (const rel of monorepoRelative[name] ?? []) out.push(resolve(monorepo, rel));
240
253
  return out;
241
254
  }
242
255
  function resolveRuntimeAssets(opts = {}) {
@@ -244,7 +257,7 @@ function resolveRuntimeAssets(opts = {}) {
244
257
  const missing = [];
245
258
  for (const asset of RUNTIME_ASSETS) {
246
259
  const cands = candidatesFor(asset.name, opts);
247
- const hit = cands.find((p) => existsSync2(p));
260
+ const hit = cands.find((p) => existsSync(p));
248
261
  if (!hit) {
249
262
  missing.push({ name: asset.name, tried: cands });
250
263
  continue;
@@ -265,15 +278,15 @@ If you are running from the monorepo, ensure \`pnpm -w build\` has run so packag
265
278
  function guessRepoRoot() {
266
279
  let cur = SELF;
267
280
  for (let i = 0; i < 8; i++) {
268
- if (existsSync2(resolve2(cur, "pnpm-workspace.yaml"))) return cur;
269
- const parent = dirname2(cur);
281
+ if (existsSync(resolve(cur, "pnpm-workspace.yaml"))) return cur;
282
+ const parent = dirname(cur);
270
283
  if (parent === cur) break;
271
284
  cur = parent;
272
285
  }
273
286
  return SELF;
274
287
  }
275
288
  async function mintSshKey(targetDir, comment) {
276
- const dir = resolve3(targetDir);
289
+ const dir = resolve2(targetDir);
277
290
  const priv = join(dir, "id_ed25519");
278
291
  const pub = `${priv}.pub`;
279
292
  await mkdir(dir, { recursive: true, mode: 448 });
@@ -286,14 +299,14 @@ async function mintSshKey(targetDir, comment) {
286
299
  return { dir, privatePath: priv, publicPath: pub, publicKey };
287
300
  }
288
301
  async function mintPrepareKey() {
289
- const root = resolve3(homedirOrCwd(), ".agentbox", "hetzner", `prepare-${Date.now().toString(36)}`);
302
+ const root = resolve2(homedirOrCwd(), ".agentbox", "hetzner", `prepare-${Date.now().toString(36)}`);
290
303
  const key = await mintSshKey(root, `agentbox-prepare-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
291
304
  return {
292
305
  ...key,
293
306
  cleanup: async () => {
294
307
  try {
295
308
  const { rm: rm3 } = await import("fs/promises");
296
- await rm3(dirname3(key.privatePath), { recursive: true, force: true });
309
+ await rm3(dirname2(key.privatePath), { recursive: true, force: true });
297
310
  } catch {
298
311
  }
299
312
  }
@@ -421,16 +434,14 @@ async function prepareHetzner(opts = {}) {
421
434
  cliRuntimeRoot: opts.cliRuntimeRoot ?? findStagedCliRuntimeRoot(),
422
435
  repoRoot: opts.repoRoot
423
436
  });
424
- const installAsset = assets.find((a) => a.name === "install-box.sh");
425
- if (!installAsset) {
426
- throw new Error("prepare: missing install-box.sh asset (this should have been caught earlier)");
427
- }
428
- const installSha = await sha256OfFile(installAsset.localPath);
437
+ const contextSha = await computeContextSha256(
438
+ assets.map((a) => ({ rel: a.name, abs: a.localPath }))
439
+ );
429
440
  if (!opts.force && existingState.base) {
430
441
  const remote = await client2.getImage(existingState.base.imageId).catch(() => null);
431
- if (remote && existingState.base.installScriptSha256 === installSha) {
442
+ if (remote && existingState.base.contextSha256 === contextSha) {
432
443
  progress(
433
- `base snapshot ${String(existingState.base.imageId)} already exists (sha matches); skipping rebuild (pass --force to override)`
444
+ `base snapshot ${String(existingState.base.imageId)} already exists (fingerprint ${contextSha.slice(0, 12)} matches); skipping rebuild (pass --force to override)`
434
445
  );
435
446
  return {
436
447
  snapshotName: existingState.base.description,
@@ -440,7 +451,9 @@ async function prepareHetzner(opts = {}) {
440
451
  if (!remote) {
441
452
  progress(`recorded base snapshot ${String(existingState.base.imageId)} is gone on Hetzner; rebuilding`);
442
453
  } else {
443
- progress(`install script changed; rebuilding base snapshot`);
454
+ progress(
455
+ `build context changed (was ${existingState.base.contextSha256?.slice(0, 12) ?? "<none>"}, now ${contextSha.slice(0, 12)}); rebuilding base snapshot`
456
+ );
444
457
  }
445
458
  }
446
459
  progress("minting ephemeral ssh key");
@@ -566,11 +579,14 @@ The full trace was preserved at /var/log/agentbox/install.log inside any box mad
566
579
  );
567
580
  progress("persisting hetzner-prepared.json");
568
581
  const state = readPreparedState();
582
+ const cliStamp = readCliStamp();
569
583
  state.base = {
570
584
  imageId: ready.id,
571
585
  description: ready.description,
572
586
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
573
- installScriptSha256: installSha
587
+ contextSha256: contextSha,
588
+ cliVersion: cliStamp.cliVersion,
589
+ cliCommit: cliStamp.cliCommit
574
590
  };
575
591
  writePreparedState(state);
576
592
  log(`prepare-hetzner: wrote ${preparedStatePath()}`);
@@ -608,10 +624,6 @@ The full trace was preserved at /var/log/agentbox/install.log inside any box mad
608
624
  await key.cleanup();
609
625
  }
610
626
  }
611
- async function sha256OfFile(path) {
612
- const buf = await readFile2(path);
613
- return createHash("sha256").update(buf).digest("hex");
614
- }
615
627
  var prepareHetznerProvider = (req) => prepareHetzner({
616
628
  name: req.name,
617
629
  hostWorkspace: req.hostWorkspace ?? process.cwd(),
@@ -649,11 +661,11 @@ var SshTunnelManager = class {
649
661
  boxSshDir,
650
662
  forwards: /* @__PURE__ */ new Map()
651
663
  };
652
- if (existsSync3(controlPath) && await this.isAlive(controlPath)) {
664
+ if (existsSync2(controlPath) && await this.isAlive(controlPath)) {
653
665
  this.boxes.set(opts.boxId, tunnel);
654
666
  return;
655
667
  }
656
- if (existsSync3(controlPath)) {
668
+ if (existsSync2(controlPath)) {
657
669
  await rm(controlPath, { force: true });
658
670
  }
659
671
  const connectTimeout = opts.connectTimeoutSeconds ?? 10;
@@ -685,7 +697,7 @@ var SshTunnelManager = class {
685
697
  `${user}@${opts.vpsHost}`
686
698
  ];
687
699
  const res = await execa3("ssh", argv, { reject: false });
688
- if (res.exitCode !== 0 || !existsSync3(controlPath)) {
700
+ if (res.exitCode !== 0 || !existsSync2(controlPath)) {
689
701
  throw new Error(
690
702
  `ssh ControlMaster failed for ${opts.boxId} (exit ${String(res.exitCode)}): ${res.stderr || res.stdout || "(no output)"}`
691
703
  );
@@ -695,11 +707,22 @@ var SshTunnelManager = class {
695
707
  /**
696
708
  * Mint (or fetch the cached) `127.0.0.1:<localPort> → vps:127.0.0.1:<remotePort>`
697
709
  * forward. Returns the local port. Idempotent per (boxId, remotePort).
710
+ *
711
+ * The cached entry is only returned when the underlying ControlMaster is
712
+ * still alive — without that check we'd happily hand back a localPort that
713
+ * stopped listening when the master died (e.g. transient network blip,
714
+ * host sleep/wake). When the master is dead we drop ALL cached forwards
715
+ * for this box (they all share one tunnel) and re-mint from scratch.
698
716
  */
699
717
  async forward(boxId, remotePort) {
700
718
  const tunnel = this.getTunnelOrThrow(boxId);
701
719
  const cached = tunnel.forwards.get(remotePort);
702
- if (cached !== void 0) return cached;
720
+ if (cached !== void 0 && await this.isAlive(tunnel.controlPath)) {
721
+ return cached;
722
+ }
723
+ if (cached !== void 0) {
724
+ tunnel.forwards.clear();
725
+ }
703
726
  const localPort = await pickFreePort();
704
727
  const argv = [
705
728
  "-O",
@@ -720,6 +743,37 @@ var SshTunnelManager = class {
720
743
  tunnel.forwards.set(remotePort, localPort);
721
744
  return localPort;
722
745
  }
746
+ /**
747
+ * Tear down a dead ControlMaster + every cached forward for this box,
748
+ * then re-open from scratch. Idempotent — if the master is already alive
749
+ * the master open() is a no-op, but the cached forwards still get
750
+ * cleared so the next forward() call re-mints them. Returns when the
751
+ * master is open and the box's forwards map is empty (ready for fresh
752
+ * forward() calls).
753
+ *
754
+ * Use case: the cloud-poller observes ECONNREFUSED on the local port and
755
+ * asks the backend to refresh the preview URL — that path calls into
756
+ * here so the master + forward both come back fresh.
757
+ */
758
+ async refresh(opts) {
759
+ const existing = this.boxes.get(opts.boxId);
760
+ if (existing) {
761
+ const alive = await this.isAlive(existing.controlPath);
762
+ if (!alive && existsSync2(existing.controlPath)) {
763
+ try {
764
+ await execa3(
765
+ "ssh",
766
+ ["-O", "exit", "-S", existing.controlPath, "dummy"],
767
+ { reject: false }
768
+ );
769
+ } catch {
770
+ }
771
+ await rm(existing.controlPath, { force: true });
772
+ }
773
+ existing.forwards.clear();
774
+ }
775
+ await this.open(opts);
776
+ }
723
777
  /** Tear down a single forward. Idempotent — unknown ports are no-ops. */
724
778
  async unforward(boxId, remotePort) {
725
779
  const tunnel = this.getTunnelOrThrow(boxId);
@@ -744,7 +798,7 @@ var SshTunnelManager = class {
744
798
  async close(boxId) {
745
799
  const tunnel = this.boxes.get(boxId);
746
800
  if (!tunnel) return;
747
- if (existsSync3(tunnel.controlPath)) {
801
+ if (existsSync2(tunnel.controlPath)) {
748
802
  await execa3("ssh", ["-O", "exit", "-S", tunnel.controlPath, "dummy"], { reject: false });
749
803
  await rm(tunnel.controlPath, { force: true });
750
804
  }
@@ -790,7 +844,7 @@ var SshTunnelManager = class {
790
844
  }
791
845
  };
792
846
  function defaultBoxSshDir(boxId) {
793
- return resolve4(homedir2(), ".agentbox", "boxes", boxId, "ssh");
847
+ return resolve3(homedir(), ".agentbox", "boxes", boxId, "ssh");
794
848
  }
795
849
  async function pickFreePort() {
796
850
  return new Promise((resolveOk, reject) => {
@@ -922,7 +976,7 @@ async function ensureLiveTarget(sandboxId) {
922
976
  throw new Error(`hetzner: server ${String(id)} has no IPv4 address`);
923
977
  }
924
978
  const state = await ensurePerBoxState(sandboxId);
925
- if (!existsSync4(state.identity)) {
979
+ if (!existsSync3(state.identity)) {
926
980
  throw new Error(
927
981
  `hetzner: per-box SSH key missing for sandbox ${sandboxId} (expected at ${state.identity}). If this box was created by a different host, you'll need to re-create it on this host.`
928
982
  );
@@ -1038,10 +1092,10 @@ var hetznerBackend = {
1038
1092
  }
1039
1093
  }
1040
1094
  try {
1041
- if (existsSync4(key.dir)) await rm2(key.dir, { recursive: true, force: true });
1095
+ if (existsSync3(key.dir)) await rm2(key.dir, { recursive: true, force: true });
1042
1096
  if (serverId !== null) {
1043
1097
  const finalDir = perBoxDir(String(serverId));
1044
- if (existsSync4(finalDir)) await rm2(finalDir, { recursive: true, force: true });
1098
+ if (existsSync3(finalDir)) await rm2(finalDir, { recursive: true, force: true });
1045
1099
  }
1046
1100
  } catch {
1047
1101
  }
@@ -1195,6 +1249,18 @@ var hetznerBackend = {
1195
1249
  void _ttl;
1196
1250
  return this.previewUrl(h, port);
1197
1251
  },
1252
+ async refreshPreviewUrl(h, port) {
1253
+ const { state, vpsIp } = await ensureLiveTarget(h.sandboxId);
1254
+ void state;
1255
+ void vpsIp;
1256
+ await tunnels.refresh({
1257
+ boxId: h.sandboxId,
1258
+ vpsHost: vpsIp,
1259
+ identity: state.identity
1260
+ });
1261
+ const localPort = await tunnels.forward(h.sandboxId, port);
1262
+ return { url: `http://127.0.0.1:${String(localPort)}` };
1263
+ },
1198
1264
  async startInBoxPortless(h, opts) {
1199
1265
  const tlsFlag = opts.tls ? "" : "--no-tls";
1200
1266
  const startCmd = `sudo portless proxy start ${tlsFlag} -p ${String(opts.proxyPort)}`.replace(/\s+/g, " ");
@@ -1336,4 +1402,4 @@ export {
1336
1402
  withHetznerRetry,
1337
1403
  writePreparedState
1338
1404
  };
1339
- //# sourceMappingURL=dist-QZGJIBT5.js.map
1405
+ //# sourceMappingURL=dist-GDHP34ZK.js.map