@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.
- package/dist/_cloud-attach-T727ZPRV.js +13 -0
- package/dist/{chunk-NW5NYTQM.js → chunk-67N47KUS.js} +359 -85
- package/dist/chunk-67N47KUS.js.map +1 -0
- package/dist/{chunk-NAVL4R34.js → chunk-6OZDFNBF.js} +1084 -516
- package/dist/chunk-6OZDFNBF.js.map +1 -0
- package/dist/chunk-BGK32PZE.js +455 -0
- package/dist/chunk-BGK32PZE.js.map +1 -0
- package/dist/{chunk-7KOEFGN2.js → chunk-FODMEHD3.js} +52 -14
- package/dist/chunk-FODMEHD3.js.map +1 -0
- package/dist/{chunk-UK72UQ5U.js → chunk-G3H2L3O2.js} +55 -4
- package/dist/chunk-G3H2L3O2.js.map +1 -0
- package/dist/{chunk-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
- package/dist/chunk-LEV3KICD.js.map +1 -0
- package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
- package/dist/{dist-R67WMLCF.js → dist-L4LCG5SJ.js} +120 -10
- package/dist/dist-L4LCG5SJ.js.map +1 -0
- package/dist/{dist-ETCFRVPA.js → dist-LOZBWMBF.js} +44 -20
- package/dist/{dist-QZGJIBT5.js → dist-ZODPD2I6.js} +142 -74
- package/dist/dist-ZODPD2I6.js.map +1 -0
- package/dist/index.js +3563 -845
- package/dist/index.js.map +1 -1
- package/dist/prepared-state-CL4CWXQA-ME4HSKDE.js +18 -0
- package/dist/prepared-state-CL4CWXQA-ME4HSKDE.js.map +1 -0
- package/package.json +4 -4
- package/runtime/daytona/custom-system-CLAUDE.md +39 -0
- package/runtime/docker/Dockerfile.box +22 -0
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +1 -1
- package/runtime/docker/packages/ctl/dist/bin.cjs +1118 -71
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
- package/runtime/docker/packages/sandbox-docker/scripts/claude-managed-settings.json +62 -1
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +15 -4
- package/runtime/docker/packages/sandbox-docker/scripts/gh-shim +263 -0
- package/runtime/docker/packages/sandbox-docker/scripts/git-shim +131 -0
- package/runtime/docker/packages/sandbox-docker/scripts/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/agentbox-codex-hooks.json +66 -35
- package/runtime/hetzner/agentbox-setup-skill.md +1 -1
- package/runtime/hetzner/claude-managed-settings.json +62 -1
- package/runtime/hetzner/ctl.cjs +1118 -71
- package/runtime/hetzner/custom-system-CLAUDE.md +26 -14
- package/runtime/hetzner/gh-shim +263 -0
- package/runtime/hetzner/git-shim +131 -0
- package/runtime/hetzner/opencode-agentbox-plugin.js +76 -0
- package/runtime/hetzner/scripts/install-box.sh +11 -2
- package/runtime/relay/bin.cjs +927 -36
- package/share/agentbox-setup/SKILL.md +1 -1
- package/share/host-skills/agentbox/SKILL.md +29 -0
- package/share/host-skills/agentbox-info/SKILL.md +211 -0
- package/share/host-skills/codex/agentbox.md +35 -0
- package/share/host-skills/opencode/agentbox.md +26 -0
- package/dist/_cloud-attach-DMVH6GWO.js +0 -12
- package/dist/chunk-7KOEFGN2.js.map +0 -1
- package/dist/chunk-NAVL4R34.js.map +0 -1
- package/dist/chunk-NW5NYTQM.js.map +0 -1
- package/dist/chunk-UK72UQ5U.js.map +0 -1
- package/dist/chunk-V5KZGB5V.js.map +0 -1
- package/dist/dist-QZGJIBT5.js.map +0 -1
- package/dist/dist-R67WMLCF.js.map +0 -1
- /package/dist/{_cloud-attach-DMVH6GWO.js.map → _cloud-attach-T727ZPRV.js.map} +0 -0
- /package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js.map → cloud-poller-SUNA6ZQC-2RG5WPRN.js.map} +0 -0
- /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-
|
|
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-
|
|
32
|
-
import
|
|
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
|
|
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 {
|
|
46
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
57
|
-
import { dirname as
|
|
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 =
|
|
150
|
+
var SCHEMA = 2;
|
|
149
151
|
function preparedStatePath() {
|
|
150
|
-
return
|
|
152
|
+
return preparedStatePathFor("hetzner");
|
|
151
153
|
}
|
|
152
154
|
function readPreparedState() {
|
|
153
|
-
const
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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 =
|
|
192
|
+
var SELF = dirname(fileURLToPath(import.meta.url));
|
|
184
193
|
function findStagedCliRuntimeRoot() {
|
|
185
194
|
const candidates = [
|
|
186
|
-
|
|
195
|
+
resolve(SELF, "..", "runtime"),
|
|
187
196
|
// <cliRoot>/dist/.. → <cliRoot> then /runtime
|
|
188
|
-
|
|
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 (
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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(
|
|
252
|
+
for (const rel of cliRelative[name] ?? []) out.push(resolve(cliRoot, rel));
|
|
238
253
|
}
|
|
239
|
-
for (const rel of monorepoRelative[name] ?? []) out.push(
|
|
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) =>
|
|
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 (
|
|
269
|
-
const parent =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
425
|
-
|
|
426
|
-
|
|
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.
|
|
444
|
+
if (remote && existingState.base.contextSha256 === contextSha) {
|
|
432
445
|
progress(
|
|
433
|
-
`base snapshot ${String(existingState.base.imageId)} already exists (
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
666
|
+
if (existsSync2(controlPath) && await this.isAlive(controlPath)) {
|
|
653
667
|
this.boxes.set(opts.boxId, tunnel);
|
|
654
668
|
return;
|
|
655
669
|
}
|
|
656
|
-
if (
|
|
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 || !
|
|
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)
|
|
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 (
|
|
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
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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-
|
|
1407
|
+
//# sourceMappingURL=dist-ZODPD2I6.js.map
|