@madarco/agentbox 0.5.0 → 0.6.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/{chunk-7J5AJLWG.js → chunk-BBZMA2K6.js} +3 -3
- package/dist/{chunk-RFC5F5HR.js → chunk-HHMWQNLF.js} +8 -8
- package/dist/chunk-HHMWQNLF.js.map +1 -0
- package/dist/{chunk-PXUBE5KS.js → chunk-HTTKML3C.js} +351 -42
- package/dist/chunk-HTTKML3C.js.map +1 -0
- package/dist/{chunk-6VTAPD4H.js → chunk-KJNZP6I3.js} +100 -21
- package/dist/chunk-KJNZP6I3.js.map +1 -0
- package/dist/{chunk-FJNIFTWK.js → chunk-M7I247BK.js} +6 -4
- package/dist/chunk-M7I247BK.js.map +1 -0
- package/dist/{create-AHZ3GVEZ-TGEDL7UX.js → create-6PWXI6HO-OWAMHBAK.js} +4 -4
- package/dist/index.js +310 -102
- package/dist/index.js.map +1 -1
- package/dist/{lifecycle-LFOL6YFM-TCHDX3J5.js → lifecycle-EMXR46DI-DUVBXNTV.js} +4 -4
- package/dist/{stats-Z4BVJODD-HEC4TMUZ.js → stats-SZXOJE3D-N7OODCHW.js} +3 -3
- package/package.json +4 -4
- package/runtime/docker/Dockerfile.box +23 -11
- package/runtime/docker/apps/cli/share/agentbox-setup/SKILL.md +19 -11
- package/runtime/docker/packages/ctl/dist/bin.cjs +56 -15
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-checkpoint-cleanup +13 -3
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-dockerd-start +87 -7
- package/runtime/docker/packages/sandbox-docker/scripts/agentbox-open +28 -0
- package/runtime/docker/packages/sandbox-docker/scripts/custom-system-CLAUDE.md +4 -9
- package/runtime/relay/bin.cjs +121 -2
- package/share/agentbox-setup/SKILL.md +19 -11
- package/dist/chunk-6VTAPD4H.js.map +0 -1
- package/dist/chunk-FJNIFTWK.js.map +0 -1
- package/dist/chunk-PXUBE5KS.js.map +0 -1
- package/dist/chunk-RFC5F5HR.js.map +0 -1
- /package/dist/{chunk-7J5AJLWG.js.map → chunk-BBZMA2K6.js.map} +0 -0
- /package/dist/{create-AHZ3GVEZ-TGEDL7UX.js.map → create-6PWXI6HO-OWAMHBAK.js.map} +0 -0
- /package/dist/{lifecycle-LFOL6YFM-TCHDX3J5.js.map → lifecycle-EMXR46DI-DUVBXNTV.js.map} +0 -0
- /package/dist/{stats-Z4BVJODD-HEC4TMUZ.js.map → stats-SZXOJE3D-N7OODCHW.js.map} +0 -0
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
removeContainer,
|
|
9
9
|
sanitizeMnemonic,
|
|
10
10
|
volumeExists
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-HHMWQNLF.js";
|
|
12
12
|
|
|
13
|
-
// ../../packages/sandbox-docker/dist/chunk-
|
|
13
|
+
// ../../packages/sandbox-docker/dist/chunk-SRQIM7LG.js
|
|
14
|
+
import { spawnSync } from "child_process";
|
|
14
15
|
import { mkdir, mkdtemp, readdir, readFile, rm, stat, writeFile } from "fs/promises";
|
|
15
16
|
import { homedir, tmpdir } from "os";
|
|
16
17
|
import { join, relative } from "path";
|
|
18
|
+
import { setTimeout as delay } from "timers/promises";
|
|
17
19
|
import { execa } from "execa";
|
|
18
20
|
import { randomBytes } from "crypto";
|
|
19
21
|
import { execa as execa2 } from "execa";
|
|
@@ -25,6 +27,7 @@ import { mkdir as mkdir2, readdir as readdir3, rm as rm2, stat as stat3 } from "
|
|
|
25
27
|
import { homedir as homedir2, platform } from "os";
|
|
26
28
|
import { join as join3, resolve } from "path";
|
|
27
29
|
import { stat as stat4 } from "fs/promises";
|
|
30
|
+
import { execa as execa5 } from "execa";
|
|
28
31
|
import { spawn } from "child_process";
|
|
29
32
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
30
33
|
import { existsSync, openSync } from "fs";
|
|
@@ -32,7 +35,7 @@ import { mkdir as mkdir3, readFile as readFile2, unlink, writeFile as writeFile2
|
|
|
32
35
|
import { request as httpRequest } from "http";
|
|
33
36
|
import { homedir as homedir3 } from "os";
|
|
34
37
|
import { dirname, join as join4, resolve as resolve2 } from "path";
|
|
35
|
-
import { setTimeout as
|
|
38
|
+
import { setTimeout as delay2 } from "timers/promises";
|
|
36
39
|
import { fileURLToPath } from "url";
|
|
37
40
|
|
|
38
41
|
// ../../packages/relay/dist/index.js
|
|
@@ -42,7 +45,7 @@ var RELAY_NETWORK_NAME = "agentbox-net";
|
|
|
42
45
|
var RELAY_IMAGE_REF = "agentbox/relay:dev";
|
|
43
46
|
var MAX_BODY_BYTES = 1024 * 1024;
|
|
44
47
|
|
|
45
|
-
// ../../packages/sandbox-docker/dist/chunk-
|
|
48
|
+
// ../../packages/sandbox-docker/dist/chunk-SRQIM7LG.js
|
|
46
49
|
function isHostPathHookCommand(command, hostHome) {
|
|
47
50
|
if (typeof command !== "string" || command.length === 0) return false;
|
|
48
51
|
if (hostHome.length === 0) return false;
|
|
@@ -79,6 +82,27 @@ function filterHostHooks(data, hostHome) {
|
|
|
79
82
|
}
|
|
80
83
|
return { data: clone, removedCommands };
|
|
81
84
|
}
|
|
85
|
+
function trustWorkspace(data, workspacePath) {
|
|
86
|
+
const clone = structuredClone(data);
|
|
87
|
+
if (clone === null || typeof clone !== "object" || Array.isArray(clone)) {
|
|
88
|
+
return { data: clone, trusted: false };
|
|
89
|
+
}
|
|
90
|
+
if (workspacePath.length === 0) return { data: clone, trusted: false };
|
|
91
|
+
const obj = clone;
|
|
92
|
+
if (obj.projects === null || typeof obj.projects !== "object" || Array.isArray(obj.projects)) {
|
|
93
|
+
obj.projects = {};
|
|
94
|
+
}
|
|
95
|
+
const projects = obj.projects;
|
|
96
|
+
const existing = projects[workspacePath];
|
|
97
|
+
const entry = existing !== null && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
|
98
|
+
if (entry.hasTrustDialogAccepted === true) {
|
|
99
|
+
projects[workspacePath] = entry;
|
|
100
|
+
return { data: clone, trusted: false };
|
|
101
|
+
}
|
|
102
|
+
entry.hasTrustDialogAccepted = true;
|
|
103
|
+
projects[workspacePath] = entry;
|
|
104
|
+
return { data: clone, trusted: true };
|
|
105
|
+
}
|
|
82
106
|
function addProjectAlias(data, fromPath, toPath) {
|
|
83
107
|
const clone = structuredClone(data);
|
|
84
108
|
if (clone === null || typeof clone !== "object" || Array.isArray(clone)) {
|
|
@@ -105,17 +129,17 @@ function addProjectAlias(data, fromPath, toPath) {
|
|
|
105
129
|
}
|
|
106
130
|
return { data: clone, aliased: true };
|
|
107
131
|
}
|
|
108
|
-
function
|
|
132
|
+
function setInstallMethodNative(data) {
|
|
109
133
|
const clone = structuredClone(data);
|
|
110
134
|
if (clone === null || typeof clone !== "object" || Array.isArray(clone)) {
|
|
111
|
-
return { data: clone,
|
|
135
|
+
return { data: clone, applied: false };
|
|
112
136
|
}
|
|
113
137
|
const obj = clone;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return { data: clone,
|
|
138
|
+
const changed = obj.installMethod !== "native" || obj.autoUpdates !== false || obj.autoUpdatesProtectedForNative !== true;
|
|
139
|
+
obj.installMethod = "native";
|
|
140
|
+
obj.autoUpdates = false;
|
|
141
|
+
obj.autoUpdatesProtectedForNative = true;
|
|
142
|
+
return { data: clone, applied: changed };
|
|
119
143
|
}
|
|
120
144
|
var SKILL_EXCLUDE_PREFIXES = ["agentbox-"];
|
|
121
145
|
var CONTAINER_PLUGINS_PREFIX = "/home/vscode/.claude/plugins/";
|
|
@@ -189,6 +213,24 @@ function mergeInstalledPlugins(hostJson, boxJson, opts) {
|
|
|
189
213
|
(host, merged) => ({ ...host, plugins: merged })
|
|
190
214
|
);
|
|
191
215
|
}
|
|
216
|
+
function referencedPluginVersionKeys(installedPluginsJson) {
|
|
217
|
+
const keys = /* @__PURE__ */ new Set();
|
|
218
|
+
if (!isPlainObject(installedPluginsJson)) return keys;
|
|
219
|
+
const plugins = installedPluginsJson["plugins"];
|
|
220
|
+
if (!isPlainObject(plugins)) return keys;
|
|
221
|
+
for (const entries of Object.values(plugins)) {
|
|
222
|
+
if (!Array.isArray(entries)) continue;
|
|
223
|
+
for (const entry of entries) {
|
|
224
|
+
if (!isPlainObject(entry)) continue;
|
|
225
|
+
const installPath = entry["installPath"];
|
|
226
|
+
if (typeof installPath !== "string") continue;
|
|
227
|
+
const segments = installPath.split("/").filter((s) => s.length > 0);
|
|
228
|
+
if (segments.length < 3) continue;
|
|
229
|
+
keys.add(segments.slice(-3).join("/"));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return keys;
|
|
233
|
+
}
|
|
192
234
|
var SHARED_CLAUDE_VOLUME = "agentbox-claude-config";
|
|
193
235
|
var DEFAULT_CLAUDE_SESSION = "claude";
|
|
194
236
|
var CONTAINER_CLAUDE_DIR = "/home/vscode/.claude";
|
|
@@ -210,6 +252,14 @@ async function pathExists(p) {
|
|
|
210
252
|
return false;
|
|
211
253
|
}
|
|
212
254
|
}
|
|
255
|
+
async function volumeHasClaudeJson(volume, image) {
|
|
256
|
+
const res = await execa(
|
|
257
|
+
"docker",
|
|
258
|
+
["run", "--rm", "-v", `${volume}:/dst`, image, "sh", "-c", "test -e /dst/_claude.json"],
|
|
259
|
+
{ reject: false }
|
|
260
|
+
);
|
|
261
|
+
return res.exitCode === 0;
|
|
262
|
+
}
|
|
213
263
|
async function findBrokenSymlinks(root) {
|
|
214
264
|
const broken = [];
|
|
215
265
|
async function walk(dir) {
|
|
@@ -244,6 +294,7 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
244
294
|
if (!await pathExists(hostClaude)) return { created, synced: false };
|
|
245
295
|
const hostClaudeJson = join(homedir(), ".claude.json");
|
|
246
296
|
const hasJson = await pathExists(hostClaudeJson);
|
|
297
|
+
const seedClaudeJson = !await volumeHasClaudeJson(spec.volume, opts.image);
|
|
247
298
|
const hostHome = homedir();
|
|
248
299
|
const hostAgents = join(homedir(), ".agents");
|
|
249
300
|
const hasAgents = await pathExists(hostAgents);
|
|
@@ -261,12 +312,13 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
261
312
|
"-v",
|
|
262
313
|
`${hostClaude}:/src-claude:ro`
|
|
263
314
|
];
|
|
264
|
-
if (hasJson) args.push("-v", `${hostClaudeJson}:/src-claude-json:ro`);
|
|
315
|
+
if (hasJson && seedClaudeJson) args.push("-v", `${hostClaudeJson}:/src-claude-json:ro`);
|
|
265
316
|
if (hasAgents) args.push("-v", `${hostAgents}:/.agents:ro`);
|
|
266
317
|
const filterDir = await mkdtemp(join(tmpdir(), "agentbox-claude-filter-"));
|
|
267
318
|
let filteredHookCount = 0;
|
|
268
|
-
let
|
|
319
|
+
let installMethodFixed = false;
|
|
269
320
|
let aliasedProjectKey = false;
|
|
321
|
+
let workspaceTrusted = false;
|
|
270
322
|
try {
|
|
271
323
|
const settingsResult = await maybeFilterTo(
|
|
272
324
|
join(hostClaude, "settings.json"),
|
|
@@ -274,21 +326,40 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
274
326
|
hostHome
|
|
275
327
|
);
|
|
276
328
|
filteredHookCount += settingsResult.removedHooks;
|
|
277
|
-
if (
|
|
329
|
+
if (!seedClaudeJson) {
|
|
330
|
+
} else if (hasJson) {
|
|
278
331
|
const jsonResult = await maybeFilterTo(
|
|
279
332
|
hostClaudeJson,
|
|
280
333
|
join(filterDir, "_claude.json"),
|
|
281
334
|
hostHome,
|
|
282
335
|
{
|
|
283
|
-
|
|
284
|
-
aliasProject: opts.hostWorkspace ? { from: opts.hostWorkspace, to: CONTAINER_WORKSPACE } : void 0
|
|
336
|
+
setInstallMethodNative: true,
|
|
337
|
+
aliasProject: opts.hostWorkspace ? { from: opts.hostWorkspace, to: CONTAINER_WORKSPACE } : void 0,
|
|
338
|
+
trustWorkspacePath: CONTAINER_WORKSPACE
|
|
285
339
|
}
|
|
286
340
|
);
|
|
287
341
|
filteredHookCount += jsonResult.removedHooks;
|
|
288
|
-
|
|
342
|
+
installMethodFixed = jsonResult.installMethodFixed;
|
|
289
343
|
aliasedProjectKey = jsonResult.aliasedProjectKey;
|
|
344
|
+
workspaceTrusted = jsonResult.workspaceTrusted;
|
|
345
|
+
} else {
|
|
346
|
+
await writeFile(
|
|
347
|
+
join(filterDir, "_claude.json"),
|
|
348
|
+
JSON.stringify(
|
|
349
|
+
{
|
|
350
|
+
installMethod: "native",
|
|
351
|
+
autoUpdates: false,
|
|
352
|
+
autoUpdatesProtectedForNative: true,
|
|
353
|
+
projects: { [CONTAINER_WORKSPACE]: { hasTrustDialogAccepted: true } }
|
|
354
|
+
},
|
|
355
|
+
null,
|
|
356
|
+
2
|
|
357
|
+
)
|
|
358
|
+
);
|
|
359
|
+
installMethodFixed = true;
|
|
360
|
+
workspaceTrusted = true;
|
|
290
361
|
}
|
|
291
|
-
if (filteredHookCount > 0 ||
|
|
362
|
+
if (filteredHookCount > 0 || installMethodFixed || aliasedProjectKey || workspaceTrusted) {
|
|
292
363
|
args.push("-v", `${filterDir}:/src-filter:ro`);
|
|
293
364
|
}
|
|
294
365
|
const brokenSymlinks = await findBrokenSymlinks(hostClaude);
|
|
@@ -335,7 +406,14 @@ async function ensureClaudeVolume(spec, opts) {
|
|
|
335
406
|
} finally {
|
|
336
407
|
await rm(filterDir, { recursive: true, force: true });
|
|
337
408
|
}
|
|
338
|
-
return {
|
|
409
|
+
return {
|
|
410
|
+
created,
|
|
411
|
+
synced: true,
|
|
412
|
+
filteredHookCount,
|
|
413
|
+
installMethodFixed,
|
|
414
|
+
aliasedProjectKey,
|
|
415
|
+
workspaceTrusted
|
|
416
|
+
};
|
|
339
417
|
}
|
|
340
418
|
async function seedSetupSkillIntoVolume(volume, image) {
|
|
341
419
|
try {
|
|
@@ -349,10 +427,10 @@ async function seedSetupSkillIntoVolume(volume, image) {
|
|
|
349
427
|
image,
|
|
350
428
|
"sh",
|
|
351
429
|
"-c",
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
//
|
|
355
|
-
`{ [
|
|
430
|
+
// Always overwrite from the image so an image upgrade propagates. Prints
|
|
431
|
+
// SEEDED on success; the whole thing is `|| true` so a missing image
|
|
432
|
+
// asset is a clean no-op, never a non-zero exit.
|
|
433
|
+
`{ [ -f ${IN_BOX_SETUP_GUIDE_PATH} ] && rm -rf /dst/skills/agentbox-setup && mkdir -p /dst/skills/agentbox-setup && cp -a ${IN_BOX_SETUP_GUIDE_PATH} ${SETUP_SKILL_DST} && chown -R 1000:1000 /dst/skills/agentbox-setup && echo SEEDED; } || true`
|
|
356
434
|
]);
|
|
357
435
|
return { seeded: stdout.includes("SEEDED") };
|
|
358
436
|
} catch {
|
|
@@ -360,19 +438,25 @@ async function seedSetupSkillIntoVolume(volume, image) {
|
|
|
360
438
|
}
|
|
361
439
|
}
|
|
362
440
|
async function maybeFilterTo(src, dest, hostHome, opts = {}) {
|
|
441
|
+
const zero = {
|
|
442
|
+
removedHooks: 0,
|
|
443
|
+
installMethodFixed: false,
|
|
444
|
+
aliasedProjectKey: false,
|
|
445
|
+
workspaceTrusted: false
|
|
446
|
+
};
|
|
363
447
|
let parsed;
|
|
364
448
|
try {
|
|
365
449
|
parsed = JSON.parse(await readFile(src, "utf8"));
|
|
366
450
|
} catch {
|
|
367
|
-
return
|
|
451
|
+
return zero;
|
|
368
452
|
}
|
|
369
453
|
const filtered = filterHostHooks(parsed, hostHome);
|
|
370
454
|
let working = filtered.data;
|
|
371
|
-
let
|
|
372
|
-
if (opts.
|
|
373
|
-
const r =
|
|
455
|
+
let installFixed = false;
|
|
456
|
+
if (opts.setInstallMethodNative) {
|
|
457
|
+
const r = setInstallMethodNative(working);
|
|
374
458
|
working = r.data;
|
|
375
|
-
|
|
459
|
+
installFixed = r.applied;
|
|
376
460
|
}
|
|
377
461
|
let aliased = false;
|
|
378
462
|
if (opts.aliasProject) {
|
|
@@ -380,14 +464,21 @@ async function maybeFilterTo(src, dest, hostHome, opts = {}) {
|
|
|
380
464
|
working = r.data;
|
|
381
465
|
aliased = r.aliased;
|
|
382
466
|
}
|
|
383
|
-
|
|
384
|
-
|
|
467
|
+
let trusted = false;
|
|
468
|
+
if (opts.trustWorkspacePath) {
|
|
469
|
+
const r = trustWorkspace(working, opts.trustWorkspacePath);
|
|
470
|
+
working = r.data;
|
|
471
|
+
trusted = r.trusted;
|
|
472
|
+
}
|
|
473
|
+
if (filtered.removedCommands.length === 0 && !installFixed && !aliased && !trusted) {
|
|
474
|
+
return zero;
|
|
385
475
|
}
|
|
386
476
|
await writeFile(dest, JSON.stringify(working, null, 2));
|
|
387
477
|
return {
|
|
388
478
|
removedHooks: filtered.removedCommands.length,
|
|
389
|
-
|
|
390
|
-
aliasedProjectKey: aliased
|
|
479
|
+
installMethodFixed: installFixed,
|
|
480
|
+
aliasedProjectKey: aliased,
|
|
481
|
+
workspaceTrusted: trusted
|
|
391
482
|
};
|
|
392
483
|
}
|
|
393
484
|
var FORWARDED_ENV_KEYS = [
|
|
@@ -435,7 +526,18 @@ async function isDir(p) {
|
|
|
435
526
|
return false;
|
|
436
527
|
}
|
|
437
528
|
}
|
|
529
|
+
async function readReferencedPluginKeys(installedPluginsJsonPath) {
|
|
530
|
+
try {
|
|
531
|
+
const raw = await readFile(installedPluginsJsonPath, "utf8");
|
|
532
|
+
return referencedPluginVersionKeys(JSON.parse(raw));
|
|
533
|
+
} catch {
|
|
534
|
+
return /* @__PURE__ */ new Set();
|
|
535
|
+
}
|
|
536
|
+
}
|
|
438
537
|
async function scanPluginCacheForRebuild(cacheRoot) {
|
|
538
|
+
const referenced = await readReferencedPluginKeys(
|
|
539
|
+
join(cacheRoot, "..", "installed_plugins.json")
|
|
540
|
+
);
|
|
439
541
|
let marketplaces;
|
|
440
542
|
try {
|
|
441
543
|
marketplaces = await readdir(cacheRoot, { withFileTypes: true });
|
|
@@ -462,6 +564,7 @@ async function scanPluginCacheForRebuild(cacheRoot) {
|
|
|
462
564
|
}
|
|
463
565
|
for (const v of versions) {
|
|
464
566
|
if (!v.isDirectory()) continue;
|
|
567
|
+
if (referenced.size > 0 && !referenced.has(`${m.name}/${p.name}/${v.name}`)) continue;
|
|
465
568
|
const vPath = join(pPath, v.name);
|
|
466
569
|
if (!await isFile(join(vPath, "package.json"))) continue;
|
|
467
570
|
if (await isFile(join(vPath, PLUGIN_INSTALLED_MARKER))) continue;
|
|
@@ -477,13 +580,38 @@ async function resolveClaudeCacheLiveOnHost(volume) {
|
|
|
477
580
|
if (!await isDir(orbstackVolumePath(volume))) return null;
|
|
478
581
|
return orbstackVolumePath(volume, "plugins", "cache");
|
|
479
582
|
}
|
|
583
|
+
async function readBoxReferencedPluginKeys(container) {
|
|
584
|
+
const res = await execa(
|
|
585
|
+
"docker",
|
|
586
|
+
[
|
|
587
|
+
"exec",
|
|
588
|
+
"--user",
|
|
589
|
+
CONTAINER_USER,
|
|
590
|
+
container,
|
|
591
|
+
"cat",
|
|
592
|
+
`${CONTAINER_CLAUDE_DIR}/plugins/installed_plugins.json`
|
|
593
|
+
],
|
|
594
|
+
{ reject: false }
|
|
595
|
+
);
|
|
596
|
+
if (res.exitCode !== 0 || !res.stdout) return /* @__PURE__ */ new Set();
|
|
597
|
+
try {
|
|
598
|
+
return referencedPluginVersionKeys(JSON.parse(res.stdout));
|
|
599
|
+
} catch {
|
|
600
|
+
return /* @__PURE__ */ new Set();
|
|
601
|
+
}
|
|
602
|
+
}
|
|
480
603
|
async function rebuildPluginNativeDeps(container, opts = {}) {
|
|
481
604
|
if (opts.volume) {
|
|
482
605
|
const cacheRoot = await resolveClaudeCacheLiveOnHost(opts.volume);
|
|
483
606
|
if (cacheRoot && !await scanPluginCacheForRebuild(cacheRoot)) {
|
|
484
|
-
return { rebuilt: [], failed: [], skipped: true };
|
|
607
|
+
return { rebuilt: [], failed: [], pruned: [], prunedBytes: 0, skipped: true };
|
|
485
608
|
}
|
|
486
609
|
}
|
|
610
|
+
const referenced = await readBoxReferencedPluginKeys(container);
|
|
611
|
+
const refSetup = referenced.size > 0 ? `cat <<'AGENTBOX_REF_EOF' > "$WORK/referenced"
|
|
612
|
+
${[...referenced].sort().join("\n")}
|
|
613
|
+
AGENTBOX_REF_EOF
|
|
614
|
+
` : "";
|
|
487
615
|
const script = `set -u
|
|
488
616
|
PLUGINS_DIR=/home/vscode/.claude/plugins/cache
|
|
489
617
|
MARKER=${PLUGIN_INSTALLED_MARKER}
|
|
@@ -494,7 +622,12 @@ MAX=4
|
|
|
494
622
|
[ -d "$PLUGINS_DIR" ] || exit 0
|
|
495
623
|
mkdir -p "$NPM_CACHE"
|
|
496
624
|
WORK=$(mktemp -d)
|
|
497
|
-
relkey() { printf '%s' "\${1#$PLUGINS_DIR/}" | tr '/' '_'; }
|
|
625
|
+
${refSetup}relkey() { printf '%s' "\${1#$PLUGINS_DIR/}" | tr '/' '_'; }
|
|
626
|
+
# True when refs are unknown (no file) or $1 (<m>/<p>/<v>) is referenced.
|
|
627
|
+
is_referenced() {
|
|
628
|
+
[ -s "$WORK/referenced" ] || return 0
|
|
629
|
+
grep -Fxq "$1" "$WORK/referenced"
|
|
630
|
+
}
|
|
498
631
|
# Run one plugin's install. $1 is frozen by value at call time, so it's safe
|
|
499
632
|
# to read from the backgrounded subshell; the rest are set-once constants.
|
|
500
633
|
do_one() {
|
|
@@ -514,10 +647,29 @@ do_one() {
|
|
|
514
647
|
printf 'FAIL\\n' > "$WORK/$key.res"
|
|
515
648
|
fi
|
|
516
649
|
}
|
|
650
|
+
# Prune pass: every unreferenced (stale) version dir loses its node_modules and
|
|
651
|
+
# our markers. Only runs when installed_plugins.json gave us a reference set.
|
|
652
|
+
if [ -s "$WORK/referenced" ]; then
|
|
653
|
+
for dir in "$PLUGINS_DIR"/*/*/*/; do
|
|
654
|
+
[ -d "$dir" ] || continue
|
|
655
|
+
rel=\${dir%/}; rel=\${rel#$PLUGINS_DIR/}
|
|
656
|
+
grep -Fxq "$rel" "$WORK/referenced" && continue
|
|
657
|
+
if [ -d "$dir/node_modules" ]; then
|
|
658
|
+
bytes=$(du -sb "$dir/node_modules" 2>/dev/null | cut -f1)
|
|
659
|
+
[ -n "$bytes" ] || bytes=0
|
|
660
|
+
rm -rf "$dir/node_modules" "$dir/$MARKER" "$dir/$FAILMARKER"
|
|
661
|
+
echo "PRUNE_OK $rel $bytes"
|
|
662
|
+
else
|
|
663
|
+
rm -f "$dir/$MARKER" "$dir/$FAILMARKER"
|
|
664
|
+
fi
|
|
665
|
+
done
|
|
666
|
+
fi
|
|
517
667
|
n=0
|
|
518
668
|
for dir in "$PLUGINS_DIR"/*/*/*/; do
|
|
519
669
|
[ -d "$dir" ] || continue
|
|
520
670
|
[ -f "$dir/package.json" ] || continue
|
|
671
|
+
rel=\${dir%/}; rel=\${rel#$PLUGINS_DIR/}
|
|
672
|
+
is_referenced "$rel" || continue
|
|
521
673
|
[ -f "$dir/$MARKER" ] && continue
|
|
522
674
|
[ -n "$(find "$dir" -maxdepth 1 -name "$FAILMARKER" -mmin -$BACKOFF_MIN 2>/dev/null)" ] && continue
|
|
523
675
|
echo "REBUILD_START \${dir#$PLUGINS_DIR/}"
|
|
@@ -554,6 +706,8 @@ rm -rf "$WORK"
|
|
|
554
706
|
);
|
|
555
707
|
const rebuilt = [];
|
|
556
708
|
const failed = [];
|
|
709
|
+
const pruned = [];
|
|
710
|
+
let prunedBytes = 0;
|
|
557
711
|
const lines = (result.stdout ?? "").split("\n");
|
|
558
712
|
let collectingFail = null;
|
|
559
713
|
for (const line of lines) {
|
|
@@ -572,9 +726,19 @@ rm -rf "$WORK"
|
|
|
572
726
|
rebuilt.push(line.slice("REBUILD_OK ".length));
|
|
573
727
|
} else if (line.startsWith("REBUILD_FAIL ")) {
|
|
574
728
|
collectingFail = { dir: line.slice("REBUILD_FAIL ".length), stderr: [] };
|
|
729
|
+
} else if (line.startsWith("PRUNE_OK ")) {
|
|
730
|
+
const rest = line.slice("PRUNE_OK ".length);
|
|
731
|
+
const sp = rest.lastIndexOf(" ");
|
|
732
|
+
if (sp > 0) {
|
|
733
|
+
const dir = rest.slice(0, sp);
|
|
734
|
+
const bytes = Number(rest.slice(sp + 1));
|
|
735
|
+
pruned.push(dir);
|
|
736
|
+
if (Number.isFinite(bytes)) prunedBytes += bytes;
|
|
737
|
+
opts.onProgress?.(`pruning stale plugin cache ${dir}`);
|
|
738
|
+
}
|
|
575
739
|
}
|
|
576
740
|
}
|
|
577
|
-
return { rebuilt, failed, skipped: false };
|
|
741
|
+
return { rebuilt, failed, pruned, prunedBytes, skipped: false };
|
|
578
742
|
}
|
|
579
743
|
var ClaudeSessionError = class extends Error {
|
|
580
744
|
constructor(message) {
|
|
@@ -732,6 +896,63 @@ function buildShellArgv(container) {
|
|
|
732
896
|
const term = process.env["TERM"] ?? "xterm-256color";
|
|
733
897
|
return ["exec", "-it", "-e", `TERM=${term}`, "--user", CONTAINER_USER, container, "bash", "-l"];
|
|
734
898
|
}
|
|
899
|
+
function buildClaudeLoginRunArgv(opts) {
|
|
900
|
+
const term = process.env["TERM"] ?? "xterm-256color";
|
|
901
|
+
return [
|
|
902
|
+
"run",
|
|
903
|
+
"-it",
|
|
904
|
+
"--rm",
|
|
905
|
+
"-e",
|
|
906
|
+
`TERM=${term}`,
|
|
907
|
+
"-e",
|
|
908
|
+
"DISPLAY=",
|
|
909
|
+
"-v",
|
|
910
|
+
`${opts.volume}:${CONTAINER_CLAUDE_DIR}`,
|
|
911
|
+
"--user",
|
|
912
|
+
CONTAINER_USER,
|
|
913
|
+
opts.image,
|
|
914
|
+
"claude",
|
|
915
|
+
"auth",
|
|
916
|
+
"login",
|
|
917
|
+
...opts.extraArgs
|
|
918
|
+
];
|
|
919
|
+
}
|
|
920
|
+
function runInteractiveClaudeLogin(dockerArgv) {
|
|
921
|
+
const child = spawnSync("docker", dockerArgv, { stdio: "inherit" });
|
|
922
|
+
return { exitCode: child.status ?? 1 };
|
|
923
|
+
}
|
|
924
|
+
async function warmUpClaudeCredentials(volume, image, opts = {}) {
|
|
925
|
+
const MAX_ATTEMPTS = 6;
|
|
926
|
+
const SLEEP_MS = 5e3;
|
|
927
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
928
|
+
opts.onProgress?.(`checking credentials... ${attempt}/${MAX_ATTEMPTS}`);
|
|
929
|
+
const res = await execa(
|
|
930
|
+
"docker",
|
|
931
|
+
[
|
|
932
|
+
"run",
|
|
933
|
+
"--rm",
|
|
934
|
+
"-v",
|
|
935
|
+
`${volume}:${CONTAINER_CLAUDE_DIR}`,
|
|
936
|
+
"--user",
|
|
937
|
+
CONTAINER_USER,
|
|
938
|
+
"-e",
|
|
939
|
+
"DISABLE_AUTOUPDATER=1",
|
|
940
|
+
image,
|
|
941
|
+
"claude",
|
|
942
|
+
"--dangerously-skip-permissions",
|
|
943
|
+
"-p",
|
|
944
|
+
"ok"
|
|
945
|
+
],
|
|
946
|
+
{ reject: false, timeout: 6e4 }
|
|
947
|
+
);
|
|
948
|
+
const out = `${res.stdout ?? ""}
|
|
949
|
+
${res.stderr ?? ""}`;
|
|
950
|
+
const apiError = /API Error|is not supported on this model|"type":\s*"error"/i.test(out);
|
|
951
|
+
if (res.exitCode === 0 && !apiError) return { warmed: true, attempts: attempt };
|
|
952
|
+
if (attempt < MAX_ATTEMPTS) await delay(SLEEP_MS);
|
|
953
|
+
}
|
|
954
|
+
return { warmed: false, attempts: MAX_ATTEMPTS };
|
|
955
|
+
}
|
|
735
956
|
function formatDetachNotice(ref) {
|
|
736
957
|
return `Session detached. Reattach with: agentbox claude attach ${ref}`;
|
|
737
958
|
}
|
|
@@ -1357,8 +1578,10 @@ async function createSnapshot(opts) {
|
|
|
1357
1578
|
await Promise.all(toPrune.map((p) => rm2(p, { recursive: true, force: true })));
|
|
1358
1579
|
return { destination, prunedPaths: toPrune };
|
|
1359
1580
|
}
|
|
1581
|
+
var CTL_DAEMON_LOG = "/var/log/agentbox/ctl-daemon.log";
|
|
1360
1582
|
async function launchCtlDaemon(container, hostSocketPath, timeoutMs = 3e3) {
|
|
1361
|
-
const
|
|
1583
|
+
const wrapped = `mkdir -p ${CTL_DAEMON_LOG.replace(/\/[^/]*$/, "")} && exec agentbox-ctl daemon >>${CTL_DAEMON_LOG} 2>&1`;
|
|
1584
|
+
const result = await execInBox(container, ["sh", "-c", wrapped], {
|
|
1362
1585
|
user: "vscode",
|
|
1363
1586
|
detach: true
|
|
1364
1587
|
});
|
|
@@ -1383,6 +1606,23 @@ async function pathExists2(p) {
|
|
|
1383
1606
|
return false;
|
|
1384
1607
|
}
|
|
1385
1608
|
}
|
|
1609
|
+
async function ensureHomeOwnedByVscode(container) {
|
|
1610
|
+
await execa5(
|
|
1611
|
+
"docker",
|
|
1612
|
+
[
|
|
1613
|
+
"exec",
|
|
1614
|
+
"--user",
|
|
1615
|
+
"root",
|
|
1616
|
+
container,
|
|
1617
|
+
"chown",
|
|
1618
|
+
"-R",
|
|
1619
|
+
"--from=root",
|
|
1620
|
+
"vscode:vscode",
|
|
1621
|
+
"/home/vscode"
|
|
1622
|
+
],
|
|
1623
|
+
{ reject: false }
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1386
1626
|
var STATE_DIR = join4(homedir3(), ".agentbox");
|
|
1387
1627
|
var PID_FILE = join4(STATE_DIR, "relay.pid");
|
|
1388
1628
|
var LOG_FILE = join4(STATE_DIR, "relay.log");
|
|
@@ -1411,7 +1651,7 @@ async function ensureRelay(opts = {}) {
|
|
|
1411
1651
|
if (existingPid !== null && await processAlive(existingPid)) {
|
|
1412
1652
|
for (let i = 0; i < 10; i++) {
|
|
1413
1653
|
if (await pingHealthz(300)) return ENDPOINT;
|
|
1414
|
-
await
|
|
1654
|
+
await delay2(200);
|
|
1415
1655
|
}
|
|
1416
1656
|
log(`relay pid ${String(existingPid)} alive but /healthz unresponsive \u2014 proceeding anyway`);
|
|
1417
1657
|
return ENDPOINT;
|
|
@@ -1445,7 +1685,7 @@ async function ensureRelay(opts = {}) {
|
|
|
1445
1685
|
log(`relay reachable on ${ENDPOINT.hostUrl}`);
|
|
1446
1686
|
return ENDPOINT;
|
|
1447
1687
|
}
|
|
1448
|
-
await
|
|
1688
|
+
await delay2(200);
|
|
1449
1689
|
}
|
|
1450
1690
|
throw new Error(
|
|
1451
1691
|
`relay did not become reachable on ${ENDPOINT.hostUrl} within 5s; see ${LOG_FILE}`
|
|
@@ -1501,7 +1741,7 @@ async function stopRelay() {
|
|
|
1501
1741
|
}
|
|
1502
1742
|
for (let i = 0; i < 20; i++) {
|
|
1503
1743
|
if (!await processAlive(pid)) break;
|
|
1504
|
-
await
|
|
1744
|
+
await delay2(100);
|
|
1505
1745
|
}
|
|
1506
1746
|
if (await processAlive(pid)) {
|
|
1507
1747
|
try {
|
|
@@ -1624,6 +1864,26 @@ async function forgetBoxFromRelay(boxId) {
|
|
|
1624
1864
|
} catch {
|
|
1625
1865
|
}
|
|
1626
1866
|
}
|
|
1867
|
+
async function setRelayNotice(boxId, kind, message, ttlMs) {
|
|
1868
|
+
try {
|
|
1869
|
+
const body = await adminPostForJson("/admin/notices/set", {
|
|
1870
|
+
boxId,
|
|
1871
|
+
kind,
|
|
1872
|
+
message,
|
|
1873
|
+
...typeof ttlMs === "number" ? { ttlMs } : {}
|
|
1874
|
+
});
|
|
1875
|
+
const id = body?.id;
|
|
1876
|
+
return typeof id === "string" && id.length > 0 ? id : null;
|
|
1877
|
+
} catch {
|
|
1878
|
+
return null;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
async function clearRelayNotice(boxId, id) {
|
|
1882
|
+
try {
|
|
1883
|
+
await adminPost("/admin/notices/clear", { boxId, id });
|
|
1884
|
+
} catch {
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1627
1887
|
async function adminPost(path, body) {
|
|
1628
1888
|
const json = JSON.stringify(body);
|
|
1629
1889
|
await new Promise((resolveP, rejectP) => {
|
|
@@ -1662,6 +1922,49 @@ async function adminPost(path, body) {
|
|
|
1662
1922
|
req.end();
|
|
1663
1923
|
});
|
|
1664
1924
|
}
|
|
1925
|
+
async function adminPostForJson(path, body) {
|
|
1926
|
+
const json = JSON.stringify(body);
|
|
1927
|
+
return new Promise((resolveP, rejectP) => {
|
|
1928
|
+
const req = httpRequest(
|
|
1929
|
+
{
|
|
1930
|
+
host: "127.0.0.1",
|
|
1931
|
+
port: PORT,
|
|
1932
|
+
method: "POST",
|
|
1933
|
+
path,
|
|
1934
|
+
headers: {
|
|
1935
|
+
"Content-Type": "application/json",
|
|
1936
|
+
"Content-Length": Buffer.byteLength(json).toString()
|
|
1937
|
+
},
|
|
1938
|
+
timeout: 3e3
|
|
1939
|
+
},
|
|
1940
|
+
(res) => {
|
|
1941
|
+
const chunks = [];
|
|
1942
|
+
res.on("data", (c) => chunks.push(c));
|
|
1943
|
+
res.on("end", () => {
|
|
1944
|
+
const status = res.statusCode ?? 0;
|
|
1945
|
+
if (status < 200 || status >= 300) {
|
|
1946
|
+
rejectP(new Error(`relay ${path} \u2192 ${String(status)}`));
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
1950
|
+
try {
|
|
1951
|
+
resolveP(text.length > 0 ? JSON.parse(text) : {});
|
|
1952
|
+
} catch (err) {
|
|
1953
|
+
rejectP(err instanceof Error ? err : new Error(String(err)));
|
|
1954
|
+
}
|
|
1955
|
+
});
|
|
1956
|
+
res.on("error", rejectP);
|
|
1957
|
+
}
|
|
1958
|
+
);
|
|
1959
|
+
req.on("error", rejectP);
|
|
1960
|
+
req.on("timeout", () => {
|
|
1961
|
+
req.destroy();
|
|
1962
|
+
rejectP(new Error(`relay ${path} timeout`));
|
|
1963
|
+
});
|
|
1964
|
+
req.write(json);
|
|
1965
|
+
req.end();
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1665
1968
|
async function rehydrateRelayRegistry(boxes) {
|
|
1666
1969
|
for (const b of boxes) {
|
|
1667
1970
|
if (!b.relayToken) continue;
|
|
@@ -1782,8 +2085,8 @@ async function ensureAgentboxTasksFile(container, services, opts = {}) {
|
|
|
1782
2085
|
return { status: "wrote" };
|
|
1783
2086
|
}
|
|
1784
2087
|
async function writeFileInBox(container, path, content) {
|
|
1785
|
-
const { execa:
|
|
1786
|
-
const result = await
|
|
2088
|
+
const { execa: execa6 } = await import("execa");
|
|
2089
|
+
const result = await execa6(
|
|
1787
2090
|
"docker",
|
|
1788
2091
|
["exec", "-i", "--user", "vscode", container, "sh", "-c", `cat > ${shellQuote(path)}`],
|
|
1789
2092
|
{ input: content, reject: false }
|
|
@@ -2296,6 +2599,9 @@ export {
|
|
|
2296
2599
|
buildClaudeAttachArgv,
|
|
2297
2600
|
buildClaudeDashboardAttachArgv,
|
|
2298
2601
|
buildShellArgv,
|
|
2602
|
+
buildClaudeLoginRunArgv,
|
|
2603
|
+
runInteractiveClaudeLogin,
|
|
2604
|
+
warmUpClaudeCredentials,
|
|
2299
2605
|
formatDetachNotice,
|
|
2300
2606
|
claudeSessionInfo,
|
|
2301
2607
|
pullClaudeExtras,
|
|
@@ -2320,12 +2626,15 @@ export {
|
|
|
2320
2626
|
snapshotPathFor,
|
|
2321
2627
|
createSnapshot,
|
|
2322
2628
|
launchCtlDaemon,
|
|
2629
|
+
ensureHomeOwnedByVscode,
|
|
2323
2630
|
ensureRelay,
|
|
2324
2631
|
stopRelay,
|
|
2325
2632
|
getRelayStatus,
|
|
2326
2633
|
generateRelayToken,
|
|
2327
2634
|
registerBoxWithRelay,
|
|
2328
2635
|
forgetBoxFromRelay,
|
|
2636
|
+
setRelayNotice,
|
|
2637
|
+
clearRelayNotice,
|
|
2329
2638
|
rehydrateRelayRegistry,
|
|
2330
2639
|
ideProfile,
|
|
2331
2640
|
SHARED_VSCODE_EXTENSIONS_VOLUME,
|
|
@@ -2343,4 +2652,4 @@ export {
|
|
|
2343
2652
|
ConfigError,
|
|
2344
2653
|
loadConfig
|
|
2345
2654
|
};
|
|
2346
|
-
//# sourceMappingURL=chunk-
|
|
2655
|
+
//# sourceMappingURL=chunk-HTTKML3C.js.map
|