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