@minionry/minion 0.7.36

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 (476) hide show
  1. package/LICENSE +14 -0
  2. package/PRIVACY.md +357 -0
  3. package/README.md +57 -0
  4. package/THIRD_PARTY_NOTICES.md +245 -0
  5. package/bin/branding.js +4 -0
  6. package/bin/commands/browser-install.js +483 -0
  7. package/bin/commands/create-sandbox.js +542 -0
  8. package/bin/commands/fingerprint.js +101 -0
  9. package/bin/commands/login.js +400 -0
  10. package/bin/commands/logout.js +93 -0
  11. package/bin/commands/minions.js +211 -0
  12. package/bin/commands/sandboxes.js +309 -0
  13. package/bin/commands/status.js +298 -0
  14. package/bin/commands/whoami.js +153 -0
  15. package/bin/launch-env.js +35 -0
  16. package/bin/machine-identity.js +221 -0
  17. package/bin/machine-layout.js +200 -0
  18. package/bin/minion.js +839 -0
  19. package/bin/platform-endpoints.js +8 -0
  20. package/bin/postinstall.js +52 -0
  21. package/bin/pty-doctor.js +326 -0
  22. package/bin/pty-locator.js +110 -0
  23. package/bin/pty-provision.js +186 -0
  24. package/bin/sandbox/Dockerfile +78 -0
  25. package/bin/sandbox/entrypoint.sh +14 -0
  26. package/bin/sandbox/install-repo.sh +53 -0
  27. package/bin/sandbox/seccomp-userns.json +883 -0
  28. package/bin/sandbox-config.js +306 -0
  29. package/dist/bin/machine-identity.js +221 -0
  30. package/dist/bin/machine-layout.js +200 -0
  31. package/dist/electron-host/agent-lease.js +141 -0
  32. package/dist/electron-host/dom-driver.js +642 -0
  33. package/dist/electron-host/ipc-protocol.js +89 -0
  34. package/dist/electron-host/js-driver.js +216 -0
  35. package/dist/electron-host/view-manager.js +254 -0
  36. package/dist/server/cli/engine-resume.js +49 -0
  37. package/dist/server/cli/eta-estimator.js +278 -0
  38. package/dist/server/cli/headless/background-activity.js +109 -0
  39. package/dist/server/cli/headless/claude-invoker-process.js +614 -0
  40. package/dist/server/cli/headless/claude-invoker-stall.js +150 -0
  41. package/dist/server/cli/headless/claude-invoker-stream.js +555 -0
  42. package/dist/server/cli/headless/claude-invoker-tools.js +148 -0
  43. package/dist/server/cli/headless/claude-invoker.js +151 -0
  44. package/dist/server/cli/headless/engine-availability.js +92 -0
  45. package/dist/server/cli/headless/engine-notice.js +185 -0
  46. package/dist/server/cli/headless/haiku-assessments.js +293 -0
  47. package/dist/server/cli/headless/headless-logger.js +75 -0
  48. package/dist/server/cli/headless/index.js +9 -0
  49. package/dist/server/cli/headless/mcp-config.js +314 -0
  50. package/dist/server/cli/headless/native-timeout-detector.js +72 -0
  51. package/dist/server/cli/headless/output-utils.js +71 -0
  52. package/dist/server/cli/headless/prompt-utils.js +82 -0
  53. package/dist/server/cli/headless/resilient-runner.js +326 -0
  54. package/dist/server/cli/headless/retry-strategies.js +292 -0
  55. package/dist/server/cli/headless/runner.js +245 -0
  56. package/dist/server/cli/headless/stall-assessor.js +295 -0
  57. package/dist/server/cli/headless/tool-watchdog.js +447 -0
  58. package/dist/server/cli/headless/types.js +18 -0
  59. package/dist/server/cli/history-portability.js +85 -0
  60. package/dist/server/cli/improvisation-attachments.js +99 -0
  61. package/dist/server/cli/improvisation-history-store.js +121 -0
  62. package/dist/server/cli/improvisation-movements.js +137 -0
  63. package/dist/server/cli/improvisation-output-queue.js +34 -0
  64. package/dist/server/cli/improvisation-retry.js +8 -0
  65. package/dist/server/cli/improvisation-session-manager.js +1016 -0
  66. package/dist/server/cli/improvisation-types.js +31 -0
  67. package/dist/server/cli/plan-lifecycle.js +72 -0
  68. package/dist/server/cli/prompt-builders.js +371 -0
  69. package/dist/server/cli/retry/retry-best-result.js +28 -0
  70. package/dist/server/cli/retry/retry-context-loss.js +75 -0
  71. package/dist/server/cli/retry/retry-recovery-strategies.js +213 -0
  72. package/dist/server/cli/retry/retry-resume-strategy.js +20 -0
  73. package/dist/server/cli/retry/retry-runner-factory.js +92 -0
  74. package/dist/server/cli/retry/retry-tool-results.js +22 -0
  75. package/dist/server/cli/retry/retry-types.js +3 -0
  76. package/dist/server/engines/EngineEvent.js +57 -0
  77. package/dist/server/engines/claude/ClaudeCodeEngine.js +316 -0
  78. package/dist/server/engines/claude/claude-command.js +32 -0
  79. package/dist/server/engines/codex/CodexEngine.js +469 -0
  80. package/dist/server/engines/codex/codex-auth.js +203 -0
  81. package/dist/server/engines/codex/codex-invoker.js +205 -0
  82. package/dist/server/engines/codex/codex-provision.js +215 -0
  83. package/dist/server/engines/credentials/credential-probe.js +88 -0
  84. package/dist/server/engines/credentials/model-profiles.js +249 -0
  85. package/dist/server/engines/credentials/provider-credentials.js +146 -0
  86. package/dist/server/engines/engine-event-stream.js +52 -0
  87. package/dist/server/engines/execution/engine-session.js +380 -0
  88. package/dist/server/engines/factory.js +42 -0
  89. package/dist/server/engines/hermes/HermesEngine.js +628 -0
  90. package/dist/server/engines/hermes/hermes-auth.js +159 -0
  91. package/dist/server/engines/hermes/hermes-gateway-home.js +271 -0
  92. package/dist/server/engines/hermes/hermes-invoker.js +350 -0
  93. package/dist/server/engines/hermes/hermes-provision.js +318 -0
  94. package/dist/server/engines/read-only.js +20 -0
  95. package/dist/server/engines/types.js +27 -0
  96. package/dist/server/handlers/company-execution-registry.js +67 -0
  97. package/dist/server/handlers/company-handlers.js +514 -0
  98. package/dist/server/handlers/company-triggers.js +189 -0
  99. package/dist/server/handlers/company-types.js +3 -0
  100. package/dist/server/index.js +244 -0
  101. package/dist/server/mcp/app-tools-bridge.js +110 -0
  102. package/dist/server/mcp/app-tools-mcp-server.js +99 -0
  103. package/dist/server/mcp/bouncer-integration.js +744 -0
  104. package/dist/server/mcp/browser-mcp-server.js +915 -0
  105. package/dist/server/mcp/browser-redaction.js +103 -0
  106. package/dist/server/mcp/classifier/BouncerClassifier.js +3 -0
  107. package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +212 -0
  108. package/dist/server/mcp/classifier/factory.js +124 -0
  109. package/dist/server/mcp/classifier/health.js +54 -0
  110. package/dist/server/mcp/classifier/telemetry.js +0 -0
  111. package/dist/server/mcp/company-patterns.js +104 -0
  112. package/dist/server/mcp/department-profiles.js +133 -0
  113. package/dist/server/mcp/quality-bridge.js +153 -0
  114. package/dist/server/mcp/quality-mcp-server.js +105 -0
  115. package/dist/server/mcp/security-analysis.js +384 -0
  116. package/dist/server/mcp/security-audit.js +152 -0
  117. package/dist/server/mcp/security-patterns.js +314 -0
  118. package/dist/server/mcp/server.js +194 -0
  119. package/dist/server/mcp/test-utils/agent-simulator.js +301 -0
  120. package/dist/server/mcp/test-utils/e2e-agent.js +103 -0
  121. package/dist/server/mcp/test-utils/e2e-capture.js +92 -0
  122. package/dist/server/mcp/test-utils/e2e-client.js +116 -0
  123. package/dist/server/mcp/test-utils/e2e-fixtures.js +87 -0
  124. package/dist/server/mcp/test-utils/e2e-gating.js +8 -0
  125. package/dist/server/mcp/test-utils/e2e-harness.js +8 -0
  126. package/dist/server/mcp/test-utils/e2e-scenarios.js +105 -0
  127. package/dist/server/routes/files.js +18 -0
  128. package/dist/server/routes/improvise.js +64 -0
  129. package/dist/server/routes/index.js +7 -0
  130. package/dist/server/routes/instances.js +41 -0
  131. package/dist/server/routes/internal.js +900 -0
  132. package/dist/server/routes/notifications.js +110 -0
  133. package/dist/server/server-setup.js +282 -0
  134. package/dist/server/services/agent-push.js +148 -0
  135. package/dist/server/services/analytics.js +197 -0
  136. package/dist/server/services/apps/build.js +249 -0
  137. package/dist/server/services/apps/greenlight.js +199 -0
  138. package/dist/server/services/apps/install.js +101 -0
  139. package/dist/server/services/apps/layout.js +35 -0
  140. package/dist/server/services/apps/serve.js +665 -0
  141. package/dist/server/services/apps/space-apps.js +248 -0
  142. package/dist/server/services/auth.js +48 -0
  143. package/dist/server/services/browser/chrome-sandbox.js +245 -0
  144. package/dist/server/services/browser/chromium-launch-spec.js +51 -0
  145. package/dist/server/services/browser/claude-reasoner.js +669 -0
  146. package/dist/server/services/browser/debug-log.js +3 -0
  147. package/dist/server/services/browser/driver-state.js +546 -0
  148. package/dist/server/services/browser/electron-provision.js +256 -0
  149. package/dist/server/services/browser/host-internal.js +245 -0
  150. package/dist/server/services/browser/host-mock.js +334 -0
  151. package/dist/server/services/browser/host-types.js +3 -0
  152. package/dist/server/services/browser/host.js +1067 -0
  153. package/dist/server/services/browser/index.js +4 -0
  154. package/dist/server/services/browser/ipc-client.js +354 -0
  155. package/dist/server/services/browser/pipewire-sink.js +199 -0
  156. package/dist/server/services/browser/preflight.js +590 -0
  157. package/dist/server/services/browser/reasoning-agent.js +659 -0
  158. package/dist/server/services/browser/sandbox-policy.js +169 -0
  159. package/dist/server/services/browser/stderr-noise-filter.js +26 -0
  160. package/dist/server/services/browser/telemetry.js +107 -0
  161. package/dist/server/services/browser/xvfb-supervisor.js +139 -0
  162. package/dist/server/services/browser-frame-policy.js +56 -0
  163. package/dist/server/services/client-id.js +32 -0
  164. package/dist/server/services/collab/registry.js +408 -0
  165. package/dist/server/services/collab/session.js +489 -0
  166. package/dist/server/services/collab/text-diff.js +28 -0
  167. package/dist/server/services/collab/types.js +19 -0
  168. package/dist/server/services/e2ee-group-key.js +197 -0
  169. package/dist/server/services/e2ee-identity.js +203 -0
  170. package/dist/server/services/file-explorer-ops.js +328 -0
  171. package/dist/server/services/file-transfer-zip.js +188 -0
  172. package/dist/server/services/files.js +111 -0
  173. package/dist/server/services/git/branch-ops.js +62 -0
  174. package/dist/server/services/git/command.js +68 -0
  175. package/dist/server/services/git/commit-message.js +74 -0
  176. package/dist/server/services/git/diff-ops.js +56 -0
  177. package/dist/server/services/git/haiku.js +76 -0
  178. package/dist/server/services/git/head-watcher.js +226 -0
  179. package/dist/server/services/git/index.js +17 -0
  180. package/dist/server/services/git/log-ops.js +98 -0
  181. package/dist/server/services/git/merge-ops.js +314 -0
  182. package/dist/server/services/git/parsers.js +401 -0
  183. package/dist/server/services/git/remote-ops.js +192 -0
  184. package/dist/server/services/git/status-ops.js +105 -0
  185. package/dist/server/services/git/suggestion-ops.js +80 -0
  186. package/dist/server/services/git/tag-ops.js +36 -0
  187. package/dist/server/services/git/types.js +4 -0
  188. package/dist/server/services/git/worktree-ops.js +114 -0
  189. package/dist/server/services/instances.js +337 -0
  190. package/dist/server/services/model-catalog.js +247 -0
  191. package/dist/server/services/observability/posthog.js +153 -0
  192. package/dist/server/services/pathUtils.js +114 -0
  193. package/dist/server/services/plan/agent-loader.js +97 -0
  194. package/dist/server/services/plan/agent-manager.js +363 -0
  195. package/dist/server/services/plan/agent-resolver.js +74 -0
  196. package/dist/server/services/plan/agents/assess-stall.md +33 -0
  197. package/dist/server/services/plan/agents/browser-planner.md +53 -0
  198. package/dist/server/services/plan/agents/browser-reasoner.md +114 -0
  199. package/dist/server/services/plan/agents/check-injection.md +95 -0
  200. package/dist/server/services/plan/agents/classify-error.md +30 -0
  201. package/dist/server/services/plan/agents/code-review.md +142 -0
  202. package/dist/server/services/plan/agents/commit-message.md +28 -0
  203. package/dist/server/services/plan/agents/detect-context-loss.md +30 -0
  204. package/dist/server/services/plan/agents/execute-issue.md +58 -0
  205. package/dist/server/services/plan/agents/fix-quality.md +25 -0
  206. package/dist/server/services/plan/agents/plan-coordinator.md +72 -0
  207. package/dist/server/services/plan/agents/pr-description.md +29 -0
  208. package/dist/server/services/plan/agents/retry-task.md +27 -0
  209. package/dist/server/services/plan/agents/review-code.md +38 -0
  210. package/dist/server/services/plan/agents/review-quality.md +45 -0
  211. package/dist/server/services/plan/agents/triage-findings.md +78 -0
  212. package/dist/server/services/plan/agents/verify-review.md +57 -0
  213. package/dist/server/services/plan/approval-evaluator.js +143 -0
  214. package/dist/server/services/plan/board-archive.js +102 -0
  215. package/dist/server/services/plan/board-config.js +108 -0
  216. package/dist/server/services/plan/board-export.js +214 -0
  217. package/dist/server/services/plan/composer-prompt.js +115 -0
  218. package/dist/server/services/plan/composer.js +664 -0
  219. package/dist/server/services/plan/config-installer.js +103 -0
  220. package/dist/server/services/plan/dependency-resolver.js +137 -0
  221. package/dist/server/services/plan/executor.js +696 -0
  222. package/dist/server/services/plan/front-matter.js +43 -0
  223. package/dist/server/services/plan/issue-classification.js +10 -0
  224. package/dist/server/services/plan/issue-effort.js +182 -0
  225. package/dist/server/services/plan/issue-loader.js +35 -0
  226. package/dist/server/services/plan/issue-prompt-builder.js +152 -0
  227. package/dist/server/services/plan/issue-retry.js +42 -0
  228. package/dist/server/services/plan/issue-writer.js +102 -0
  229. package/dist/server/services/plan/metrics-collector.js +272 -0
  230. package/dist/server/services/plan/output-manager.js +77 -0
  231. package/dist/server/services/plan/parser-core.js +435 -0
  232. package/dist/server/services/plan/parser-migration.js +108 -0
  233. package/dist/server/services/plan/parser.js +258 -0
  234. package/dist/server/services/plan/plan-runner-factory.js +68 -0
  235. package/dist/server/services/plan/pm-timeline.js +320 -0
  236. package/dist/server/services/plan/progress-log.js +101 -0
  237. package/dist/server/services/plan/prompt-builder.js +142 -0
  238. package/dist/server/services/plan/quality-delta.js +274 -0
  239. package/dist/server/services/plan/readiness-planner.js +34 -0
  240. package/dist/server/services/plan/review-gate.js +409 -0
  241. package/dist/server/services/plan/review-report.js +134 -0
  242. package/dist/server/services/plan/review-verdict-stream.js +161 -0
  243. package/dist/server/services/plan/state-reconciler.js +238 -0
  244. package/dist/server/services/plan/template-diff.js +175 -0
  245. package/dist/server/services/plan/template-instantiator.js +516 -0
  246. package/dist/server/services/plan/template-manifest.js +56 -0
  247. package/dist/server/services/plan/template-types.js +3 -0
  248. package/dist/server/services/plan/trigger-evaluator.js +211 -0
  249. package/dist/server/services/plan/types.js +3 -0
  250. package/dist/server/services/plan/watcher.js +73 -0
  251. package/dist/server/services/platform-credentials.js +56 -0
  252. package/dist/server/services/platform-e2ee.js +421 -0
  253. package/dist/server/services/platform-envelope.js +102 -0
  254. package/dist/server/services/platform-heartbeat.js +88 -0
  255. package/dist/server/services/platform-reconnect.js +60 -0
  256. package/dist/server/services/platform-token-lifecycle.js +95 -0
  257. package/dist/server/services/platform-url.js +17 -0
  258. package/dist/server/services/platform.js +656 -0
  259. package/dist/server/services/runtime-info.js +9 -0
  260. package/dist/server/services/schedule/agent-schedule-store.js +232 -0
  261. package/dist/server/services/schedule/agent-scheduler.js +323 -0
  262. package/dist/server/services/schedule/daily-timing.js +105 -0
  263. package/dist/server/services/schedule/quality-schedule-launch.js +11 -0
  264. package/dist/server/services/schedule/quality-schedule-store.js +211 -0
  265. package/dist/server/services/schedule/quality-scheduler.js +215 -0
  266. package/dist/server/services/schedule/run-bootstrap.js +329 -0
  267. package/dist/server/services/schedule/schedule-store.js +238 -0
  268. package/dist/server/services/schedule/schedule-wire.js +77 -0
  269. package/dist/server/services/schedule/scheduler.js +310 -0
  270. package/dist/server/services/schedule/template-source.js +97 -0
  271. package/dist/server/services/sdk/agent-sessions.js +264 -0
  272. package/dist/server/services/sdk/agents.js +557 -0
  273. package/dist/server/services/sdk/app-endpoints.js +48 -0
  274. package/dist/server/services/sdk/app-tools.js +303 -0
  275. package/dist/server/services/sdk/audit.js +48 -0
  276. package/dist/server/services/sdk/browser-transport.js +188 -0
  277. package/dist/server/services/sdk/browser.js +605 -0
  278. package/dist/server/services/sdk/composer.js +129 -0
  279. package/dist/server/services/sdk/connect.js +273 -0
  280. package/dist/server/services/sdk/consent-cooldown.js +31 -0
  281. package/dist/server/services/sdk/endpoint-url.js +141 -0
  282. package/dist/server/services/sdk/endpoints.js +314 -0
  283. package/dist/server/services/sdk/file-change-notify.js +36 -0
  284. package/dist/server/services/sdk/files-app.js +163 -0
  285. package/dist/server/services/sdk/files-search.js +131 -0
  286. package/dist/server/services/sdk/files-transfer.js +279 -0
  287. package/dist/server/services/sdk/files-workspace.js +233 -0
  288. package/dist/server/services/sdk/git.js +520 -0
  289. package/dist/server/services/sdk/grant-store.js +318 -0
  290. package/dist/server/services/sdk/host-ops.js +84 -0
  291. package/dist/server/services/sdk/inference-claude.js +214 -0
  292. package/dist/server/services/sdk/inference.js +247 -0
  293. package/dist/server/services/sdk/live-registry.js +42 -0
  294. package/dist/server/services/sdk/local-app-bindings.js +71 -0
  295. package/dist/server/services/sdk/pipeline.js +190 -0
  296. package/dist/server/services/sdk/pm-execute.js +228 -0
  297. package/dist/server/services/sdk/pm-planning-tap.js +29 -0
  298. package/dist/server/services/sdk/pm-schedule.js +165 -0
  299. package/dist/server/services/sdk/pm.js +634 -0
  300. package/dist/server/services/sdk/quality-change-notify.js +20 -0
  301. package/dist/server/services/sdk/quality-schedule.js +165 -0
  302. package/dist/server/services/sdk/quality.js +602 -0
  303. package/dist/server/services/sdk/rate-limits.js +198 -0
  304. package/dist/server/services/sdk/registry.js +597 -0
  305. package/dist/server/services/sdk/revoke.js +86 -0
  306. package/dist/server/services/sdk/run-profile.js +41 -0
  307. package/dist/server/services/sdk/sandbox.js +89 -0
  308. package/dist/server/services/sdk/storage.js +110 -0
  309. package/dist/server/services/sdk/streams.js +147 -0
  310. package/dist/server/services/sdk/terminal.js +201 -0
  311. package/dist/server/services/sdk/test-utils/workshop-browser-app.js +466 -0
  312. package/dist/server/services/sentry.js +99 -0
  313. package/dist/server/services/settings.js +374 -0
  314. package/dist/server/services/snippets/expander.js +87 -0
  315. package/dist/server/services/snippets/index.js +6 -0
  316. package/dist/server/services/snippets/loader.js +119 -0
  317. package/dist/server/services/snippets/parser.js +52 -0
  318. package/dist/server/services/snippets/types.js +3 -0
  319. package/dist/server/services/snippets/watcher.js +60 -0
  320. package/dist/server/services/space-layout.js +80 -0
  321. package/dist/server/services/system-prompts.js +219 -0
  322. package/dist/server/services/terminal/pty-manager.js +206 -0
  323. package/dist/server/services/terminal/pty-utils.js +150 -0
  324. package/dist/server/services/timeline/assembler.js +890 -0
  325. package/dist/server/services/timeline/contract.js +196 -0
  326. package/dist/server/services/timeline/index.js +4 -0
  327. package/dist/server/services/websocket/agent-schedule-handlers.js +217 -0
  328. package/dist/server/services/websocket/apps-handlers.js +399 -0
  329. package/dist/server/services/websocket/ask-user-question-bridge.js +112 -0
  330. package/dist/server/services/websocket/autocomplete-index.js +415 -0
  331. package/dist/server/services/websocket/autocomplete-match.js +243 -0
  332. package/dist/server/services/websocket/autocomplete.js +407 -0
  333. package/dist/server/services/websocket/browser-agent-runtime.js +208 -0
  334. package/dist/server/services/websocket/browser-device-profile.js +46 -0
  335. package/dist/server/services/websocket/browser-handlers.js +377 -0
  336. package/dist/server/services/websocket/browser-host-broadcast.js +179 -0
  337. package/dist/server/services/websocket/browser-ice-config.js +121 -0
  338. package/dist/server/services/websocket/browser-payloads.js +222 -0
  339. package/dist/server/services/websocket/browser-viewer-lifecycle.js +84 -0
  340. package/dist/server/services/websocket/browser-viewer-registry.js +118 -0
  341. package/dist/server/services/websocket/collab-broadcast.js +11 -0
  342. package/dist/server/services/websocket/collab-handlers.js +116 -0
  343. package/dist/server/services/websocket/commit-suggestion-service.js +132 -0
  344. package/dist/server/services/websocket/debug-log.js +38 -0
  345. package/dist/server/services/websocket/dispatch-util.js +10 -0
  346. package/dist/server/services/websocket/engine-auth-handlers.js +323 -0
  347. package/dist/server/services/websocket/engine-credential-watcher.js +83 -0
  348. package/dist/server/services/websocket/engine-profile-handlers.js +92 -0
  349. package/dist/server/services/websocket/file-definition-handlers.js +180 -0
  350. package/dist/server/services/websocket/file-download-handler.js +291 -0
  351. package/dist/server/services/websocket/file-explorer-handlers.js +291 -0
  352. package/dist/server/services/websocket/file-search-handlers.js +267 -0
  353. package/dist/server/services/websocket/file-share-handlers.js +328 -0
  354. package/dist/server/services/websocket/file-transfer-http.js +361 -0
  355. package/dist/server/services/websocket/file-upload-handler.js +267 -0
  356. package/dist/server/services/websocket/file-utils.js +346 -0
  357. package/dist/server/services/websocket/git-branch-handlers.js +90 -0
  358. package/dist/server/services/websocket/git-diff-handlers.js +59 -0
  359. package/dist/server/services/websocket/git-handlers.js +212 -0
  360. package/dist/server/services/websocket/git-head-watcher.js +85 -0
  361. package/dist/server/services/websocket/git-log-handlers.js +104 -0
  362. package/dist/server/services/websocket/git-pr-handlers.js +56 -0
  363. package/dist/server/services/websocket/git-tag-handlers.js +53 -0
  364. package/dist/server/services/websocket/git-utils.js +11 -0
  365. package/dist/server/services/websocket/git-worktree-handlers.js +521 -0
  366. package/dist/server/services/websocket/handler-context.js +113 -0
  367. package/dist/server/services/websocket/handler.js +1197 -0
  368. package/dist/server/services/websocket/index.js +5 -0
  369. package/dist/server/services/websocket/movement-written-files.js +26 -0
  370. package/dist/server/services/websocket/msg-id-tracker.js +48 -0
  371. package/dist/server/services/websocket/plan-board-handlers.js +410 -0
  372. package/dist/server/services/websocket/plan-execution-handlers.js +244 -0
  373. package/dist/server/services/websocket/plan-handlers.js +67 -0
  374. package/dist/server/services/websocket/plan-helpers.js +161 -0
  375. package/dist/server/services/websocket/plan-issue-handlers.js +168 -0
  376. package/dist/server/services/websocket/plan-sprint-handlers.js +190 -0
  377. package/dist/server/services/websocket/plan-transfer-handlers.js +310 -0
  378. package/dist/server/services/websocket/pm-timeline-broadcast.js +28 -0
  379. package/dist/server/services/websocket/post-session-scan.js +132 -0
  380. package/dist/server/services/websocket/prompt-queue-handlers.js +96 -0
  381. package/dist/server/services/websocket/quality-agent-cost.js +229 -0
  382. package/dist/server/services/websocket/quality-agent-notes.js +113 -0
  383. package/dist/server/services/websocket/quality-change-activity.js +187 -0
  384. package/dist/server/services/websocket/quality-complexity.js +345 -0
  385. package/dist/server/services/websocket/quality-dependencies.js +445 -0
  386. package/dist/server/services/websocket/quality-duplication.js +162 -0
  387. package/dist/server/services/websocket/quality-eta.js +65 -0
  388. package/dist/server/services/websocket/quality-fingerprint.js +104 -0
  389. package/dist/server/services/websocket/quality-grading.js +575 -0
  390. package/dist/server/services/websocket/quality-handlers.js +48 -0
  391. package/dist/server/services/websocket/quality-linting.js +270 -0
  392. package/dist/server/services/websocket/quality-operations.js +54 -0
  393. package/dist/server/services/websocket/quality-persistence.js +410 -0
  394. package/dist/server/services/websocket/quality-review-agent.js +526 -0
  395. package/dist/server/services/websocket/quality-run.js +220 -0
  396. package/dist/server/services/websocket/quality-secrets.js +0 -0
  397. package/dist/server/services/websocket/quality-service.js +775 -0
  398. package/dist/server/services/websocket/quality-tools.js +383 -0
  399. package/dist/server/services/websocket/quality-triage-agent.js +101 -0
  400. package/dist/server/services/websocket/quality-types.js +149 -0
  401. package/dist/server/services/websocket/recommended-answers.js +28 -0
  402. package/dist/server/services/websocket/schedule-handlers.js +197 -0
  403. package/dist/server/services/websocket/sdk-agent-host.js +569 -0
  404. package/dist/server/services/websocket/sdk-browser-host.js +459 -0
  405. package/dist/server/services/websocket/sdk-composer-host.js +10 -0
  406. package/dist/server/services/websocket/sdk-git-host.js +115 -0
  407. package/dist/server/services/websocket/sdk-handlers.js +242 -0
  408. package/dist/server/services/websocket/sdk-pm-execution-host.js +63 -0
  409. package/dist/server/services/websocket/sdk-pm-schedule-host.js +21 -0
  410. package/dist/server/services/websocket/sdk-quality-schedule-host.js +34 -0
  411. package/dist/server/services/websocket/sdk-terminal-host.js +140 -0
  412. package/dist/server/services/websocket/session-handlers.js +565 -0
  413. package/dist/server/services/websocket/session-history.js +335 -0
  414. package/dist/server/services/websocket/session-initialization.js +315 -0
  415. package/dist/server/services/websocket/session-registry.js +340 -0
  416. package/dist/server/services/websocket/session-role-directory.js +39 -0
  417. package/dist/server/services/websocket/settings-handlers.js +276 -0
  418. package/dist/server/services/websocket/skill-handlers.js +324 -0
  419. package/dist/server/services/websocket/skill-watcher.js +83 -0
  420. package/dist/server/services/websocket/snippet-handlers.js +261 -0
  421. package/dist/server/services/websocket/tab-broadcast.js +35 -0
  422. package/dist/server/services/websocket/tab-event-buffer.js +110 -0
  423. package/dist/server/services/websocket/tab-event-replay.js +47 -0
  424. package/dist/server/services/websocket/tab-handlers.js +211 -0
  425. package/dist/server/services/websocket/terminal-handlers.js +268 -0
  426. package/dist/server/services/websocket/types.js +96 -0
  427. package/dist/server/types/company-messages.js +102 -0
  428. package/dist/server/utils/agent-manager.js +202 -0
  429. package/dist/server/utils/console-timestamps.js +24 -0
  430. package/dist/server/utils/file-store.js +112 -0
  431. package/dist/server/utils/paths.js +28 -0
  432. package/dist/server/utils/port-manager.js +43 -0
  433. package/dist/server/utils/port.js +204 -0
  434. package/electron-host/dist/agent-lease.js +141 -0
  435. package/electron-host/dist/capture-recovery.js +77 -0
  436. package/electron-host/dist/codec-policy.js +37 -0
  437. package/electron-host/dist/controller-channel.js +60 -0
  438. package/electron-host/dist/controller-handlers.js +153 -0
  439. package/electron-host/dist/device-emulator.js +106 -0
  440. package/electron-host/dist/dom-driver.js +642 -0
  441. package/electron-host/dist/domain-registry.js +159 -0
  442. package/electron-host/dist/domain-validators.js +308 -0
  443. package/electron-host/dist/editable-focus-reporter.js +143 -0
  444. package/electron-host/dist/fake-peer.test-util.js +105 -0
  445. package/electron-host/dist/host-log.js +31 -0
  446. package/electron-host/dist/host-wire.js +11 -0
  447. package/electron-host/dist/input-router.js +421 -0
  448. package/electron-host/dist/ipc-protocol.js +89 -0
  449. package/electron-host/dist/ipc-server.js +193 -0
  450. package/electron-host/dist/js-driver.js +216 -0
  451. package/electron-host/dist/main-guards.js +70 -0
  452. package/electron-host/dist/main.js +716 -0
  453. package/electron-host/dist/roster-sync.js +87 -0
  454. package/electron-host/dist/rpc-context.js +1 -0
  455. package/electron-host/dist/rpc-tab-methods.js +138 -0
  456. package/electron-host/dist/rpc-webrtc-methods.js +121 -0
  457. package/electron-host/dist/screen-capture-cache.js +147 -0
  458. package/electron-host/dist/sdk-injector.js +166 -0
  459. package/electron-host/dist/sdp.js +102 -0
  460. package/electron-host/dist/session-surface.js +78 -0
  461. package/electron-host/dist/surface-sizing.js +59 -0
  462. package/electron-host/dist/view-manager.js +254 -0
  463. package/electron-host/dist/viewer-registry.js +46 -0
  464. package/electron-host/dist/viewer-session.js +288 -0
  465. package/electron-host/dist/webrtc-channels.js +14 -0
  466. package/electron-host/dist/webrtc-controller.js +57 -0
  467. package/electron-host/dist/webrtc-ice-filter.js +13 -0
  468. package/electron-host/dist/webrtc-peer.js +45 -0
  469. package/electron-host/package.json +12 -0
  470. package/electron-host/tsconfig.json +23 -0
  471. package/electron-host/webrtc-controller-preload.cjs +3 -0
  472. package/electron-host/webrtc-controller.html +14 -0
  473. package/package.json +85 -0
  474. package/vendor/e2ee/LICENSE +14 -0
  475. package/vendor/e2ee/package.json +14 -0
  476. package/vendor/e2ee/src/index.js +310 -0
@@ -0,0 +1,1067 @@
1
+ // Copyright (c) 2025-present Minionry, Inc.
2
+ // SPDX-License-Identifier: LicenseRef-Minionry-Software
3
+ import { spawn as defaultSpawn } from 'node:child_process';
4
+ import { EventEmitter } from 'node:events';
5
+ import { childEnvOverrides, chromiumLaunchFlags, withoutCoreDumps } from './chromium-launch-spec.js';
6
+ import { browserLog } from './debug-log.js';
7
+ import { BOOT_PACKAGE_VERSION, buildChildEnv, checkSpawnPreconditions, classifyCrash, DEFAULT_INITIAL_URL, DEFAULT_VIEWPORT, describeSpawnError, extractCandidate, extractLeaseExpired, LIFECYCLE_EVENTS, MAX_RESTARTS, RECOVER_THROTTLE_MS, RESTART_BACKOFFS_MS, resolveDefaultAppDir, resolveDefaultElectronBinary, resolveSdkStubPath, SIGTERM_GRACE_MS, STARTUP_TIMEOUT_MS, TabInfoCache, } from './host-internal.js';
8
+ import { PipeWireSinkSupervisor } from './pipewire-sink.js';
9
+ import { ChromiumSandboxPolicy } from './sandbox-policy.js';
10
+ import { shouldForwardStderrLine } from './stderr-noise-filter.js';
11
+ import { XvfbSupervisor } from './xvfb-supervisor.js';
12
+ const log = browserLog('host');
13
+ const IPC_DEADLINE_SLACK_MS = 5_000;
14
+ function ipcDeadlineFor(opTimeoutMs) {
15
+ return typeof opTimeoutMs === 'number' && opTimeoutMs > 0
16
+ ? { timeoutMs: opTimeoutMs + IPC_DEADLINE_SLACK_MS }
17
+ : undefined;
18
+ }
19
+ const STDERR_TAIL_LINES = 5;
20
+ function readOutboundSignal(kind, payload) {
21
+ if (!payload || typeof payload !== 'object') {
22
+ log.error('signal.out.malformed', `kind=${kind} payload=${typeof payload}`);
23
+ return null;
24
+ }
25
+ const obj = payload;
26
+ if (typeof obj.sessionId !== 'string' || !obj.sessionId) {
27
+ log.error('signal.out.noSession', `kind=${kind} — dropped`);
28
+ return null;
29
+ }
30
+ return { obj, sessionId: obj.sessionId };
31
+ }
32
+ function readSelectedCandidateType(raw) {
33
+ if (raw === null)
34
+ return null;
35
+ return raw === 'host' || raw === 'srflx' || raw === 'prflx' || raw === 'relay' ? raw : undefined;
36
+ }
37
+ function describeControllerState(obj) {
38
+ const reason = typeof obj.reason === 'string' ? obj.reason : '-';
39
+ const path = typeof obj.selectedCandidateType === 'string' ? obj.selectedCandidateType : '-';
40
+ return ` state=${String(obj.state)} reason=${reason} path=${path}`;
41
+ }
42
+ export class ElectronBrowserHost extends EventEmitter {
43
+ spawnFn;
44
+ now;
45
+ setTimeoutFn;
46
+ clearTimeoutFn;
47
+ ipcClientFactory;
48
+ resolvedAppDir;
49
+ electronBinaryOverride;
50
+ checkPreconditions;
51
+ bootVersion;
52
+ resolvedElectronBinary = null;
53
+ platform;
54
+ env;
55
+ xvfbSupervisor;
56
+ pipewireSinkSupervisor;
57
+ sandboxPolicy;
58
+ sidecarRelease = Promise.resolve();
59
+ lifecycleEpoch = 0;
60
+ state = 'idle';
61
+ url = null;
62
+ viewport = null;
63
+ child = null;
64
+ childPid = null;
65
+ childExited = false;
66
+ helloSeen = false;
67
+ ipc = null;
68
+ childFailure = null;
69
+ crashedTabs = new Map();
70
+ childStderrTail = [];
71
+ stopRequested = false;
72
+ restartAttempts = 0;
73
+ restartTimer = null;
74
+ lastRecoverAt = 0;
75
+ lastCrashReason = null;
76
+ lastStartOptions = null;
77
+ startPromise = null;
78
+ startResolve = null;
79
+ startReject = null;
80
+ startTimer = null;
81
+ tabInfoCache = new TabInfoCache();
82
+ currentTabId = null;
83
+ agentTabAliases = new Map();
84
+ dom;
85
+ js;
86
+ sdk;
87
+ constructor(options = {}) {
88
+ super();
89
+ this.spawnFn = options.spawn ?? defaultSpawn;
90
+ this.now = options.now ?? Date.now;
91
+ this.setTimeoutFn = options.timers?.setTimeout ?? setTimeout;
92
+ this.clearTimeoutFn = options.timers?.clearTimeout ?? clearTimeout;
93
+ this.ipcClientFactory = options.ipcClientFactory ?? null;
94
+ this.resolvedAppDir = options.appDir ?? resolveDefaultAppDir();
95
+ this.electronBinaryOverride = options.electronBinaryPath ?? null;
96
+ this.checkPreconditions = options.checkPreconditions ?? checkSpawnPreconditions;
97
+ this.bootVersion = options.bootVersion === undefined ? BOOT_PACKAGE_VERSION : options.bootVersion;
98
+ this.platform = options.platform ?? process.platform;
99
+ this.env = options.env ?? process.env;
100
+ this.xvfbSupervisor = options.xvfbSupervisor ?? new XvfbSupervisor();
101
+ this.pipewireSinkSupervisor =
102
+ options.pipewireSinkSupervisor ?? new PipeWireSinkSupervisor();
103
+ this.sandboxPolicy =
104
+ options.sandboxPolicy ?? new ChromiumSandboxPolicy({ env: this.env, platform: this.platform });
105
+ if (this.sandboxPolicy.parsed.invalid !== undefined) {
106
+ console.warn(`[ElectronBrowserHost] MINIONRY_CHROMIUM_SANDBOX=${JSON.stringify(this.sandboxPolicy.parsed.invalid)} is not one of auto|on|off — treating it as "on" (sandbox required, no fallback)`);
107
+ }
108
+ this.dom = this.buildDomSurface();
109
+ this.js = this.buildJsSurface();
110
+ this.sdk = this.buildSdkSurface();
111
+ }
112
+ emit(event, ...args) {
113
+ const result = super.emit(event, ...args);
114
+ if (LIFECYCLE_EVENTS.has(event)) {
115
+ super.emit('status', this.getStatus());
116
+ }
117
+ return result;
118
+ }
119
+ async start(opts) {
120
+ log('start.enter', `state=${this.state} orchestra=${opts.orchestraId}`);
121
+ if (this.state === 'ready' || this.state === 'starting') {
122
+ if (this.startPromise === null) {
123
+ log.warn('start.invariant', `state=${this.state} but startPromise is null`);
124
+ return Promise.reject(new Error(`ElectronBrowserHost.start: invariant violated — state=${this.state} but startPromise is null. ` +
125
+ 'This can only happen if startPromise was externally reset.'));
126
+ }
127
+ log('start.idempotent', `state=${this.state} returning existing startPromise`);
128
+ return this.startPromise;
129
+ }
130
+ if (this.child !== null) {
131
+ log.warn('start.childStillExiting', `state=${this.state} pid=${this.childPid ?? 'none'}`);
132
+ return Promise.reject(new Error('ElectronBrowserHost.start: the previous browser process is still exiting — try again in a moment'));
133
+ }
134
+ this.stopRequested = false;
135
+ this.lifecycleEpoch += 1;
136
+ this.lastStartOptions = opts;
137
+ this.viewport = opts.viewport ?? DEFAULT_VIEWPORT;
138
+ this.url = opts.initialUrl ?? DEFAULT_INITIAL_URL;
139
+ this.state = 'starting';
140
+ const startPromise = new Promise((res, rej) => {
141
+ this.startResolve = res;
142
+ this.startReject = rej;
143
+ });
144
+ this.startPromise = startPromise;
145
+ this.resolvedElectronBinary = this.electronBinaryOverride ?? resolveDefaultElectronBinary();
146
+ const blocker = this.checkPreconditions({
147
+ electronBinary: this.resolvedElectronBinary,
148
+ appDir: this.resolvedAppDir,
149
+ bootVersion: this.bootVersion,
150
+ });
151
+ if (blocker !== null) {
152
+ log.error('start.precondition', blocker);
153
+ this.transitionToCrashed(null, null, `cannot start the browser: ${blocker}`, { permanent: true });
154
+ return startPromise;
155
+ }
156
+ this.launch(opts.orchestraId);
157
+ return startPromise;
158
+ }
159
+ launch(orchestraId) {
160
+ if (this.startTimer) {
161
+ this.clearTimeoutFn(this.startTimer);
162
+ this.startTimer = null;
163
+ }
164
+ this.pipewireSinkSupervisor.start(orchestraId, (err) => this.emit('error', err));
165
+ try {
166
+ this.spawnChild();
167
+ }
168
+ catch (err) {
169
+ log.error('start.spawnFailed', err);
170
+ const message = err instanceof Error ? err.message : String(err);
171
+ this.releaseChild();
172
+ this.transitionToCrashed(null, null, `spawn failed — ${message}`, { permanent: true });
173
+ return;
174
+ }
175
+ this.startTimer = this.setTimeoutFn(() => {
176
+ this.startTimer = null;
177
+ const tail = this.childStderrTail.length > 0
178
+ ? ` — last stderr: ${this.childStderrTail.join(' | ')}`
179
+ : '';
180
+ const err = new Error(`ElectronBrowserHost.start: child did not signal ready within ${STARTUP_TIMEOUT_MS}ms${tail}`);
181
+ this.state = 'crashed';
182
+ this.lastCrashReason = err.message;
183
+ this.emit('crashed', { code: null, signal: null, reason: err.message });
184
+ this.failStart(err);
185
+ this.terminateChild();
186
+ }, STARTUP_TIMEOUT_MS);
187
+ }
188
+ async stop() {
189
+ if (this.state === 'idle' || this.state === 'stopped') {
190
+ this.state = 'stopped';
191
+ return;
192
+ }
193
+ this.stopRequested = true;
194
+ this.lifecycleEpoch += 1;
195
+ this.cancelPendingRestart();
196
+ this.failStart(new Error('ElectronBrowserHost.start: aborted by stop()'));
197
+ if (!this.child) {
198
+ this.state = 'stopped';
199
+ this.emit('stopped');
200
+ return;
201
+ }
202
+ const exited = this.waitForChildExit();
203
+ await this.gracefulShutdown();
204
+ await exited;
205
+ this.state = 'stopped';
206
+ this.emit('stopped');
207
+ }
208
+ async restart(opts) {
209
+ const next = opts ?? this.lastStartOptions;
210
+ if (!next) {
211
+ throw new Error('ElectronBrowserHost.restart: no prior start options to reuse');
212
+ }
213
+ this.state = 'restarting';
214
+ this.emit('restarting');
215
+ await this.stop();
216
+ this.stopRequested = false;
217
+ await this.start(next);
218
+ }
219
+ async recover(opts) {
220
+ const next = opts ?? this.lastStartOptions;
221
+ if (!next) {
222
+ throw new Error('ElectronBrowserHost.recover: no prior start options to reuse');
223
+ }
224
+ log('recover.enter', `state=${this.state} prevAttempts=${this.restartAttempts} hasTimer=${this.restartTimer !== null}`);
225
+ if (this.state === 'ready') {
226
+ log('recover.noop', 'state=ready');
227
+ return;
228
+ }
229
+ if (this.state === 'starting') {
230
+ log('recover.joinInflight', 'state=starting');
231
+ return this.startPromise ?? Promise.resolve();
232
+ }
233
+ if (this.state === 'restarting' && this.restartTimer === null) {
234
+ log('recover.joinInflight', 'state=restarting (start in flight)');
235
+ return this.startPromise ?? Promise.resolve();
236
+ }
237
+ if (this.child !== null) {
238
+ log('recover.awaitChildExit', `state=${this.state} pid=${this.childPid ?? 'none'}`);
239
+ await this.waitForChildExit();
240
+ return this.recover(next);
241
+ }
242
+ const nowMs = this.now();
243
+ const sinceLast = nowMs - this.lastRecoverAt;
244
+ if (this.lastRecoverAt !== 0 && sinceLast < RECOVER_THROTTLE_MS) {
245
+ const retryInMs = RECOVER_THROTTLE_MS - sinceLast;
246
+ log('recover.throttled.noop', `retryInMs=${retryInMs} → silent no-op`);
247
+ return;
248
+ }
249
+ this.lastRecoverAt = nowMs;
250
+ const prevAttempts = this.restartAttempts;
251
+ this.cancelPendingRestart();
252
+ this.restartAttempts = 0;
253
+ this.childStderrTail.length = 0;
254
+ this.stopRequested = false;
255
+ log('recover.transition', `prevAttempts=${prevAttempts} → starting`);
256
+ this.emit('recovering', { prevAttempts });
257
+ return this.start(next);
258
+ }
259
+ async navigate(url, tabId) {
260
+ log('navigate', `${url}${tabId ? ` tab=${tabId.slice(0, 8)}` : ''}`);
261
+ this.ensureReady('navigate');
262
+ if (tabId) {
263
+ await this.ipc.call('tab.navigate', {
264
+ id: tabId,
265
+ url,
266
+ });
267
+ }
268
+ else {
269
+ await this.ipc.call('view.navigate', { url });
270
+ }
271
+ this.url = url;
272
+ }
273
+ async setBounds(bounds, sessionId) {
274
+ log('setBounds', `${bounds.width}x${bounds.height} at (${bounds.x},${bounds.y})${bounds.mobile ? ' mobile' : ''}${sessionId ? ` session=${sessionId.slice(0, 8)}` : ''}`);
275
+ this.ensureReady('setBounds');
276
+ await this.ipc.call('view.setBounds', { ...bounds, sessionId });
277
+ this.viewport = { width: bounds.width, height: bounds.height };
278
+ }
279
+ async setTabDeviceProfile(tabId, profile) {
280
+ log('setTabDeviceProfile', `tab=${tabId.slice(0, 8)} ${profile.formFactor} ${profile.width}x${profile.height}`);
281
+ this.ensureReady('setTabDeviceProfile');
282
+ this.tabInfoCache.upsert(tabId, { deviceProfile: profile });
283
+ await this.ipc.call('tab.setDeviceProfile', {
284
+ tabId,
285
+ profile: {
286
+ formFactor: profile.formFactor,
287
+ width: profile.width,
288
+ height: profile.height,
289
+ },
290
+ });
291
+ this.emitTabs();
292
+ }
293
+ async forwardInput(ev, sessionId, tabId) {
294
+ this.ensureReady('forwardInput');
295
+ const session = sessionId ? { sessionId } : {};
296
+ this.ipc.notify('input.forward', tabId ? { type: 'browser_input', tabId, data: ev, ...session } : { ...ev, ...session });
297
+ }
298
+ async signal(envelope) {
299
+ log('signal.in', `kind=${envelope.kind} sessionId=${envelope.sessionId.slice(0, 8)}`);
300
+ this.ensureReady('signal');
301
+ switch (envelope.kind) {
302
+ case 'offer':
303
+ return;
304
+ case 'answer':
305
+ await this.ipc.call('webrtc.answer', { sessionId: envelope.sessionId, sdp: envelope.sdp });
306
+ return;
307
+ case 'ice':
308
+ await this.ipc.call('webrtc.ice', { sessionId: envelope.sessionId, candidate: envelope.candidate });
309
+ return;
310
+ case 'state':
311
+ if (envelope.state === 'closed') {
312
+ await this.closeCaptureFor(envelope.sessionId);
313
+ }
314
+ return;
315
+ }
316
+ }
317
+ async openTab(url) {
318
+ log('openTab.enter', url);
319
+ this.ensureReady('openTab');
320
+ const tab = await this.ipc.call('tab.create', { url });
321
+ log.info('openTab.created', `id=${tab.id.slice(0, 8)} url=${url}`);
322
+ this.tabInfoCache.upsert(tab.id, { url });
323
+ this.currentTabId = tab.id;
324
+ this.emitTabs();
325
+ return tab;
326
+ }
327
+ async closeTab(id) {
328
+ log('closeTab', id.slice(0, 8));
329
+ this.ensureReady('closeTab');
330
+ await this.ipc.call('tab.close', { id });
331
+ this.crashedTabs.delete(id);
332
+ if (this.tabInfoCache.has(id)) {
333
+ this.tabInfoCache.delete(id);
334
+ if (this.currentTabId === id) {
335
+ this.currentTabId = this.pickNextFocused();
336
+ }
337
+ this.emitTabs();
338
+ }
339
+ }
340
+ async switchTab(id) {
341
+ log('switchTab', id.slice(0, 8));
342
+ this.ensureReady('switchTab');
343
+ await this.ipc.call('tab.switch', { id });
344
+ if (this.tabInfoCache.has(id) && this.currentTabId !== id) {
345
+ this.currentTabId = id;
346
+ this.emitTabs();
347
+ }
348
+ }
349
+ async switchViewerTab(sessionId, tabId) {
350
+ log('switchViewerTab', `session=${sessionId.slice(0, 8)} tab=${tabId.slice(0, 8)}`);
351
+ this.ensureReady('switchViewerTab');
352
+ await this.ipc.call('webrtc.switchTab', { sessionId, tabId });
353
+ }
354
+ async goBack(tabId) {
355
+ this.ensureReady('goBack');
356
+ await this.ipc.call('view.back', { tabId });
357
+ }
358
+ async goForward(tabId) {
359
+ this.ensureReady('goForward');
360
+ await this.ipc.call('view.forward', { tabId });
361
+ }
362
+ async reload(tabId) {
363
+ this.ensureReady('reload');
364
+ await this.ipc.call('view.reload', { tabId });
365
+ }
366
+ async stopLoading(tabId) {
367
+ this.ensureReady('stopLoading');
368
+ await this.ipc.call('view.stop', { tabId });
369
+ }
370
+ async setIceConfig(iceServers) {
371
+ log.info('setIceConfig', `servers=${iceServers.length}`);
372
+ this.ensureReady('setIceConfig');
373
+ await this.ipc.call('webrtc.setConfig', { iceServers });
374
+ }
375
+ async startCapture(sessionId, tabId) {
376
+ log.info('startCapture', `session=${sessionId.slice(0, 8)} tab=${tabId.slice(0, 8)} state=${this.state}`);
377
+ this.ensureReady('startCapture');
378
+ await this.ipc.call('webrtc.offer', { sessionId, tabId });
379
+ }
380
+ async closeCaptureFor(sessionId) {
381
+ log.info('closeCaptureFor', `session=${sessionId.slice(0, 8)} state=${this.state}`);
382
+ if (this.state !== 'ready' || !this.ipc)
383
+ return;
384
+ try {
385
+ await this.ipc.call('webrtc.closeOne', { sessionId });
386
+ }
387
+ catch (err) {
388
+ log.warn('closeCaptureFor.err', err instanceof Error ? err.message : String(err));
389
+ }
390
+ }
391
+ async leaseTab(req) {
392
+ this.ensureReady('leaseTab');
393
+ const tabId = this.resolveAgentTabId(req.tabId);
394
+ return this.ipc.call('agent.tab.lease', tabId === req.tabId ? req : { ...req, tabId });
395
+ }
396
+ async releaseTab(req) {
397
+ this.ensureReady('releaseTab');
398
+ const tabId = this.resolveAgentTabId(req.tabId);
399
+ return this.ipc.call('agent.tab.release', tabId === req.tabId ? req : { ...req, tabId });
400
+ }
401
+ async heartbeat(req) {
402
+ this.ensureReady('heartbeat');
403
+ const tabIds = req.tabIds.map((id) => this.resolveAgentTabId(id));
404
+ const changed = tabIds.some((id, i) => id !== req.tabIds[i]);
405
+ return this.ipc.call('agent.tab.heartbeat', changed ? { ...req, tabIds } : req);
406
+ }
407
+ async listLeases(filter) {
408
+ this.ensureReady('listLeases');
409
+ return this.ipc.call('agent.tab.list', filter);
410
+ }
411
+ async releasePlan(planId) {
412
+ this.ensureReady('releasePlan');
413
+ return this.ipc.call('agent.plan.release', { planId });
414
+ }
415
+ getTabInfo(tabId) {
416
+ return this.tabInfoCache.get(tabId);
417
+ }
418
+ buildDomSurface() {
419
+ const call = async (method, params, opts) => {
420
+ this.ensureReady(`dom.${method.slice(4)}`);
421
+ const tabId = this.resolveAgentTabId(params.tabId);
422
+ return this.ipc.call(method, tabId === params.tabId ? params : { ...params, tabId }, opts);
423
+ };
424
+ return {
425
+ snapshot: (args) => call('dom.snapshot', args),
426
+ click: (args) => call('dom.click', args),
427
+ type: (args) => call('dom.type', args),
428
+ hover: (args) => call('dom.hover', args),
429
+ pressKey: (args) => call('dom.press_key', args),
430
+ selectOption: (args) => call('dom.select_option', args),
431
+ fillForm: (args) => call('dom.fill_form', args),
432
+ dragDrop: (args) => call('dom.drag_drop', args),
433
+ fileUpload: (args) => call('dom.file_upload', args),
434
+ handleDialog: (args) => call('dom.handle_dialog', args),
435
+ waitFor: (args) => call('dom.wait_for', args, ipcDeadlineFor(args.timeoutMs)),
436
+ screenshot: (args) => call('dom.screenshot', args),
437
+ console: (args) => call('dom.console', args),
438
+ network: (args) => call('dom.network', args),
439
+ };
440
+ }
441
+ buildJsSurface() {
442
+ return {
443
+ evaluate: async (args) => {
444
+ this.ensureReady('js.evaluate');
445
+ const tabId = this.resolveAgentTabId(args.tabId);
446
+ return this.ipc.call('js.evaluate', tabId === args.tabId ? args : { ...args, tabId }, ipcDeadlineFor(args.timeoutMs));
447
+ },
448
+ };
449
+ }
450
+ buildSdkSurface() {
451
+ return {
452
+ deliver: async (args) => {
453
+ this.ensureReady('sdk.deliver');
454
+ await this.ipc.call('sdk.deliver', args);
455
+ },
456
+ };
457
+ }
458
+ getStatus() {
459
+ return {
460
+ state: this.state,
461
+ url: this.url,
462
+ viewport: this.viewport,
463
+ ...(this.state === 'crashed' && this.lastCrashReason
464
+ ? { reason: this.lastCrashReason }
465
+ : {}),
466
+ };
467
+ }
468
+ isReady() {
469
+ return this.state === 'ready';
470
+ }
471
+ getState() {
472
+ return this.state;
473
+ }
474
+ spawnChild() {
475
+ const xvfb = this.xvfbSupervisor.start((err) => this.emit('error', err));
476
+ const envOverrides = childEnvOverrides({
477
+ platform: this.platform,
478
+ env: this.env,
479
+ xvfbDisplayNumber: xvfb.displayNumber,
480
+ sdkStubPath: resolveSdkStubPath(),
481
+ audioSinkOverrides: this.pipewireSinkSupervisor.envOverrides(),
482
+ });
483
+ const sandbox = this.sandboxPolicy.decide();
484
+ if (sandbox.reason === 'auto-root')
485
+ console.warn(`[ElectronBrowserHost] ${sandbox.detail}`);
486
+ const chromiumFlags = chromiumLaunchFlags({
487
+ platform: this.platform,
488
+ env: this.env,
489
+ xvfbDisplayNumber: xvfb.displayNumber,
490
+ sandboxEnabled: sandbox.enabled,
491
+ });
492
+ const binary = this.resolvedElectronBinary;
493
+ if (binary === null) {
494
+ throw new Error('ElectronBrowserHost.spawnChild: no Electron binary resolved — start() runs the preconditions first');
495
+ }
496
+ const exec = withoutCoreDumps(this.platform, binary, [this.resolvedAppDir, '--minionry-host', ...chromiumFlags]);
497
+ const child = this.spawnFn(exec.command, exec.args, {
498
+ cwd: this.resolvedAppDir,
499
+ stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'],
500
+ env: buildChildEnv(this.env, envOverrides),
501
+ detached: false,
502
+ windowsHide: true,
503
+ });
504
+ child.once('error', (err) => {
505
+ if (this.child === child)
506
+ this.onChildError(err);
507
+ });
508
+ this.child = child;
509
+ this.childPid = typeof child.pid === 'number' ? child.pid : null;
510
+ this.childExited = false;
511
+ this.helloSeen = false;
512
+ this.childFailure = null;
513
+ this.childStderrTail.length = 0;
514
+ if (this.childPid === null) {
515
+ log.warn('spawn.noPid', `binary=${binary} — awaiting the spawn error event`);
516
+ return;
517
+ }
518
+ log.info('spawn', `pid=${child.pid} binary=${binary} via=${exec.command === binary ? 'direct' : exec.command} appDir=${this.resolvedAppDir} flags=[${chromiumFlags.join(' ')}] xvfb=${xvfb.displayNumber ?? 'none'} sandbox=${sandbox.reason}`);
519
+ this.wireChildStdio(child);
520
+ if (this.ipcClientFactory) {
521
+ const readFromChild = child.stdio[4];
522
+ const writeToChild = child.stdio[3];
523
+ if (!readFromChild || !writeToChild) {
524
+ throw new Error('Electron child did not expose the expected fd3/fd4 RPC pipes');
525
+ }
526
+ const ipc = this.ipcClientFactory({
527
+ readFromChild,
528
+ writeToChild,
529
+ onFatal: (err) => {
530
+ if (this.child !== child)
531
+ return;
532
+ log.error('ipc.unreadable', err);
533
+ this.childFailure = `the RPC stream from the browser process became unreadable (${err.message})`;
534
+ this.terminateChild();
535
+ },
536
+ });
537
+ this.ipc = ipc;
538
+ this.subscribeIpcEvents(ipc);
539
+ }
540
+ }
541
+ wireChildStdio(child) {
542
+ child.stdout?.on('data', (chunk) => {
543
+ const text = chunk.toString('utf8').trimEnd();
544
+ if (text)
545
+ console.log(`[electron-host] ${text}`);
546
+ });
547
+ child.stderr?.on('data', (chunk) => this.onChildStderr(child, chunk));
548
+ child.once('exit', (code, signal) => {
549
+ if (this.child !== child) {
550
+ log.warn('child.staleExit', `pid=${child.pid ?? 'none'} code=${code} signal=${signal}`);
551
+ return;
552
+ }
553
+ this.childExited = true;
554
+ this.onChildExit(code, signal);
555
+ });
556
+ }
557
+ onChildStderr(child, chunk) {
558
+ const debug = this.env.MINIONRY_BROWSER_DEBUG === '1';
559
+ const lines = chunk
560
+ .toString('utf8')
561
+ .split('\n')
562
+ .map((line) => line.trim())
563
+ .filter((line) => line.length > 0 && shouldForwardStderrLine(line, { debug }));
564
+ if (lines.length === 0)
565
+ return;
566
+ console.error(`[electron-host err] ${lines.join('\n')}`);
567
+ if (this.child !== child)
568
+ return;
569
+ this.childStderrTail.push(...lines);
570
+ this.childStderrTail.splice(0, Math.max(0, this.childStderrTail.length - STDERR_TAIL_LINES));
571
+ }
572
+ subscribeIpcEvents(ipc) {
573
+ ipc.on('host.hello', () => this.onChildHello());
574
+ ipc.on('host.capture_ready', () => this.onCaptureReady());
575
+ ipc.on('status.change', (payload) => this.onStatusChangeIpc(payload));
576
+ ipc.on('webrtc.offer', (payload) => this.relayOutboundSignal('offer', payload));
577
+ ipc.on('webrtc.ice', (payload) => this.relayOutboundSignal('ice', payload));
578
+ ipc.on('webrtc.state', (payload) => this.relayOutboundSignal('state', payload));
579
+ ipc.on('lease.expired', (payload) => this.onLeaseExpired(payload));
580
+ ipc.on('tab.created', (payload) => this.onTabCreatedIpc(payload));
581
+ ipc.on('tab.navigated', (payload) => this.onTabNavigatedIpc(payload));
582
+ ipc.on('tab.failed', (payload) => this.onTabFailedIpc(payload));
583
+ ipc.on('tab.titleChanged', (payload) => this.onTabTitleChangedIpc(payload));
584
+ ipc.on('tab.closed', (payload) => this.onTabClosedIpc(payload));
585
+ ipc.on('tab.crashed', (payload) => this.onTabCrashedIpc(payload));
586
+ ipc.on('browser.inputFocus', (payload) => this.onInputFocusIpc(payload));
587
+ ipc.on('sdk.request', (payload) => this.onSdkRequestIpc(payload));
588
+ }
589
+ onChildHello() {
590
+ log.info('childHello', `state=${this.state} pid=${this.childPid ?? 'none'}`);
591
+ this.helloSeen = true;
592
+ if (this.state !== 'starting' && this.state !== 'restarting')
593
+ return;
594
+ if (this.startTimer) {
595
+ this.clearTimeoutFn(this.startTimer);
596
+ this.startTimer = null;
597
+ }
598
+ this.state = 'ready';
599
+ this.restartAttempts = 0;
600
+ this.lastCrashReason = null;
601
+ this.emit('ready');
602
+ this.startResolve?.();
603
+ this.startResolve = null;
604
+ this.startReject = null;
605
+ }
606
+ onCaptureReady() {
607
+ log.info('captureReady', `state=${this.state}`);
608
+ }
609
+ onStatusChangeIpc(payload) {
610
+ if (!payload || typeof payload !== 'object')
611
+ return;
612
+ const obj = payload;
613
+ const state = typeof obj.state === 'string' ? obj.state : String(obj.state);
614
+ const reason = typeof obj.reason === 'string' ? obj.reason : undefined;
615
+ if (state === 'controller_crashed') {
616
+ this.childFailure = `capture controller failed: ${reason ?? 'no reason given'}`;
617
+ log.error('child.status', `state=${state} reason=${reason ?? '-'} — the child is exiting`);
618
+ return;
619
+ }
620
+ if (reason)
621
+ log.info('child.status', `state=${state} reason=${reason}`);
622
+ else
623
+ log('child.status', `state=${state}`);
624
+ }
625
+ onTabCrashedIpc(payload) {
626
+ if (!payload || typeof payload !== 'object')
627
+ return;
628
+ const obj = payload;
629
+ const tabId = typeof obj.tabId === 'string' ? obj.tabId : null;
630
+ if (!tabId)
631
+ return;
632
+ const reason = typeof obj.reason === 'string' ? obj.reason : 'unknown';
633
+ const willRestart = obj.willRestart === true;
634
+ log.info('ipc.tabCrashed', `id=${tabId.slice(0, 8)} reason=${reason} willRestart=${willRestart}`);
635
+ if (willRestart || !this.tabInfoCache.has(tabId))
636
+ return;
637
+ this.crashedTabs.set(tabId, `This tab crashed (${reason}) and was not restarted. Reload it to try again.`);
638
+ this.emitTabs();
639
+ }
640
+ relayOutboundSignal(kind, payload) {
641
+ const signal = readOutboundSignal(kind, payload);
642
+ if (!signal)
643
+ return;
644
+ const { obj, sessionId } = signal;
645
+ const stateDetail = kind === 'state' ? describeControllerState(obj) : '';
646
+ (kind === 'ice' ? log : log.info)('signal.out', `kind=${kind} session=${sessionId.slice(0, 8)}${stateDetail}`);
647
+ switch (kind) {
648
+ case 'offer':
649
+ case 'answer': {
650
+ const sdp = typeof obj.sdp === 'string' ? obj.sdp : null;
651
+ if (!sdp) {
652
+ log.error('signal.out.noSdp', `kind=${kind} session=${sessionId.slice(0, 8)} — dropped`);
653
+ return;
654
+ }
655
+ this.emit('signal', { kind, sessionId, sdp });
656
+ return;
657
+ }
658
+ case 'ice': {
659
+ const candidate = extractCandidate(obj.candidate);
660
+ this.emit('signal', { kind, sessionId, candidate });
661
+ return;
662
+ }
663
+ case 'state': {
664
+ const state = obj.state;
665
+ if (state !== 'negotiating' &&
666
+ state !== 'connected' &&
667
+ state !== 'failed' &&
668
+ state !== 'closed') {
669
+ log.error('signal.out.badState', `session=${sessionId.slice(0, 8)} state=${String(state)} — dropped`);
670
+ return;
671
+ }
672
+ const reason = typeof obj.reason === 'string' ? obj.reason : undefined;
673
+ const selectedCandidateType = readSelectedCandidateType(obj.selectedCandidateType);
674
+ this.emit('signal', {
675
+ kind,
676
+ sessionId,
677
+ state,
678
+ reason,
679
+ ...(selectedCandidateType !== undefined ? { selectedCandidateType } : {}),
680
+ });
681
+ return;
682
+ }
683
+ }
684
+ }
685
+ onLeaseExpired(payload) {
686
+ const event = extractLeaseExpired(payload);
687
+ if (event === null)
688
+ return;
689
+ this.emit('leaseExpired', event);
690
+ }
691
+ onInputFocusIpc(payload) {
692
+ if (!payload || typeof payload !== 'object')
693
+ return;
694
+ const obj = payload;
695
+ if (typeof obj.sessionId !== 'string' || typeof obj.focused !== 'boolean') {
696
+ return;
697
+ }
698
+ this.emit('inputFocus', {
699
+ sessionId: obj.sessionId,
700
+ ...(typeof obj.tabId === 'string' ? { tabId: obj.tabId } : {}),
701
+ focused: obj.focused,
702
+ });
703
+ }
704
+ onSdkRequestIpc(payload) {
705
+ if (!payload || typeof payload !== 'object')
706
+ return;
707
+ const obj = payload;
708
+ if (typeof obj.pageId !== 'number' ||
709
+ typeof obj.tabId !== 'string' ||
710
+ typeof obj.sessionId !== 'string' ||
711
+ typeof obj.origin !== 'string' ||
712
+ obj.origin === '' ||
713
+ typeof obj.payload !== 'string') {
714
+ log.warn('sdk.request.malformed', 'dropped an sdk.request IPC payload with missing/invalid fields');
715
+ return;
716
+ }
717
+ const event = {
718
+ pageId: obj.pageId,
719
+ tabId: obj.tabId,
720
+ sessionId: obj.sessionId,
721
+ origin: obj.origin,
722
+ payload: obj.payload,
723
+ };
724
+ this.emit('sdkRequest', event);
725
+ }
726
+ onTabCreatedIpc(payload) {
727
+ if (!payload || typeof payload !== 'object')
728
+ return;
729
+ const obj = payload;
730
+ const id = typeof obj.id === 'string' ? obj.id : null;
731
+ if (!id)
732
+ return;
733
+ const url = typeof obj.url === 'string' ? obj.url : '';
734
+ const isNew = !this.tabInfoCache.has(id);
735
+ log('ipc.tabCreated', `id=${id.slice(0, 8)} url=${url} isNew=${isNew}`);
736
+ this.tabInfoCache.upsert(id, { url, title: '' });
737
+ if (isNew) {
738
+ if (this.currentTabId === null)
739
+ this.currentTabId = id;
740
+ this.emitTabs();
741
+ }
742
+ }
743
+ onTabNavigatedIpc(payload) {
744
+ if (!payload || typeof payload !== 'object')
745
+ return;
746
+ const obj = payload;
747
+ const tabId = typeof obj.tabId === 'string' ? obj.tabId : null;
748
+ const url = typeof obj.url === 'string' ? obj.url : null;
749
+ if (!tabId || url === null)
750
+ return;
751
+ const canGoBack = obj.canGoBack === true;
752
+ const canGoForward = obj.canGoForward === true;
753
+ log('ipc.tabNavigated', `id=${tabId.slice(0, 8)} url=${url} back=${canGoBack} fwd=${canGoForward}`);
754
+ const recovered = this.crashedTabs.delete(tabId);
755
+ const prev = this.tabInfoCache.get(tabId);
756
+ if (!recovered && prev?.url === url && prev?.canGoBack === canGoBack && prev?.canGoForward === canGoForward) {
757
+ return;
758
+ }
759
+ this.tabInfoCache.upsert(tabId, { url, canGoBack, canGoForward });
760
+ this.emitTabs();
761
+ }
762
+ onTabFailedIpc(payload) {
763
+ if (!payload || typeof payload !== 'object')
764
+ return;
765
+ const obj = payload;
766
+ const tabId = typeof obj.tabId === 'string' ? obj.tabId : null;
767
+ const url = typeof obj.url === 'string' ? obj.url : null;
768
+ if (!tabId || url === null)
769
+ return;
770
+ const errorCode = typeof obj.errorCode === 'number' ? obj.errorCode : 0;
771
+ log('ipc.tabFailed', `id=${tabId.slice(0, 8)} url=${url} code=${errorCode}`);
772
+ if (!this.tabInfoCache.has(tabId))
773
+ return;
774
+ const prev = this.tabInfoCache.get(tabId);
775
+ if (prev?.url === url)
776
+ return;
777
+ this.tabInfoCache.upsert(tabId, { url });
778
+ this.emitTabs();
779
+ }
780
+ onTabTitleChangedIpc(payload) {
781
+ if (!payload || typeof payload !== 'object')
782
+ return;
783
+ const obj = payload;
784
+ const tabId = typeof obj.tabId === 'string' ? obj.tabId : null;
785
+ const title = typeof obj.title === 'string' ? obj.title : null;
786
+ if (!tabId || title === null)
787
+ return;
788
+ log('ipc.tabTitle', `id=${tabId.slice(0, 8)} title="${title}"`);
789
+ const prev = this.tabInfoCache.get(tabId);
790
+ if (prev?.title === title)
791
+ return;
792
+ this.tabInfoCache.upsert(tabId, { title });
793
+ this.emitTabs();
794
+ }
795
+ onTabClosedIpc(payload) {
796
+ if (!payload || typeof payload !== 'object')
797
+ return;
798
+ const obj = payload;
799
+ const id = typeof obj.id === 'string' ? obj.id : null;
800
+ if (!id)
801
+ return;
802
+ log('ipc.tabClosed', `id=${id.slice(0, 8)} cached=${this.tabInfoCache.has(id)}`);
803
+ this.crashedTabs.delete(id);
804
+ if (!this.tabInfoCache.has(id))
805
+ return;
806
+ this.tabInfoCache.delete(id);
807
+ if (this.currentTabId === id) {
808
+ this.currentTabId = this.pickNextFocused();
809
+ }
810
+ this.emitTabs();
811
+ }
812
+ buildTabsSnapshot() {
813
+ const entries = this.tabInfoCache.entries();
814
+ const tabs = entries.map((e) => {
815
+ const crashed = this.crashedTabs.get(e.id);
816
+ return {
817
+ id: e.id,
818
+ url: e.url,
819
+ title: e.title,
820
+ ...(e.faviconUrl !== undefined ? { favicon: e.faviconUrl } : {}),
821
+ isLoading: false,
822
+ canGoBack: e.canGoBack ?? false,
823
+ canGoForward: e.canGoForward ?? false,
824
+ ...(e.deviceProfile !== undefined ? { deviceProfile: e.deviceProfile } : {}),
825
+ ...(crashed !== undefined ? { error: crashed } : {}),
826
+ };
827
+ });
828
+ const focused = this.currentTabId && this.tabInfoCache.has(this.currentTabId)
829
+ ? this.currentTabId
830
+ : null;
831
+ return { tabs, currentTabId: focused };
832
+ }
833
+ emitTabs() {
834
+ const snapshot = this.buildTabsSnapshot();
835
+ log('emitTabs', `count=${snapshot.tabs.length} current=${this.currentTabId?.slice(0, 8) ?? 'null'}`);
836
+ this.emit('tabs', snapshot);
837
+ }
838
+ getTabsSnapshot() {
839
+ return this.buildTabsSnapshot();
840
+ }
841
+ pickNextFocused() {
842
+ const entries = this.tabInfoCache.entries();
843
+ return entries[0]?.id ?? null;
844
+ }
845
+ resolveAgentTabId(tabId) {
846
+ if (this.tabInfoCache.has(tabId))
847
+ return tabId;
848
+ const aliased = this.agentTabAliases.get(tabId);
849
+ if (aliased && this.tabInfoCache.has(aliased))
850
+ return aliased;
851
+ const current = this.currentTabId;
852
+ if (current && this.tabInfoCache.has(current)) {
853
+ log('resolveAgentTabId.adopt', `unknown=${tabId.slice(0, 8)} -> current=${current.slice(0, 8)}`);
854
+ this.agentTabAliases.set(tabId, current);
855
+ return current;
856
+ }
857
+ return tabId;
858
+ }
859
+ onChildError(err) {
860
+ if (this.childPid === null) {
861
+ const reason = `spawn failed — ${describeSpawnError(err, this.resolvedElectronBinary ?? 'electron', this.resolvedAppDir)}`;
862
+ this.releaseChild();
863
+ this.transitionToCrashed(null, null, reason, { permanent: true });
864
+ return;
865
+ }
866
+ this.childFailure = `spawn error: ${err.message}`;
867
+ this.terminateChild();
868
+ }
869
+ onChildExit(code, signal) {
870
+ const wasIntentional = this.stopRequested;
871
+ const failure = this.childFailure;
872
+ this.childFailure = null;
873
+ this.releaseChild();
874
+ if (wasIntentional) {
875
+ return;
876
+ }
877
+ const exit = signal ? `child terminated by ${signal}` : `child exited with code ${code ?? 'unknown'}`;
878
+ this.transitionToCrashed(code, signal, failure ? `${exit} — ${failure}` : exit);
879
+ }
880
+ transitionToCrashed(code, signal, reason, opts = {}) {
881
+ if (this.state === 'stopped' || this.stopRequested)
882
+ return;
883
+ const verdict = classifyCrash(reason, this.childStderrTail);
884
+ let remedy = verdict.hint;
885
+ let retryUnsandboxed = false;
886
+ if (verdict.sandboxFailure !== null && !this.helloSeen) {
887
+ const failure = this.sandboxPolicy.recordFailure(verdict.sandboxFailure);
888
+ if (failure.fallBack && this.state === 'starting' && this.child === null && this.lastStartOptions) {
889
+ this.relaunchWithoutSandbox(this.lastStartOptions.orchestraId, reason, failure.notice);
890
+ return;
891
+ }
892
+ remedy = failure.notice;
893
+ if (failure.fallBack && this.child === null)
894
+ retryUnsandboxed = true;
895
+ }
896
+ const permanent = opts.permanent === true || (verdict.permanent && !retryUnsandboxed);
897
+ const tail = this.childStderrTail.length > 0
898
+ ? ` — last stderr: ${this.childStderrTail.join(' | ')}`
899
+ : '';
900
+ const hint = remedy ? ` — ${remedy}` : '';
901
+ const fullReason = `${reason}${tail}${hint}`;
902
+ log.error('crashed', `prevState=${this.state} code=${code} signal=${signal} permanent=${permanent} reason=${fullReason}`);
903
+ this.state = 'crashed';
904
+ this.lastCrashReason = fullReason;
905
+ this.emit('crashed', { code, signal, reason: fullReason });
906
+ this.failStart(new Error(`ElectronBrowserHost: ${fullReason}`));
907
+ if (permanent) {
908
+ console.error(`[ElectronBrowserHost] not retrying — the failure is not transient: ${fullReason}`);
909
+ return;
910
+ }
911
+ this.scheduleRestartIfBudgetRemains();
912
+ }
913
+ relaunchWithoutSandbox(orchestraId, reason, notice) {
914
+ const tail = this.childStderrTail.length > 0
915
+ ? ` — last stderr: ${this.childStderrTail.join(' | ')}`
916
+ : '';
917
+ log.warn('sandbox.fallback', `${reason}${tail}`);
918
+ console.warn(`[ElectronBrowserHost] ${notice}`);
919
+ this.childStderrTail.length = 0;
920
+ const epoch = this.lifecycleEpoch;
921
+ void this.sidecarRelease.then(() => {
922
+ if (epoch !== this.lifecycleEpoch ||
923
+ this.stopRequested ||
924
+ this.state !== 'starting' ||
925
+ this.child !== null) {
926
+ return;
927
+ }
928
+ try {
929
+ this.launch(orchestraId);
930
+ }
931
+ catch (err) {
932
+ const message = err instanceof Error ? err.message : String(err);
933
+ this.releaseChild();
934
+ this.transitionToCrashed(null, null, `sandbox fallback relaunch failed — ${message}`, { permanent: true });
935
+ }
936
+ });
937
+ }
938
+ scheduleRestartIfBudgetRemains() {
939
+ if (!this.lastStartOptions)
940
+ return;
941
+ if (this.restartAttempts >= MAX_RESTARTS) {
942
+ console.error(`[ElectronBrowserHost] restart budget exhausted (attempts=${this.restartAttempts}); host stays crashed`);
943
+ return;
944
+ }
945
+ const delay = RESTART_BACKOFFS_MS[this.restartAttempts] ?? RESTART_BACKOFFS_MS.at(-1) ?? 1_000;
946
+ this.restartAttempts += 1;
947
+ console.warn(`[ElectronBrowserHost] scheduling restart attempt ${this.restartAttempts}/${MAX_RESTARTS} in ${delay}ms`);
948
+ this.state = 'restarting';
949
+ this.emit('restarting');
950
+ this.cancelPendingRestart();
951
+ this.restartTimer = this.setTimeoutFn(() => {
952
+ this.restartTimer = null;
953
+ if (this.stopRequested)
954
+ return;
955
+ void this.start(this.lastStartOptions).catch(() => {
956
+ });
957
+ }, delay);
958
+ }
959
+ cancelPendingRestart() {
960
+ if (this.restartTimer) {
961
+ this.clearTimeoutFn(this.restartTimer);
962
+ this.restartTimer = null;
963
+ }
964
+ }
965
+ async gracefulShutdown() {
966
+ if (!this.child)
967
+ return;
968
+ const ipc = this.ipc;
969
+ try {
970
+ if (ipc) {
971
+ await Promise.race([
972
+ ipc.call('host.shutdown', undefined),
973
+ this.delay(SIGTERM_GRACE_MS),
974
+ ]);
975
+ }
976
+ }
977
+ catch {
978
+ }
979
+ if (!this.childExited && this.childPid !== null) {
980
+ this.killChild('SIGTERM');
981
+ await Promise.race([this.waitForChildExit(), this.delay(SIGTERM_GRACE_MS)]);
982
+ if (!this.childExited) {
983
+ this.killChild('SIGKILL');
984
+ }
985
+ }
986
+ try {
987
+ await ipc?.close();
988
+ }
989
+ catch {
990
+ }
991
+ }
992
+ killChild(signal) {
993
+ if (!this.child)
994
+ return;
995
+ try {
996
+ this.child.kill(signal);
997
+ }
998
+ catch {
999
+ }
1000
+ }
1001
+ terminateChild() {
1002
+ const child = this.child;
1003
+ if (!child)
1004
+ return;
1005
+ this.killChild('SIGTERM');
1006
+ const escalation = this.setTimeoutFn(() => {
1007
+ if (this.child !== child)
1008
+ return;
1009
+ log.warn('child.sigkill', `pid=${child.pid ?? 'none'} — still alive ${SIGTERM_GRACE_MS}ms after SIGTERM`);
1010
+ this.killChild('SIGKILL');
1011
+ }, SIGTERM_GRACE_MS);
1012
+ child.once('exit', () => this.clearTimeoutFn(escalation));
1013
+ }
1014
+ waitForChildExit() {
1015
+ if (!this.child)
1016
+ return Promise.resolve();
1017
+ return new Promise((res) => {
1018
+ this.child.once('exit', () => res());
1019
+ });
1020
+ }
1021
+ releaseChild() {
1022
+ if (this.startTimer) {
1023
+ this.clearTimeoutFn(this.startTimer);
1024
+ this.startTimer = null;
1025
+ }
1026
+ this.child = null;
1027
+ this.childPid = null;
1028
+ const ipc = this.ipc;
1029
+ this.ipc = null;
1030
+ void ipc?.close().catch(() => undefined);
1031
+ this.sidecarRelease = Promise.allSettled([
1032
+ this.xvfbSupervisor.stop(),
1033
+ this.pipewireSinkSupervisor.stop(),
1034
+ ]).then(() => undefined);
1035
+ const hadTabs = this.tabInfoCache.entries().length > 0;
1036
+ this.tabInfoCache.clear();
1037
+ this.crashedTabs.clear();
1038
+ this.currentTabId = null;
1039
+ this.agentTabAliases.clear();
1040
+ if (hadTabs)
1041
+ this.emitTabs();
1042
+ }
1043
+ failStart(err) {
1044
+ if (this.startTimer) {
1045
+ this.clearTimeoutFn(this.startTimer);
1046
+ this.startTimer = null;
1047
+ }
1048
+ if (this.startReject) {
1049
+ this.startReject(err);
1050
+ this.startResolve = null;
1051
+ this.startReject = null;
1052
+ }
1053
+ }
1054
+ ensureReady(op) {
1055
+ if (this.state !== 'ready') {
1056
+ throw new Error(`ElectronBrowserHost.${op}: host is not ready (state=${this.state})`);
1057
+ }
1058
+ if (!this.ipc) {
1059
+ throw new Error(`ElectronBrowserHost.${op}: no IPC client configured — wire ipcClientFactory (IS-018)`);
1060
+ }
1061
+ }
1062
+ delay(ms) {
1063
+ return new Promise((res) => this.setTimeoutFn(res, ms));
1064
+ }
1065
+ }
1066
+ export const BrowserHostService = ElectronBrowserHost;
1067
+ export { MockBrowserHost } from './host-mock.js';