dev3000 0.0.185 → 0.0.187

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +102 -13
  2. package/bin/dev3000 +1 -2
  3. package/dist/cdp-monitor.d.ts +7 -12
  4. package/dist/cdp-monitor.d.ts.map +1 -1
  5. package/dist/cdp-monitor.js +86 -170
  6. package/dist/cdp-monitor.js.map +1 -1
  7. package/dist/cli.d.ts +1 -2
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +1 -1369
  10. package/dist/cli.js.map +1 -1
  11. package/dist/index.d.ts +1 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +0 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/portless.d.ts +10 -0
  16. package/dist/portless.d.ts.map +1 -1
  17. package/dist/portless.js +44 -1
  18. package/dist/portless.js.map +1 -1
  19. package/dist/runtime/bridge.d.ts +26 -0
  20. package/dist/runtime/bridge.d.ts.map +1 -0
  21. package/dist/runtime/bridge.js +340 -0
  22. package/dist/runtime/bridge.js.map +1 -0
  23. package/dist/runtime/chrome-isolation.d.ts +16 -0
  24. package/dist/runtime/chrome-isolation.d.ts.map +1 -0
  25. package/dist/runtime/chrome-isolation.js +97 -0
  26. package/dist/runtime/chrome-isolation.js.map +1 -0
  27. package/dist/runtime/cli.js +116 -26
  28. package/dist/runtime/cli.js.map +1 -1
  29. package/dist/runtime/event-views.d.ts +21 -0
  30. package/dist/runtime/event-views.d.ts.map +1 -0
  31. package/dist/runtime/event-views.js +53 -0
  32. package/dist/runtime/event-views.js.map +1 -0
  33. package/dist/runtime/harness.d.ts +16 -0
  34. package/dist/runtime/harness.d.ts.map +1 -0
  35. package/dist/runtime/harness.js +68 -0
  36. package/dist/runtime/harness.js.map +1 -0
  37. package/dist/runtime/local-proxy.d.ts +12 -0
  38. package/dist/runtime/local-proxy.d.ts.map +1 -0
  39. package/dist/runtime/local-proxy.js +114 -0
  40. package/dist/runtime/local-proxy.js.map +1 -0
  41. package/dist/runtime/runtime.d.ts +14 -0
  42. package/dist/runtime/runtime.d.ts.map +1 -1
  43. package/dist/runtime/runtime.js +82 -21
  44. package/dist/runtime/runtime.js.map +1 -1
  45. package/dist/runtime/sandbox-agent.d.ts +6 -0
  46. package/dist/runtime/sandbox-agent.d.ts.map +1 -0
  47. package/dist/runtime/sandbox-agent.js +35 -0
  48. package/dist/runtime/sandbox-agent.js.map +1 -0
  49. package/dist/runtime/sandbox-command.d.ts +9 -0
  50. package/dist/runtime/sandbox-command.d.ts.map +1 -0
  51. package/dist/runtime/sandbox-command.js +101 -0
  52. package/dist/runtime/sandbox-command.js.map +1 -0
  53. package/dist/runtime/sandbox-proxy.d.ts +11 -0
  54. package/dist/runtime/sandbox-proxy.d.ts.map +1 -0
  55. package/dist/runtime/sandbox-proxy.js +123 -0
  56. package/dist/runtime/sandbox-proxy.js.map +1 -0
  57. package/dist/runtime/sandbox-sessions.d.ts +31 -0
  58. package/dist/runtime/sandbox-sessions.d.ts.map +1 -0
  59. package/dist/runtime/sandbox-sessions.js +102 -0
  60. package/dist/runtime/sandbox-sessions.js.map +1 -0
  61. package/dist/runtime/sandbox.d.ts +21 -0
  62. package/dist/runtime/sandbox.d.ts.map +1 -0
  63. package/dist/runtime/sandbox.js +481 -0
  64. package/dist/runtime/sandbox.js.map +1 -0
  65. package/dist/utils/agent-browser.d.ts +2 -120
  66. package/dist/utils/agent-browser.d.ts.map +1 -1
  67. package/dist/utils/agent-browser.js +13 -376
  68. package/dist/utils/agent-browser.js.map +1 -1
  69. package/dist/utils/project-name.d.ts +11 -1
  70. package/dist/utils/project-name.d.ts.map +1 -1
  71. package/dist/utils/project-name.js +14 -1
  72. package/dist/utils/project-name.js.map +1 -1
  73. package/package.json +13 -22
  74. package/skills/d3k/SKILL.md +23 -1
  75. package/dist/commands/crawl.d.ts +0 -12
  76. package/dist/commands/crawl.d.ts.map +0 -1
  77. package/dist/commands/crawl.js +0 -101
  78. package/dist/commands/crawl.js.map +0 -1
  79. package/dist/commands/errors.d.ts +0 -14
  80. package/dist/commands/errors.d.ts.map +0 -1
  81. package/dist/commands/errors.js +0 -246
  82. package/dist/commands/errors.js.map +0 -1
  83. package/dist/commands/fix.d.ts +0 -13
  84. package/dist/commands/fix.d.ts.map +0 -1
  85. package/dist/commands/fix.js +0 -219
  86. package/dist/commands/fix.js.map +0 -1
  87. package/dist/commands/logs.d.ts +0 -13
  88. package/dist/commands/logs.d.ts.map +0 -1
  89. package/dist/commands/logs.js +0 -146
  90. package/dist/commands/logs.js.map +0 -1
  91. package/dist/commands/resume.d.ts +0 -11
  92. package/dist/commands/resume.d.ts.map +0 -1
  93. package/dist/commands/resume.js +0 -75
  94. package/dist/commands/resume.js.map +0 -1
  95. package/dist/commands/status.d.ts +0 -21
  96. package/dist/commands/status.d.ts.map +0 -1
  97. package/dist/commands/status.js +0 -44
  98. package/dist/commands/status.js.map +0 -1
  99. package/dist/components/AgentSelector.d.ts +0 -12
  100. package/dist/components/AgentSelector.d.ts.map +0 -1
  101. package/dist/components/AgentSelector.js +0 -99
  102. package/dist/components/AgentSelector.js.map +0 -1
  103. package/dist/components/PackageSelector.d.ts +0 -13
  104. package/dist/components/PackageSelector.d.ts.map +0 -1
  105. package/dist/components/PackageSelector.js +0 -79
  106. package/dist/components/PackageSelector.js.map +0 -1
  107. package/dist/constants/log-colors.d.ts +0 -43
  108. package/dist/constants/log-colors.d.ts.map +0 -1
  109. package/dist/constants/log-colors.js +0 -59
  110. package/dist/constants/log-colors.js.map +0 -1
  111. package/dist/dev-environment.d.ts +0 -187
  112. package/dist/dev-environment.d.ts.map +0 -1
  113. package/dist/dev-environment.js +0 -2248
  114. package/dist/dev-environment.js.map +0 -1
  115. package/dist/screencast-manager.d.ts +0 -78
  116. package/dist/screencast-manager.d.ts.map +0 -1
  117. package/dist/screencast-manager.js +0 -529
  118. package/dist/screencast-manager.js.map +0 -1
  119. package/dist/services/parsers/error-detectors/base.d.ts +0 -24
  120. package/dist/services/parsers/error-detectors/base.d.ts.map +0 -1
  121. package/dist/services/parsers/error-detectors/base.js +0 -44
  122. package/dist/services/parsers/error-detectors/base.js.map +0 -1
  123. package/dist/services/parsers/error-detectors/index.d.ts +0 -6
  124. package/dist/services/parsers/error-detectors/index.d.ts.map +0 -1
  125. package/dist/services/parsers/error-detectors/index.js +0 -6
  126. package/dist/services/parsers/error-detectors/index.js.map +0 -1
  127. package/dist/services/parsers/error-detectors/nextjs.d.ts +0 -9
  128. package/dist/services/parsers/error-detectors/nextjs.d.ts.map +0 -1
  129. package/dist/services/parsers/error-detectors/nextjs.js +0 -52
  130. package/dist/services/parsers/error-detectors/nextjs.js.map +0 -1
  131. package/dist/services/parsers/index.d.ts +0 -9
  132. package/dist/services/parsers/index.d.ts.map +0 -1
  133. package/dist/services/parsers/index.js +0 -10
  134. package/dist/services/parsers/index.js.map +0 -1
  135. package/dist/services/parsers/log-parsers/base.d.ts +0 -26
  136. package/dist/services/parsers/log-parsers/base.d.ts.map +0 -1
  137. package/dist/services/parsers/log-parsers/base.js +0 -6
  138. package/dist/services/parsers/log-parsers/base.js.map +0 -1
  139. package/dist/services/parsers/log-parsers/index.d.ts +0 -6
  140. package/dist/services/parsers/log-parsers/index.d.ts.map +0 -1
  141. package/dist/services/parsers/log-parsers/index.js +0 -5
  142. package/dist/services/parsers/log-parsers/index.js.map +0 -1
  143. package/dist/services/parsers/log-parsers/standard.d.ts +0 -9
  144. package/dist/services/parsers/log-parsers/standard.d.ts.map +0 -1
  145. package/dist/services/parsers/log-parsers/standard.js +0 -17
  146. package/dist/services/parsers/log-parsers/standard.js.map +0 -1
  147. package/dist/services/parsers/output-processor.d.ts +0 -39
  148. package/dist/services/parsers/output-processor.d.ts.map +0 -1
  149. package/dist/services/parsers/output-processor.js +0 -54
  150. package/dist/services/parsers/output-processor.js.map +0 -1
  151. package/dist/skills/d3k/internal-skill.md +0 -32
  152. package/dist/skills/index.d.ts +0 -54
  153. package/dist/skills/index.d.ts.map +0 -1
  154. package/dist/skills/index.js +0 -214
  155. package/dist/skills/index.js.map +0 -1
  156. package/dist/src/tui-interface-impl.tsx +0 -644
  157. package/dist/tui-interface-impl.d.ts +0 -30
  158. package/dist/tui-interface-impl.d.ts.map +0 -1
  159. package/dist/tui-interface-impl.js +0 -403
  160. package/dist/tui-interface-impl.js.map +0 -1
  161. package/dist/tui-interface-opentui.d.ts +0 -33
  162. package/dist/tui-interface-opentui.d.ts.map +0 -1
  163. package/dist/tui-interface-opentui.js +0 -1128
  164. package/dist/tui-interface-opentui.js.map +0 -1
  165. package/dist/tui-interface.d.ts +0 -38
  166. package/dist/tui-interface.d.ts.map +0 -1
  167. package/dist/tui-interface.js +0 -84
  168. package/dist/tui-interface.js.map +0 -1
  169. package/dist/utils/agent-detection.d.ts +0 -17
  170. package/dist/utils/agent-detection.d.ts.map +0 -1
  171. package/dist/utils/agent-detection.js +0 -81
  172. package/dist/utils/agent-detection.js.map +0 -1
  173. package/dist/utils/agent-selection.d.ts +0 -25
  174. package/dist/utils/agent-selection.d.ts.map +0 -1
  175. package/dist/utils/agent-selection.js +0 -73
  176. package/dist/utils/agent-selection.js.map +0 -1
  177. package/dist/utils/browser-command-argv.d.ts +0 -19
  178. package/dist/utils/browser-command-argv.d.ts.map +0 -1
  179. package/dist/utils/browser-command-argv.js +0 -149
  180. package/dist/utils/browser-command-argv.js.map +0 -1
  181. package/dist/utils/build-version.d.ts +0 -2
  182. package/dist/utils/build-version.d.ts.map +0 -1
  183. package/dist/utils/build-version.js +0 -8
  184. package/dist/utils/build-version.js.map +0 -1
  185. package/dist/utils/cli-options.d.ts +0 -5
  186. package/dist/utils/cli-options.d.ts.map +0 -1
  187. package/dist/utils/cli-options.js +0 -36
  188. package/dist/utils/cli-options.js.map +0 -1
  189. package/dist/utils/d3k-dir.d.ts +0 -3
  190. package/dist/utils/d3k-dir.d.ts.map +0 -1
  191. package/dist/utils/d3k-dir.js +0 -12
  192. package/dist/utils/d3k-dir.js.map +0 -1
  193. package/dist/utils/d3k-skill-installer.d.ts +0 -4
  194. package/dist/utils/d3k-skill-installer.d.ts.map +0 -1
  195. package/dist/utils/d3k-skill-installer.js +0 -30
  196. package/dist/utils/d3k-skill-installer.js.map +0 -1
  197. package/dist/utils/log-filename.d.ts +0 -30
  198. package/dist/utils/log-filename.d.ts.map +0 -1
  199. package/dist/utils/log-filename.js +0 -60
  200. package/dist/utils/log-filename.js.map +0 -1
  201. package/dist/utils/project-detector.d.ts +0 -25
  202. package/dist/utils/project-detector.d.ts.map +0 -1
  203. package/dist/utils/project-detector.js +0 -153
  204. package/dist/utils/project-detector.js.map +0 -1
  205. package/dist/utils/project-metadata.d.ts +0 -4
  206. package/dist/utils/project-metadata.d.ts.map +0 -1
  207. package/dist/utils/project-metadata.js +0 -48
  208. package/dist/utils/project-metadata.js.map +0 -1
  209. package/dist/utils/repo-validate.d.ts +0 -2
  210. package/dist/utils/repo-validate.d.ts.map +0 -1
  211. package/dist/utils/repo-validate.js +0 -10
  212. package/dist/utils/repo-validate.js.map +0 -1
  213. package/dist/utils/session.d.ts +0 -19
  214. package/dist/utils/session.d.ts.map +0 -1
  215. package/dist/utils/session.js +0 -65
  216. package/dist/utils/session.js.map +0 -1
  217. package/dist/utils/skill-installer.d.ts +0 -65
  218. package/dist/utils/skill-installer.d.ts.map +0 -1
  219. package/dist/utils/skill-installer.js +0 -265
  220. package/dist/utils/skill-installer.js.map +0 -1
  221. package/dist/utils/timestamp.d.ts +0 -20
  222. package/dist/utils/timestamp.d.ts.map +0 -1
  223. package/dist/utils/timestamp.js +0 -57
  224. package/dist/utils/timestamp.js.map +0 -1
  225. package/dist/utils/tmux-helpers.d.ts +0 -36
  226. package/dist/utils/tmux-helpers.d.ts.map +0 -1
  227. package/dist/utils/tmux-helpers.js +0 -118
  228. package/dist/utils/tmux-helpers.js.map +0 -1
  229. package/dist/utils/user-config.d.ts +0 -12
  230. package/dist/utils/user-config.d.ts.map +0 -1
  231. package/dist/utils/user-config.js +0 -46
  232. package/dist/utils/user-config.js.map +0 -1
  233. package/dist/utils/version-check.d.ts +0 -52
  234. package/dist/utils/version-check.d.ts.map +0 -1
  235. package/dist/utils/version-check.js +0 -304
  236. package/dist/utils/version-check.js.map +0 -1
  237. package/src/tui-interface-impl.tsx +0 -644
@@ -0,0 +1,31 @@
1
+ import { type Harness } from "./harness.js";
2
+ export interface SandboxSession {
3
+ version: 1;
4
+ id: string;
5
+ project: string;
6
+ projectName: string;
7
+ team: string;
8
+ scope?: string;
9
+ sandbox: string;
10
+ hostname: string;
11
+ appUrl: string;
12
+ workdir: string;
13
+ branch?: string;
14
+ command?: string;
15
+ installCommand?: string;
16
+ timeout: string;
17
+ harness?: Harness;
18
+ fx: boolean;
19
+ ownsSandbox: boolean;
20
+ createdAt: string;
21
+ lastUsedAt: string;
22
+ closedAt?: string;
23
+ launcherPid?: number;
24
+ }
25
+ export declare const sandboxSessionsDirectory: () => string;
26
+ export declare function readSandboxSession(id: string, directory?: string): SandboxSession;
27
+ export declare function listSandboxSessions(directory?: string): SandboxSession[];
28
+ export declare function saveSandboxSession(record: SandboxSession, directory?: string): void;
29
+ export declare function sandboxLauncherAlive(record: SandboxSession): boolean;
30
+ export declare function formatSandboxSessions(records: SandboxSession[]): string;
31
+ //# sourceMappingURL=sandbox-sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-sessions.d.ts","sourceRoot":"","sources":["../../src/runtime/sandbox-sessions.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAE1D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAA;IACV,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,EAAE,EAAE,OAAO,CAAA;IACX,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,eAAO,MAAM,wBAAwB,cAAoD,CAAA;AAgCzF,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,SAA6B,GAAG,cAAc,CAUrG;AAED,wBAAgB,mBAAmB,CAAC,SAAS,SAA6B,GAAG,cAAc,EAAE,CAkB5F;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,SAA6B,GAAG,IAAI,CAMvG;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAQpE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,MAAM,CAevE"}
@@ -0,0 +1,102 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { HARNESS_NAMES } from "./harness.js";
6
+ export const sandboxSessionsDirectory = () => join(homedir(), ".d3k", "sandbox-sessions");
7
+ const isId = (id) => /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/.test(id);
8
+ function validSession(value) {
9
+ if (!value || typeof value !== "object")
10
+ return false;
11
+ const record = value;
12
+ return (record.version === 1 &&
13
+ (record.harness === undefined || HARNESS_NAMES.includes(record.harness)) &&
14
+ typeof record.id === "string" &&
15
+ isId(record.id) &&
16
+ [
17
+ record.project,
18
+ record.projectName,
19
+ record.team,
20
+ record.sandbox,
21
+ record.hostname,
22
+ record.appUrl,
23
+ record.workdir,
24
+ record.timeout,
25
+ record.createdAt,
26
+ record.lastUsedAt
27
+ ].every((value) => typeof value === "string" && !!value) &&
28
+ typeof record.fx === "boolean" &&
29
+ typeof record.ownsSandbox === "boolean" &&
30
+ [record.scope, record.branch, record.command, record.installCommand, record.closedAt].every((value) => value === undefined || typeof value === "string") &&
31
+ (record.launcherPid === undefined || (Number.isSafeInteger(record.launcherPid) && record.launcherPid > 0)));
32
+ }
33
+ export function readSandboxSession(id, directory = sandboxSessionsDirectory()) {
34
+ if (!isId(id))
35
+ throw new Error("Invalid resume ID. Run d3k --resume to list saved sandbox sessions.");
36
+ let record;
37
+ try {
38
+ record = JSON.parse(readFileSync(join(directory, `${id}.json`), "utf8"));
39
+ }
40
+ catch {
41
+ throw new Error(`No saved sandbox session ${id}. Run d3k --resume to list sessions.`);
42
+ }
43
+ if (!validSession(record) || record.id !== id)
44
+ throw new Error(`Invalid saved sandbox session ${id}`);
45
+ return record;
46
+ }
47
+ export function listSandboxSessions(directory = sandboxSessionsDirectory()) {
48
+ let files;
49
+ try {
50
+ files = readdirSync(directory);
51
+ }
52
+ catch (error) {
53
+ if (error.code === "ENOENT")
54
+ return [];
55
+ throw error;
56
+ }
57
+ return files
58
+ .flatMap((file) => {
59
+ if (!file.endsWith(".json") || !isId(file.slice(0, -5)))
60
+ return [];
61
+ try {
62
+ return [readSandboxSession(file.slice(0, -5), directory)];
63
+ }
64
+ catch {
65
+ return [];
66
+ }
67
+ })
68
+ .sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt));
69
+ }
70
+ export function saveSandboxSession(record, directory = sandboxSessionsDirectory()) {
71
+ if (!validSession(record))
72
+ throw new Error("Invalid sandbox session metadata");
73
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
74
+ const temporary = join(directory, `${record.id}.${randomUUID()}.tmp`);
75
+ writeFileSync(temporary, JSON.stringify(record), { mode: 0o600 });
76
+ renameSync(temporary, join(directory, `${record.id}.json`));
77
+ }
78
+ export function sandboxLauncherAlive(record) {
79
+ if (!record.launcherPid)
80
+ return false;
81
+ try {
82
+ process.kill(record.launcherPid, 0);
83
+ return true;
84
+ }
85
+ catch (error) {
86
+ return error.code === "EPERM";
87
+ }
88
+ }
89
+ export function formatSandboxSessions(records) {
90
+ if (!records.length)
91
+ return "No saved sandbox sessions. Start one with d3k --project <team/project>.";
92
+ const clean = (text) => Array.from(text, (character) => {
93
+ const code = character.codePointAt(0) || 0;
94
+ return code < 32 || (code >= 127 && code <= 159) ? " " : character;
95
+ }).join("");
96
+ return [
97
+ "Saved sandbox sessions (remote availability is checked when resuming):",
98
+ ...records.map((record) => `${record.id} ${clean(record.scope || record.team)}/${clean(record.projectName)} ${clean(record.sandbox)}${record.branch ? ` branch: ${clean(record.branch)}` : ""}\n ${clean(record.appUrl)} last used: ${clean(record.lastUsedAt)}${sandboxLauncherAlive(record) ? " companion running" : ""}`),
99
+ "Resume with: d3k --resume <id>"
100
+ ].join("\n");
101
+ }
102
+ //# sourceMappingURL=sandbox-sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-sessions.js","sourceRoot":"","sources":["../../src/runtime/sandbox-sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type Harness } from "./harness.js";
2
+ export interface SandboxOptions {
3
+ project: string;
4
+ resume?: string | boolean;
5
+ harness?: Harness;
6
+ scope?: string;
7
+ branch?: string;
8
+ name?: string;
9
+ attach?: string;
10
+ hostname?: string;
11
+ region?: string;
12
+ command?: string;
13
+ installCommand?: string;
14
+ shell?: boolean;
15
+ headless?: boolean;
16
+ timeout?: string;
17
+ fx?: boolean;
18
+ }
19
+ export declare function sandboxWorkdir(rootDirectory?: string | null, repositoryRoot?: string): string;
20
+ export declare function launchSandbox(options: SandboxOptions): Promise<void>;
21
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/runtime/sandbox.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,KAAK,OAAO,EAAmC,MAAM,cAAc,CAAA;AAU5E,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,EAAE,CAAC,EAAE,OAAO,CAAA;CACb;AA+DD,wBAAgB,cAAc,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,cAAc,SAAoB,GAAG,MAAM,CAKxG;AAcD,wBAAsB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqZ1E"}
@@ -0,0 +1,481 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
3
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
4
+ import { mkdir, writeFile } from "node:fs/promises";
5
+ import { createServer } from "node:net";
6
+ import { homedir, tmpdir } from "node:os";
7
+ import { dirname, join, posix, resolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { parseEnv } from "node:util";
10
+ import { Sandbox } from "@vercel/sandbox";
11
+ import { CDPMonitor } from "../cdp-monitor.js";
12
+ import { isPortlessHostnameHeld, preparePortlessRuntime, registerPortlessAlias, removePortlessAlias } from "../portless.js";
13
+ import { connectCompanion } from "./bridge.js";
14
+ import { portlessCertificatePin, sandboxChromePath } from "./chrome-isolation.js";
15
+ import { harnessInvocation, harnessSkill } from "./harness.js";
16
+ import { startLocalProxy } from "./local-proxy.js";
17
+ import { prepareSandboxFx, SANDBOX_AGENT_BIN, SANDBOX_AGENT_DIRECTORY, SANDBOX_TOOLS } from "./sandbox-agent.js";
18
+ import { listSandboxSessions, readSandboxSession, saveSandboxSession } from "./sandbox-sessions.js";
19
+ const REMOTE_TOOLS = SANDBOX_TOOLS;
20
+ const REMOTE_ENTRY = `${REMOTE_TOOLS}/node_modules/dev3000/dist/runtime/cli.js`;
21
+ const APP_PORT = 3000;
22
+ const BRIDGE_PORT = 9223;
23
+ /** No shell interpolation. Bounded captured output also keeps credential-bearing CLI output out of logs. */
24
+ async function run(binary, args, options = {}) {
25
+ return new Promise((resolveRun, reject) => {
26
+ if (options.signal?.aborted) {
27
+ reject(new Error("Session ended"));
28
+ return;
29
+ }
30
+ const child = spawn(binary, args, {
31
+ cwd: options.cwd,
32
+ env: options.env,
33
+ stdio: options.inherit ? "inherit" : [options.input === undefined ? "ignore" : "pipe", "pipe", "pipe"]
34
+ });
35
+ let killTimer;
36
+ const abort = () => {
37
+ child.kill("SIGTERM");
38
+ killTimer = setTimeout(() => child.kill("SIGKILL"), 1000);
39
+ killTimer.unref();
40
+ };
41
+ options.signal?.addEventListener("abort", abort, { once: true });
42
+ const cleanup = () => {
43
+ options.signal?.removeEventListener("abort", abort);
44
+ if (killTimer)
45
+ clearTimeout(killTimer);
46
+ };
47
+ let stdout = "";
48
+ let stderr = "";
49
+ child.stdout?.on("data", (chunk) => {
50
+ stdout += chunk.toString();
51
+ if (stdout.length > 4 * 1024 * 1024)
52
+ child.kill();
53
+ });
54
+ child.stderr?.on("data", (chunk) => {
55
+ stderr = (stderr + chunk.toString()).slice(-8192);
56
+ });
57
+ child.on("error", (error) => {
58
+ cleanup();
59
+ reject(error);
60
+ });
61
+ child.stdin?.on("error", () => { }); // Early CLI exits still report their bounded stderr below.
62
+ if (options.input !== undefined)
63
+ child.stdin?.end(options.input);
64
+ child.on("exit", (code) => {
65
+ cleanup();
66
+ if (options.signal?.aborted)
67
+ resolveRun(stdout);
68
+ else if (code === 0)
69
+ resolveRun(stdout);
70
+ else
71
+ reject(new Error(`${binary} ${args[0]} failed: ${stderr || `exit ${code}`}`));
72
+ });
73
+ });
74
+ }
75
+ export function sandboxWorkdir(rootDirectory, repositoryRoot = "/vercel/sandbox") {
76
+ const root = posix.resolve(repositoryRoot, rootDirectory || ".");
77
+ if (root !== repositoryRoot && !root.startsWith(`${repositoryRoot}/`))
78
+ throw new Error("Project rootDirectory escapes the repository");
79
+ return root;
80
+ }
81
+ async function freePort() {
82
+ const server = createServer();
83
+ await new Promise((resolvePort, reject) => {
84
+ server.once("error", reject);
85
+ server.listen(0, "127.0.0.1", resolvePort);
86
+ });
87
+ const address = server.address();
88
+ if (!address || typeof address === "string")
89
+ throw new Error("No Chrome port available");
90
+ await new Promise((resolveClose) => server.close(() => resolveClose()));
91
+ return address.port;
92
+ }
93
+ export async function launchSandbox(options) {
94
+ const isolatedBrowserPath = sandboxChromePath();
95
+ const scope = options.scope ? ["--scope", options.scope] : [];
96
+ const log = (message) => console.error(`[d3k sandbox] ${message}`);
97
+ const temporary = mkdtempSync(join(tmpdir(), "d3k-sandbox-"));
98
+ let localProxy;
99
+ let localRoute;
100
+ let aliasRegistered = false;
101
+ let sandbox;
102
+ let browser;
103
+ let companion;
104
+ let savedSession;
105
+ let remoteStarted = false;
106
+ let originalPorts;
107
+ let closing = false;
108
+ let signalReceived = false;
109
+ let remoteWorkdir = "/vercel/sandbox";
110
+ let finish = () => { };
111
+ const shellAbort = new AbortController();
112
+ const ended = new Promise((resolveEnd) => {
113
+ finish = () => {
114
+ browser?.prepareShutdown();
115
+ companion?.close();
116
+ shellAbort.abort();
117
+ resolveEnd();
118
+ };
119
+ });
120
+ const onSignal = () => {
121
+ signalReceived = true;
122
+ finish();
123
+ };
124
+ process.on("SIGINT", onSignal);
125
+ process.on("SIGTERM", onSignal);
126
+ try {
127
+ const routing = preparePortlessRuntime(`${options.hostname || options.project.toLowerCase().replace(/[^a-z0-9-]/g, "-")}.d3k`);
128
+ if (!routing.runtime)
129
+ throw new Error(routing.error || "Canonical Portless is required");
130
+ localRoute = routing.runtime;
131
+ if (isPortlessHostnameHeld(localRoute.name))
132
+ throw new Error(`${localRoute.url} is already in use; stop that session or choose another --hostname`);
133
+ log(`Resolving ${options.project} through Vercel CLI`);
134
+ const project = JSON.parse(await run("vercel", ["api", `/v9/projects/${encodeURIComponent(options.project)}`, ...scope, "--raw"], {
135
+ cwd: temporary
136
+ }));
137
+ if (!project.id || !project.accountId || !project.name)
138
+ throw new Error("Vercel returned an invalid project");
139
+ sandboxWorkdir(project.rootDirectory);
140
+ const { token } = JSON.parse(await run("vercel", ["project", "token", project.id, "--scope", project.accountId, "--format", "json"], {
141
+ cwd: temporary
142
+ }));
143
+ if (!token)
144
+ throw new Error("Vercel CLI did not return a project OIDC token; update the CLI and run vercel login");
145
+ const credentials = { token, projectId: project.id, teamId: project.accountId };
146
+ if (signalReceived)
147
+ return;
148
+ if (options.attach) {
149
+ // Read configured routes before resuming: resume responses may also contain reserved system ports.
150
+ // The first command below resumes compute automatically after user ports have been configured.
151
+ sandbox = await Sandbox.get({ ...credentials, name: options.attach });
152
+ await sandbox.update({ timeout: Number(options.timeout || 30) * 60000 });
153
+ const ports = sandbox.routes.map((route) => route.port);
154
+ if (!ports.includes(APP_PORT) || !ports.includes(BRIDGE_PORT)) {
155
+ originalPorts = ports;
156
+ await sandbox.update({ ports: [...new Set([...ports, APP_PORT, BRIDGE_PORT])] });
157
+ }
158
+ }
159
+ else {
160
+ if (project.link?.type !== "github" || !project.link.org || !project.link.repo)
161
+ throw new Error("This project needs a linked GitHub repository");
162
+ log("Creating a sandbox and cloning the linked public repository remotely");
163
+ sandbox = await Sandbox.create({
164
+ ...credentials,
165
+ ...(options.name ? { name: options.name } : {}),
166
+ ...(options.region ? { region: options.region } : {}),
167
+ timeout: Number(options.timeout || 30) * 60000,
168
+ ports: [APP_PORT, BRIDGE_PORT],
169
+ source: {
170
+ type: "git",
171
+ url: `https://github.com/${project.link.org}/${project.link.repo}.git`,
172
+ ...(options.branch ? { revision: options.branch } : {})
173
+ }
174
+ }).catch((error) => {
175
+ throw new Error(`${error instanceof Error ? error.message : "Could not create the source sandbox"}. Automatic cloning currently requires a public GitHub repository. For a private repository, use --attach <name> with an existing sandbox that already contains the checkout.`, { cause: error });
176
+ });
177
+ }
178
+ log(`Sandbox ${sandbox.name} is running`);
179
+ // Image WORKDIR and source checkout location differ between Sandbox images.
180
+ let repositoryRoot;
181
+ const candidates = [sandbox.cwd, ...(project.link ? [posix.join(sandbox.cwd, project.link.repo)] : [])];
182
+ for (const cwd of candidates) {
183
+ const result = await sandbox
184
+ .runCommand({ cmd: "git", args: ["rev-parse", "--show-toplevel"], cwd })
185
+ .catch(() => null);
186
+ if (result?.exitCode === 0) {
187
+ repositoryRoot = (await result.stdout()).trim();
188
+ break;
189
+ }
190
+ }
191
+ if (!repositoryRoot)
192
+ throw new Error("Could not locate the repository in this sandbox; expected its working directory or a directory named after the linked repository");
193
+ const workdir = sandboxWorkdir(project.rootDirectory, repositoryRoot);
194
+ remoteWorkdir = workdir;
195
+ if (signalReceived)
196
+ return;
197
+ // Install this exact d3k distribution, including unreleased local changes. No project checkout is used locally.
198
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
199
+ if (!existsSync(join(packageRoot, "dist/runtime/cli.js")))
200
+ throw new Error("Build d3k before starting a sandbox from a source checkout");
201
+ const archive = join(temporary, "dev3000.tgz");
202
+ await run("bun", ["pm", "pack", "--filename", archive, "--quiet"], { cwd: packageRoot });
203
+ const mkdirResult = await sandbox.runCommand("mkdir", ["-p", REMOTE_TOOLS]);
204
+ if (mkdirResult.exitCode !== 0)
205
+ throw new Error("Could not prepare remote d3k tools directory");
206
+ await sandbox.writeFiles([{ path: `${REMOTE_TOOLS}/dev3000.tgz`, content: readFileSync(archive) }]);
207
+ log("Installing d3k in the sandbox");
208
+ const installTools = await sandbox.runCommand({
209
+ cmd: "bun",
210
+ args: ["add", "--cwd", REMOTE_TOOLS, `${REMOTE_TOOLS}/dev3000.tgz`],
211
+ timeoutMs: 180000
212
+ });
213
+ if (installTools.exitCode !== 0)
214
+ throw new Error(`Remote d3k install failed: ${(await installTools.stderr()).slice(-4000)}`);
215
+ const existing = await sandbox.runCommand({ cmd: "node", args: [REMOTE_ENTRY, "status"], cwd: workdir });
216
+ if (existing.exitCode === 0 && JSON.parse(await existing.stdout()).running) {
217
+ if (typeof options.resume !== "string")
218
+ throw new Error("A runtime is already active in this sandbox; stop it before pairing a different browser");
219
+ log("Stopping the previous remote runtime before creating a fresh browser connection");
220
+ const stopped = await sandbox.runCommand({
221
+ cmd: "node",
222
+ args: [REMOTE_ENTRY, "stop"],
223
+ cwd: workdir,
224
+ timeoutMs: 10000
225
+ });
226
+ if (stopped.exitCode !== 0)
227
+ throw new Error("Could not stop the previous remote runtime");
228
+ const stopDeadline = Date.now() + 15000;
229
+ let stoppedRuntime = false;
230
+ while (Date.now() < stopDeadline) {
231
+ const status = await sandbox.runCommand({ cmd: "node", args: [REMOTE_ENTRY, "status"], cwd: workdir });
232
+ if (status.exitCode === 0 && !JSON.parse(await status.stdout()).running) {
233
+ stoppedRuntime = true;
234
+ break;
235
+ }
236
+ await new Promise((resolveWait) => setTimeout(resolveWait, 300));
237
+ }
238
+ if (!stoppedRuntime)
239
+ throw new Error("Previous remote runtime did not finish stopping");
240
+ }
241
+ const previous = typeof options.resume === "string"
242
+ ? readSandboxSession(options.resume)
243
+ : listSandboxSessions().find((record) => record.project === project.id && record.team === project.accountId && record.sandbox === sandbox?.name);
244
+ const branchResult = await sandbox.runCommand({ cmd: "git", args: ["branch", "--show-current"], cwd: workdir });
245
+ const now = new Date().toISOString();
246
+ savedSession = {
247
+ version: 1,
248
+ id: previous?.id || randomUUID(),
249
+ project: project.id,
250
+ projectName: project.name,
251
+ team: project.accountId,
252
+ scope: previous?.scope || options.scope,
253
+ sandbox: sandbox.name,
254
+ hostname: localRoute.name.replace(/\.d3k$/, ""),
255
+ appUrl: localRoute.url,
256
+ workdir,
257
+ branch: branchResult.exitCode === 0 ? (await branchResult.stdout()).trim() || undefined : undefined,
258
+ command: options.command || project.devCommand || undefined,
259
+ installCommand: options.installCommand,
260
+ timeout: options.timeout || "30",
261
+ fx: options.fx !== false,
262
+ harness: options.harness,
263
+ ownsSandbox: previous?.ownsSandbox ?? !options.attach,
264
+ createdAt: previous?.createdAt || now,
265
+ lastUsedAt: now,
266
+ launcherPid: process.pid
267
+ };
268
+ saveSandboxSession(savedSession);
269
+ log("Loading Development environment through Vercel CLI");
270
+ const envPath = join(temporary, "development.env");
271
+ await run("vercel", [
272
+ "env",
273
+ "pull",
274
+ envPath,
275
+ "--project",
276
+ project.id,
277
+ "--scope",
278
+ project.accountId,
279
+ "--environment",
280
+ "development",
281
+ "--yes"
282
+ ], { cwd: temporary });
283
+ const development = readFileSync(envPath, "utf8");
284
+ rmSync(envPath);
285
+ const appEnv = Object.fromEntries(Object.entries(parseEnv(development)).filter((entry) => typeof entry[1] === "string"));
286
+ // A development OIDC token manages infrastructure; it does not belong in app/agent environment.
287
+ delete appEnv.VERCEL_OIDC_TOKEN;
288
+ delete appEnv.VERCEL_TOKEN;
289
+ delete appEnv.VERCEL_AUTH_TOKEN;
290
+ // Remove credentials from pre-hardening sessions before any repository code runs.
291
+ const legacyCleanup = await sandbox.runCommand({
292
+ cmd: "rm",
293
+ args: ["-f", `${SANDBOX_AGENT_DIRECTORY}/gateway.json`, `${SANDBOX_AGENT_BIN}/fx`]
294
+ });
295
+ if (legacyCleanup.exitCode !== 0)
296
+ throw new Error("Could not remove legacy sandbox harness credentials");
297
+ const setup = options.installCommand || project.installCommand || "bun install";
298
+ log(`Installing project dependencies: ${setup}`);
299
+ const installed = await sandbox.runCommand({
300
+ cmd: "sh",
301
+ args: ["-c", setup],
302
+ cwd: workdir,
303
+ env: appEnv,
304
+ timeoutMs: 300000
305
+ });
306
+ if (installed.exitCode !== 0)
307
+ throw new Error(`Project dependency installation failed: ${(await installed.stderr()).slice(-4000)}`);
308
+ if (signalReceived)
309
+ return;
310
+ if (options.fx !== false && options.shell !== false) {
311
+ log("Preparing fx; authenticate the harness in the sandbox before use");
312
+ await prepareSandboxFx(sandbox);
313
+ }
314
+ if (signalReceived)
315
+ return;
316
+ const upstreamUrl = sandbox.domain(APP_PORT);
317
+ const appUrl = localRoute.url;
318
+ const proxyToken = randomBytes(32).toString("hex");
319
+ localProxy = await startLocalProxy({ appUrl, upstreamUrl, token: proxyToken });
320
+ registerPortlessAlias(localRoute, localProxy.port);
321
+ aliasRegistered = true;
322
+ const bridgeUrl = `${sandbox.domain(BRIDGE_PORT).replace(/^https:/, "wss:")}/bridge`;
323
+ const pairingToken = randomBytes(32).toString("hex");
324
+ const config = { appUrl, serverPort: APP_PORT, bridgePort: BRIDGE_PORT, token: pairingToken, proxyToken };
325
+ const args = [REMOTE_ENTRY, "sandbox-serve", "--startup-timeout", "300000"];
326
+ const command = options.command || project.devCommand;
327
+ if (command)
328
+ args.push("--command", command);
329
+ log("Starting the remote dev server and browser bridge");
330
+ const remote = await sandbox.runCommand({
331
+ cmd: "node",
332
+ args,
333
+ cwd: workdir,
334
+ env: appEnv,
335
+ stdin: true,
336
+ detached: true
337
+ });
338
+ let remoteExit;
339
+ void remote
340
+ .wait()
341
+ .then((result) => {
342
+ remoteExit = result.exitCode;
343
+ finish();
344
+ })
345
+ .catch(() => {
346
+ remoteExit = -1;
347
+ finish();
348
+ });
349
+ await remote.writeStdin(JSON.stringify(config));
350
+ await remote.closeStdin();
351
+ remoteStarted = true;
352
+ // The profile key uses remote project identity, never the caller's local cwd.
353
+ const key = createHash("sha256").update(`${project.accountId}/${project.id}`).digest("hex").slice(0, 20);
354
+ const directory = join(homedir(), ".d3k", "sandboxes", key);
355
+ await mkdir(directory, { recursive: true, mode: 0o700 });
356
+ await writeFile(join(directory, "session.json"), JSON.stringify({ project: project.id, team: project.accountId, name: sandbox.name, appUrl }), { mode: 0o600 });
357
+ const chromePort = await freePort();
358
+ browser = new CDPMonitor(join(directory, "chrome-profile"), (_source, message) => {
359
+ if (message.startsWith("[CDP]"))
360
+ log(message);
361
+ }, false, isolatedBrowserPath, undefined, undefined, 60000, chromePort, options.headless);
362
+ browser.requireFilesystemIsolation(await portlessCertificatePin(appUrl));
363
+ browser.setOnWindowClosedCallback(onSignal);
364
+ await browser.start();
365
+ companion = connectCompanion({
366
+ url: bridgeUrl,
367
+ token: pairingToken,
368
+ chromePort,
369
+ onState: (connected) => {
370
+ if (!closing && !signalReceived)
371
+ log(connected ? "Local Chrome connected" : "Browser disconnected; reconnecting");
372
+ }
373
+ });
374
+ const deadline = Date.now() + 300000;
375
+ let ready = false;
376
+ while (!signalReceived && Date.now() < deadline) {
377
+ const status = await sandbox.runCommand({ cmd: "node", args: [REMOTE_ENTRY, "status"], cwd: workdir });
378
+ if (status.exitCode === 0) {
379
+ const state = JSON.parse(await status.stdout());
380
+ if (state.ready && state.browserConnected) {
381
+ ready = true;
382
+ break;
383
+ }
384
+ }
385
+ if (remoteExit !== undefined) {
386
+ throw new Error(`Remote runtime exited during startup: ${(await remote.stderr()).slice(-4000)}`);
387
+ }
388
+ await new Promise((resolveWait) => setTimeout(resolveWait, 1500));
389
+ }
390
+ if (signalReceived)
391
+ return;
392
+ if (!ready)
393
+ throw new Error("Sandbox runtime did not become ready within five minutes");
394
+ log(`Ready: ${appUrl}`);
395
+ log(`Sandbox: ${sandbox.name}. Shell directory: ${workdir}`);
396
+ if (options.shell !== false && process.stdin.isTTY) {
397
+ const path = await sandbox.runCommand({ cmd: "printenv", args: ["PATH"] });
398
+ if (path.exitCode !== 0)
399
+ throw new Error("Could not resolve remote shell PATH");
400
+ const contextDirectory = `${REMOTE_TOOLS}/harness-context`;
401
+ const skillPath = `${contextDirectory}/.fx/skills/d3k/SKILL.md`;
402
+ if (options.harness) {
403
+ const prepared = await sandbox.runCommand({ cmd: "mkdir", args: ["-p", `${contextDirectory}/.fx/skills/d3k`] });
404
+ if (prepared.exitCode !== 0)
405
+ throw new Error("Could not prepare harness skill context");
406
+ await sandbox.writeFiles([{ path: skillPath, content: Buffer.from(harnessSkill()) }]);
407
+ }
408
+ const invocation = options.harness ? harnessInvocation(options.harness, skillPath, contextDirectory) : undefined;
409
+ await run("vercel", [
410
+ "sandbox",
411
+ invocation ? "exec" : "connect",
412
+ sandbox.name,
413
+ "--project",
414
+ project.id,
415
+ "--scope",
416
+ project.accountId,
417
+ "--workdir",
418
+ workdir,
419
+ "--env",
420
+ `PATH=${options.fx !== false ? `${SANDBOX_AGENT_BIN}:` : ""}${REMOTE_TOOLS}/node_modules/.bin:${(await path.stdout()).trim()}`,
421
+ ...(invocation ? ["--interactive", "--", invocation.binary, ...invocation.args] : [])
422
+ ], { inherit: true, env: { ...process.env, VERCEL_OIDC_TOKEN: token }, signal: shellAbort.signal });
423
+ }
424
+ else {
425
+ log(options.attach
426
+ ? "Companion retained. Press Ctrl+C to stop this runtime; the attached sandbox stays running."
427
+ : "Companion retained. Press Ctrl+C to stop compute and preserve the sandbox filesystem.");
428
+ await ended;
429
+ if (!signalReceived && remoteExit !== undefined && remoteExit !== 0)
430
+ throw new Error(`Remote runtime exited (${remoteExit}): ${(await remote.stderr()).slice(-4000)}`);
431
+ }
432
+ }
433
+ finally {
434
+ if (!closing) {
435
+ closing = true;
436
+ companion?.close();
437
+ if (aliasRegistered && localRoute) {
438
+ try {
439
+ removePortlessAlias(localRoute);
440
+ }
441
+ catch {
442
+ log("Could not remove the local proxy route");
443
+ }
444
+ }
445
+ await localProxy?.close();
446
+ await browser?.shutdown().catch(() => { });
447
+ if (sandbox) {
448
+ if (options.harness)
449
+ await sandbox.runCommand({ cmd: "rm", args: ["-rf", `${REMOTE_TOOLS}/harness-context`] }).catch(() => { });
450
+ if (remoteStarted)
451
+ await sandbox
452
+ .runCommand({ cmd: "node", args: [REMOTE_ENTRY, "stop"], cwd: remoteWorkdir, timeoutMs: 10000 })
453
+ .catch(() => { });
454
+ // An attached sandbox belongs to its existing owner. Only stop sandboxes created by this invocation.
455
+ if (!options.attach || (typeof options.resume === "string" && savedSession?.ownsSandbox))
456
+ await sandbox
457
+ .stop()
458
+ .catch(() => log(`Could not stop sandbox ${sandbox?.name}; stop it with vercel sandbox stop`));
459
+ else if (originalPorts)
460
+ await sandbox.update({ ports: originalPorts }).catch(() => log("Could not restore attached sandbox ports"));
461
+ }
462
+ }
463
+ process.off("SIGINT", onSignal);
464
+ process.off("SIGTERM", onSignal);
465
+ rmSync(temporary, { recursive: true, force: true });
466
+ if (savedSession) {
467
+ savedSession.closedAt = new Date().toISOString();
468
+ delete savedSession.launcherPid;
469
+ try {
470
+ saveSandboxSession(savedSession);
471
+ }
472
+ catch {
473
+ log("Could not update saved session metadata");
474
+ }
475
+ console.log(`Resume this sandbox: d3k --resume ${savedSession.id}`);
476
+ }
477
+ if (remoteStarted)
478
+ log("Session closed");
479
+ }
480
+ }
481
+ //# sourceMappingURL=sandbox.js.map