@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.
- package/dist/_cloud-attach-ZXBCNWJX.js +13 -0
- package/dist/{chunk-NW5NYTQM.js → chunk-BXQMIEHC.js} +459 -110
- package/dist/chunk-BXQMIEHC.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-7KOEFGN2.js → chunk-GU5LW4B5.js} +385 -31
- package/dist/chunk-GU5LW4B5.js.map +1 -0
- package/dist/chunk-KL36BRN4.js +455 -0
- package/dist/chunk-KL36BRN4.js.map +1 -0
- package/dist/{chunk-V5KZGB5V.js → chunk-LEV3KICD.js} +18 -2
- package/dist/chunk-LEV3KICD.js.map +1 -0
- package/dist/chunk-MTVI44DW.js +662 -0
- package/dist/chunk-MTVI44DW.js.map +1 -0
- package/dist/{chunk-NAVL4R34.js → chunk-NCJP5MTN.js} +1281 -556
- package/dist/chunk-NCJP5MTN.js.map +1 -0
- package/dist/{cloud-poller-ZIWSADJB-JXFRJUEM.js → cloud-poller-SUNA6ZQC-2RG5WPRN.js} +2 -2
- package/dist/{dist-ETCFRVPA.js → dist-32EZBYG4.js} +50 -20
- package/dist/{dist-R67WMLCF.js → dist-CX5CGVEB.js} +120 -10
- package/dist/dist-CX5CGVEB.js.map +1 -0
- package/dist/{dist-QZGJIBT5.js → dist-GDHP34ZK.js} +141 -75
- package/dist/dist-GDHP34ZK.js.map +1 -0
- package/dist/dist-XML54CNB.js +849 -0
- package/dist/dist-XML54CNB.js.map +1 -0
- package/dist/index.js +3881 -867
- package/dist/index.js.map +1 -1
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js +18 -0
- package/dist/prepared-state-CL4CWXQA-H5THETIM.js.map +1 -0
- package/package.json +7 -5
- 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 +1214 -98
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-codex-hooks.json +66 -35
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-vnc-start +15 -1
- 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/agentbox-vnc-start +15 -1
- package/runtime/hetzner/claude-managed-settings.json +62 -1
- package/runtime/hetzner/ctl.cjs +1214 -98
- 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 +1146 -63
- package/runtime/vercel/agentbox-checkpoint-cleanup +52 -0
- package/runtime/vercel/agentbox-codex-hooks.json +68 -0
- package/runtime/vercel/agentbox-open +28 -0
- package/runtime/vercel/agentbox-setup-skill.md +196 -0
- package/runtime/vercel/agentbox-vnc-start +91 -0
- package/runtime/vercel/claude-managed-settings.json +115 -0
- package/runtime/vercel/ctl.cjs +23466 -0
- package/runtime/vercel/custom-system-CLAUDE.md +50 -0
- package/runtime/vercel/gh-shim +263 -0
- package/runtime/vercel/git-shim +131 -0
- package/runtime/vercel/scripts/provision.sh +274 -0
- 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-ZXBCNWJX.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-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-
|
|
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-
|
|
32
|
-
import
|
|
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
|
|
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,56 @@ 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
|
-
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
|
-
|
|
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 =
|
|
190
|
+
var SELF = dirname(fileURLToPath(import.meta.url));
|
|
184
191
|
function findStagedCliRuntimeRoot() {
|
|
185
192
|
const candidates = [
|
|
186
|
-
|
|
193
|
+
resolve(SELF, "..", "runtime"),
|
|
187
194
|
// <cliRoot>/dist/.. → <cliRoot> then /runtime
|
|
188
|
-
|
|
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 (
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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(
|
|
250
|
+
for (const rel of cliRelative[name] ?? []) out.push(resolve(cliRoot, rel));
|
|
238
251
|
}
|
|
239
|
-
for (const rel of monorepoRelative[name] ?? []) out.push(
|
|
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) =>
|
|
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 (
|
|
269
|
-
const parent =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
425
|
-
|
|
426
|
-
|
|
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.
|
|
442
|
+
if (remote && existingState.base.contextSha256 === contextSha) {
|
|
432
443
|
progress(
|
|
433
|
-
`base snapshot ${String(existingState.base.imageId)} already exists (
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
664
|
+
if (existsSync2(controlPath) && await this.isAlive(controlPath)) {
|
|
653
665
|
this.boxes.set(opts.boxId, tunnel);
|
|
654
666
|
return;
|
|
655
667
|
}
|
|
656
|
-
if (
|
|
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 || !
|
|
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)
|
|
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 (
|
|
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
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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-
|
|
1405
|
+
//# sourceMappingURL=dist-GDHP34ZK.js.map
|