@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
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_RELAY_PORT,
|
|
4
4
|
readBoxStatus
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6OZDFNBF.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/_cloud-attach.ts
|
|
8
|
+
import { spawn as spawn3 } from "child_process";
|
|
6
9
|
|
|
7
10
|
// src/provider/registry.ts
|
|
8
11
|
var KNOWN = ["docker", "daytona", "hetzner"];
|
|
@@ -12,16 +15,16 @@ function isKnownProvider(name) {
|
|
|
12
15
|
async function getProvider(name) {
|
|
13
16
|
switch (name) {
|
|
14
17
|
case "docker": {
|
|
15
|
-
const mod = await import("./dist-
|
|
18
|
+
const mod = await import("./dist-LOZBWMBF.js");
|
|
16
19
|
return mod.dockerProvider;
|
|
17
20
|
}
|
|
18
21
|
case "daytona": {
|
|
19
|
-
const mod = await import("./dist-
|
|
22
|
+
const mod = await import("./dist-L4LCG5SJ.js");
|
|
20
23
|
await mod.ensureDaytonaCredentials();
|
|
21
24
|
return mod.daytonaProvider;
|
|
22
25
|
}
|
|
23
26
|
case "hetzner": {
|
|
24
|
-
const mod = await import("./dist-
|
|
27
|
+
const mod = await import("./dist-ZODPD2I6.js");
|
|
25
28
|
await mod.ensureHetznerCredentials();
|
|
26
29
|
return mod.hetznerProvider;
|
|
27
30
|
}
|
|
@@ -51,13 +54,13 @@ async function loadPtyBackend() {
|
|
|
51
54
|
try {
|
|
52
55
|
const ptyMod = await import("@homebridge/node-pty-prebuilt-multiarch");
|
|
53
56
|
const xtermMod = await import("@xterm/headless");
|
|
54
|
-
const
|
|
57
|
+
const spawn4 = ptyMod["spawn"] ?? ptyMod["default"]?.["spawn"];
|
|
55
58
|
const Terminal = xtermMod["Terminal"] ?? xtermMod["default"]?.["Terminal"];
|
|
56
|
-
if (typeof
|
|
59
|
+
if (typeof spawn4 !== "function" || typeof Terminal !== "function") {
|
|
57
60
|
return null;
|
|
58
61
|
}
|
|
59
62
|
return {
|
|
60
|
-
ptySpawn:
|
|
63
|
+
ptySpawn: spawn4,
|
|
61
64
|
termCtor: Terminal
|
|
62
65
|
};
|
|
63
66
|
} catch {
|
|
@@ -116,23 +119,38 @@ async function spawnInITerm2(args) {
|
|
|
116
119
|
const inner = shellJoin(args.argv);
|
|
117
120
|
const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${inner}`;
|
|
118
121
|
const cmdLit = `"${appleScriptEscape(cmdLine)}"`;
|
|
119
|
-
let
|
|
122
|
+
let lines;
|
|
120
123
|
let noteKind;
|
|
121
124
|
switch (args.mode) {
|
|
122
125
|
case "split":
|
|
123
|
-
|
|
126
|
+
lines = [
|
|
127
|
+
'tell application "iTerm"',
|
|
128
|
+
" tell current session of current window to set _s to (split vertically with default profile)",
|
|
129
|
+
` tell _s to write text ${cmdLit}`,
|
|
130
|
+
"end tell"
|
|
131
|
+
];
|
|
124
132
|
noteKind = "iTerm2 split";
|
|
125
133
|
break;
|
|
126
134
|
case "tab":
|
|
127
|
-
|
|
135
|
+
lines = [
|
|
136
|
+
'tell application "iTerm"',
|
|
137
|
+
" tell current window to set _t to (create tab with default profile)",
|
|
138
|
+
` tell current session of _t to write text ${cmdLit}`,
|
|
139
|
+
"end tell"
|
|
140
|
+
];
|
|
128
141
|
noteKind = "iTerm2 tab";
|
|
129
142
|
break;
|
|
130
143
|
case "window":
|
|
131
|
-
|
|
144
|
+
lines = [
|
|
145
|
+
'tell application "iTerm"',
|
|
146
|
+
" set _w to (create window with default profile)",
|
|
147
|
+
` tell current session of _w to write text ${cmdLit}`,
|
|
148
|
+
"end tell"
|
|
149
|
+
];
|
|
132
150
|
noteKind = "iTerm2 window";
|
|
133
151
|
break;
|
|
134
152
|
}
|
|
135
|
-
const r = await runQuiet("osascript", ["-e",
|
|
153
|
+
const r = await runQuiet("osascript", ["-e", lines.join("\n")]);
|
|
136
154
|
if (r.code !== 0) {
|
|
137
155
|
return {
|
|
138
156
|
launched: false,
|
|
@@ -1114,11 +1132,23 @@ async function cloudAgentAttach(args) {
|
|
|
1114
1132
|
throw new Error(`provider '${provider.name}' does not support interactive attach`);
|
|
1115
1133
|
}
|
|
1116
1134
|
const command = buildCloudAttachInnerCommand(args.binary, args.extraArgs);
|
|
1135
|
+
const safeOpenIn = args.box.provider === "daytona" ? "same" : args.openIn;
|
|
1136
|
+
if (safeOpenIn && safeOpenIn !== "same" && args.extraArgs && args.extraArgs.length > 0) {
|
|
1137
|
+
const pre = await provider.buildAttach(args.box, "agent", {
|
|
1138
|
+
sessionName: args.sessionName,
|
|
1139
|
+
command,
|
|
1140
|
+
detached: true
|
|
1141
|
+
});
|
|
1142
|
+
try {
|
|
1143
|
+
await runDetached(pre.argv);
|
|
1144
|
+
} finally {
|
|
1145
|
+
if (pre.cleanup) await pre.cleanup();
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1117
1148
|
const spec = await provider.buildAttach(args.box, "agent", {
|
|
1118
1149
|
sessionName: args.sessionName,
|
|
1119
1150
|
command
|
|
1120
1151
|
});
|
|
1121
|
-
const safeOpenIn = args.box.provider === "daytona" ? "same" : args.openIn;
|
|
1122
1152
|
try {
|
|
1123
1153
|
const code = await runWrappedAttach({
|
|
1124
1154
|
container: args.box.name,
|
|
@@ -1137,6 +1167,13 @@ async function cloudAgentAttach(args) {
|
|
|
1137
1167
|
if (spec.cleanup) await spec.cleanup();
|
|
1138
1168
|
}
|
|
1139
1169
|
}
|
|
1170
|
+
function runDetached(argv) {
|
|
1171
|
+
return new Promise((resolve) => {
|
|
1172
|
+
const child = spawn3(argv[0], argv.slice(1), { stdio: "ignore" });
|
|
1173
|
+
child.on("error", () => resolve());
|
|
1174
|
+
child.on("exit", () => resolve());
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1140
1177
|
|
|
1141
1178
|
export {
|
|
1142
1179
|
isKnownProvider,
|
|
@@ -1144,6 +1181,7 @@ export {
|
|
|
1144
1181
|
providerForBox,
|
|
1145
1182
|
providerForCreate,
|
|
1146
1183
|
loadPtyBackend,
|
|
1184
|
+
detectHostTerminal,
|
|
1147
1185
|
NEW_BOX_ID,
|
|
1148
1186
|
NEW_BOX_LABEL,
|
|
1149
1187
|
sidebarLines,
|
|
@@ -1159,4 +1197,4 @@ export {
|
|
|
1159
1197
|
buildCloudAttachInnerCommand,
|
|
1160
1198
|
cloudAgentAttach
|
|
1161
1199
|
};
|
|
1162
|
-
//# sourceMappingURL=chunk-
|
|
1200
|
+
//# sourceMappingURL=chunk-FODMEHD3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/_cloud-attach.ts","../src/provider/registry.ts","../src/wrapped-pty/run.ts","../src/pty/pty-backend.ts","../src/terminal/host.ts","../src/wrapped-pty/input-router.ts","../src/dashboard/sidebar.ts","../src/wrapped-pty/footer.ts","../src/wrapped-pty/prompt-client.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { DEFAULT_RELAY_PORT } from '@agentbox/sandbox-docker';\nimport type { BoxRecord } from '@agentbox/core';\nimport type { AttachOpenIn } from '@agentbox/config';\nimport { providerForBox } from '../provider/registry.js';\nimport { runWrappedAttach } from '../wrapped-pty/index.js';\n\nconst RELAY_HOST_URL = `http://127.0.0.1:${String(DEFAULT_RELAY_PORT)}`;\n\n/**\n * Attach to (or create) a tmux session inside a cloud sandbox over SSH and\n * run an agent CLI inside it. Shared between `agentbox claude`/`codex`/\n * `opencode` so the SSH + tmux mechanics live in one place.\n *\n * The inner command tmux runs is `bash -lc 'exec <binary>'`:\n * - login shell so `/home/vscode/.local/bin` is on PATH and `/etc/profile.d/\n * agentbox.sh` exports `AGENTBOX_BOX_*` env;\n * - `exec` so the agent gets PID 2 (Ctrl-c in the agent kills the session\n * cleanly rather than dropping to bash).\n *\n * When `extraArgs` is non-empty, we base64-encode the argv (one arg per line)\n * and hand the inner shell a small `mapfile`-based launcher that reconstructs\n * the array — see `buildCloudAttachInnerCommand`. Base64 is alphanumeric+`/+=`\n * so it survives every shell-quoting layer (host single-quote, SSH, tmux,\n * bash) untouched, which avoids the 3-layer escaping mess the literal form\n * would otherwise require.\n */\nexport interface CloudAgentAttachArgs {\n box: BoxRecord;\n /** In-sandbox binary path or name (`claude`, `codex`, `opencode`). */\n binary: string;\n /** Tmux session name (e.g. `claude`). */\n sessionName: string;\n /** Mode label for the wrapper's footer. */\n mode: 'claude' | 'codex' | 'opencode';\n /**\n * Extra args the user typed after `--`. Passed through to the in-box agent\n * verbatim via a base64-encoded launcher. Limitation: args containing\n * literal `\\n` aren't supported (none of claude/codex/opencode flags do).\n */\n extraArgs?: string[];\n /**\n * Where to open the attached session in the host's terminal (`split`/`window`/\n * `tab`/`same`). Forwarded to `runWrappedAttach`. Daytona attaches are forced\n * to `same` for now because `provider.buildAttach()` may return a `cleanup`\n * that tears down per-call SSH tunnels — running cleanup while a detached\n * new pane still holds the connection would kill the pane. Hetzner's\n * ControlMaster is per-box-lifetime so spawn-and-detach is safe there.\n */\n openIn?: AttachOpenIn;\n}\n\n/**\n * Render the inner shell command tmux runs inside the cloud sandbox. Exported\n * so unit tests can exercise the base64 round-trip without spinning up SSH.\n *\n * Empty `extraArgs` keeps the no-args path identical to the pre-args\n * behaviour — `bash -lc 'exec <binary>'` with a backslash-space so the outer\n * shell-quoting layers don't split `exec` from the binary name.\n */\nexport function buildCloudAttachInnerCommand(binary: string, extraArgs?: string[]): string {\n if (!extraArgs || extraArgs.length === 0) {\n return `bash -lc exec\\\\ ${binary}`;\n }\n // One arg per line, base64-encoded. The launcher runs `mapfile -t A` against\n // the decoded stream, then `exec <binary> \"${A[@]}\"` so each arg lands as\n // its own argv element — quotes/spaces inside an arg are preserved exactly\n // because base64 is opaque to every outer shell quoting pass.\n const blob = Buffer.from(extraArgs.join('\\n'), 'utf8').toString('base64');\n // **bash -lc body MUST be single-quoted, not double-quoted.** When tmux\n // launches the session command, it goes through `/bin/sh -c <cmd>`. If we\n // double-quote, sh's parser sees `\"${A[@]}\"` and expands it eagerly —\n // before mapfile ever runs — to the empty string, so claude is invoked as\n // `claude \"\"` and the wizard's initial prompt is silently dropped. Single\n // quotes are inert in sh's parser: the literal `${A[@]}` reaches bash,\n // which expands it AFTER mapfile populates A. The outer shellSingle wrap\n // in renderInnerCommand re-escapes any internal `'` as `'\\''`, so this\n // composes fine.\n return `bash -lc 'mapfile -t A < <(echo ${blob} | base64 -d); exec ${binary} \"\\${A[@]}\"'`;\n}\n\nexport async function cloudAgentAttach(args: CloudAgentAttachArgs): Promise<void> {\n const provider = await providerForBox(args.box);\n if (!provider.buildAttach) {\n throw new Error(`provider '${provider.name}' does not support interactive attach`);\n }\n const command = buildCloudAttachInnerCommand(args.binary, args.extraArgs);\n // Daytona-only: force inline attach. `spec.cleanup` would otherwise run as\n // soon as the host process returns from the spawn (before the new pane has\n // released the per-call SSH tunnel), breaking the detached attach.\n const safeOpenIn: AttachOpenIn | undefined =\n args.box.provider === 'daytona' ? 'same' : args.openIn;\n\n // New-terminal attaches (tab/window/split) re-invoke `agentbox <agent> attach`\n // in the fresh pane, and that re-invocation carries NO `extraArgs` — so for a\n // resume/teleport launch (`claude --resume <id>`, etc.) the session would\n // otherwise be created fresh, dropping the resumed session. Pre-create the\n // session detached here with the full command; the re-invoked attach then\n // finds it via `tmux has-session` and just attaches. (Inline attach runs the\n // full command itself, so it doesn't need this.)\n if (safeOpenIn && safeOpenIn !== 'same' && args.extraArgs && args.extraArgs.length > 0) {\n const pre = await provider.buildAttach(args.box, 'agent', {\n sessionName: args.sessionName,\n command,\n detached: true,\n });\n try {\n await runDetached(pre.argv);\n } finally {\n if (pre.cleanup) await pre.cleanup();\n }\n }\n\n const spec = await provider.buildAttach(args.box, 'agent', {\n sessionName: args.sessionName,\n command,\n });\n try {\n const code = await runWrappedAttach({\n container: args.box.name,\n command: spec.argv[0],\n dockerArgv: spec.argv.slice(1),\n relayBaseUrl: RELAY_HOST_URL,\n boxId: args.box.id,\n boxName: args.box.name,\n projectIndex: args.box.projectIndex,\n mode: args.mode,\n detachable: true,\n openIn: safeOpenIn,\n });\n process.exit(code);\n } finally {\n if (spec.cleanup) await spec.cleanup();\n }\n}\n\n/**\n * Run an attach-style argv non-interactively to completion (used for the\n * `detached` session pre-start). stdio is ignored — the remote command only\n * creates + configures the tmux session and exits; there's nothing to show.\n * Resolves on exit regardless of code (a non-zero here shouldn't block the\n * subsequent attach, which surfaces any real failure to the user).\n */\nfunction runDetached(argv: string[]): Promise<void> {\n return new Promise((resolve) => {\n const child = spawn(argv[0]!, argv.slice(1), { stdio: 'ignore' });\n child.on('error', () => resolve());\n child.on('exit', () => resolve());\n });\n}\n","/**\n * Provider registry — resolves a `Provider` for either an existing box (from\n * its `provider` discriminator) or a fresh `create` (from --provider flag /\n * config / default). Lazy `import()` keeps the Daytona SDK out of the Docker\n * hot path.\n */\n\nimport type { EffectiveConfig } from '@agentbox/config';\nimport type { BoxRecord, Provider, ProviderName } from '@agentbox/core';\n\nexport type KnownProviderName = 'docker' | 'daytona' | 'hetzner';\n\nconst KNOWN: readonly KnownProviderName[] = ['docker', 'daytona', 'hetzner'];\n\nexport function isKnownProvider(name: string): name is KnownProviderName {\n return (KNOWN as readonly string[]).includes(name);\n}\n\nexport async function getProvider(name: ProviderName): Promise<Provider> {\n switch (name) {\n case 'docker': {\n const mod = await import('@agentbox/sandbox-docker');\n return mod.dockerProvider;\n }\n case 'daytona': {\n // Single lazy import covers both the first-run prompt gate and the\n // provider itself — keeps the Daytona SDK off the Docker hot path.\n // The prompt is a no-op when env is already configured or stdin isn't\n // a TTY (scripted callers get the SDK's \"not configured\" error instead\n // of a hung prompt).\n const mod = await import('@agentbox/sandbox-daytona');\n await mod.ensureDaytonaCredentials();\n return mod.daytonaProvider;\n }\n case 'hetzner': {\n // Same lazy-import pattern as daytona. `ensureHetznerCredentials` walks\n // the user through `agentbox hetzner login` on first use. The base-\n // snapshot gate (`ensureHetznerBaseSnapshot`) is deliberately *not*\n // called here: it would chicken-and-egg `agentbox prepare --provider\n // hetzner` (which exists precisely to BUILD the snapshot). The gate\n // lives inside `backend.provision` instead — `prepare` calls the REST\n // client directly, never `provision`, so it slips past the gate while\n // `create`/`claude`/etc. still trip it.\n const mod = await import('@agentbox/sandbox-hetzner');\n await mod.ensureHetznerCredentials();\n return mod.hetznerProvider;\n }\n default:\n throw new Error(`unknown sandbox provider: ${String(name)}`);\n }\n}\n\n/** Provider for an existing box record. Defaults to 'docker' for legacy records. */\nexport async function providerForBox(box: BoxRecord): Promise<Provider> {\n return getProvider(box.provider ?? 'docker');\n}\n\nexport interface CreateProviderChoice {\n /** Explicit --provider flag, if the command exposed one. */\n flag?: string;\n /** Effective config (carries box.provider for the layered default). */\n config: EffectiveConfig;\n}\n\n/**\n * Provider for a fresh `agentbox create`. Precedence: --provider flag >\n * box.provider config > 'docker'. Throws if the resolved name isn't registered.\n */\nexport async function providerForCreate(choice: CreateProviderChoice): Promise<Provider> {\n const flag = choice.flag?.trim();\n const name = (flag && flag.length > 0 ? flag : choice.config.box.provider) as ProviderName;\n if (typeof name !== 'string' || name.length === 0 || !isKnownProvider(name)) {\n throw new Error(\n `unknown sandbox provider \"${String(name)}\" (known: ${KNOWN.join(', ')})`,\n );\n }\n return getProvider(name);\n}\n","import { spawn, spawnSync } from 'node:child_process';\nimport { readBoxStatus } from '@agentbox/sandbox-docker';\nimport type { AttachOpenIn } from '@agentbox/config';\nimport { loadPtyBackend } from '../pty/pty-backend.js';\nimport { detectHostTerminal, spawnInNewTerminal } from '../terminal/host.js';\nimport {\n createInputRouter,\n type InputRouter,\n type LeaderAction,\n} from './input-router.js';\nimport {\n CURSOR_RESTORE,\n CURSOR_SAVE,\n cursorMoveTo,\n renderFooter,\n SYNC_BEGIN,\n SYNC_END,\n type FooterState,\n} from './footer.js';\nimport { postAnswer, subscribePrompts, type PromptStream } from './prompt-client.js';\nimport type { BoxNoticeEvent, PromptAskEvent } from '@agentbox/relay';\n\nexport interface WrappedAttachOptions {\n /** Docker container name (only used for log lines). */\n container: string;\n /** Full docker argv (e.g. result of buildClaudeAttachArgv). */\n dockerArgv: string[];\n /**\n * The program to spawn for the PTY. Defaults to `'docker'` (the historical\n * behavior; `dockerArgv` is then the docker subcommand argv). Cloud boxes\n * pass `'ssh'` with the Daytona SSH argv instead.\n */\n command?: string;\n /** Relay base URL — http://127.0.0.1:8787 in normal use. */\n relayBaseUrl: string;\n boxId: string;\n /** Friendly box name; rendered in the idle footer. */\n boxName: string;\n /** Per-project box index (BoxRecord.projectIndex). Used together with\n * boxId/boxName to read the per-box status.json for the live session\n * title. Pre-feature boxes lack it; absent is fine. */\n projectIndex?: number;\n /** Mode label affects the idle footer state label only. */\n mode: 'claude' | 'shell' | 'codex' | 'opencode';\n /** Whether the inner session can be detached (tmux-backed). Drives the\n * `Ctrl+a d` detach chord + footer hint. Defaults to `mode === 'claude'`\n * (claude is always tmux-backed); a tmux-backed `agentbox shell` passes\n * `true`, a `--no-tmux` shell leaves it false. */\n detachable?: boolean;\n /** Optional notice printed to stdout *after* the pty exits with code 0\n * (mirrors today's `formatDetachNotice` for `agentbox claude`). */\n detachNotice?: string;\n /** Optional sink for non-fatal errors that we'd otherwise swallow (Ctrl+a\n * action spawn failures, status-poll failures, unexpected prompt-capture\n * rejections). Callers wire this to their command log so post-mortem\n * inspection isn't blind. */\n onError?: (msg: string) => void;\n /** Where to open the attached session. When set to anything other than\n * `same` (or undefined) and the host shell is running inside tmux or iTerm2,\n * the attach runs in a fresh pane/tab/window and this function returns 0\n * without taking over the current terminal. Outside tmux/iTerm2 it falls\n * back to inline attach (the original behavior). */\n openIn?: AttachOpenIn;\n}\n\nconst FOOTER_ROWS = 1;\nconst STATUS_POLL_INTERVAL_MS = 3000;\n/** Spinner advance cadence while a `notice` footer is active. */\nconst SPINNER_INTERVAL_MS = 120;\n/** How long the post-action confirmation flash stays in the footer. */\nconst FLASH_DURATION_MS = 2000;\n\n/** Per-action confirmation text shown in the footer flash. */\nconst ACTION_FLASH: Record<Exclude<LeaderAction, 'detach'>, string> = {\n screen: 'Opening noVNC viewer…',\n code: 'Launching VS Code / Cursor…',\n url: 'Opening box URL…',\n};\n\n/** Per-action `agentbox` subcommand: `<sub> <boxId> <...flags>`. */\nconst ACTION_CMD: Record<\n Exclude<LeaderAction, 'detach'>,\n { sub: string; flags: string[] }\n> = {\n screen: { sub: 'screen', flags: [] },\n // --no-wait: don't block on `wait-ready` — the box is already running.\n code: { sub: 'code', flags: ['--no-wait'] },\n url: { sub: 'url', flags: [] },\n};\n\n/** Recursive `agentbox <agent> attach <box> --attach-in same` argv for the\n * new-pane re-entry. Returns null for modes that don't have an `attach`\n * subcommand (notably `shell`), so the caller can skip new-pane spawning. */\nfunction buildAgentboxAttachArgv(\n mode: WrappedAttachOptions['mode'],\n boxName: string,\n): string[] | null {\n if (mode !== 'claude' && mode !== 'codex' && mode !== 'opencode') return null;\n return [mode, 'attach', boxName, '--attach-in', 'same'];\n}\n\n/**\n * Replace `spawnSync('docker', argv, { stdio: 'inherit' })` with a\n * node-pty wrapper that reserves the bottom row for a permission-prompt\n * footer. Falls back transparently to today's spawnSync behavior when\n * node-pty isn't available (optional dep missing), or when stdin/stdout\n * isn't a TTY (piping / non-interactive use).\n *\n * Returns the pty's exit code; caller `process.exit`s with it.\n */\nexport async function runWrappedAttach(opts: WrappedAttachOptions): Promise<number> {\n const command = opts.command ?? 'docker';\n const logErr = (msg: string): void => {\n opts.onError?.(msg);\n };\n\n // Open-in-new-terminal short-circuit: if the user asked for split/window/tab\n // and we're inside tmux or iTerm2, re-invoke `agentbox <agent> attach <box>\n // --attach-in same` in a fresh pane so the new pane runs the full wrapper\n // (footer + prompt channel) against the already-prepared session — same UX\n // as inline, just in a new pane. The host process then exits 0. Unknown\n // hosts, shell mode (no attach subcommand to recurse into), and spawn\n // failures fall through to the inline attach below.\n const openIn = opts.openIn ?? 'same';\n if (openIn !== 'same') {\n const subArgv = buildAgentboxAttachArgv(opts.mode, opts.boxName);\n const host = subArgv ? detectHostTerminal() : 'unknown';\n if (subArgv && host !== 'unknown' && process.argv[1]) {\n const r = await spawnInNewTerminal({\n host,\n mode: openIn,\n argv: [process.execPath, process.argv[1], ...subArgv],\n cwd: process.cwd(),\n title: opts.boxName,\n });\n if (r.launched) {\n process.stdout.write(r.note + '\\n');\n return 0;\n }\n if (r.error) logErr(r.error);\n // fall through to inline attach\n }\n }\n\n if (!process.stdout.isTTY || !process.stdin.isTTY) {\n // Non-interactive path: piping / scripts. Don't wrap — preserves\n // machine-readable stdout, no footer corruption.\n return runFallback(command, opts.dockerArgv);\n }\n const backend = await loadPtyBackend();\n if (!backend) {\n // One-line stderr notice; preserves current behavior bit-for-bit.\n process.stderr.write(\n 'agentbox: permission prompts disabled (node-pty backend unavailable)\\n',\n );\n return runFallback(command, opts.dockerArgv);\n }\n\n const cols = process.stdout.columns ?? 80;\n const rows = process.stdout.rows ?? 24;\n const innerRows = Math.max(1, rows - FOOTER_ROWS);\n\n const pty = backend.ptySpawn(command, opts.dockerArgv, {\n name: 'xterm-256color',\n cols,\n rows: innerRows,\n env: process.env,\n });\n\n // claude is always tmux-backed; a tmux-backed `agentbox shell` opts in via\n // `detachable: true`, a `--no-tmux` shell leaves it false (nothing to detach).\n const detachable = opts.detachable ?? opts.mode === 'claude';\n\n // Idle footer = dashboard's statusLine() with a single hint (`Control+a:\n // Actions`, expanding to the chord menu while the leader is open). Session\n // title + claude activity come from the per-box status.json polled below.\n let leaderActive = false;\n const buildIdle = (sessionTitle?: string, claudeActivity?: string): FooterState => ({\n kind: 'idle',\n boxName: opts.boxName,\n sessionTitle,\n claudeActivity,\n mode: opts.mode,\n detachable,\n leaderActive,\n });\n let footerState: FooterState = buildIdle();\n let lastSessionTitle: string | undefined;\n let lastActivity: string | undefined;\n // Prompt + notice + flash are tracked independently of `footerState`;\n // `recomputeFooter` derives the visible state (prompt > notice > flash > idle).\n let capturingPrompt: PromptAskEvent | null = null;\n let activeNotice: BoxNoticeEvent | null = null;\n let noticeFrame = 0;\n let spinnerTimer: ReturnType<typeof setInterval> | null = null;\n // Transient confirmation shown after a Ctrl+a action fires.\n let flashMessage: string | null = null;\n let flashTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Lazy SGR mirror: when the inner pty's most recent attribute is bright\n // bold, our footer paint won't reset it correctly via the inner program's\n // next byte. We always end the footer with SGR reset, but the inner\n // program may be in the middle of a graphics run when the footer redraw\n // happens — wrap the redraw in cursor save/restore + sync output so the\n // inner program never sees our cursor moves and the user sees one atomic\n // frame.\n const redrawFooter = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n const line = renderFooter(footerState, cs);\n // Position at the last row, then write. Save/restore around the lot so\n // the inner pty's cursor doesn't move.\n const payload =\n SYNC_BEGIN +\n CURSOR_SAVE +\n cursorMoveTo(rs, 1) +\n line +\n CURSOR_RESTORE +\n SYNC_END;\n process.stdout.write(payload);\n };\n\n // Derive `footerState` from the current prompt / notice / flash / idle\n // inputs. A pending prompt outranks a notice (it hard-blocks the box's\n // RPC); a notice outranks a flash; a flash outranks idle. Called after any\n // input changes.\n const recomputeFooter = (): void => {\n if (capturingPrompt) {\n footerState = { kind: 'prompt', prompt: capturingPrompt };\n } else if (activeNotice) {\n footerState = { kind: 'notice', message: activeNotice.message, frame: noticeFrame };\n } else if (flashMessage) {\n footerState = { kind: 'flash', message: flashMessage };\n } else {\n footerState = buildIdle(lastSessionTitle, lastActivity);\n }\n };\n\n const startSpinner = (): void => {\n if (spinnerTimer) return;\n spinnerTimer = setInterval(() => {\n noticeFrame++;\n // Only repaint while the notice is the visible state — if a prompt is\n // covering it the frame still advances so it resumes mid-animation.\n if (footerState.kind === 'notice') {\n recomputeFooter();\n redrawFooter();\n }\n }, SPINNER_INTERVAL_MS);\n if (typeof spinnerTimer.unref === 'function') spinnerTimer.unref();\n };\n const stopSpinner = (): void => {\n if (spinnerTimer) {\n clearInterval(spinnerTimer);\n spinnerTimer = null;\n }\n };\n\n // Wire pty -> stdout. The inner program writes raw bytes; we forward as-is.\n // The outer terminal has `rows` real rows, but the pty thinks it has `innerRows`.\n // The inner program's writes can still physically touch row `rows` (our footer\n // row) via: (1) scroll when its bottom line emits a newline — the terminal\n // scrolls the whole screen and row `rows` gets cleared; (2) clear-screen\n // sequences like `\\x1b[2J`; (3) alt-screen entry `\\x1b[?1049h`; (4) column\n // wraparound from the inner program's last row. The scroll-region setup\n // below limits (1); always-repaint here handles the rest. Each redraw is\n // wrapped in synchronized output (DECSET 2026) so the user never sees a\n // half-painted frame on terminals that support it (iTerm2/WezTerm/kitty/\n // Apple Terminal/Ghostty).\n pty.onData((d: string) => {\n process.stdout.write(d);\n redrawFooter();\n });\n\n // Ctrl+a leader chord map — keys mirror the dashboard's (`c`/`s`/`u`).\n // A detachable (tmux-backed) session also gets `d: detach`; a plain\n // `--no-tmux` shell has nothing to detach from.\n const leaderChords: Record<string, LeaderAction> = detachable\n ? { c: 'code', s: 'screen', u: 'url', d: 'detach' }\n : { c: 'code', s: 'screen', u: 'url' };\n\n // Run a Ctrl+a leader action. `detach` writes the tmux detach sequence to\n // the pty (`\\x02` = Ctrl+b, tmux's secondary prefix; `d` = detach-client) —\n // the attach process then exits 0 and teardown runs normally. The other\n // actions shell out to the real `agentbox` subcommand, detached, so the\n // long-running open/launch never blocks (or corrupts) this terminal.\n const runAction = (name: LeaderAction): void => {\n if (name === 'detach') {\n pty.write('\\x02d');\n return;\n }\n const cliEntry = process.argv[1];\n if (typeof cliEntry === 'string' && cliEntry.length > 0) {\n const cmd = ACTION_CMD[name];\n try {\n spawn(\n process.execPath,\n [cliEntry, cmd.sub, opts.boxId, ...cmd.flags],\n { detached: true, stdio: 'ignore' },\n ).unref();\n } catch (e) {\n // Best-effort — the footer flash still shows. Surface for inspection.\n logErr(`leader-action spawn (${name}) failed: ${(e as Error).message}`);\n }\n }\n flashMessage = ACTION_FLASH[name];\n if (flashTimer) clearTimeout(flashTimer);\n flashTimer = setTimeout(() => {\n flashTimer = null;\n flashMessage = null;\n recomputeFooter();\n redrawFooter();\n }, FLASH_DURATION_MS);\n if (typeof flashTimer.unref === 'function') flashTimer.unref();\n recomputeFooter();\n redrawFooter();\n };\n\n // Wire stdin -> pty (through the router so prompts + the leader can intercept).\n const router: InputRouter = createInputRouter({\n onForward: (b) => {\n // node-pty wants utf8 strings; stdin is binary safe via Buffer.\n pty.write(b.toString('utf8'));\n },\n onAnswer: (body) => {\n // Fire-and-forget; the relay-side route is idempotent. We don't\n // block the input flow on the network roundtrip.\n void postAnswer({ relayBaseUrl: opts.relayBaseUrl, body });\n capturingPrompt = null;\n recomputeFooter();\n redrawFooter();\n },\n leaderChords,\n onLeaderChange: (open) => {\n leaderActive = open;\n recomputeFooter();\n redrawFooter();\n },\n onAction: (name) => {\n runAction(name);\n },\n });\n\n if (process.stdin.isTTY) process.stdin.setRawMode(true);\n process.stdin.resume();\n const onStdinData = (chunk: Buffer): void => {\n router.feed(chunk);\n };\n process.stdin.on('data', onStdinData);\n\n // Resize: keep the pty one row shorter than the host terminal; the footer\n // owns the last row directly. Re-apply the scroll region too — most\n // terminals reset DECSTBM on resize.\n const onResize = (): void => {\n const cs = process.stdout.columns ?? cols;\n const rs = process.stdout.rows ?? rows;\n const inner = Math.max(1, rs - FOOTER_ROWS);\n pty.resize(cs, inner);\n process.stdout.write(`\\x1b[1;${String(inner)}r`);\n redrawFooter();\n };\n process.stdout.on('resize', onResize);\n\n // SSE: subscribe to the relay's prompt stream for this box.\n const stream: PromptStream = subscribePrompts({\n relayBaseUrl: opts.relayBaseUrl,\n boxId: opts.boxId,\n onPrompt: (ev: PromptAskEvent) => {\n capturingPrompt = ev;\n recomputeFooter();\n redrawFooter();\n // capture() returns a Promise that resolves with the answer body; the\n // input-router's onAnswer callback already POSTs and resets the footer.\n // We just need to await so unhandled rejections (router.abort) don't\n // crash the process.\n router.capture(ev).catch((e: unknown) => {\n // Expected reasons: sibling answered ('resolved-elsewhere'), pty exit.\n // Anything else is a real bug worth surfacing.\n const msg = e instanceof Error ? e.message : String(e);\n if (msg !== 'resolved-elsewhere') {\n logErr(`prompt capture rejected: ${msg}`);\n }\n });\n },\n onResolved: (id: string) => {\n // Clear footer if it's still showing this id (sibling wrapper won).\n if (capturingPrompt && capturingPrompt.id === id) {\n capturingPrompt = null;\n router.abort('resolved-elsewhere');\n recomputeFooter();\n redrawFooter();\n }\n },\n onNotice: (ev: BoxNoticeEvent) => {\n activeNotice = ev;\n startSpinner();\n recomputeFooter();\n redrawFooter();\n },\n onNoticeCleared: (id: string) => {\n if (activeNotice && activeNotice.id === id) {\n activeNotice = null;\n stopSpinner();\n recomputeFooter();\n redrawFooter();\n }\n },\n });\n\n // Poll the box's status.json for `claude.sessionTitle` so the idle\n // footer can show what claude set as its terminal title (mirrors the\n // dashboard's sidebar entry). Best-effort — paused/stopped boxes and\n // pre-status-feature boxes return null and we just keep the previous\n // title (or no title).\n const pollStatus = async (): Promise<void> => {\n try {\n const status = await readBoxStatus({\n id: opts.boxId,\n name: opts.boxName,\n projectIndex: opts.projectIndex,\n });\n const nextTitle = status?.claude?.sessionTitle?.trim() || undefined;\n const nextActivity = status?.claude?.state || undefined;\n if (nextTitle === lastSessionTitle && nextActivity === lastActivity) return;\n lastSessionTitle = nextTitle;\n lastActivity = nextActivity;\n if (footerState.kind === 'idle') {\n recomputeFooter();\n redrawFooter();\n }\n } catch (e) {\n // readBoxStatus already swallows the common cases (paused/stopped/pre-feature);\n // anything reaching here is unexpected and worth a log line.\n logErr(`status poll failed: ${(e as Error).message}`);\n }\n };\n void pollStatus();\n const statusTimer = setInterval(() => {\n void pollStatus();\n }, STATUS_POLL_INTERVAL_MS);\n if (typeof statusTimer.unref === 'function') statusTimer.unref();\n\n // Restrict the outer terminal's scroll region to rows 1..innerRows so the\n // inner program's natural scrolling (bottom-line newline) doesn't push\n // content into our footer row. DECSTBM also resets the cursor to (1,1) on\n // some terminals, so we follow it with a cursor restore. Reverted in\n // teardown via `\\x1b[r` (clear scroll region -> full screen).\n process.stdout.write(`\\x1b[1;${String(innerRows)}r`);\n\n // Plain shell (`--no-tmux`): bash doesn't enter alt-screen, so without help\n // the user's pre-shell host-terminal content stays visible above bash's\n // freshly drawn prompt. Clear the visible screen + home the cursor before\n // the pty's first write. We don't touch scrollback (`\\x1b[3J`) — the user's\n // pre-shell context stays scroll-up-able. Claude and the tmux-backed shell\n // skip this: they enter their own alt-screen on init and would just\n // overpaint anyway (clearing first would only flicker).\n if (opts.mode === 'shell' && !detachable) {\n process.stdout.write('\\x1b[H\\x1b[2J');\n }\n\n // Initial paint so the idle footer appears immediately.\n redrawFooter();\n\n // Wait for the pty to exit, then tear down everything.\n const exitCode = await new Promise<number>((resolve) => {\n pty.onExit(({ exitCode }) => resolve(exitCode));\n });\n\n // Teardown order: stop reading stdin, restore cooked mode, drop SSE,\n // dispose the router (rejects any in-flight capture), clear the footer\n // row so the shell prompt below doesn't sit on top of our bar.\n process.stdin.off('data', onStdinData);\n process.stdout.off('resize', onResize);\n clearInterval(statusTimer);\n stopSpinner();\n if (flashTimer) clearTimeout(flashTimer);\n if (process.stdin.isTTY) process.stdin.setRawMode(false);\n process.stdin.pause();\n stream.close();\n router.dispose();\n const rsFinal = process.stdout.rows ?? rows;\n const csFinal = process.stdout.columns ?? cols;\n // Clear the scroll region first so the cursor moves below can reach row N\n // without the terminal trying to keep them inside the smaller region.\n // Then move to the footer row, erase it, return the cursor.\n process.stdout.write(\n '\\x1b[r' +\n cursorMoveTo(rsFinal, 1) +\n `\\x1b[2K` +\n cursorMoveTo(rsFinal, csFinal),\n );\n\n if (exitCode === 0 && opts.detachNotice) {\n // Match the cosmetic of the old attachClaudeSession: overwrite tmux's\n // own `[detached]` line if it's visible, then print the reattach hint.\n process.stdout.write('\\x1b[1A\\x1b[2K\\r' + opts.detachNotice + '\\n');\n }\n return exitCode;\n}\n\n/**\n * Fallback when node-pty is unavailable or stdio isn't a TTY. Identical to\n * today's call: blocking spawnSync with inherited stdio.\n */\nfunction runFallback(command: string, argv: string[]): number {\n const child = spawnSync(command, argv, { stdio: 'inherit' });\n return child.status ?? 0;\n}\n","import type { Terminal as XtermTerminal } from '@xterm/headless';\n\n/**\n * The `@xterm/headless` `Terminal` class. Injected (not imported) because\n * @xterm/headless is CJS — a static ESM named import breaks Node's loader for\n * the whole CLI, so callers dynamic-import it and pass the ctor through.\n *\n * Used by the dashboard (for its xterm-headless screen-state mirror). The\n * wrapped-pty wrapper does not need it — the inner program writes raw bytes\n * directly to the user's terminal, no parsing required.\n */\nexport type TerminalCtor = new (opts: {\n cols: number;\n rows: number;\n allowProposedApi: boolean;\n scrollback: number;\n convertEol: boolean;\n}) => XtermTerminal;\n\n/**\n * Minimal shape of a node-pty IPty (avoids a hard type dep on the optional\n * module — node-pty is in optionalDependencies, may not be installed).\n */\nexport interface IPtyLike {\n onData(cb: (d: string) => void): void;\n onExit(cb: (e: { exitCode: number }) => void): void;\n write(d: string): void;\n resize(cols: number, rows: number): void;\n kill(): void;\n}\n\nexport type PtySpawn = (\n file: string,\n args: string[],\n opts: { name: string; cols: number; rows: number; env: NodeJS.ProcessEnv },\n) => IPtyLike;\n\nexport interface PtyBackend {\n ptySpawn: PtySpawn;\n /** Present for callers that also need the xterm headless ctor (dashboard). */\n termCtor: TerminalCtor;\n}\n\n/**\n * Dynamic-load the optional pty + xterm/headless backends. Returns null\n * when either prebuild is missing (we don't throw — callers decide how to\n * degrade). Centralized here so the dashboard and the wrapped-pty wrapper\n * use the same exact load dance.\n */\nexport async function loadPtyBackend(): Promise<PtyBackend | null> {\n try {\n const ptyMod = (await import('@homebridge/node-pty-prebuilt-multiarch')) as Record<\n string,\n unknown\n >;\n const xtermMod = (await import('@xterm/headless')) as Record<string, unknown>;\n const spawn =\n (ptyMod['spawn'] as unknown) ??\n (ptyMod['default'] as Record<string, unknown> | undefined)?.['spawn'];\n const Terminal =\n (xtermMod['Terminal'] as unknown) ??\n (xtermMod['default'] as Record<string, unknown> | undefined)?.['Terminal'];\n if (typeof spawn !== 'function' || typeof Terminal !== 'function') {\n return null;\n }\n return {\n ptySpawn: spawn as unknown as PtySpawn,\n termCtor: Terminal as unknown as TerminalCtor,\n };\n } catch {\n return null;\n }\n}\n","import { spawn } from 'node:child_process';\nimport type { AttachOpenIn } from '@agentbox/config';\n\nexport type HostTerminal = 'tmux' | 'iterm2' | 'unknown';\n\n/**\n * Identify the user's host terminal from env vars. tmux wins over iTerm2 even\n * when nested — when `TMUX` is set, the tmux CLI is the right primitive (it can\n * split the current pane / open a new window without going through AppleScript).\n *\n * macOS-only by design: the CLI itself is macOS-only (see CLAUDE.md), so we\n * don't try to recognize gnome-terminal / alacritty / Windows Terminal.\n */\nexport function detectHostTerminal(env: NodeJS.ProcessEnv = process.env): HostTerminal {\n const tmux = env['TMUX'];\n if (tmux && tmux.length > 0) return 'tmux';\n const termProgram = env['TERM_PROGRAM'];\n if (termProgram === 'iTerm.app') return 'iterm2';\n return 'unknown';\n}\n\n/** Single-quote a string so it survives a shell parse intact. */\nfunction shellQuote(s: string): string {\n if (s.length === 0) return \"''\";\n // Replace any internal `'` with the four-byte sequence `'\\''` (close, escaped\n // quote, reopen). Cheaper than picking double-quotes — no $/`/\\ to worry about.\n return \"'\" + s.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n\n/** Escape a string for embedding in a double-quoted AppleScript literal. */\nfunction appleScriptEscape(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/** Join an argv into a single shell-safe command line. */\nfunction shellJoin(argv: string[]): string {\n return argv.map(shellQuote).join(' ');\n}\n\nexport interface SpawnInNewTerminalArgs {\n host: Exclude<HostTerminal, 'unknown'>;\n /** Where to open the session in that host's terminology. `'same'` is rejected\n * by the caller — we never produce `same` here. */\n mode: Exclude<AttachOpenIn, 'same'>;\n /** Full argv to run in the new pane: `[program, ...args]`. The first element\n * is the binary; the rest are passed verbatim. */\n argv: string[];\n /** Working directory for the new pane. Passed to tmux via `-c` and prepended\n * to the iTerm2 command as `cd <cwd> && exec …`. */\n cwd: string;\n /** Short title for the new tmux window / iTerm2 tab when applicable. */\n title: string;\n}\n\nexport interface SpawnInNewTerminalResult {\n launched: boolean;\n /** One-line user-facing message printed to the host's stdout on success.\n * Empty string when `launched` is false. */\n note: string;\n /** stderr captured from the spawner, when `launched` is false. Used only for\n * the command log; not surfaced to the user. */\n error?: string;\n}\n\n/**\n * Open a fresh tmux pane / iTerm2 split-tab-window and run `<command> <argv...>`\n * there. Returns synchronously after the new pane is requested — the inner\n * command runs in its own terminal and is no longer this process's child.\n *\n * On failure (tmux/osascript exits non-zero, or wasn't found), the caller is\n * expected to fall back to inline attach.\n */\nexport async function spawnInNewTerminal(\n args: SpawnInNewTerminalArgs,\n): Promise<SpawnInNewTerminalResult> {\n if (args.host === 'tmux') return spawnInTmux(args);\n return spawnInITerm2(args);\n}\n\nasync function spawnInTmux(args: SpawnInNewTerminalArgs): Promise<SpawnInNewTerminalResult> {\n // `-c <cwd>` drops the new pane in the host pane's directory so the\n // recursive `agentbox` invocation can resolve project-scoped refs (and so\n // any commands the user runs after detaching start somewhere sensible).\n // The command is passed as a single shell-quoted positional after `--`;\n // tmux hands it to /bin/sh -c, which is why each argv element needs\n // single-quoting.\n const cmdStr = shellJoin(args.argv);\n let tmuxArgv: string[];\n let noteKind: string;\n if (args.mode === 'split') {\n tmuxArgv = ['split-window', '-h', '-c', args.cwd, '--', cmdStr];\n noteKind = 'tmux split';\n } else {\n // `window` and `tab` both map to tmux's only \"another full screen\" primitive.\n tmuxArgv = ['new-window', '-n', args.title, '-c', args.cwd, '--', cmdStr];\n noteKind = 'tmux window';\n }\n const r = await runQuiet('tmux', tmuxArgv);\n if (r.code !== 0) {\n return {\n launched: false,\n note: '',\n error: `tmux ${tmuxArgv.join(' ')} exited ${String(r.code)}: ${r.stderr.trim()}`,\n };\n }\n return {\n launched: true,\n note: `Attached in new ${noteKind} — Ctrl+a d to detach the box's tmux session.`,\n };\n}\n\nasync function spawnInITerm2(args: SpawnInNewTerminalArgs): Promise<SpawnInNewTerminalResult> {\n // iTerm2 launches `command` through a shell, but doesn't honor a starting\n // directory parameter on its AppleScript verbs. Prepend `cd <cwd> && exec`\n // so the new tab/window/split lands in the host pane's cwd and replaces\n // the launching shell with the agentbox process.\n const inner = shellJoin(args.argv);\n const cmdLine = `cd ${shellQuote(args.cwd)} && exec ${inner}`;\n const cmdLit = `\"${appleScriptEscape(cmdLine)}\"`;\n\n // Always create the tab/window/split first, then `write text` into its\n // session. The `... with default profile command \"<cmd>\"` parameter form is\n // unreliable on iTerm 3.7 betas — it fails (returns `missing value`) and the\n // command bounces to Terminal.app instead of running in iTerm. The\n // create-then-write-text form is the supported path and works across\n // versions, so every mode uses it.\n let lines: string[];\n let noteKind: string;\n switch (args.mode) {\n case 'split':\n lines = [\n 'tell application \"iTerm\"',\n ' tell current session of current window to set _s to (split vertically with default profile)',\n ` tell _s to write text ${cmdLit}`,\n 'end tell',\n ];\n noteKind = 'iTerm2 split';\n break;\n case 'tab':\n lines = [\n 'tell application \"iTerm\"',\n ' tell current window to set _t to (create tab with default profile)',\n ` tell current session of _t to write text ${cmdLit}`,\n 'end tell',\n ];\n noteKind = 'iTerm2 tab';\n break;\n case 'window':\n lines = [\n 'tell application \"iTerm\"',\n ' set _w to (create window with default profile)',\n ` tell current session of _w to write text ${cmdLit}`,\n 'end tell',\n ];\n noteKind = 'iTerm2 window';\n break;\n }\n\n const r = await runQuiet('osascript', ['-e', lines.join('\\n')]);\n if (r.code !== 0) {\n return {\n launched: false,\n note: '',\n error: `osascript exited ${String(r.code)}: ${r.stderr.trim()}`,\n };\n }\n return {\n launched: true,\n note: `Attached in new ${noteKind} — Ctrl+a d to detach the box's tmux session.`,\n };\n}\n\ninterface QuietResult {\n code: number;\n stderr: string;\n}\n\n/** Spawn `cmd argv...`, capture stderr, ignore stdout. Resolves on exit. */\nfunction runQuiet(cmd: string, argv: string[]): Promise<QuietResult> {\n return new Promise((resolve) => {\n const child = spawn(cmd, argv, { stdio: ['ignore', 'ignore', 'pipe'] });\n let stderr = '';\n child.stderr?.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8');\n });\n child.on('error', (err) => {\n resolve({ code: 127, stderr: err.message });\n });\n child.on('exit', (code) => {\n resolve({ code: typeof code === 'number' ? code : 1, stderr });\n });\n });\n}\n","import type { PromptAnswerBody, PromptAskEvent } from '@agentbox/relay';\n\n/**\n * Steady-state input forwarder + active-prompt capture + Ctrl+a leader.\n *\n * In steady state every byte goes to the pty unmodified — *unless* a\n * `leaderChords` map is supplied, in which case `Ctrl+a` (0x01) opens the\n * actions menu (leader-only: a literal Ctrl+a needs a double-press).\n *\n * Only when `capture()` is awaiting does the router intercept the next\n * keystroke and resolve the prompt with a y/n/cancel answer. Anything else\n * the user types while a prompt is active is dropped (not forwarded) — the\n * inner program doesn't see partial keys.\n */\nexport interface InputRouter {\n /** True while a prompt is being captured. Used by the run loop to know\n * whether to redraw the footer eagerly. */\n readonly capturing: boolean;\n /** Feed raw bytes from process.stdin. Forwards or captures internally. */\n feed(buf: Buffer): void;\n /** Activate prompt capture. Resolves with the answer body. Subsequent\n * capture() calls before resolution overwrite the previous prompt (the\n * newer one wins — relay broadcast order is canonical). */\n capture(p: PromptAskEvent): Promise<PromptAnswerBody>;\n /** Reject the in-flight capture (pty exit, sibling-wrapper answered). */\n abort(reason: 'pty-exit' | 'resolved-elsewhere'): void;\n dispose(): void;\n}\n\ninterface ActivePrompt {\n ev: PromptAskEvent;\n resolve: (b: PromptAnswerBody) => void;\n reject: (e: Error) => void;\n}\n\n/** Actions reachable from the Ctrl+a leader menu. */\nexport type LeaderAction = 'screen' | 'code' | 'url' | 'detach';\n\nconst KEY_ENTER = 0x0d;\nconst KEY_LF = 0x0a;\nconst KEY_ESC = 0x1b;\nconst KEY_CTRL_C = 0x03;\nconst KEY_Y_LOW = 0x79;\nconst KEY_Y_UP = 0x59;\nconst KEY_N_LOW = 0x6e;\nconst KEY_N_UP = 0x4e;\nconst KEY_LEADER = 0x01; // Ctrl-a\n\nconst DEFAULT_LEADER_TIMEOUT_MS = 2000;\n\nexport interface InputRouterOptions {\n onForward: (b: Buffer) => void;\n /** Called when a prompt's capture is resolved — the run loop POSTs the answer. */\n onAnswer: (body: PromptAnswerBody) => void;\n /** Ctrl+a leader chord map: a single lowercase character → action. When\n * omitted or empty the leader is disabled and `Ctrl+a` forwards verbatim. */\n leaderChords?: Readonly<Record<string, LeaderAction>>;\n /** Fired when the leader menu opens (true) / closes (false). */\n onLeaderChange?: (active: boolean) => void;\n /** Fired when a recognized chord key resolves the leader. */\n onAction?: (name: LeaderAction) => void;\n /** ms the leader menu stays open with no key before auto-closing (default 2000). */\n leaderTimeoutMs?: number;\n /** Injected for unit tests; defaults to global timers. */\n setTimer?: (ms: number, fn: () => void) => unknown;\n clearTimer?: (h: unknown) => void;\n}\n\nexport function createInputRouter(opts: InputRouterOptions): InputRouter {\n let active: ActivePrompt | null = null;\n let disposed = false;\n\n const leaderChords = opts.leaderChords ?? {};\n const leaderEnabled = Object.keys(leaderChords).length > 0;\n const leaderTimeoutMs = opts.leaderTimeoutMs ?? DEFAULT_LEADER_TIMEOUT_MS;\n const setTimer = opts.setTimer ?? ((ms, fn) => setTimeout(fn, ms) as unknown);\n const clearTimer =\n opts.clearTimer ?? ((h) => clearTimeout(h as ReturnType<typeof setTimeout>));\n let leader = false;\n let leaderTimer: unknown = null;\n\n const disarmLeader = (): void => {\n if (leaderTimer != null) {\n clearTimer(leaderTimer);\n leaderTimer = null;\n }\n };\n\n const exitLeader = (): void => {\n if (!leader) return;\n leader = false;\n disarmLeader();\n opts.onLeaderChange?.(false);\n };\n\n const enterLeader = (): void => {\n leader = true;\n disarmLeader();\n // Leader-only: a lone Ctrl+a just times the menu out — it is never\n // auto-forwarded. A literal Ctrl+a is sent via a double-press.\n leaderTimer = setTimer(leaderTimeoutMs, () => {\n leaderTimer = null;\n exitLeader();\n });\n opts.onLeaderChange?.(true);\n };\n\n // The leader is open and `b` is the chord byte that resolves it.\n const resolveLeaderByte = (b: number): void => {\n if (b === KEY_LEADER) {\n // Double Ctrl+a → one literal Ctrl+a to the inner program.\n exitLeader();\n opts.onForward(Buffer.from([KEY_LEADER]));\n return;\n }\n if (b === KEY_ESC) {\n // Esc dismisses the menu; nothing forwarded.\n exitLeader();\n return;\n }\n const action = leaderChords[String.fromCharCode(b).toLowerCase()];\n if (action) {\n exitLeader();\n opts.onAction?.(action);\n return;\n }\n // Unrecognized chord: close the menu, forward the key so typing isn't lost.\n exitLeader();\n opts.onForward(Buffer.from([b]));\n };\n\n const settle = (\n answer: PromptAnswerBody['answer'],\n cancelled?: boolean,\n ): void => {\n if (!active) return;\n const body: PromptAnswerBody = {\n id: active.ev.id,\n answer,\n ...(cancelled ? { cancelled: true } : {}),\n };\n const p = active;\n active = null;\n p.resolve(body);\n opts.onAnswer(body);\n };\n\n const handleCapturedByte = (b: number): void => {\n if (!active) return;\n if (b === KEY_Y_LOW || b === KEY_Y_UP) {\n settle('y');\n return;\n }\n if (b === KEY_N_LOW || b === KEY_N_UP) {\n settle('n');\n return;\n }\n if (b === KEY_ESC || b === KEY_CTRL_C) {\n settle('n', true);\n return;\n }\n if (b === KEY_ENTER || b === KEY_LF) {\n // Enter accepts the default answer.\n const def = active.ev.defaultAnswer ?? 'n';\n settle(def);\n return;\n }\n // Anything else: ignored (not forwarded, not consumed).\n };\n\n // Leader-aware steady-state forwarding: scan bytes, batching non-leader\n // runs into a single onForward call, and intercept `Ctrl+a` chords.\n const feedSteady = (buf: Buffer): void => {\n let chunkStart = 0;\n const flushChunk = (end: number): void => {\n if (end > chunkStart) opts.onForward(buf.subarray(chunkStart, end));\n chunkStart = end;\n };\n for (let i = 0; i < buf.length; i++) {\n const byte = buf[i];\n if (byte === undefined) continue;\n if (leader) {\n resolveLeaderByte(byte);\n chunkStart = i + 1;\n continue;\n }\n if (byte === KEY_LEADER) {\n flushChunk(i); // forward everything typed before the Ctrl+a\n chunkStart = i + 1;\n enterLeader();\n }\n }\n flushChunk(buf.length);\n };\n\n return {\n get capturing(): boolean {\n return active !== null;\n },\n feed(buf: Buffer): void {\n if (disposed) return;\n if (active) {\n // A multi-byte read starting with ESC is a CSI/SS3/OSC escape\n // sequence — mouse click (`\\x1b[<…M/m`), arrow / function key,\n // window-focus event, bracketed-paste markers, etc. Drop the\n // whole chunk: the user pressed something we don't model as a\n // confirmation key, and they'd be (correctly) surprised if a stray\n // mouse click registered as \"deny\". A *real* Esc keypress arrives\n // as a single byte in its own read, which still cancels below.\n if (buf.length > 1 && buf[0] === KEY_ESC) return;\n // Process bytes one at a time so a paste of \"yes\\n\" is handled\n // sanely: the 'y' settles, the rest is dropped — we don't want\n // stray bytes leaking to the pty after the prompt closed mid-buf.\n // (After settle, `active` is null; remaining bytes fall through to\n // forward path below.)\n for (let i = 0; i < buf.length; i++) {\n const byte = buf[i];\n if (byte === undefined) continue;\n if (active) {\n handleCapturedByte(byte);\n } else {\n // Active became null mid-buffer (settled). Forward the rest as\n // a normal keystroke chunk.\n opts.onForward(buf.subarray(i));\n return;\n }\n }\n return;\n }\n if (!leaderEnabled) {\n opts.onForward(buf);\n return;\n }\n feedSteady(buf);\n },\n capture(ev: PromptAskEvent): Promise<PromptAnswerBody> {\n return new Promise<PromptAnswerBody>((resolve, reject) => {\n // A relay prompt outranks the actions menu — close the leader first.\n if (leader) exitLeader();\n if (active) {\n // A new prompt arrived before the old one was answered — abort\n // the old one (treated as cancelled) and switch to the new one.\n // The relay already broadcast `prompt-ask` for both; we owe the\n // first an answer or it'll stay pending forever.\n settle('n', true);\n }\n active = { ev, resolve, reject };\n });\n },\n abort(reason): void {\n if (!active) return;\n const p = active;\n active = null;\n const msg = reason === 'pty-exit' ? 'pty exited' : 'resolved by sibling wrapper';\n p.reject(new Error(msg));\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n disarmLeader();\n if (active) {\n const p = active;\n active = null;\n p.reject(new Error('input router disposed'));\n }\n },\n };\n}\n","export interface SidebarBox {\n id: string;\n name: string;\n /** Container state: 'running' | 'paused' | 'stopped' | 'missing' | … */\n state: string;\n /** Activity of the agent this box runs (claude / codex) — 'working' | 'idle'\n * | 'waiting' | 'unknown' | undefined. Resolved from whichever agent is\n * active (see `resolveAgent` in commands/dashboard.ts). */\n activity?: string;\n /** The in-box terminal/session title the active agent set, or undefined. */\n sessionTitle?: string;\n /** 1-based per-project box number, shown as `[N]`; undefined for\n * pre-feature boxes and the synthetic \"+ New box\" entry. */\n index?: number;\n /** Absolute project root; used to group boxes under a project header.\n * Undefined for pre-feature boxes and the synthetic \"+ New box\" entry. */\n project?: string;\n /** This box has an unanswered relay `prompt-ask` event (e.g. agentbox-ctl\n * git push / cp / download waiting for user confirmation). The compositor\n * injects this flag from its in-memory map of active prompts. Overrides\n * the activity cell — `▲ prompt` reads more urgent than `● working`. */\n pendingPrompt?: boolean;\n /** This box has an active relay notice (currently: a checkpoint is being\n * captured, freezing the box). Injected by the compositor; shown as\n * `◆ checkpoint` in the activity cell. Outranked by `pendingPrompt`. */\n checkpointing?: boolean;\n}\n\n/** Per-row ownership + styling map returned alongside the rendered lines so\n * the compositor can highlight the selected box and style headers without\n * re-deriving the (now non-uniform) layout. */\nexport interface SidebarRender {\n lines: string[];\n /** boxId rendered on row `i`, else null (banner / group header / blank). */\n rowOwner: (string | null)[];\n /** true for the banner and project-header rows (styled like the banner). */\n headerRows: boolean[];\n}\n\n/** Truncate to `max` printable chars, appending `…` when it had to cut\n * (keeps the head). */\nfunction ellipsize(s: string, max: number): string {\n if (max <= 0) return '';\n if (s.length <= max) return s;\n if (max === 1) return '…';\n return s.slice(0, max - 1) + '…';\n}\n\n/** Truncate keeping the *tail* (the distinguishing part of a box name like\n * `…-78b94c78`), prepending `…` when it had to cut. */\nfunction ellipsizeHead(s: string, max: number): string {\n if (max <= 0) return '';\n if (s.length <= max) return s;\n if (max === 1) return '…';\n return '…' + s.slice(s.length - (max - 1));\n}\n\nexport function activityCell(b: SidebarBox): string {\n // Pending relay prompt outranks every other state — the user needs to\n // act before whatever the box is doing can continue.\n if (b.pendingPrompt) return '▲ prompt';\n // A checkpoint freezes the box; surface it over the activity state.\n if (b.checkpointing) return '◆ checkpoint';\n if (b.state !== 'running') return `[${b.state}]`;\n switch (b.activity) {\n case 'working':\n return '● working';\n case 'idle':\n return '○ idle';\n case 'waiting':\n return '◐ waiting';\n default:\n return '? unknown';\n }\n}\n\n/** Synthetic sidebar entry pinned at the top: selecting it opens the create\n * menu. Carried in the compositor's box list like a real box (sentinel id),\n * so selection/switch/highlight need no special-casing. */\nexport const NEW_BOX_ID = '__agentbox_new__';\nexport const NEW_BOX_LABEL = '+ New box';\n\n/** Sidebar banner label (rendered into the top border). */\nexport const SIDEBAR_HEADER = 'AgentBox';\n\n/** Top border: a flat line on the top + a rounded corner into the right edge\n * only (no left/bottom, to save space): `──── AgentBox ─────…` filling\n * exactly `w`. The left end is a straight line; the matching rounded\n * top-right corner (`╮`) is drawn by the compositor at the sidebar separator\n * column. */\nfunction topBorder(label: string, w: number): string {\n const lead = `──── ${label} `;\n if (lead.length >= w) return lead.slice(0, w);\n return lead + '─'.repeat(w - lead.length);\n}\n/** Lines `sidebarLines` reserves before the box rows (banner + blank). The\n * compositor uses this to locate the selected box row for highlighting. */\nexport const SIDEBAR_HEADER_LINES = 2;\n\nfunction fit(s: string, w: number): string {\n if (s.length === w) return s;\n if (s.length > w) return s.slice(0, w);\n return s + ' '.repeat(w - s.length);\n}\n\n/** `s` centered in a field of `w` columns (truncated if it doesn't fit). */\nfunction center(s: string, w: number): string {\n if (s.length >= w) return s.slice(0, w);\n const pad = w - s.length;\n const leftPad = Math.floor(pad / 2);\n return ' '.repeat(leftPad) + s + ' '.repeat(pad - leftPad);\n}\n\n/** `basename` of an absolute project root, for the group header label. */\nfunction projectLabel(project: string | undefined): string {\n if (!project) return '(no project)';\n const parts = project.split('/').filter(Boolean);\n return parts[parts.length - 1] ?? project;\n}\n\n/** Strip the leading decoration Claude prepends to its terminal title (the\n * spinner glyph, e.g. `✳ `) plus any leading symbols/asterisks/space, so the\n * sidebar shows just the words. Falls back to the trimmed original if the\n * title is all decoration. */\nfunction stripTitleGlyph(s: string): string {\n const t = s.replace(/^[\\s\\p{S}*·]+/u, '');\n return t.length > 0 ? t : s.trim();\n}\n\n/**\n * Render one box row: `marker<num> <title|name> <status>`. The number and\n * the status are width-protected; the middle (title, else the box name with\n * its meaningful tail kept) flexes and ellipsizes so the status is never\n * eaten. Compact: no brackets, no glyph, single-char marker.\n */\nfunction boxRow(b: SidebarBox, marker: string, w: number): string {\n const numStr = b.index != null ? `${b.index} ` : '';\n const status = activityCell(b);\n const left = `${marker}${numStr}`;\n // -2: 1 gap before status, 1 margin after so the label doesn't touch the\n // sidebar's right border.\n const room = w - left.length - status.length - 2;\n if (room <= 0) return fit(`${left}${status}`, w);\n const middle =\n b.state === 'running' && b.sessionTitle\n ? ellipsize(stripTitleGlyph(b.sessionTitle), room)\n : ellipsizeHead(b.name, room);\n // Left segment padded so the status sits one column in from the right edge,\n // with a trailing space as the right margin.\n return fit(`${left}${middle}`, w - status.length - 1) + status + ' ';\n}\n\n/**\n * The sidebar region as exactly `h` lines, each exactly `w` columns, plus a\n * per-row ownership/style map. Pure — no ANSI positioning (the compositor\n * places it). Boxes are grouped under a ` ── <project> ── ` header (callers\n * pass them pre-sorted by project).\n */\nexport function sidebarLines(\n boxes: SidebarBox[],\n selectedId: string,\n w: number,\n h: number,\n): SidebarRender {\n const lines: string[] = [topBorder(SIDEBAR_HEADER, w), fit('', w)];\n const rowOwner: (string | null)[] = [null, null];\n const headerRows: boolean[] = [true, false];\n const push = (line: string, owner: string | null, header: boolean): void => {\n lines.push(fit(line, w));\n rowOwner.push(owner);\n headerRows.push(header);\n };\n\n let prevProject: string | undefined;\n let seenGroup = false;\n for (const b of boxes) {\n const marker = b.id === selectedId ? '▸' : ' ';\n if (b.id === NEW_BOX_ID) {\n push(`${marker}${NEW_BOX_LABEL}`, b.id, false);\n continue;\n }\n if (!seenGroup || b.project !== prevProject) {\n push(center(` ── ${projectLabel(b.project)} ── `, w), null, true);\n prevProject = b.project;\n seenGroup = true;\n }\n push(boxRow(b, marker, w), b.id, false);\n }\n if (boxes.length === 0) push(' (no boxes)', null, false);\n while (lines.length < h) push('', null, false);\n return {\n lines: lines.slice(0, h),\n rowOwner: rowOwner.slice(0, h),\n headerRows: headerRows.slice(0, h),\n };\n}\n\n/**\n * Centered action menu for a running box with no Claude session.\n * Exactly `h` lines, each exactly `w` columns. Pure.\n */\nexport function menuLines(boxName: string, w: number, h: number): string[] {\n const body = [\n '',\n ` No agent session in ${boxName}.`,\n '',\n ' [c] Start Claude',\n ' [x] Start Codex',\n ' [o] Start OpenCode',\n ' [s] Open a shell',\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then c/s/u/q (code/screen/url/quit)',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n/**\n * Centered action menu for a non-running box (paused/stopped): resume +\n * destroy, with a two-step destroy confirm (the TUI can't show a prompt).\n * Exactly `h` lines, each exactly `w` columns. Pure.\n */\nexport function lifecycleMenuLines(\n boxName: string,\n state: 'paused' | 'stopped',\n confirmDestroy: boolean,\n w: number,\n h: number,\n): string[] {\n const body = confirmDestroy\n ? [\n '',\n ` Destroy ${boxName}?`,\n ' This removes the container and its volumes.',\n '',\n ' [y] Yes, destroy',\n ' [any other key] Cancel',\n ]\n : [\n '',\n ` Box ${boxName} is ${state}.`,\n '',\n state === 'paused' ? ' [u] Unpause' : ' [s] Start',\n ' [d] Destroy',\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then q quit',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n/**\n * Centered menu for the synthetic \"+ New box\" entry. Exactly `h` lines, each\n * exactly `w` columns. Pure.\n */\nexport function createMenuLines(where: string, w: number, h: number): string[] {\n const body = [\n '',\n ' Create a new box',\n '',\n ' [c] Create + launch Claude',\n ' [x] Create + launch Codex',\n ' [o] Create + launch OpenCode',\n ' [n] Create only',\n '',\n ` in ${where}`,\n '',\n ' Ctrl+Option+↑/↓ switch · Ctrl-a then q quit',\n ];\n const top = Math.max(0, Math.floor((h - body.length) / 2));\n const out: string[] = [];\n for (let i = 0; i < h; i++) out.push(fit(body[i - top] ?? '', w));\n return out;\n}\n\n// Status-bar palette — matches the in-box tmux footer\n// (`buildTmuxSessionArgs`): dark bar, blue brand block, dim-grey hints\n// with white key chords.\n/** The footer/sidebar background gray. Truecolor (not palette index 236) so\n * it pins an exact RGB — terminals can remap/shade indexed colors per\n * context, which made the sidebar and status bar look like different grays.\n * Single source so the two regions can't drift. */\nexport const BAR_BG = '\\x1b[48;2;48;48;48m';\nconst BAR_BASE = BAR_BG + '\\x1b[38;5;250m';\nconst BAR_BRAND = '\\x1b[48;5;39m\\x1b[38;5;16m'; // blue block (not bold)\nconst BRAND_BOLD = '\\x1b[1m'; // box name only\nconst BRAND_NOBOLD = '\\x1b[22m';\nconst HINT_KEY = '\\x1b[38;5;255m'; // white: the key chord\nconst HINT_TXT = '\\x1b[38;5;245m'; // gray: labels + separators\nconst BAR_RESET = '\\x1b[0m';\n\n// [key chord, label]. Modifiers spelled out (no ⌥/^ glyphs); arrows use the\n// ↑/↓ glyphs. Rendered as `KEYS: label` with the chord white, label gray.\nconst SWITCH_HINT: readonly [string, string] = ['Control+Option+↑/↓', 'switch'];\nconst HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n SWITCH_HINT,\n ['Control+a c', 'code'],\n ['Control+a s', 'screen'],\n ['Control+a u', 'url'],\n ['Control+a q', 'quit'],\n];\n\n/** Minimal hint tier when the bar is too narrow for the full `HINT_GROUPS`:\n * box switching (always important) + the leader. Pressing `Ctrl-a` then\n * expands to `ADVANCED_HINT_GROUPS` (the compositor swaps while the leader is\n * active). */\nexport const COLLAPSED_HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n SWITCH_HINT,\n ['Control+a', 'more'],\n];\n\n/** The expanded \"which-key\" chord menu shown while the Ctrl-a leader is\n * pending — every chord, compact (`KEY: label`), reverts on the next key. */\nexport const ADVANCED_HINT_GROUPS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n ['t', 'stop'],\n ['p', 'pause'],\n ['d', 'destroy'],\n ['q', 'quit'],\n];\n\n/**\n * Status line, exactly `w` printable columns, colored to match the in-box tmux\n * footer (dark bar, blue ` agentbox ▸ … ` brand block on the left, dim-grey\n * shortcut hints on the right). `stateLabel` overrides the box's activity text\n * (used for `shell` / `menu` panes where the box `activity` would otherwise\n * show a misleading `unknown`). `fallbackGroups`, when given, is the narrow-bar tier\n * tried before brand-core-only — used to keep one essential chord pinned\n * (instead of the default dashboard `COLLAPSED_HINT_GROUPS`).\n */\nexport function statusLine(\n box: SidebarBox | undefined,\n w: number,\n stateLabel?: string,\n groups: ReadonlyArray<readonly [string, string]> = HINT_GROUPS,\n fallbackGroups?: ReadonlyArray<readonly [string, string]>,\n): string {\n const state =\n stateLabel ?? (box ? (box.state === 'running' ? (box.activity ?? 'unknown') : box.state) : '');\n // \"agentbox ▸ \" stays normal weight; only the box name + state are bold.\n const brandPrefix = box ? ' agentbox ▸ ' : ' agentbox ';\n // Brand *core* (no title) — the width-protected segment. The title is the\n // lowest-priority segment: it only fills space left after brand + hints.\n const base = box ? `${box.name} (${state})` : '';\n const coreMain = box ? `${base} ` : '';\n const corePlain = brandPrefix + coreMain;\n\n const SEP = ' │ ';\n const renderHints = (\n g: ReadonlyArray<readonly [string, string]>,\n ): { plain: string; styled: string } => ({\n plain: g.map(([k, l]) => `${k}: ${l}`).join(SEP) + ' ',\n styled:\n g.map(([k, l]) => `${HINT_KEY}${k}${HINT_TXT}: ${l}`).join(`${HINT_TXT}${SEP}`) + ' ',\n });\n\n // Hint tier: shortcuts beat the title. Try the requested groups; if the\n // brand core + those hints overflow, fall back to the narrow-bar tier\n // (`fallbackGroups` if supplied, else the dashboard's minimal leader hint);\n // if even that overflows, render brand-core-only (title can never push the\n // box name off-screen).\n let hints: { plain: string; styled: string } | null = null;\n for (const g of [groups, fallbackGroups ?? COLLAPSED_HINT_GROUPS]) {\n const h = renderHints(g);\n if (corePlain.length + h.plain.length + 1 <= w) {\n hints = h;\n break;\n }\n }\n if (!hints) {\n return BAR_BASE + BAR_BRAND + fit(corePlain, w) + BAR_RESET;\n }\n\n // Title fills only the leftover, ellipsized; dropped entirely when there's\n // no meaningful room (≈ ` — ` + a few chars). Capped at 40 cols as before.\n const room = w - corePlain.length - hints.plain.length - 1;\n let titleSeg = '';\n if (box?.sessionTitle && room >= 7) {\n titleSeg = ` — ${ellipsize(box.sessionTitle, Math.min(40, room - 3))}`;\n }\n\n const leftPlain = brandPrefix + base + titleSeg + (box ? ' ' : '');\n const leftStyled =\n BAR_BRAND + brandPrefix + BRAND_BOLD + base + titleSeg + (box ? ' ' : '') + BRAND_NOBOLD;\n const gap = w - leftPlain.length - hints.plain.length;\n // brand block (name + title bold) → base bar → gap → white/gray hints.\n return (\n BAR_BASE +\n leftStyled +\n BAR_BASE +\n ' '.repeat(gap) +\n hints.styled +\n BAR_RESET\n );\n}\n","import { BAR_BG, statusLine, type SidebarBox } from '../dashboard/sidebar.js';\nimport type { PromptAskEvent } from '@agentbox/relay';\n\n/**\n * Footer rendering state. `idle` reuses the dashboard's `statusLine` shape\n * (brand chip + box name + optional session title + right-aligned hint);\n * `prompt` is shown while a `prompt-ask` event is being captured; `notice`\n * is an animated informational warning (e.g. checkpoint in progress);\n * `flash` is a transient confirmation after a Ctrl+a action fires.\n */\nexport type FooterState =\n | {\n kind: 'idle';\n boxName: string;\n /** Claude's tmux pane title (from BoxStatus.claude.sessionTitle).\n * Undefined until the first status poll completes (or in shell mode). */\n sessionTitle?: string;\n /** Claude activity hint shown in `(<state>)` after the name. Same field\n * the dashboard sidebar uses (`working` / `idle` / `waiting` / etc.). */\n claudeActivity?: string;\n /** Mode drives the state label: claude shows claude activity, the\n * others show `(shell)` / `(codex)` / `(opencode)`. */\n mode: 'claude' | 'shell' | 'codex' | 'opencode';\n /** Whether the session can be detached (tmux-backed). Drives the\n * expanded leader menu + the pinned `Control+a d: detach` hint. */\n detachable?: boolean;\n /** True while the Ctrl+a leader menu is open — swaps the collapsed\n * `Control+a: Actions` hint for the expanded chord list. */\n leaderActive?: boolean;\n }\n | { kind: 'prompt'; prompt: PromptAskEvent }\n | {\n kind: 'notice';\n /** Warning text, e.g. \"Checkpoint in progress — …\". */\n message: string;\n /** Monotonic counter; the spinner glyph is `SPINNER_FRAMES[frame % len]`. */\n frame: number;\n }\n | {\n kind: 'flash';\n /** Transient confirmation text, e.g. \"Opening noVNC viewer…\". */\n message: string;\n };\n\n/**\n * Spinner cycle for the `notice` footer. Solid half-filled circles, not\n * braille: braille glyphs read as a faint dot cluster on the yellow banner\n * (set vs unset dots are hard to tell apart), so the motion gets lost. The\n * rotating black half of these is unambiguous.\n */\nexport const SPINNER_FRAMES = ['◐', '◓', '◑', '◒'] as const;\n\nconst URGENT = '\\x1b[38;5;220m\\x1b[1m'; // bright yellow + bold (active prompt)\nconst TXT = '\\x1b[38;5;250m'; // dim gray body text\nconst SUBTLE = '\\x1b[38;5;245m'; // very dim (Y/N hint)\nconst RESET = '\\x1b[0m';\n// Notice footer = a full-width warning banner: bright yellow background with\n// near-black bold text. High contrast so the \"box is frozen\" state is\n// unmissable — deliberately louder than the dim-on-dark idle/prompt bars.\nconst NOTICE_BG = '\\x1b[48;5;220m'; // bright yellow background\nconst NOTICE_FG = '\\x1b[38;5;16m\\x1b[1m'; // near-black + bold text\n// Flash footer = a calm one-line confirmation on the normal dark bar.\nconst FLASH_FG = '\\x1b[38;5;150m\\x1b[1m'; // soft green + bold\n\n/** Collapsed idle hint (plain `--no-tmux` shell) — the leader is hidden\n * behind one chord. */\nconst COLLAPSED_HINTS_PLAIN: ReadonlyArray<readonly [string, string]> = [\n ['Control+a', 'Actions'],\n];\n/** Collapsed idle hint (detachable session) — the detach chord stays pinned\n * on the right even while the actions menu is closed. */\nconst COLLAPSED_HINTS_DETACHABLE: ReadonlyArray<readonly [string, string]> = [\n ['Control+a', 'Actions'],\n ['Control+a d', 'detach'],\n];\n/** Narrow-bar fallback for a detachable session: drop the `Actions` hint\n * first, but never the detach chord. */\nconst DETACH_PIN_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['Control+a d', 'detach'],\n];\n/** Expanded which-key menu shown while the Ctrl+a leader is open. A\n * detachable (tmux-backed) session also gets `d: detach`; a plain shell\n * has nothing to detach from. */\nconst DETACHABLE_LEADER_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n ['d', 'detach'],\n];\nconst PLAIN_LEADER_HINTS: ReadonlyArray<readonly [string, string]> = [\n ['c', 'code'],\n ['s', 'screen'],\n ['u', 'url'],\n];\n\n/**\n * Truncate `s` to exactly `width` visible columns, padding with spaces when\n * shorter. ANSI SGR sequences must NOT be present in the input.\n */\nfunction padTo(visible: string, width: number): string {\n if (visible.length === width) return visible;\n if (visible.length > width) {\n if (width <= 1) return visible.slice(0, width);\n return visible.slice(0, width - 1) + '…';\n }\n return visible + ' '.repeat(width - visible.length);\n}\n\n/**\n * Render the footer row as a single ANSI string. Caller positions the\n * cursor at the last row, col 0 before writing, and restores it afterwards.\n * Always ends with SGR reset so the inner pty's next byte starts clean.\n */\nexport function renderFooter(state: FooterState, cols: number): string {\n if (cols <= 0) return '';\n if (state.kind === 'idle') {\n const sidebarBox: SidebarBox = {\n id: '', // unused by statusLine\n name: state.boxName,\n state: 'running', // we're attached, so the container is up\n activity: state.claudeActivity,\n sessionTitle: state.sessionTitle,\n };\n const isClaude = state.mode === 'claude';\n const detachable = state.detachable ?? isClaude;\n // Shell/codex modes have no claude activity to surface — passing\n // `stateLabel` overrides statusLine's default (which would otherwise show\n // `(unknown)` because `claudeActivity` is undefined and the container is\n // running).\n const stateLabel = isClaude ? undefined : state.mode === 'shell' ? 'shell' : state.mode;\n if (state.leaderActive) {\n const leaderHints = detachable ? DETACHABLE_LEADER_HINTS : PLAIN_LEADER_HINTS;\n return statusLine(sidebarBox, cols, stateLabel, leaderHints);\n }\n // Collapsed: a detachable session keeps the detach chord pinned on the\n // right (its narrow-bar fallback drops `Actions` first, never `detach`).\n const collapsed = detachable ? COLLAPSED_HINTS_DETACHABLE : COLLAPSED_HINTS_PLAIN;\n const fallback = detachable ? DETACH_PIN_HINTS : undefined;\n return statusLine(sidebarBox, cols, stateLabel, collapsed, fallback);\n }\n if (state.kind === 'flash') {\n // Flash state: a brief \"<arrow> <message>\" confirmation on the dark bar.\n const prefix = ' ▸ '; // ▸\n const inner = Math.max(0, cols - prefix.length);\n const message = padTo(state.message, inner);\n return `${BAR_BG}${FLASH_FG}${prefix}${TXT}${message}${RESET}`;\n }\n if (state.kind === 'notice') {\n // Notice state: \"<spinner> <message>\" rendered as a full-width\n // high-contrast yellow warning banner. The spinner reassures the user\n // the box is busy, not stuck.\n const spinner = SPINNER_FRAMES[state.frame % SPINNER_FRAMES.length]!;\n const prefix = ` ${spinner} `;\n const inner = Math.max(0, cols - prefix.length);\n const message = padTo(state.message, inner);\n return `${NOTICE_BG}${NOTICE_FG}${prefix}${message}${RESET}`;\n }\n // Prompt state: \"[!] <message> [detail] [y/N]\"\n // The y/N hint is suffixed; we squeeze the message+detail into the space\n // left over (truncating message first, then detail).\n const def = state.prompt.defaultAnswer ?? 'n';\n const yn = def === 'y' ? '[Y/n]' : '[y/N]';\n const tag = ' [!] ';\n const sep = ' ';\n const hintW = ` ${yn} `.length;\n const inner = Math.max(0, cols - tag.length - hintW);\n const detailRaw = state.prompt.detail ?? '';\n let message = state.prompt.message;\n let detail = detailRaw;\n const messageBudget = Math.max(8, inner - (detail.length > 0 ? sep.length + 8 : 0));\n if (message.length > messageBudget) {\n message = message.slice(0, Math.max(0, messageBudget - 1)) + '…';\n }\n const usedByMessage = message.length;\n const detailBudget = Math.max(0, inner - usedByMessage - sep.length);\n if (detail.length > detailBudget) {\n detail = detailBudget <= 1 ? '' : detail.slice(0, detailBudget - 1) + '…';\n }\n const middlePlain = detail.length > 0 ? `${message}${sep}${detail}` : message;\n const padded = padTo(middlePlain, inner);\n return `${BAR_BG}${URGENT}${tag}${TXT}${padded}${SUBTLE} ${yn} ${RESET}`;\n}\n\n/**\n * ANSI sequence to move the cursor to (row, col) — 1-based, terminal convention.\n */\nexport function cursorMoveTo(row: number, col: number): string {\n return `\\x1b[${String(row)};${String(col)}H`;\n}\n\nexport const CURSOR_SAVE = '\\x1b7';\nexport const CURSOR_RESTORE = '\\x1b8';\n\n/**\n * Synchronized output toggles (DECSET/DECRST 2026). Wrap a multi-write\n * footer paint so terminals that support it commit one atomic frame.\n */\nexport const SYNC_BEGIN = '\\x1b[?2026h';\nexport const SYNC_END = '\\x1b[?2026l';\n","import { request as httpRequest, type IncomingMessage } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport type { BoxNoticeEvent, PromptAnswerBody, PromptAskEvent } from '@agentbox/relay';\n\n/**\n * SSE subscription back to the relay's `GET /admin/prompts/stream`. The\n * relay pushes:\n * - `event: prompt-ask` data: PromptAskEvent (with id)\n * - `event: prompt-resolved` data: { id }\n * - `event: notice-set` data: BoxNoticeEvent (with id)\n * - `event: notice-clear` data: { id }\n * - `event: ping` data: { ts }\n *\n * We reconnect with exponential backoff on any error or close — the only\n * way to know the relay is back is to keep trying. Subscribers are\n * loopback-only so latency is sub-ms.\n */\nexport interface PromptStream {\n /** Stop subscribing; aborts any in-flight reconnect attempt. */\n close(): void;\n}\n\nexport interface SubscribeOptions {\n relayBaseUrl: string;\n boxId: string;\n onPrompt: (ev: PromptAskEvent) => void;\n /** Server-driven: a sibling wrapper (or this one) answered; the run loop\n * clears the footer for stale ids it didn't originate. */\n onResolved: (id: string) => void;\n /** A box-level informational notice was set (e.g. checkpoint in progress). */\n onNotice?: (ev: BoxNoticeEvent) => void;\n /** A previously-set notice was cleared (explicitly or via its TTL). */\n onNoticeCleared?: (id: string) => void;\n onError?: (err: Error) => void;\n}\n\nconst INITIAL_BACKOFF_MS = 200;\nconst MAX_BACKOFF_MS = 5_000;\n\nexport function subscribePrompts(opts: SubscribeOptions): PromptStream {\n let closed = false;\n let req: ReturnType<typeof httpRequest> | null = null;\n let res: IncomingMessage | null = null;\n let reconnectTimer: NodeJS.Timeout | null = null;\n let backoffMs = INITIAL_BACKOFF_MS;\n let url: URL;\n try {\n url = new URL(opts.relayBaseUrl);\n } catch (err) {\n if (opts.onError) opts.onError(err instanceof Error ? err : new Error(String(err)));\n return { close: () => {} };\n }\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n\n function scheduleReconnect(): void {\n if (closed) return;\n const delay = backoffMs;\n backoffMs = Math.min(MAX_BACKOFF_MS, backoffMs * 2);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n connect();\n }, delay);\n if (typeof reconnectTimer.unref === 'function') reconnectTimer.unref();\n }\n\n /**\n * SSE message parser: server sends `event: <type>\\n` then `data: <json>\\n\\n`.\n * The relay never splits an event across writes (one chunk per dispatch),\n * but we still buffer by message boundary `\\n\\n` so a mid-message slice\n * doesn't corrupt parsing.\n */\n let buffer = '';\n function consumeMessages(): void {\n let idx = buffer.indexOf('\\n\\n');\n while (idx !== -1) {\n const raw = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n idx = buffer.indexOf('\\n\\n');\n // Drop the SSE comment line we send on connect (`: connected`).\n if (raw.startsWith(':')) continue;\n let event = '';\n let dataLine = '';\n for (const line of raw.split('\\n')) {\n if (line.startsWith('event:')) event = line.slice('event:'.length).trim();\n else if (line.startsWith('data:')) dataLine = line.slice('data:'.length).trim();\n }\n if (event === 'prompt-ask' && dataLine.length > 0) {\n try {\n const ev = JSON.parse(dataLine) as PromptAskEvent;\n if (ev && typeof ev.id === 'string') opts.onPrompt(ev);\n } catch {\n /* malformed; relay should never send this — ignore rather than die */\n }\n } else if (event === 'prompt-resolved' && dataLine.length > 0) {\n try {\n const payload = JSON.parse(dataLine) as { id?: string };\n if (payload && typeof payload.id === 'string') opts.onResolved(payload.id);\n } catch {\n /* malformed; ignore */\n }\n } else if (event === 'notice-set' && dataLine.length > 0) {\n try {\n const ev = JSON.parse(dataLine) as BoxNoticeEvent;\n if (ev && typeof ev.id === 'string') opts.onNotice?.(ev);\n } catch {\n /* malformed; ignore */\n }\n } else if (event === 'notice-clear' && dataLine.length > 0) {\n try {\n const payload = JSON.parse(dataLine) as { id?: string };\n if (payload && typeof payload.id === 'string') opts.onNoticeCleared?.(payload.id);\n } catch {\n /* malformed; ignore */\n }\n }\n // 'ping' has no caller-visible side effect — its purpose is to keep\n // the socket from going idle and to let the wrapper detect dead links\n // via socket-level errors. No-op here.\n }\n }\n\n function connect(): void {\n if (closed) return;\n req = transport({\n host: url.hostname,\n port,\n method: 'GET',\n path: `${url.pathname.replace(/\\/$/, '')}/admin/prompts/stream?boxId=${encodeURIComponent(opts.boxId)}`,\n headers: { Accept: 'text/event-stream' },\n });\n req.on('response', (r) => {\n res = r;\n if (r.statusCode !== 200) {\n // 400/403 — relay says \"no for you\"; bail without retrying since\n // these are config errors (no boxId, not loopback) that won't fix\n // themselves.\n if (opts.onError) opts.onError(new Error(`SSE stream returned ${String(r.statusCode)}`));\n r.resume();\n close();\n return;\n }\n backoffMs = INITIAL_BACKOFF_MS; // reset on a healthy connect\n r.setEncoding('utf8');\n r.on('data', (chunk: string) => {\n buffer += chunk;\n consumeMessages();\n });\n r.on('end', () => {\n if (!closed) scheduleReconnect();\n });\n r.on('error', () => {\n if (!closed) scheduleReconnect();\n });\n });\n req.on('error', () => {\n if (!closed) scheduleReconnect();\n });\n req.end();\n }\n\n function close(): void {\n if (closed) return;\n closed = true;\n if (reconnectTimer) clearTimeout(reconnectTimer);\n try {\n res?.destroy();\n } catch {\n /* best-effort */\n }\n try {\n req?.destroy();\n } catch {\n /* best-effort */\n }\n }\n\n connect();\n return { close };\n}\n\n/**\n * POST a PromptAnswerBody to /admin/prompts/answer. Fire-and-(mostly)-\n * forget: we don't retry on failure because the relay's `prompts.resolve`\n * is idempotent and a double-resolve returns 404. If the relay was dead,\n * the SSE reconnect loop will repush any prompts that are still pending.\n */\nexport interface PostAnswerOptions {\n relayBaseUrl: string;\n body: PromptAnswerBody;\n}\n\nexport interface PostAnswerResult {\n ok: boolean;\n status: number;\n}\n\nexport function postAnswer(opts: PostAnswerOptions): Promise<PostAnswerResult> {\n return new Promise<PostAnswerResult>((resolve) => {\n let url: URL;\n try {\n url = new URL(opts.relayBaseUrl);\n } catch {\n resolve({ ok: false, status: 0 });\n return;\n }\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n const json = JSON.stringify(opts.body);\n const req = transport(\n {\n host: url.hostname,\n port,\n method: 'POST',\n path: `${url.pathname.replace(/\\/$/, '')}/admin/prompts/answer`,\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json).toString(),\n },\n timeout: 3000,\n },\n (res) => {\n res.resume();\n const status = res.statusCode ?? 0;\n // 204 = accepted; 404 = already answered (idempotent). Both are \"done\".\n resolve({ ok: status === 204 || status === 404, status });\n },\n );\n req.on('error', () => resolve({ ok: false, status: 0 }));\n req.on('timeout', () => {\n req.destroy();\n resolve({ ok: false, status: 0 });\n });\n req.write(json);\n req.end();\n });\n}\n"],"mappings":"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","names":["spawn","spawn","spawn","inner","message","spawn","exitCode","spawn"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// ../../packages/relay/dist/chunk-
|
|
3
|
+
// ../../packages/relay/dist/chunk-YVJLTAM3.js
|
|
4
4
|
import { request as httpRequest } from "http";
|
|
5
5
|
import { request as httpsRequest } from "https";
|
|
6
6
|
import { setTimeout as delay } from "timers/promises";
|
|
@@ -10,9 +10,23 @@ var REQUEST_TIMEOUT_MS = 25e3;
|
|
|
10
10
|
var FAST_REQUEST_TIMEOUT_MS = 8e3;
|
|
11
11
|
var FAST_MODE_DECAY_POLLS = 5;
|
|
12
12
|
var STOPPED_TICK_MS = 250;
|
|
13
|
+
function isConnectionLevelError(err) {
|
|
14
|
+
const code = err?.code;
|
|
15
|
+
if (code && CONNECTION_LEVEL_CODES.has(code)) return true;
|
|
16
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
17
|
+
return /\b(ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ECONNRESET|EPIPE)\b/.test(msg);
|
|
18
|
+
}
|
|
19
|
+
var CONNECTION_LEVEL_CODES = /* @__PURE__ */ new Set([
|
|
20
|
+
"ECONNREFUSED",
|
|
21
|
+
"ENOTFOUND",
|
|
22
|
+
"EHOSTUNREACH",
|
|
23
|
+
"ECONNRESET",
|
|
24
|
+
"EPIPE"
|
|
25
|
+
]);
|
|
13
26
|
var CloudBoxPoller = class {
|
|
14
27
|
constructor(deps) {
|
|
15
28
|
this.deps = deps;
|
|
29
|
+
this.currentPreviewUrl = deps.previewUrl;
|
|
16
30
|
}
|
|
17
31
|
deps;
|
|
18
32
|
stopped = false;
|
|
@@ -26,6 +40,15 @@ var CloudBoxPoller = class {
|
|
|
26
40
|
* within ~5 successful round-trips.
|
|
27
41
|
*/
|
|
28
42
|
fastModePolls = 0;
|
|
43
|
+
/**
|
|
44
|
+
* Mutable copy of `deps.previewUrl`. We don't read `deps.previewUrl`
|
|
45
|
+
* directly after construction because `recoverPreviewUrl` may hand us a
|
|
46
|
+
* new URL (e.g. Hetzner reopens its SSH ControlMaster and the `-L`
|
|
47
|
+
* forward gets a new ephemeral local port).
|
|
48
|
+
*/
|
|
49
|
+
currentPreviewUrl;
|
|
50
|
+
/** Guards against recovery storms — at most one recovery attempt in flight. */
|
|
51
|
+
recovering = null;
|
|
29
52
|
start() {
|
|
30
53
|
if (this.loopPromise) return;
|
|
31
54
|
this.loopPromise = this.run().catch((err) => {
|
|
@@ -42,7 +65,7 @@ var CloudBoxPoller = class {
|
|
|
42
65
|
* and the in-box agent finally sees the answer.
|
|
43
66
|
*/
|
|
44
67
|
async respond(actionId, result) {
|
|
45
|
-
const base = this.
|
|
68
|
+
const base = this.currentPreviewUrl.replace(/\/+$/, "");
|
|
46
69
|
const url = new URL(`${base}/bridge/action-result`);
|
|
47
70
|
const isHttps = url.protocol === "https:";
|
|
48
71
|
const transport = isHttps ? httpsRequest : httpRequest;
|
|
@@ -132,6 +155,9 @@ var CloudBoxPoller = class {
|
|
|
132
155
|
this.fastModePolls = FAST_MODE_DECAY_POLLS;
|
|
133
156
|
}
|
|
134
157
|
this.log(`poll error: ${msg}`);
|
|
158
|
+
if (this.deps.recoverPreviewUrl && isConnectionLevelError(err)) {
|
|
159
|
+
await this.tryRecoverPreviewUrl();
|
|
160
|
+
}
|
|
135
161
|
await this.backoff();
|
|
136
162
|
}
|
|
137
163
|
if (this.currentBackoffMs === 0 && this.fastModePolls > 0) {
|
|
@@ -144,8 +170,33 @@ var CloudBoxPoller = class {
|
|
|
144
170
|
this.currentBackoffMs = this.currentBackoffMs === 0 ? BACKOFF_BASE_MS : Math.min(this.currentBackoffMs * 2, BACKOFF_MAX_MS);
|
|
145
171
|
await delay(this.currentBackoffMs);
|
|
146
172
|
}
|
|
173
|
+
async tryRecoverPreviewUrl() {
|
|
174
|
+
if (!this.deps.recoverPreviewUrl) return;
|
|
175
|
+
if (this.recovering) {
|
|
176
|
+
await this.recovering;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.recovering = (async () => {
|
|
180
|
+
try {
|
|
181
|
+
const next = await this.deps.recoverPreviewUrl();
|
|
182
|
+
if (typeof next === "string" && next.length > 0 && next !== this.currentPreviewUrl) {
|
|
183
|
+
this.log(`preview URL recovered: ${this.currentPreviewUrl} \u2192 ${next}`);
|
|
184
|
+
this.currentPreviewUrl = next;
|
|
185
|
+
this.currentBackoffMs = 0;
|
|
186
|
+
} else if (typeof next === "string" && next === this.currentPreviewUrl) {
|
|
187
|
+
this.log("preview URL recovered (unchanged)");
|
|
188
|
+
this.currentBackoffMs = 0;
|
|
189
|
+
}
|
|
190
|
+
} catch (err) {
|
|
191
|
+
this.log(`preview URL recover failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
192
|
+
} finally {
|
|
193
|
+
this.recovering = null;
|
|
194
|
+
}
|
|
195
|
+
})();
|
|
196
|
+
await this.recovering;
|
|
197
|
+
}
|
|
147
198
|
async pollOnce() {
|
|
148
|
-
const base = this.
|
|
199
|
+
const base = this.currentPreviewUrl.replace(/\/+$/, "");
|
|
149
200
|
const url = new URL(`${base}/bridge/poll?since=${String(this.cursor)}`);
|
|
150
201
|
const isHttps = url.protocol === "https:";
|
|
151
202
|
const transport = isHttps ? httpsRequest : httpRequest;
|
|
@@ -234,4 +285,4 @@ export {
|
|
|
234
285
|
CloudBoxPoller,
|
|
235
286
|
CloudBoxPollers
|
|
236
287
|
};
|
|
237
|
-
//# sourceMappingURL=chunk-
|
|
288
|
+
//# sourceMappingURL=chunk-G3H2L3O2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/relay/src/cloud-poller.ts"],"sourcesContent":["/**\n * `CloudBoxPoller` — host-resident loop that drains a cloud box's in-sandbox\n * relay over its Daytona preview URL. One poller per cloud box, owned by the\n * host relay process (so status / autopause behave the same whether or not a\n * CLI is attached, matching the Docker model).\n *\n * v0 responsibility: pull `/bridge/poll`, forward events + status into the\n * host relay's stores. Executing drained `HostAction`s (real `git push` on\n * the host, with `askPrompt` gating) and posting results back is the next\n * layer, tracked alongside Phase 4's host-RPC execution.\n */\n\nimport { request as httpRequest } from 'node:http';\nimport { request as httpsRequest } from 'node:https';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport type {\n BridgeActionResultBody,\n BridgePollResponse,\n HostAction,\n HostActionResult,\n RelayEvent,\n} from './types.js';\n\nexport interface CloudBoxPollerDeps {\n boxId: string;\n /** Preview URL of the in-sandbox relay's `/bridge/*` surface. */\n previewUrl: string;\n /** Bearer for `/bridge/*` auth (Daytona's `x-daytona-preview-token` is added separately when known). */\n bridgeToken: string;\n /** Optional Daytona preview token header for the proxy layer. */\n previewToken?: string;\n /** Fired for each batch of new events drained from the box. */\n onEvents?: (events: RelayEvent[]) => void;\n /** Fired when a new box-status snapshot is observed. */\n onStatus?: (status: unknown) => void;\n /**\n * Fired for each parked host action the box wants the host to execute.\n * The `respond` callback POSTs back to `/bridge/action-result` and\n * unblocks the in-box `/rpc` caller — the handler MUST call it (with a\n * non-zero exitCode and stderr message even when the host can't execute\n * the action) so the box never hangs.\n */\n onAction?: (\n action: HostAction,\n respond: (result: HostActionResult) => Promise<void>,\n ) => Promise<void> | void;\n /**\n * Optional: called when the poller observes a connection-level failure\n * (ECONNREFUSED, ENOTFOUND, etc.) on `previewUrl`. Should re-establish\n * whatever underlying transport powers the URL (e.g. reopen an SSH\n * ControlMaster + re-mint its `-L` forward) and return the (possibly new)\n * URL the poller should use from here on. Return `null`/`undefined` to\n * keep the current URL.\n *\n * Use case: Hetzner's preview URLs are SSH `-L` forwards. If the\n * ControlMaster dies (host sleep/wake, network blip), the local port\n * stops listening — without this callback the poller would back off\n * forever against a dead `127.0.0.1:<localPort>`. Daytona's preview is a\n * permanent CloudFront alias and doesn't need this.\n */\n recoverPreviewUrl?: () => Promise<string | null | undefined>;\n logger?: (line: string) => void;\n}\n\nconst BACKOFF_BASE_MS = 1500;\nconst BACKOFF_MAX_MS = 30_000;\n/**\n * Default request timeout. Daytona's CloudFront edge sits in front of the\n * in-sandbox relay and 504s once its idle window expires (~25-30s in\n * practice). Keep the default a hair below that so the poller's timer\n * fires before CloudFront's does, then short-cycle aggressively when we\n * actually see a 504 — see {@link FAST_REQUEST_TIMEOUT_MS}.\n */\nconst REQUEST_TIMEOUT_MS = 25_000;\n/**\n * Faster timeout used after a recent 504 response. Cuts the request short\n * enough that we round-trip multiple times in the same window where the\n * edge proxy would otherwise wedge — recovers throughput quickly when the\n * box is healthy but the edge is flaky.\n */\nconst FAST_REQUEST_TIMEOUT_MS = 8_000;\n/**\n * Number of consecutive non-504 polls before reverting from the fast\n * timeout back to the default. Decays naturally; no separate timer.\n */\nconst FAST_MODE_DECAY_POLLS = 5;\nconst STOPPED_TICK_MS = 250;\n\n/**\n * True for connection-level failures — the local transport behind\n * `previewUrl` is dead (refused, no route, no DNS, host unreachable),\n * not a remote 5xx or a logical error. These are the cases where calling\n * `recoverPreviewUrl` (e.g. Hetzner's SSH tunnel reopen) has a chance of\n * actually fixing anything.\n */\nfunction isConnectionLevelError(err: unknown): boolean {\n const code = (err as NodeJS.ErrnoException | undefined)?.code;\n if (code && CONNECTION_LEVEL_CODES.has(code)) return true;\n const msg = err instanceof Error ? err.message : String(err);\n // node:http surfaces the underlying error message rather than a code on\n // some failure paths — match the well-known signatures too.\n return /\\b(ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ECONNRESET|EPIPE)\\b/.test(msg);\n}\nconst CONNECTION_LEVEL_CODES = new Set([\n 'ECONNREFUSED',\n 'ENOTFOUND',\n 'EHOSTUNREACH',\n 'ECONNRESET',\n 'EPIPE',\n]);\n\nexport class CloudBoxPoller {\n private stopped = false;\n private cursor = 0;\n private currentBackoffMs = 0;\n private loopPromise: Promise<void> | null = null;\n /**\n * Counts down from {@link FAST_MODE_DECAY_POLLS} after each 504. While > 0\n * the next poll uses {@link FAST_REQUEST_TIMEOUT_MS}. Successful polls\n * decrement, so a flaky edge converges back to the default timeout\n * within ~5 successful round-trips.\n */\n private fastModePolls = 0;\n /**\n * Mutable copy of `deps.previewUrl`. We don't read `deps.previewUrl`\n * directly after construction because `recoverPreviewUrl` may hand us a\n * new URL (e.g. Hetzner reopens its SSH ControlMaster and the `-L`\n * forward gets a new ephemeral local port).\n */\n private currentPreviewUrl: string;\n /** Guards against recovery storms — at most one recovery attempt in flight. */\n private recovering: Promise<void> | null = null;\n\n constructor(private readonly deps: CloudBoxPollerDeps) {\n this.currentPreviewUrl = deps.previewUrl;\n }\n\n start(): void {\n if (this.loopPromise) return;\n this.loopPromise = this.run().catch((err) => {\n this.log(`poller crashed: ${err instanceof Error ? err.message : String(err)}`);\n });\n }\n\n async stop(): Promise<void> {\n this.stopped = true;\n if (this.loopPromise) await this.loopPromise;\n }\n\n /**\n * Post a `HostActionResult` back to the in-sandbox relay's\n * `/bridge/action-result`. The matching parked `/rpc` Promise resolves\n * and the in-box agent finally sees the answer.\n */\n async respond(actionId: string, result: HostActionResult): Promise<void> {\n const base = this.currentPreviewUrl.replace(/\\/+$/, '');\n const url = new URL(`${base}/bridge/action-result`);\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n const body: BridgeActionResultBody = {\n id: actionId,\n exitCode: result.exitCode,\n stdout: result.stdout,\n stderr: result.stderr,\n };\n const payload = JSON.stringify(body);\n await new Promise<void>((resolve, reject) => {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(payload).toString(),\n Authorization: `Bearer ${this.deps.bridgeToken}`,\n };\n if (this.deps.previewToken) {\n headers['x-daytona-preview-token'] = this.deps.previewToken;\n }\n const req = transport(\n {\n host: url.hostname,\n port,\n method: 'POST',\n path: url.pathname,\n headers,\n timeout: REQUEST_TIMEOUT_MS,\n },\n (res) => {\n res.resume();\n if ((res.statusCode ?? 0) >= 200 && (res.statusCode ?? 0) < 300) resolve();\n else reject(new Error(`bridge/action-result → ${String(res.statusCode)}`));\n },\n );\n req.on('error', reject);\n req.on('timeout', () => {\n req.destroy();\n reject(new Error('bridge/action-result timeout'));\n });\n req.write(payload);\n req.end();\n });\n }\n\n private log(line: string): void {\n this.deps.logger?.(`[cloud-poller ${this.deps.boxId}] ${line}`);\n }\n\n private async run(): Promise<void> {\n while (!this.stopped) {\n try {\n const body = await this.pollOnce();\n this.currentBackoffMs = 0;\n if (body.events.length > 0) this.deps.onEvents?.(body.events);\n if (body.status != null) this.deps.onStatus?.(body.status);\n for (const action of body.actions) {\n // Fire-and-forget — the action executor either runs inline (v0) or\n // hands the action to its own queue (Phase 4 executor). Failures\n // there are not the poller's concern (but they MUST still respond\n // so the in-box RPC unblocks, hence the fallback below).\n try {\n const respond = (r: HostActionResult): Promise<void> => this.respond(action.id, r);\n if (this.deps.onAction) {\n await this.deps.onAction(action, respond);\n } else {\n await respond({\n exitCode: 1,\n stdout: '',\n stderr: `host has no executor for action '${action.method}'\\n`,\n });\n }\n } catch (err) {\n this.log(`action handler error: ${err instanceof Error ? err.message : String(err)}`);\n // Best-effort: tell the box we failed so its /rpc unblocks.\n try {\n await this.respond(action.id, {\n exitCode: 1,\n stdout: '',\n stderr: `host action handler crashed: ${err instanceof Error ? err.message : String(err)}\\n`,\n });\n } catch {\n // ignore\n }\n }\n }\n if (typeof body.cursor === 'number' && body.cursor > this.cursor) {\n this.cursor = body.cursor;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('→ 504') || msg.includes('504:')) {\n // Edge proxy timeout — the box is likely fine; arm fast-mode so the\n // next polls clip the request short of the next 504 window.\n this.fastModePolls = FAST_MODE_DECAY_POLLS;\n }\n this.log(`poll error: ${msg}`);\n // A connection-level failure (`ECONNREFUSED`/`ENOTFOUND`/`EHOSTUNREACH`)\n // usually means the transport behind `currentPreviewUrl` is dead, not\n // that the box went away. Hetzner's SSH `-L` forward stops listening\n // when the ControlMaster dies (host sleep/wake, network blip); the\n // recovery hook reopens it and hands us a fresh URL.\n if (this.deps.recoverPreviewUrl && isConnectionLevelError(err)) {\n await this.tryRecoverPreviewUrl();\n }\n await this.backoff();\n }\n // Successful poll decays fast-mode toward the default timeout.\n if (this.currentBackoffMs === 0 && this.fastModePolls > 0) {\n this.fastModePolls -= 1;\n }\n // Tight loop only when we got something fresh — otherwise yield briefly\n // so we don't spin under a misbehaving preview proxy.\n if (this.currentBackoffMs === 0) await delay(STOPPED_TICK_MS);\n }\n }\n\n private async backoff(): Promise<void> {\n this.currentBackoffMs =\n this.currentBackoffMs === 0\n ? BACKOFF_BASE_MS\n : Math.min(this.currentBackoffMs * 2, BACKOFF_MAX_MS);\n await delay(this.currentBackoffMs);\n }\n\n private async tryRecoverPreviewUrl(): Promise<void> {\n if (!this.deps.recoverPreviewUrl) return;\n // De-dupe: a poll storm shouldn't spawn N parallel recoveries.\n if (this.recovering) {\n await this.recovering;\n return;\n }\n this.recovering = (async () => {\n try {\n const next = await this.deps.recoverPreviewUrl!();\n if (typeof next === 'string' && next.length > 0 && next !== this.currentPreviewUrl) {\n this.log(`preview URL recovered: ${this.currentPreviewUrl} → ${next}`);\n this.currentPreviewUrl = next;\n // Drop the backoff so the next loop iteration retries immediately\n // against the fresh URL.\n this.currentBackoffMs = 0;\n } else if (typeof next === 'string' && next === this.currentPreviewUrl) {\n this.log('preview URL recovered (unchanged)');\n this.currentBackoffMs = 0;\n }\n } catch (err) {\n this.log(`preview URL recover failed: ${err instanceof Error ? err.message : String(err)}`);\n } finally {\n this.recovering = null;\n }\n })();\n await this.recovering;\n }\n\n private async pollOnce(): Promise<BridgePollResponse> {\n const base = this.currentPreviewUrl.replace(/\\/+$/, '');\n const url = new URL(`${base}/bridge/poll?since=${String(this.cursor)}`);\n const isHttps = url.protocol === 'https:';\n const transport = isHttps ? httpsRequest : httpRequest;\n const port = url.port.length > 0 ? Number.parseInt(url.port, 10) : isHttps ? 443 : 80;\n const timeoutMs = this.fastModePolls > 0 ? FAST_REQUEST_TIMEOUT_MS : REQUEST_TIMEOUT_MS;\n\n return new Promise<BridgePollResponse>((resolve, reject) => {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.deps.bridgeToken}`,\n Accept: 'application/json',\n };\n if (this.deps.previewToken) {\n headers['x-daytona-preview-token'] = this.deps.previewToken;\n }\n const req = transport(\n {\n host: url.hostname,\n port,\n method: 'GET',\n path: `${url.pathname}${url.search}`,\n headers,\n timeout: timeoutMs,\n },\n (res) => {\n const status = res.statusCode ?? 0;\n const chunks: Buffer[] = [];\n res.on('data', (c: Buffer) => chunks.push(c));\n res.on('end', () => {\n const text = Buffer.concat(chunks).toString('utf8');\n if (status < 200 || status >= 300) {\n reject(new Error(`bridge/poll → ${String(status)}: ${text.slice(0, 200)}`));\n return;\n }\n try {\n const parsed = JSON.parse(text) as Partial<BridgePollResponse>;\n resolve({\n actions: Array.isArray(parsed.actions) ? (parsed.actions as HostAction[]) : [],\n events: Array.isArray(parsed.events) ? (parsed.events as RelayEvent[]) : [],\n status: parsed.status ?? null,\n cursor: typeof parsed.cursor === 'number' ? parsed.cursor : this.cursor,\n });\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)));\n }\n });\n res.on('error', reject);\n },\n );\n req.on('error', reject);\n req.on('timeout', () => {\n req.destroy();\n reject(new Error('bridge/poll timeout'));\n });\n req.end();\n });\n }\n}\n\n/**\n * Lifecycle helper: keep one poller per cloud box, start on register, stop on\n * forget. The host relay's `BoxRegistry` knows nothing about pollers; this\n * type sits alongside it.\n */\nexport class CloudBoxPollers {\n private readonly map = new Map<string, CloudBoxPoller>();\n\n /** Idempotent: replacing an existing poller for a box stops the old one first. */\n start(boxId: string, deps: CloudBoxPollerDeps): void {\n const existing = this.map.get(boxId);\n if (existing) {\n void existing.stop();\n }\n const poller = new CloudBoxPoller(deps);\n this.map.set(boxId, poller);\n poller.start();\n }\n\n async stop(boxId: string): Promise<void> {\n const p = this.map.get(boxId);\n if (!p) return;\n this.map.delete(boxId);\n await p.stop();\n }\n\n async stopAll(): Promise<void> {\n const ps = [...this.map.values()];\n this.map.clear();\n await Promise.all(ps.map((p) => p.stop()));\n }\n\n size(): number {\n return this.map.size;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// ../../packages/sandbox-daytona/dist/chunk-
|
|
3
|
+
// ../../packages/sandbox-daytona/dist/chunk-X3N7NH3C.js
|
|
4
4
|
import { existsSync } from "fs";
|
|
5
5
|
import { dirname, resolve } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
@@ -48,6 +48,21 @@ function resolveDockerfileContext() {
|
|
|
48
48
|
}
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
+
function resolveDaytonaCustomClaudeMd() {
|
|
52
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
53
|
+
const staged = resolve(here, "..", "runtime", "daytona", "custom-system-CLAUDE.md");
|
|
54
|
+
if (existsSync(staged)) return staged;
|
|
55
|
+
const monorepoRoot = resolve(here, "..", "..", "..");
|
|
56
|
+
const dev = resolve(
|
|
57
|
+
monorepoRoot,
|
|
58
|
+
"packages",
|
|
59
|
+
"sandbox-daytona",
|
|
60
|
+
"scripts",
|
|
61
|
+
"custom-system-CLAUDE.md"
|
|
62
|
+
);
|
|
63
|
+
if (existsSync(dev)) return dev;
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
51
66
|
var DAYTONA_KEYS = [
|
|
52
67
|
"DAYTONA_API_KEY",
|
|
53
68
|
"DAYTONA_JWT_TOKEN",
|
|
@@ -710,6 +725,7 @@ function maskKey(value) {
|
|
|
710
725
|
|
|
711
726
|
export {
|
|
712
727
|
resolveDockerfileContext,
|
|
728
|
+
resolveDaytonaCustomClaudeMd,
|
|
713
729
|
ensureDaytonaEnvLoaded,
|
|
714
730
|
DEFAULT_BOX_IMAGE_REF,
|
|
715
731
|
getClient,
|
|
@@ -719,4 +735,4 @@ export {
|
|
|
719
735
|
readDaytonaCredStatus,
|
|
720
736
|
maskKey
|
|
721
737
|
};
|
|
722
|
-
//# sourceMappingURL=chunk-
|
|
738
|
+
//# sourceMappingURL=chunk-LEV3KICD.js.map
|