miladyai 2.0.0-alpha.27

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 (241) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +7 -0
  2. package/dist/actions/emote.js +64 -0
  3. package/dist/actions/restart.js +81 -0
  4. package/dist/actions/send-message.js +152 -0
  5. package/dist/agent-admin-routes.js +82 -0
  6. package/dist/agent-lifecycle-routes.js +79 -0
  7. package/dist/agent-transfer-routes.js +102 -0
  8. package/dist/api/agent-admin-routes.js +82 -0
  9. package/dist/api/agent-lifecycle-routes.js +79 -0
  10. package/dist/api/agent-transfer-routes.js +102 -0
  11. package/dist/api/apps-hyperscape-routes.js +58 -0
  12. package/dist/api/apps-routes.js +114 -0
  13. package/dist/api/auth-routes.js +56 -0
  14. package/dist/api/autonomy-routes.js +44 -0
  15. package/dist/api/bug-report-routes.js +111 -0
  16. package/dist/api/character-routes.js +195 -0
  17. package/dist/api/cloud-routes.js +330 -0
  18. package/dist/api/cloud-status-routes.js +155 -0
  19. package/dist/api/compat-utils.js +111 -0
  20. package/dist/api/database.js +735 -0
  21. package/dist/api/diagnostics-routes.js +205 -0
  22. package/dist/api/drop-service.js +134 -0
  23. package/dist/api/early-logs.js +86 -0
  24. package/dist/api/http-helpers.js +131 -0
  25. package/dist/api/knowledge-routes.js +534 -0
  26. package/dist/api/memory-bounds.js +71 -0
  27. package/dist/api/models-routes.js +28 -0
  28. package/dist/api/og-tracker.js +36 -0
  29. package/dist/api/permissions-routes.js +109 -0
  30. package/dist/api/plugin-validation.js +198 -0
  31. package/dist/api/provider-switch-config.js +41 -0
  32. package/dist/api/registry-routes.js +86 -0
  33. package/dist/api/registry-service.js +164 -0
  34. package/dist/api/sandbox-routes.js +1112 -0
  35. package/dist/api/server.js +7949 -0
  36. package/dist/api/subscription-routes.js +172 -0
  37. package/dist/api/terminal-run-limits.js +24 -0
  38. package/dist/api/training-routes.js +158 -0
  39. package/dist/api/trajectory-routes.js +300 -0
  40. package/dist/api/trigger-routes.js +246 -0
  41. package/dist/api/twitter-verify.js +134 -0
  42. package/dist/api/tx-service.js +108 -0
  43. package/dist/api/wallet-routes.js +266 -0
  44. package/dist/api/wallet.js +568 -0
  45. package/dist/api/whatsapp-routes.js +182 -0
  46. package/dist/api/zip-utils.js +109 -0
  47. package/dist/apps-hyperscape-routes.js +58 -0
  48. package/dist/apps-routes.js +114 -0
  49. package/dist/ascii.js +20 -0
  50. package/dist/auth/anthropic.js +44 -0
  51. package/dist/auth/apply-stealth.js +41 -0
  52. package/dist/auth/claude-code-stealth.js +78 -0
  53. package/dist/auth/credentials.js +156 -0
  54. package/dist/auth/index.js +5 -0
  55. package/dist/auth/openai-codex.js +66 -0
  56. package/dist/auth/types.js +9 -0
  57. package/dist/auth-routes.js +56 -0
  58. package/dist/autonomy-routes.js +44 -0
  59. package/dist/bug-report-routes.js +111 -0
  60. package/dist/build-info.json +6 -0
  61. package/dist/character-routes.js +195 -0
  62. package/dist/cli/argv.js +63 -0
  63. package/dist/cli/banner.js +34 -0
  64. package/dist/cli/cli-name.js +21 -0
  65. package/dist/cli/cli-utils.js +16 -0
  66. package/dist/cli/git-commit.js +78 -0
  67. package/dist/cli/parse-duration.js +15 -0
  68. package/dist/cli/plugins-cli.js +590 -0
  69. package/dist/cli/profile-utils.js +9 -0
  70. package/dist/cli/profile.js +95 -0
  71. package/dist/cli/program/build-program.js +17 -0
  72. package/dist/cli/program/command-registry.js +23 -0
  73. package/dist/cli/program/help.js +47 -0
  74. package/dist/cli/program/preaction.js +33 -0
  75. package/dist/cli/program/register.config.js +106 -0
  76. package/dist/cli/program/register.configure.js +20 -0
  77. package/dist/cli/program/register.dashboard.js +124 -0
  78. package/dist/cli/program/register.models.js +23 -0
  79. package/dist/cli/program/register.setup.js +36 -0
  80. package/dist/cli/program/register.start.js +22 -0
  81. package/dist/cli/program/register.subclis.js +70 -0
  82. package/dist/cli/program/register.tui.js +163 -0
  83. package/dist/cli/program/register.update.js +154 -0
  84. package/dist/cli/program.js +3 -0
  85. package/dist/cli/run-main.js +37 -0
  86. package/dist/cli/version.js +7 -0
  87. package/dist/cloud/validate-url.js +93 -0
  88. package/dist/cloud-routes.js +330 -0
  89. package/dist/cloud-status-routes.js +155 -0
  90. package/dist/compat-utils.js +111 -0
  91. package/dist/config/config.js +69 -0
  92. package/dist/config/env-vars.js +19 -0
  93. package/dist/config/includes.js +121 -0
  94. package/dist/config/object-utils.js +7 -0
  95. package/dist/config/paths.js +38 -0
  96. package/dist/config/plugin-auto-enable.js +231 -0
  97. package/dist/config/schema.js +864 -0
  98. package/dist/config/telegram-custom-commands.js +76 -0
  99. package/dist/config/zod-schema.agent-runtime.js +519 -0
  100. package/dist/config/zod-schema.core.js +538 -0
  101. package/dist/config/zod-schema.hooks.js +103 -0
  102. package/dist/config/zod-schema.js +488 -0
  103. package/dist/config/zod-schema.providers-core.js +785 -0
  104. package/dist/config/zod-schema.session.js +73 -0
  105. package/dist/core-plugins.js +37 -0
  106. package/dist/custom-actions.js +250 -0
  107. package/dist/database.js +735 -0
  108. package/dist/diagnostics/integration-observability.js +57 -0
  109. package/dist/diagnostics-routes.js +205 -0
  110. package/dist/drop-service.js +134 -0
  111. package/dist/early-logs.js +24 -0
  112. package/dist/eliza.js +2061 -0
  113. package/dist/emotes/catalog.js +271 -0
  114. package/dist/entry.js +40 -0
  115. package/dist/hooks/discovery.js +167 -0
  116. package/dist/hooks/eligibility.js +64 -0
  117. package/dist/hooks/index.js +4 -0
  118. package/dist/hooks/loader.js +147 -0
  119. package/dist/hooks/registry.js +55 -0
  120. package/dist/http-helpers.js +131 -0
  121. package/dist/index.js +49 -0
  122. package/dist/knowledge-routes.js +534 -0
  123. package/dist/memory-bounds.js +71 -0
  124. package/dist/milady-plugin.js +90 -0
  125. package/dist/models-routes.js +28 -0
  126. package/dist/onboarding-names.js +78 -0
  127. package/dist/onboarding-presets.js +922 -0
  128. package/dist/package.json +1 -0
  129. package/dist/permissions-routes.js +109 -0
  130. package/dist/plugin-validation.js +107 -0
  131. package/dist/plugins/whatsapp/actions.js +91 -0
  132. package/dist/plugins/whatsapp/index.js +16 -0
  133. package/dist/plugins/whatsapp/service.js +270 -0
  134. package/dist/provider-switch-config.js +41 -0
  135. package/dist/providers/admin-trust.js +46 -0
  136. package/dist/providers/autonomous-state.js +101 -0
  137. package/dist/providers/session-bridge.js +86 -0
  138. package/dist/providers/session-utils.js +36 -0
  139. package/dist/providers/simple-mode.js +50 -0
  140. package/dist/providers/ui-catalog.js +15 -0
  141. package/dist/providers/workspace-provider.js +93 -0
  142. package/dist/providers/workspace.js +348 -0
  143. package/dist/registry-routes.js +86 -0
  144. package/dist/registry-service.js +164 -0
  145. package/dist/restart.js +40 -0
  146. package/dist/runtime/core-plugins.js +37 -0
  147. package/dist/runtime/custom-actions.js +250 -0
  148. package/dist/runtime/eliza.js +2061 -0
  149. package/dist/runtime/embedding-manager-support.js +185 -0
  150. package/dist/runtime/embedding-manager.js +193 -0
  151. package/dist/runtime/embedding-presets.js +54 -0
  152. package/dist/runtime/embedding-state.js +8 -0
  153. package/dist/runtime/milady-plugin.js +90 -0
  154. package/dist/runtime/onboarding-names.js +78 -0
  155. package/dist/runtime/restart.js +40 -0
  156. package/dist/runtime/version.js +7 -0
  157. package/dist/sandbox-routes.js +1112 -0
  158. package/dist/security/audit-log.js +149 -0
  159. package/dist/security/network-policy.js +70 -0
  160. package/dist/server.js +7949 -0
  161. package/dist/services/agent-export.js +559 -0
  162. package/dist/services/app-manager.js +389 -0
  163. package/dist/services/browser-capture.js +86 -0
  164. package/dist/services/fallback-training-service.js +128 -0
  165. package/dist/services/mcp-marketplace.js +134 -0
  166. package/dist/services/plugin-installer.js +396 -0
  167. package/dist/services/plugin-manager-types.js +15 -0
  168. package/dist/services/registry-client-app-meta.js +144 -0
  169. package/dist/services/registry-client-endpoints.js +166 -0
  170. package/dist/services/registry-client-local.js +271 -0
  171. package/dist/services/registry-client-network.js +93 -0
  172. package/dist/services/registry-client-queries.js +70 -0
  173. package/dist/services/registry-client.js +157 -0
  174. package/dist/services/sandbox-engine.js +511 -0
  175. package/dist/services/sandbox-manager.js +297 -0
  176. package/dist/services/self-updater.js +175 -0
  177. package/dist/services/skill-catalog-client.js +119 -0
  178. package/dist/services/skill-marketplace.js +521 -0
  179. package/dist/services/stream-manager.js +236 -0
  180. package/dist/services/update-checker.js +121 -0
  181. package/dist/services/update-notifier.js +29 -0
  182. package/dist/services/version-compat.js +78 -0
  183. package/dist/services/whatsapp-pairing.js +196 -0
  184. package/dist/shared/ui-catalog-prompt.js +728 -0
  185. package/dist/subscription-routes.js +172 -0
  186. package/dist/terminal/links.js +19 -0
  187. package/dist/terminal/palette.js +14 -0
  188. package/dist/terminal/theme.js +25 -0
  189. package/dist/terminal-run-limits.js +24 -0
  190. package/dist/training-routes.js +158 -0
  191. package/dist/trajectory-routes.js +300 -0
  192. package/dist/trigger-routes.js +246 -0
  193. package/dist/triggers/action.js +218 -0
  194. package/dist/triggers/runtime.js +281 -0
  195. package/dist/triggers/scheduling.js +295 -0
  196. package/dist/triggers/types.js +5 -0
  197. package/dist/tui/components/assistant-message.js +76 -0
  198. package/dist/tui/components/chat-editor.js +34 -0
  199. package/dist/tui/components/embeddings-overlay.js +46 -0
  200. package/dist/tui/components/footer.js +60 -0
  201. package/dist/tui/components/index.js +15 -0
  202. package/dist/tui/components/modal-frame.js +45 -0
  203. package/dist/tui/components/modal-style.js +15 -0
  204. package/dist/tui/components/model-selector.js +70 -0
  205. package/dist/tui/components/pinned-chat-layout.js +46 -0
  206. package/dist/tui/components/plugins-endpoints-tab.js +196 -0
  207. package/dist/tui/components/plugins-installed-tab-view.js +69 -0
  208. package/dist/tui/components/plugins-installed-tab.js +319 -0
  209. package/dist/tui/components/plugins-overlay-catalog.js +81 -0
  210. package/dist/tui/components/plugins-overlay-data-api.js +21 -0
  211. package/dist/tui/components/plugins-overlay-data-shared.js +20 -0
  212. package/dist/tui/components/plugins-overlay-data.js +323 -0
  213. package/dist/tui/components/plugins-overlay.js +117 -0
  214. package/dist/tui/components/plugins-store-tab.js +148 -0
  215. package/dist/tui/components/settings-overlay.js +61 -0
  216. package/dist/tui/components/status-bar.js +64 -0
  217. package/dist/tui/components/tool-execution.js +68 -0
  218. package/dist/tui/components/user-message.js +22 -0
  219. package/dist/tui/eliza-tui-bridge.js +606 -0
  220. package/dist/tui/index.js +370 -0
  221. package/dist/tui/modal-presets.js +33 -0
  222. package/dist/tui/model-spec.js +46 -0
  223. package/dist/tui/sse-parser.js +78 -0
  224. package/dist/tui/theme.js +110 -0
  225. package/dist/tui/titlebar-spinner.js +62 -0
  226. package/dist/tui/tui-app.js +311 -0
  227. package/dist/tui/ws-client.js +215 -0
  228. package/dist/twitter-verify.js +134 -0
  229. package/dist/tx-service.js +108 -0
  230. package/dist/utils/exec-safety.js +17 -0
  231. package/dist/utils/globals.js +20 -0
  232. package/dist/utils/milady-root.js +61 -0
  233. package/dist/utils/number-parsing.js +37 -0
  234. package/dist/version-resolver.js +37 -0
  235. package/dist/version.js +7 -0
  236. package/dist/wallet-routes.js +266 -0
  237. package/dist/wallet.js +568 -0
  238. package/dist/whatsapp-routes.js +182 -0
  239. package/dist/zip-utils.js +109 -0
  240. package/milady.mjs +14 -0
  241. package/package.json +111 -0
@@ -0,0 +1,1112 @@
1
+ import { __require } from "./_virtual/_rolldown/runtime.js";
2
+ import { readJsonBody as readJsonBody$1, readRequestBody, sendJson as sendJson$1 } from "./http-helpers.js";
3
+ import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
4
+ import { platform, tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { execFileSync, execSync } from "node:child_process";
7
+
8
+ //#region src/api/sandbox-routes.ts
9
+ /** Sandbox capability API routes: status, exec, browser, screen, audio, computer use. */
10
+ const MAX_COMPUTER_INPUT_LENGTH = 4096;
11
+ const MAX_KEYPRESS_LENGTH = 128;
12
+ const SAFE_KEYPRESS_PATTERN = /^[A-Za-z0-9+_.,: -]+$/;
13
+ const ALLOWED_AUDIO_FORMATS = new Set([
14
+ "wav",
15
+ "mp3",
16
+ "ogg",
17
+ "flac",
18
+ "m4a"
19
+ ]);
20
+ const MIN_AUDIO_RECORD_DURATION_MS = 250;
21
+ const MAX_AUDIO_RECORD_DURATION_MS = 3e4;
22
+ /** Returns `true` if handled, `false` to fall through. */
23
+ async function handleSandboxRoute(req, res, pathname, method, state) {
24
+ if (!pathname.startsWith("/api/sandbox")) return false;
25
+ const mgr = state.sandboxManager;
26
+ if (method === "GET" && pathname === "/api/sandbox/platform") {
27
+ sendJson(res, 200, getPlatformInfo());
28
+ return true;
29
+ }
30
+ if (method === "POST" && pathname === "/api/sandbox/docker/start") {
31
+ try {
32
+ sendJson(res, 200, attemptDockerStart());
33
+ } catch (err) {
34
+ sendJson(res, 500, {
35
+ success: false,
36
+ error: `Failed to start Docker: ${err instanceof Error ? err.message : String(err)}`
37
+ });
38
+ }
39
+ return true;
40
+ }
41
+ if (!mgr) {
42
+ sendJson(res, 503, { error: "Sandbox manager not initialized" });
43
+ return true;
44
+ }
45
+ if (method === "GET" && pathname === "/api/sandbox/status") {
46
+ sendJson(res, 200, mgr.getStatus());
47
+ return true;
48
+ }
49
+ if (method === "GET" && pathname === "/api/sandbox/events") {
50
+ sendJson(res, 200, { events: mgr.getEventLog().slice(-100) });
51
+ return true;
52
+ }
53
+ if (method === "POST" && pathname === "/api/sandbox/start") {
54
+ try {
55
+ await mgr.start();
56
+ sendJson(res, 200, mgr.getStatus());
57
+ } catch (err) {
58
+ sendJson(res, 500, { error: `Failed to start sandbox: ${err instanceof Error ? err.message : String(err)}` });
59
+ }
60
+ return true;
61
+ }
62
+ if (method === "POST" && pathname === "/api/sandbox/stop") {
63
+ try {
64
+ await mgr.stop();
65
+ sendJson(res, 200, mgr.getStatus());
66
+ } catch (err) {
67
+ sendJson(res, 500, { error: `Failed to stop sandbox: ${err instanceof Error ? err.message : String(err)}` });
68
+ }
69
+ return true;
70
+ }
71
+ if (method === "POST" && pathname === "/api/sandbox/recover") {
72
+ try {
73
+ await mgr.recover();
74
+ sendJson(res, 200, mgr.getStatus());
75
+ } catch (err) {
76
+ sendJson(res, 500, { error: `Recovery failed: ${err instanceof Error ? err.message : String(err)}` });
77
+ }
78
+ return true;
79
+ }
80
+ if (method === "POST" && pathname === "/api/sandbox/exec") {
81
+ const parsed = await readJsonBody(req, res);
82
+ if (!parsed) return true;
83
+ if (!parsed.command || typeof parsed.command !== "string") {
84
+ sendJson(res, 400, { error: "Missing 'command' field" });
85
+ return true;
86
+ }
87
+ const result = await mgr.exec({
88
+ command: parsed.command,
89
+ workdir: parsed.workdir,
90
+ timeoutMs: parsed.timeoutMs
91
+ });
92
+ sendJson(res, result.exitCode === 0 ? 200 : 422, result);
93
+ return true;
94
+ }
95
+ if (method === "GET" && pathname === "/api/sandbox/browser") {
96
+ sendJson(res, 200, {
97
+ cdpEndpoint: mgr.getBrowserCdpEndpoint(),
98
+ wsEndpoint: mgr.getBrowserWsEndpoint()
99
+ });
100
+ return true;
101
+ }
102
+ if (method === "GET" && pathname === "/api/sandbox/screen/screenshot") {
103
+ try {
104
+ const screenshot = captureScreenshot();
105
+ res.writeHead(200, {
106
+ "Content-Type": "image/png",
107
+ "Content-Length": screenshot.length
108
+ });
109
+ res.end(screenshot);
110
+ } catch (err) {
111
+ sendJson(res, 500, { error: `Screenshot failed: ${err instanceof Error ? err.message : String(err)}` });
112
+ }
113
+ return true;
114
+ }
115
+ if (method === "POST" && pathname === "/api/sandbox/screen/screenshot") {
116
+ const rawBody = await readBody(req);
117
+ if (!rawBody || !rawBody.trim()) {
118
+ sendJson(res, 200, {
119
+ format: "png",
120
+ encoding: "base64",
121
+ width: null,
122
+ height: null,
123
+ data: captureScreenshot().toString("base64")
124
+ });
125
+ return true;
126
+ }
127
+ let regionInput;
128
+ try {
129
+ regionInput = JSON.parse(rawBody);
130
+ } catch {
131
+ sendJson(res, 400, { error: "Invalid JSON body" });
132
+ return true;
133
+ }
134
+ const region = resolveScreenshotRegion(regionInput);
135
+ if (region.error) {
136
+ sendJson(res, 400, { error: region.error });
137
+ return true;
138
+ }
139
+ try {
140
+ sendJson(res, 200, {
141
+ format: "png",
142
+ encoding: "base64",
143
+ width: null,
144
+ height: null,
145
+ data: captureScreenshot(region.region).toString("base64")
146
+ });
147
+ } catch (err) {
148
+ sendJson(res, 500, { error: `Screenshot failed: ${err instanceof Error ? err.message : String(err)}` });
149
+ }
150
+ return true;
151
+ }
152
+ if (method === "GET" && pathname === "/api/sandbox/screen/windows") {
153
+ try {
154
+ sendJson(res, 200, { windows: listWindows() });
155
+ } catch (err) {
156
+ sendJson(res, 200, {
157
+ windows: [],
158
+ error: String(err)
159
+ });
160
+ }
161
+ return true;
162
+ }
163
+ if (method === "POST" && pathname === "/api/sandbox/audio/record") {
164
+ const body = await readBody(req);
165
+ let durationMs = 5e3;
166
+ if (body) {
167
+ let parsed;
168
+ try {
169
+ parsed = JSON.parse(body);
170
+ } catch {
171
+ sendJson(res, 400, { error: "Invalid JSON in request body" });
172
+ return true;
173
+ }
174
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
175
+ sendJson(res, 400, { error: "Request body must be a JSON object" });
176
+ return true;
177
+ }
178
+ const bodyValues = parsed;
179
+ if (Object.hasOwn(bodyValues, "durationMs")) {
180
+ const durationValue = bodyValues.durationMs;
181
+ if (typeof durationValue !== "number") {
182
+ sendJson(res, 400, { error: "durationMs must be a finite number" });
183
+ return true;
184
+ }
185
+ if (!Number.isFinite(durationValue)) {
186
+ sendJson(res, 400, { error: "durationMs must be a finite number" });
187
+ return true;
188
+ }
189
+ if (!Number.isInteger(durationValue)) {
190
+ sendJson(res, 400, { error: "durationMs must be an integer number of milliseconds" });
191
+ return true;
192
+ }
193
+ if (durationValue < MIN_AUDIO_RECORD_DURATION_MS || durationValue > MAX_AUDIO_RECORD_DURATION_MS) {
194
+ sendJson(res, 400, { error: `durationMs must be between ${MIN_AUDIO_RECORD_DURATION_MS} and ${MAX_AUDIO_RECORD_DURATION_MS} milliseconds` });
195
+ return true;
196
+ }
197
+ durationMs = durationValue;
198
+ }
199
+ }
200
+ try {
201
+ const audio = await recordAudio(durationMs);
202
+ sendJson(res, 200, {
203
+ format: "wav",
204
+ encoding: "base64",
205
+ durationMs,
206
+ data: audio.toString("base64")
207
+ });
208
+ } catch (err) {
209
+ sendJson(res, 500, { error: `Audio recording failed: ${err instanceof Error ? err.message : String(err)}` });
210
+ }
211
+ return true;
212
+ }
213
+ if (method === "POST" && pathname === "/api/sandbox/audio/play") {
214
+ const parsed = await readJsonBody(req, res);
215
+ if (!parsed) return true;
216
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
217
+ sendJson(res, 400, { error: "Body must be a JSON object" });
218
+ return true;
219
+ }
220
+ const payload = parsed;
221
+ if (typeof payload.data !== "string" || !payload.data.trim()) {
222
+ sendJson(res, 400, { error: "Missing 'data' field (base64 audio)" });
223
+ return true;
224
+ }
225
+ const formatResult = resolveAudioFormat(payload.format);
226
+ if (formatResult.error) {
227
+ sendJson(res, 400, { error: formatResult.error });
228
+ return true;
229
+ }
230
+ try {
231
+ await playAudio(Buffer.from(payload.data, "base64"), formatResult.format);
232
+ sendJson(res, 200, { success: true });
233
+ } catch (err) {
234
+ sendJson(res, 500, { error: `Audio playback failed: ${err instanceof Error ? err.message : String(err)}` });
235
+ }
236
+ return true;
237
+ }
238
+ if (method === "POST" && pathname === "/api/sandbox/computer/click") {
239
+ const parsed = await readJsonBody(req, res);
240
+ if (!parsed) return true;
241
+ const clickPayload = resolveClickPayload(parsed);
242
+ if (clickPayload.error) {
243
+ sendJson(res, 400, { error: clickPayload.error });
244
+ return true;
245
+ }
246
+ try {
247
+ const { x, y, button } = clickPayload;
248
+ performClick(x, y, button);
249
+ sendJson(res, 200, {
250
+ success: true,
251
+ x,
252
+ y,
253
+ button
254
+ });
255
+ } catch (err) {
256
+ sendJson(res, 500, { error: `Click failed: ${err instanceof Error ? err.message : String(err)}` });
257
+ }
258
+ return true;
259
+ }
260
+ if (method === "POST" && pathname === "/api/sandbox/computer/type") {
261
+ const parsed = await readJsonBody(req, res);
262
+ if (!parsed) return true;
263
+ const typePayload = resolveTypePayload(parsed);
264
+ if (typePayload.error) {
265
+ sendJson(res, 400, { error: typePayload.error });
266
+ return true;
267
+ }
268
+ try {
269
+ const { text } = typePayload;
270
+ performType(text);
271
+ sendJson(res, 200, {
272
+ success: true,
273
+ length: text.length
274
+ });
275
+ } catch (err) {
276
+ sendJson(res, 500, { error: `Type failed: ${err instanceof Error ? err.message : String(err)}` });
277
+ }
278
+ return true;
279
+ }
280
+ if (method === "POST" && pathname === "/api/sandbox/computer/keypress") {
281
+ const parsed = await readJsonBody(req, res);
282
+ if (!parsed) return true;
283
+ const keypressPayload = resolveKeypressPayload(parsed);
284
+ if (keypressPayload.error) {
285
+ sendJson(res, 400, { error: keypressPayload.error });
286
+ return true;
287
+ }
288
+ try {
289
+ const { keys } = keypressPayload;
290
+ performKeypress(keys);
291
+ sendJson(res, 200, {
292
+ success: true,
293
+ keys
294
+ });
295
+ } catch (err) {
296
+ sendJson(res, 500, { error: `Keypress failed: ${err instanceof Error ? err.message : String(err)}` });
297
+ }
298
+ return true;
299
+ }
300
+ if (method === "POST" && pathname === "/api/sandbox/sign") {
301
+ const signer = state.signingService;
302
+ if (!signer) {
303
+ sendJson(res, 503, { error: "Signing service not configured" });
304
+ return true;
305
+ }
306
+ const body = await readJsonBody(req, res);
307
+ if (body === null) return true;
308
+ const parsed = resolveSigningRequestPayload(body);
309
+ if ("error" in parsed) {
310
+ sendJson(res, 400, { error: parsed.error });
311
+ return true;
312
+ }
313
+ try {
314
+ const result = await signer.submitSigningRequest(parsed.request);
315
+ sendJson(res, result.success ? 200 : 403, result);
316
+ } catch (err) {
317
+ sendJson(res, 400, { error: `Invalid request: ${err instanceof Error ? err.message : String(err)}` });
318
+ }
319
+ return true;
320
+ }
321
+ if (method === "POST" && pathname === "/api/sandbox/sign/approve") {
322
+ const signer = state.signingService;
323
+ if (!signer) {
324
+ sendJson(res, 503, { error: "Signing service not configured" });
325
+ return true;
326
+ }
327
+ const body = await readJsonBody(req, res);
328
+ if (!body) return true;
329
+ try {
330
+ const { requestId } = body;
331
+ const result = await signer.approveRequest(requestId);
332
+ sendJson(res, result.success ? 200 : 403, result);
333
+ } catch (err) {
334
+ sendJson(res, 400, { error: String(err) });
335
+ }
336
+ return true;
337
+ }
338
+ if (method === "POST" && pathname === "/api/sandbox/sign/reject") {
339
+ const signer = state.signingService;
340
+ if (!signer) {
341
+ sendJson(res, 503, { error: "Signing service not configured" });
342
+ return true;
343
+ }
344
+ const body = await readJsonBody(req, res);
345
+ if (!body) return true;
346
+ try {
347
+ const { requestId } = body;
348
+ sendJson(res, 200, { rejected: signer.rejectRequest(requestId) });
349
+ } catch (err) {
350
+ sendJson(res, 400, { error: String(err) });
351
+ }
352
+ return true;
353
+ }
354
+ if (method === "GET" && pathname === "/api/sandbox/sign/pending") {
355
+ const signer = state.signingService;
356
+ if (!signer) {
357
+ sendJson(res, 503, { error: "Signing service not configured" });
358
+ return true;
359
+ }
360
+ sendJson(res, 200, { pending: signer.getPendingApprovals() });
361
+ return true;
362
+ }
363
+ if (method === "GET" && pathname === "/api/sandbox/sign/address") {
364
+ const signer = state.signingService;
365
+ if (!signer) {
366
+ sendJson(res, 503, { error: "Signing service not configured" });
367
+ return true;
368
+ }
369
+ try {
370
+ sendJson(res, 200, { address: await signer.getAddress() });
371
+ } catch (err) {
372
+ sendJson(res, 500, { error: String(err) });
373
+ }
374
+ return true;
375
+ }
376
+ if (method === "GET" && pathname === "/api/sandbox/capabilities") {
377
+ sendJson(res, 200, detectCapabilities());
378
+ return true;
379
+ }
380
+ sendJson(res, 404, { error: `Unknown sandbox route: ${method} ${pathname}` });
381
+ return true;
382
+ }
383
+ function asObject(value) {
384
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
385
+ return value;
386
+ }
387
+ function resolveSigningRequestPayload(input) {
388
+ const obj = asObject(input);
389
+ if (!obj) return { error: "Signing payload must be a JSON object" };
390
+ const requestId = obj.requestId;
391
+ const chainId = parseFiniteInteger(obj.chainId);
392
+ const to = obj.to;
393
+ const value = obj.value;
394
+ const data = obj.data;
395
+ const nonce = obj.nonce === void 0 ? void 0 : parseFiniteInteger(obj.nonce);
396
+ const rawGasLimit = obj.gasLimit;
397
+ const createdAt = parseFiniteInteger(obj.createdAt);
398
+ if (typeof requestId !== "string" || !requestId.trim()) return { error: "Signing payload requires a non-empty string 'requestId'" };
399
+ if (chainId === null || chainId < 0) return { error: "Signing payload requires an integer 'chainId' >= 0" };
400
+ if (typeof to !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(to.trim())) return { error: "Signing payload requires a hex 'to' address (e.g., 0x followed by 40 hex characters)" };
401
+ if (typeof value !== "string" || !value.trim()) return { error: "Signing payload requires a non-empty string 'value'" };
402
+ if (typeof data !== "string" || !data.trim()) return { error: "Signing payload requires a non-empty string 'data'" };
403
+ if (nonce === null) return { error: "'nonce' must be a non-negative integer when provided" };
404
+ if (createdAt === null) return { error: "Signing payload requires an integer 'createdAt'" };
405
+ if (rawGasLimit !== void 0 && typeof rawGasLimit !== "string") return { error: "Signing payload 'gasLimit' must be a string when provided" };
406
+ const gasLimit = rawGasLimit?.trim();
407
+ if (gasLimit === "") return { error: "Signing payload 'gasLimit' cannot be empty when provided" };
408
+ return { request: {
409
+ requestId: requestId.trim(),
410
+ chainId,
411
+ to: to.trim(),
412
+ value: value.trim(),
413
+ data,
414
+ ...nonce === void 0 ? {} : { nonce },
415
+ ...gasLimit === void 0 ? {} : { gasLimit },
416
+ createdAt
417
+ } };
418
+ }
419
+ function parseFiniteInteger(value) {
420
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
421
+ if (!Number.isInteger(value)) return null;
422
+ return value;
423
+ }
424
+ function resolveScreenshotRegion(input) {
425
+ if (input === void 0 || input === null) return {};
426
+ const obj = asObject(input);
427
+ if (!obj) return { error: "Screenshot region payload must be a JSON object" };
428
+ if (!("x" in obj || "y" in obj || "width" in obj || "height" in obj)) return {};
429
+ const x = parseFiniteInteger(obj.x);
430
+ const y = parseFiniteInteger(obj.y);
431
+ const width = parseFiniteInteger(obj.width);
432
+ const height = parseFiniteInteger(obj.height);
433
+ if (x === null || y === null || width === null || height === null) return { error: "Region requires integer x, y, width, and height values" };
434
+ if (width <= 0 || height <= 0) return { error: "Region width and height must be greater than 0" };
435
+ return { region: {
436
+ x,
437
+ y,
438
+ width,
439
+ height
440
+ } };
441
+ }
442
+ function resolveClickPayload(input) {
443
+ const obj = asObject(input);
444
+ if (!obj) return {
445
+ x: 0,
446
+ y: 0,
447
+ button: "left",
448
+ error: "Click payload must be a JSON object"
449
+ };
450
+ const x = parseFiniteInteger(obj.x);
451
+ const y = parseFiniteInteger(obj.y);
452
+ if (x === null || y === null) return {
453
+ x: 0,
454
+ y: 0,
455
+ button: "left",
456
+ error: "Click payload requires integer x and y coordinates"
457
+ };
458
+ const rawButton = obj.button;
459
+ let button = "left";
460
+ if (rawButton !== void 0) {
461
+ if (rawButton !== "left" && rawButton !== "right") return {
462
+ x,
463
+ y,
464
+ button,
465
+ error: "button must be either 'left' or 'right'"
466
+ };
467
+ button = rawButton;
468
+ }
469
+ return {
470
+ x,
471
+ y,
472
+ button
473
+ };
474
+ }
475
+ function resolveTypePayload(input) {
476
+ const obj = asObject(input);
477
+ if (!obj) return {
478
+ text: "",
479
+ error: "Type payload must be a JSON object"
480
+ };
481
+ if (typeof obj.text !== "string") return {
482
+ text: "",
483
+ error: "Type payload requires a string 'text' field"
484
+ };
485
+ if (obj.text.length === 0) return {
486
+ text: "",
487
+ error: "text cannot be empty"
488
+ };
489
+ if (obj.text.length > MAX_COMPUTER_INPUT_LENGTH) return {
490
+ text: "",
491
+ error: `text exceeds maximum length (${MAX_COMPUTER_INPUT_LENGTH})`
492
+ };
493
+ return { text: obj.text };
494
+ }
495
+ function resolveKeypressPayload(input) {
496
+ const obj = asObject(input);
497
+ if (!obj) return {
498
+ keys: "",
499
+ error: "Keypress payload must be a JSON object"
500
+ };
501
+ if (typeof obj.keys !== "string") return {
502
+ keys: "",
503
+ error: "Keypress payload requires a string 'keys' field"
504
+ };
505
+ const keys = obj.keys.trim();
506
+ if (!keys) return {
507
+ keys: "",
508
+ error: "keys cannot be empty"
509
+ };
510
+ if (keys.length > MAX_KEYPRESS_LENGTH) return {
511
+ keys: "",
512
+ error: `keys exceeds maximum length (${MAX_KEYPRESS_LENGTH})`
513
+ };
514
+ if (!SAFE_KEYPRESS_PATTERN.test(keys)) return {
515
+ keys: "",
516
+ error: "keys contains unsupported characters; allowed: letters, numbers, space, +, _, ., ,, :, -"
517
+ };
518
+ return { keys };
519
+ }
520
+ function resolveAudioFormat(input) {
521
+ if (input === void 0 || input === null) return { format: "wav" };
522
+ if (typeof input !== "string") return {
523
+ format: "wav",
524
+ error: "format must be a string"
525
+ };
526
+ const normalized = input.trim().toLowerCase();
527
+ if (!normalized) return { format: "wav" };
528
+ if (!/^[a-z0-9]+$/.test(normalized)) return {
529
+ format: "wav",
530
+ error: "format contains unsupported characters; use one of: wav, mp3, ogg, flac, m4a"
531
+ };
532
+ if (!ALLOWED_AUDIO_FORMATS.has(normalized)) return {
533
+ format: "wav",
534
+ error: "format must be one of: wav, mp3, ogg, flac, m4a"
535
+ };
536
+ return { format: normalized };
537
+ }
538
+ function runCommand(command, args, timeout) {
539
+ execFileSync(command, args, {
540
+ timeout,
541
+ stdio: [
542
+ "ignore",
543
+ "pipe",
544
+ "pipe"
545
+ ]
546
+ });
547
+ }
548
+ function captureScreenshot(region) {
549
+ const os = platform();
550
+ const tmpFile = join(tmpdir(), `sandbox-screenshot-${Date.now()}.png`);
551
+ try {
552
+ if (os === "darwin") if (region) runCommand("screencapture", [
553
+ `-R${region.x},${region.y},${region.width},${region.height}`,
554
+ "-x",
555
+ tmpFile
556
+ ], 1e4);
557
+ else runCommand("screencapture", ["-x", tmpFile], 1e4);
558
+ else if (os === "linux") if (commandExists("import")) if (region) runCommand("import", [
559
+ "-window",
560
+ "root",
561
+ "-crop",
562
+ `${region.width}x${region.height}+${region.x}+${region.y}`,
563
+ tmpFile
564
+ ], 1e4);
565
+ else runCommand("import", [
566
+ "-window",
567
+ "root",
568
+ tmpFile
569
+ ], 1e4);
570
+ else if (commandExists("scrot")) runCommand("scrot", [tmpFile], 1e4);
571
+ else if (commandExists("gnome-screenshot")) runCommand("gnome-screenshot", ["-f", tmpFile], 1e4);
572
+ else throw new Error("No screenshot tool available. Install ImageMagick, scrot, or gnome-screenshot.");
573
+ else if (os === "win32") execSync(`powershell -Command "${[
574
+ `Add-Type -AssemblyName System.Windows.Forms`,
575
+ `$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds`,
576
+ `$bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)`,
577
+ `$graphics = [System.Drawing.Graphics]::FromImage($bitmap)`,
578
+ `$graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size)`,
579
+ `$bitmap.Save('${tmpFile.replace(/\//g, "\\")}')`,
580
+ `$graphics.Dispose()`,
581
+ `$bitmap.Dispose()`
582
+ ].join("; ")}"`, {
583
+ timeout: 15e3,
584
+ stdio: [
585
+ "ignore",
586
+ "pipe",
587
+ "pipe"
588
+ ]
589
+ });
590
+ else throw new Error(`Screenshot not supported on platform: ${os}`);
591
+ const data = readFileSync(tmpFile);
592
+ try {
593
+ unlinkSync(tmpFile);
594
+ } catch {}
595
+ return data;
596
+ } catch (err) {
597
+ try {
598
+ unlinkSync(tmpFile);
599
+ } catch {}
600
+ throw err;
601
+ }
602
+ }
603
+ function listWindows() {
604
+ const os = platform();
605
+ if (os === "darwin") try {
606
+ return execSync(`osascript -e '
607
+ tell application "System Events"
608
+ set windowList to {}
609
+ repeat with proc in (every process whose visible is true)
610
+ try
611
+ repeat with w in (every window of proc)
612
+ set end of windowList to (name of proc) & "|||" & (name of w) & "|||" & (id of w as text)
613
+ end repeat
614
+ end try
615
+ end repeat
616
+ return windowList as text
617
+ end tell'`, {
618
+ encoding: "utf-8",
619
+ timeout: 1e4,
620
+ stdio: [
621
+ "ignore",
622
+ "pipe",
623
+ "ignore"
624
+ ]
625
+ }).split(", ").filter(Boolean).map((entry) => {
626
+ const parts = entry.split("|||");
627
+ return {
628
+ app: parts[0] ?? "unknown",
629
+ title: parts[1] ?? "unknown",
630
+ id: parts[2] ?? "0"
631
+ };
632
+ });
633
+ } catch {
634
+ return [];
635
+ }
636
+ if (os === "linux") try {
637
+ return execSync("wmctrl -l 2>/dev/null || xdotool search --name \"\" getwindowname 2>/dev/null", {
638
+ encoding: "utf-8",
639
+ timeout: 5e3
640
+ }).split("\n").filter(Boolean).map((line, i) => ({
641
+ id: String(i),
642
+ title: line.trim(),
643
+ app: "unknown"
644
+ }));
645
+ } catch {
646
+ return [];
647
+ }
648
+ if (os === "win32") try {
649
+ const output = execSync(`powershell -Command "Get-Process | Where-Object {$_.MainWindowTitle} | Select-Object Id, MainWindowTitle | ConvertTo-Json"`, {
650
+ encoding: "utf-8",
651
+ timeout: 1e4
652
+ });
653
+ const processes = JSON.parse(output);
654
+ return (Array.isArray(processes) ? processes : [processes]).map((p) => ({
655
+ id: String(p.Id),
656
+ title: p.MainWindowTitle,
657
+ app: "unknown"
658
+ }));
659
+ } catch {
660
+ return [];
661
+ }
662
+ return [];
663
+ }
664
+ async function recordAudio(durationMs) {
665
+ const os = platform();
666
+ const durationSec = Math.ceil(durationMs / 1e3);
667
+ const tmpFile = join(tmpdir(), `sandbox-audio-${Date.now()}.wav`);
668
+ if (os === "darwin") if (commandExists("rec")) execSync(`rec -q ${tmpFile} trim 0 ${durationSec}`, {
669
+ timeout: durationMs + 5e3,
670
+ stdio: [
671
+ "ignore",
672
+ "pipe",
673
+ "pipe"
674
+ ]
675
+ });
676
+ else if (commandExists("ffmpeg")) execSync(`ffmpeg -f avfoundation -i ":0" -t ${durationSec} -y ${tmpFile} 2>/dev/null`, {
677
+ timeout: durationMs + 1e4,
678
+ stdio: [
679
+ "ignore",
680
+ "pipe",
681
+ "pipe"
682
+ ]
683
+ });
684
+ else throw new Error("No audio recording tool available. Install sox or ffmpeg.");
685
+ else if (os === "linux") if (commandExists("arecord")) execSync(`arecord -d ${durationSec} -f cd ${tmpFile}`, {
686
+ timeout: durationMs + 5e3,
687
+ stdio: [
688
+ "ignore",
689
+ "pipe",
690
+ "pipe"
691
+ ]
692
+ });
693
+ else if (commandExists("ffmpeg")) execSync(`ffmpeg -f pulse -i default -t ${durationSec} -y ${tmpFile} 2>/dev/null`, {
694
+ timeout: durationMs + 1e4,
695
+ stdio: [
696
+ "ignore",
697
+ "pipe",
698
+ "pipe"
699
+ ]
700
+ });
701
+ else throw new Error("No audio recording tool available. Install alsa-utils or ffmpeg.");
702
+ else if (os === "win32") if (commandExists("ffmpeg")) execSync(`ffmpeg -f dshow -i audio="Microphone" -t ${durationSec} -y "${tmpFile.replace(/\//g, "\\")}" 2>NUL`, {
703
+ timeout: durationMs + 1e4,
704
+ stdio: [
705
+ "ignore",
706
+ "pipe",
707
+ "pipe"
708
+ ]
709
+ });
710
+ else throw new Error("No audio recording tool available. Install ffmpeg.");
711
+ else throw new Error(`Audio recording not supported on platform: ${os}`);
712
+ const data = readFileSync(tmpFile);
713
+ try {
714
+ unlinkSync(tmpFile);
715
+ } catch {}
716
+ return data;
717
+ }
718
+ async function playAudio(data, format) {
719
+ const os = platform();
720
+ const tmpFile = join(tmpdir(), `sandbox-play-${Date.now()}.${format}`);
721
+ writeFileSync(tmpFile, data);
722
+ try {
723
+ if (os === "darwin") runCommand("afplay", [tmpFile], 6e4);
724
+ else if (os === "linux") if (commandExists("aplay")) runCommand("aplay", [tmpFile], 6e4);
725
+ else if (commandExists("paplay")) runCommand("paplay", [tmpFile], 6e4);
726
+ else if (commandExists("ffplay")) runCommand("ffplay", [
727
+ "-autoexit",
728
+ "-nodisp",
729
+ tmpFile
730
+ ], 6e4);
731
+ else throw new Error("No audio playback tool available.");
732
+ else if (os === "win32") runCommand("powershell", ["-Command", `(New-Object Media.SoundPlayer '${tmpFile.replace(/\//g, "\\").replace(/'/g, "''")}').PlaySync()`], 6e4);
733
+ } finally {
734
+ try {
735
+ unlinkSync(tmpFile);
736
+ } catch {}
737
+ }
738
+ }
739
+ function toAppleScriptStringLiteral(value) {
740
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
741
+ }
742
+ function performClick(x, y, button) {
743
+ const os = platform();
744
+ if (os === "darwin") if (commandExists("cliclick")) runCommand("cliclick", [`${button === "right" ? "rc" : "c"}:${x},${y}`], 5e3);
745
+ else runCommand("osascript", ["-e", `tell application "System Events" to click at {${x}, ${y}}`], 5e3);
746
+ else if (os === "linux") if (commandExists("xdotool")) {
747
+ const btn = button === "right" ? "3" : "1";
748
+ runCommand("xdotool", [
749
+ "mousemove",
750
+ String(x),
751
+ String(y),
752
+ "click",
753
+ btn
754
+ ], 5e3);
755
+ } else throw new Error("xdotool required for mouse control on Linux.");
756
+ else if (os === "win32") runCommand("powershell", ["-Command", [
757
+ `Add-Type -AssemblyName System.Windows.Forms`,
758
+ `[System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${x},${y})`,
759
+ `Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);' -Name Win32Mouse -Namespace Win32`,
760
+ button === "right" ? `[Win32.Win32Mouse]::mouse_event(0x0008, 0, 0, 0, 0); [Win32.Win32Mouse]::mouse_event(0x0010, 0, 0, 0, 0)` : `[Win32.Win32Mouse]::mouse_event(0x0002, 0, 0, 0, 0); [Win32.Win32Mouse]::mouse_event(0x0004, 0, 0, 0, 0)`
761
+ ].join("; ")], 5e3);
762
+ }
763
+ function performType(text) {
764
+ const os = platform();
765
+ if (os === "darwin") if (commandExists("cliclick")) runCommand("cliclick", [`t:${text}`], 1e4);
766
+ else runCommand("osascript", ["-e", `tell application "System Events" to keystroke ${toAppleScriptStringLiteral(text)}`], 1e4);
767
+ else if (os === "linux") if (commandExists("xdotool")) runCommand("xdotool", [
768
+ "type",
769
+ "--",
770
+ text
771
+ ], 1e4);
772
+ else throw new Error("xdotool required for keyboard input on Linux.");
773
+ else if (os === "win32") runCommand("powershell", ["-Command", `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${text.replace(/'/g, "''")}')`], 1e4);
774
+ }
775
+ function performKeypress(keys) {
776
+ const os = platform();
777
+ if (os === "darwin") if (commandExists("cliclick")) runCommand("cliclick", [`kp:${keys}`], 5e3);
778
+ else {
779
+ const numericCode = {
780
+ return: 36,
781
+ enter: 36,
782
+ tab: 48,
783
+ space: 49,
784
+ escape: 53,
785
+ esc: 53,
786
+ left: 123,
787
+ right: 124,
788
+ down: 125,
789
+ up: 126
790
+ }[keys.trim().toLowerCase()] ?? (Number.isInteger(Number(keys.trim())) ? Number(keys.trim()) : null);
791
+ if (numericCode !== null) runCommand("osascript", ["-e", `tell application "System Events" to key code ${numericCode}`], 5e3);
792
+ else runCommand("osascript", ["-e", `tell application "System Events" to keystroke ${toAppleScriptStringLiteral(keys)}`], 5e3);
793
+ }
794
+ else if (os === "linux") if (commandExists("xdotool")) runCommand("xdotool", ["key", keys], 5e3);
795
+ else throw new Error("xdotool required for key input on Linux.");
796
+ else if (os === "win32") runCommand("powershell", ["-Command", `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${keys.replace(/'/g, "''")}')`], 5e3);
797
+ }
798
+ function detectCapabilities() {
799
+ const os = platform();
800
+ const caps = {};
801
+ if (os === "darwin") caps.screenshot = {
802
+ available: true,
803
+ tool: "screencapture (built-in)"
804
+ };
805
+ else if (os === "linux") if (commandExists("import")) caps.screenshot = {
806
+ available: true,
807
+ tool: "ImageMagick import"
808
+ };
809
+ else if (commandExists("scrot")) caps.screenshot = {
810
+ available: true,
811
+ tool: "scrot"
812
+ };
813
+ else if (commandExists("gnome-screenshot")) caps.screenshot = {
814
+ available: true,
815
+ tool: "gnome-screenshot"
816
+ };
817
+ else caps.screenshot = {
818
+ available: false,
819
+ tool: "none (install ImageMagick, scrot, or gnome-screenshot)"
820
+ };
821
+ else if (os === "win32") caps.screenshot = {
822
+ available: true,
823
+ tool: "PowerShell System.Drawing"
824
+ };
825
+ else caps.screenshot = {
826
+ available: false,
827
+ tool: "unsupported platform"
828
+ };
829
+ if (os === "darwin") if (commandExists("rec")) caps.audioRecord = {
830
+ available: true,
831
+ tool: "sox rec"
832
+ };
833
+ else if (commandExists("ffmpeg")) caps.audioRecord = {
834
+ available: true,
835
+ tool: "ffmpeg"
836
+ };
837
+ else caps.audioRecord = {
838
+ available: false,
839
+ tool: "none (install sox or ffmpeg)"
840
+ };
841
+ else if (os === "linux") if (commandExists("arecord")) caps.audioRecord = {
842
+ available: true,
843
+ tool: "arecord"
844
+ };
845
+ else if (commandExists("ffmpeg")) caps.audioRecord = {
846
+ available: true,
847
+ tool: "ffmpeg"
848
+ };
849
+ else caps.audioRecord = {
850
+ available: false,
851
+ tool: "none (install alsa-utils or ffmpeg)"
852
+ };
853
+ else if (os === "win32") if (commandExists("ffmpeg")) caps.audioRecord = {
854
+ available: true,
855
+ tool: "ffmpeg"
856
+ };
857
+ else caps.audioRecord = {
858
+ available: false,
859
+ tool: "none (install ffmpeg)"
860
+ };
861
+ else caps.audioRecord = {
862
+ available: false,
863
+ tool: "unsupported"
864
+ };
865
+ if (os === "darwin") caps.audioPlay = {
866
+ available: true,
867
+ tool: "afplay (built-in)"
868
+ };
869
+ else if (os === "linux") if (commandExists("aplay")) caps.audioPlay = {
870
+ available: true,
871
+ tool: "aplay"
872
+ };
873
+ else if (commandExists("paplay")) caps.audioPlay = {
874
+ available: true,
875
+ tool: "paplay"
876
+ };
877
+ else if (commandExists("ffplay")) caps.audioPlay = {
878
+ available: true,
879
+ tool: "ffplay"
880
+ };
881
+ else caps.audioPlay = {
882
+ available: false,
883
+ tool: "none"
884
+ };
885
+ else if (os === "win32") caps.audioPlay = {
886
+ available: true,
887
+ tool: "PowerShell SoundPlayer"
888
+ };
889
+ else caps.audioPlay = {
890
+ available: false,
891
+ tool: "unsupported"
892
+ };
893
+ if (os === "darwin") if (commandExists("cliclick")) caps.computerUse = {
894
+ available: true,
895
+ tool: "cliclick"
896
+ };
897
+ else caps.computerUse = {
898
+ available: true,
899
+ tool: "AppleScript (limited)"
900
+ };
901
+ else if (os === "linux") if (commandExists("xdotool")) caps.computerUse = {
902
+ available: true,
903
+ tool: "xdotool"
904
+ };
905
+ else caps.computerUse = {
906
+ available: false,
907
+ tool: "none (install xdotool)"
908
+ };
909
+ else if (os === "win32") caps.computerUse = {
910
+ available: true,
911
+ tool: "PowerShell SendKeys"
912
+ };
913
+ else caps.computerUse = {
914
+ available: false,
915
+ tool: "unsupported"
916
+ };
917
+ if (os === "darwin") caps.windowList = {
918
+ available: true,
919
+ tool: "AppleScript"
920
+ };
921
+ else if (os === "linux") if (commandExists("wmctrl")) caps.windowList = {
922
+ available: true,
923
+ tool: "wmctrl"
924
+ };
925
+ else if (commandExists("xdotool")) caps.windowList = {
926
+ available: true,
927
+ tool: "xdotool"
928
+ };
929
+ else caps.windowList = {
930
+ available: false,
931
+ tool: "none (install wmctrl or xdotool)"
932
+ };
933
+ else if (os === "win32") caps.windowList = {
934
+ available: true,
935
+ tool: "PowerShell Get-Process"
936
+ };
937
+ else caps.windowList = {
938
+ available: false,
939
+ tool: "unsupported"
940
+ };
941
+ caps.browser = {
942
+ available: true,
943
+ tool: "CDP via sandbox browser container"
944
+ };
945
+ caps.shell = {
946
+ available: true,
947
+ tool: "docker exec"
948
+ };
949
+ return caps;
950
+ }
951
+ function getPlatformInfo() {
952
+ const os = platform();
953
+ let dockerInstalled = false;
954
+ let dockerRunning = false;
955
+ let appleContainerAvailable = false;
956
+ try {
957
+ execSync(`${os === "win32" ? "where" : "which"} docker`, {
958
+ stdio: "ignore",
959
+ timeout: 3e3
960
+ });
961
+ dockerInstalled = true;
962
+ } catch {}
963
+ if (dockerInstalled) try {
964
+ execSync("docker info", {
965
+ stdio: "ignore",
966
+ timeout: 5e3
967
+ });
968
+ dockerRunning = true;
969
+ } catch {}
970
+ if (os === "darwin") try {
971
+ execSync("which container", {
972
+ stdio: "ignore",
973
+ timeout: 3e3
974
+ });
975
+ appleContainerAvailable = true;
976
+ } catch {}
977
+ return {
978
+ platform: os,
979
+ arch: __require("node:os").arch(),
980
+ dockerInstalled,
981
+ dockerRunning,
982
+ dockerAvailable: dockerRunning,
983
+ appleContainerAvailable,
984
+ wsl2: os === "win32" ? isWsl2Available() : false,
985
+ recommended: os === "darwin" && appleContainerAvailable ? "apple-container" : "docker"
986
+ };
987
+ }
988
+ function isWsl2Available() {
989
+ try {
990
+ execSync("wsl --status", {
991
+ stdio: "ignore",
992
+ timeout: 5e3
993
+ });
994
+ return true;
995
+ } catch {
996
+ return false;
997
+ }
998
+ }
999
+ function attemptDockerStart() {
1000
+ const os = platform();
1001
+ try {
1002
+ if (os === "darwin") {
1003
+ execSync("open -a \"Docker\"", {
1004
+ timeout: 5e3,
1005
+ stdio: "ignore"
1006
+ });
1007
+ return {
1008
+ success: true,
1009
+ message: "Docker Desktop is starting on macOS. Give it a moment~",
1010
+ waitMs: 15e3
1011
+ };
1012
+ }
1013
+ if (os === "win32") {
1014
+ const paths = ["\"C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe\"", "\"C:\\Program Files (x86)\\Docker\\Docker\\Docker Desktop.exe\""];
1015
+ let started = false;
1016
+ for (const p of paths) try {
1017
+ execSync(`start "" ${p}`, {
1018
+ timeout: 5e3,
1019
+ stdio: "ignore",
1020
+ shell: "cmd.exe"
1021
+ });
1022
+ started = true;
1023
+ break;
1024
+ } catch {}
1025
+ if (!started) execSync("start \"\" \"Docker Desktop\"", {
1026
+ timeout: 5e3,
1027
+ stdio: "ignore",
1028
+ shell: "cmd.exe"
1029
+ });
1030
+ return {
1031
+ success: true,
1032
+ message: "Docker Desktop is starting on Windows. This may take 30 seconds~",
1033
+ waitMs: 3e4
1034
+ };
1035
+ }
1036
+ if (os === "linux") {
1037
+ try {
1038
+ execSync("sudo systemctl start docker", {
1039
+ timeout: 1e4,
1040
+ stdio: "ignore"
1041
+ });
1042
+ return {
1043
+ success: true,
1044
+ message: "Docker daemon started via systemctl",
1045
+ waitMs: 5e3
1046
+ };
1047
+ } catch {}
1048
+ try {
1049
+ execSync("sudo service docker start", {
1050
+ timeout: 1e4,
1051
+ stdio: "ignore"
1052
+ });
1053
+ return {
1054
+ success: true,
1055
+ message: "Docker daemon started via service",
1056
+ waitMs: 5e3
1057
+ };
1058
+ } catch {}
1059
+ return {
1060
+ success: false,
1061
+ message: "Could not auto-start Docker on Linux. Run: sudo systemctl start docker",
1062
+ waitMs: 0
1063
+ };
1064
+ }
1065
+ return {
1066
+ success: false,
1067
+ message: `Auto-start not supported on ${os}`,
1068
+ waitMs: 0
1069
+ };
1070
+ } catch (err) {
1071
+ return {
1072
+ success: false,
1073
+ message: `Failed: ${err instanceof Error ? err.message : String(err)}`,
1074
+ waitMs: 0
1075
+ };
1076
+ }
1077
+ }
1078
+ function commandExists(cmd) {
1079
+ try {
1080
+ execSync(`${platform() === "win32" ? "where" : "which"} ${cmd}`, {
1081
+ stdio: "ignore",
1082
+ timeout: 3e3
1083
+ });
1084
+ return true;
1085
+ } catch {
1086
+ return false;
1087
+ }
1088
+ }
1089
+ function sendJson(res, status, data) {
1090
+ sendJson$1(res, data, status);
1091
+ }
1092
+ function readBody(req) {
1093
+ return readRequestBody(req, {
1094
+ maxBytes: 10 * 1024 * 1024,
1095
+ returnNullOnTooLarge: true,
1096
+ returnNullOnError: true,
1097
+ destroyOnTooLarge: true
1098
+ });
1099
+ }
1100
+ function readJsonBody(req, res) {
1101
+ return readJsonBody$1(req, res, {
1102
+ maxBytes: 10 * 1024 * 1024,
1103
+ requireObject: false,
1104
+ readErrorStatus: 400,
1105
+ parseErrorStatus: 400,
1106
+ readErrorMessage: "Missing request body",
1107
+ parseErrorMessage: "Invalid JSON in request body"
1108
+ });
1109
+ }
1110
+
1111
+ //#endregion
1112
+ export { handleSandboxRoute };