@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,1197 @@
1
+ // Copyright (c) 2025-present Minionry, Inc.
2
+ // SPDX-License-Identifier: LicenseRef-Minionry-Software
3
+ import { randomUUID } from 'node:crypto';
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { dirname } from 'node:path';
6
+ import { can, isViewOperation, normalizeStoredRole } from '@minionry/roles';
7
+ import { machineFile } from '../../../bin/machine-layout.js';
8
+ import { setAgentBrowserHostAttached } from '../../cli/headless/mcp-config.js';
9
+ import { getBrowserDriverState } from '../browser/driver-state.js';
10
+ import { toBrowserPreflightPayload } from '../browser/preflight.js';
11
+ import { CollabSessionRegistry } from '../collab/registry.js';
12
+ import { COLLAB_EVENT_BUFFER_MAX_AGE_MS, COLLAB_EVENT_BUFFER_MAX_BYTES, COLLAB_EVENT_BUFFER_MAX_EVENTS, } from '../collab/types.js';
13
+ import { respawnInstance } from '../instances.js';
14
+ import { PmTimelineRegistry } from '../plan/pm-timeline.js';
15
+ import { parseDeclaredEnvironment, resolveCliEnvironment } from '../platform-url.js';
16
+ import { getAgentScheduler } from '../schedule/agent-scheduler.js';
17
+ import { getQualityScheduler } from '../schedule/quality-scheduler.js';
18
+ import { getScheduler } from '../schedule/scheduler.js';
19
+ import { attachSdkBrowserTransport } from '../sdk/browser-transport.js';
20
+ import { teardownSdkForViewer, teardownSdkOnTransportClose } from '../sdk/revoke.js';
21
+ import { captureException } from '../sentry.js';
22
+ import { createExpander, defaultSnippetSources, SnippetRegistry, SnippetWatcher, } from '../snippets/index.js';
23
+ import { getPTYManager } from '../terminal/pty-manager.js';
24
+ import { ChatTimelineRegistry } from '../timeline/index.js';
25
+ import { handleAgentScheduleMessage } from './agent-schedule-handlers.js';
26
+ import { handleAppsMessage } from './apps-handlers.js';
27
+ import { resolvePendingQuestion } from './ask-user-question-bridge.js';
28
+ import { AutocompleteService } from './autocomplete.js';
29
+ import { activeBrowserAgentTarget } from './browser-agent-runtime.js';
30
+ import { attachBrowserHostBroadcast, broadcastAgentTabState, browserHostPolicy, handleBrowserHostMessage, onViewerDisconnected as onBrowserViewerDisconnected, onWsContextClosed as onBrowserWsContextClosed, } from './browser-handlers.js';
31
+ import { addressToViewer } from './browser-viewer-registry.js';
32
+ import { handleCollabMessage } from './collab-handlers.js';
33
+ import { createCommitSuggestionService, GIT_VIEW_TAB_ID, isAnyExecutionActive } from './commit-suggestion-service.js';
34
+ import { debugLog } from './debug-log.js';
35
+ import { handleEngineAuthMessage } from './engine-auth-handlers.js';
36
+ import { EngineCredentialWatcher } from './engine-credential-watcher.js';
37
+ import { handleModelProfileMessage } from './engine-profile-handlers.js';
38
+ import { FileDownloadHandler } from './file-download-handler.js';
39
+ import { handleFileExplorerMessage, handleFileMessage, resolveFileDomainRoot } from './file-explorer-handlers.js';
40
+ import { handleFileShareMessage, isFileShareRequestType } from './file-share-handlers.js';
41
+ import { FileUploadHandler } from './file-upload-handler.js';
42
+ import { handleGitMessage } from './git-handlers.js';
43
+ import { GitHeadWatcher } from './git-head-watcher.js';
44
+ import { hasScopeBinding, resolveScopeDir } from './handler-context.js';
45
+ import { MsgIdTracker } from './msg-id-tracker.js';
46
+ import { maybeStartAutoImplement } from './plan-execution-handlers.js';
47
+ import { handlePlanMessage } from './plan-handlers.js';
48
+ import { executorCache } from './plan-helpers.js';
49
+ import { buildLateAnswerPrompt } from './recommended-answers.js';
50
+ import { handleScheduleMessage } from './schedule-handlers.js';
51
+ import { installSdkAgentHost, installSdkQualityReviewHost, installSdkSessionHost } from './sdk-agent-host.js';
52
+ import { installSdkBrowserHost } from './sdk-browser-host.js';
53
+ import { installSdkComposerHost } from './sdk-composer-host.js';
54
+ import { installSdkGitHost, notifySdkGitCommitSuggestion } from './sdk-git-host.js';
55
+ import { handleSdkMessage } from './sdk-handlers.js';
56
+ import { installSdkTerminalHost } from './sdk-terminal-host.js';
57
+ import { handleHistoryMessage, handleSessionMessage, initializeTab, resolveTabSession, restoreWorktreeFromRegistry, resumeHistoricalSession } from './session-handlers.js';
58
+ import { SessionRegistry } from './session-registry.js';
59
+ import { SessionRoleDirectory } from './session-role-directory.js';
60
+ import { generateNotificationSummary, handleCreateSystemPrompt, handleDeleteSystemPrompt, handleGetModelCatalog, handleGetSettings, handleGetSystemPromptProfiles, handleReadSystemPrompt, handleSaveSystemPrompt, handleUpdateSettings } from './settings-handlers.js';
61
+ import { handleListSkills } from './skill-handlers.js';
62
+ import { SkillsWatcher } from './skill-watcher.js';
63
+ import { buildSnippetListResultData, handleSnippetMessage } from './snippet-handlers.js';
64
+ import { SeqEventBuffer, SeqEventBufferRegistry, TabEventBufferRegistry } from './tab-event-buffer.js';
65
+ import { handleCreateTab, handleGetActiveTabs, handleMarkTabViewed, handleRemoveTab, handleReorderTabs, handleSetTabEngine, handleSyncTabMeta } from './tab-handlers.js';
66
+ import { cleanupTerminalSubscribers, handleTerminalMessage } from './terminal-handlers.js';
67
+ const log = debugLog('handler');
68
+ export class WebSocketImproviseHandler {
69
+ sessions = new Map();
70
+ connections = new Map();
71
+ autocompleteService;
72
+ frecencyPath;
73
+ usageReporter = null;
74
+ gitScopes = new Map();
75
+ gitBranches = new Map();
76
+ sessionRegistry = null;
77
+ allConnections = new Set();
78
+ activeSearches = new Map();
79
+ terminalListenerCleanups = new Map();
80
+ terminalSubscribers = new Map();
81
+ fileUploadHandler = null;
82
+ fileDownloadHandler = null;
83
+ gitHeadWatcher = null;
84
+ skillsWatcher = null;
85
+ engineCredentialWatcher = null;
86
+ snippetRegistry = null;
87
+ snippetExpander = null;
88
+ snippetWatcher = null;
89
+ tabEventBuffers = new TabEventBufferRegistry();
90
+ chatTimelines = new ChatTimelineRegistry();
91
+ pmEventBuffers = new SeqEventBufferRegistry();
92
+ pmTimelines = new PmTimelineRegistry();
93
+ collabEventBuffers = new SeqEventBufferRegistry(() => new SeqEventBuffer(COLLAB_EVENT_BUFFER_MAX_EVENTS, COLLAB_EVENT_BUFFER_MAX_AGE_MS, Date.now, COLLAB_EVENT_BUFFER_MAX_BYTES));
94
+ collabSessions = new CollabSessionRegistry(this);
95
+ msgIdTracker = new MsgIdTracker();
96
+ commitSuggestions = null;
97
+ instanceRegistry;
98
+ shutdownInProgress = false;
99
+ browserHost = null;
100
+ browserHostBroadcastDispose = null;
101
+ browserHostLeaseExpiredDispose = null;
102
+ browserHostErrorDispose = null;
103
+ browserHostSdkDispose = null;
104
+ browserDriverStateDispose = null;
105
+ currentWorkingDir = null;
106
+ sessionRoles = new SessionRoleDirectory();
107
+ constructor(instanceRegistry = null, browserHost = null) {
108
+ this.frecencyPath = machineFile('frecency');
109
+ const frecencyData = this.loadFrecencyData();
110
+ this.autocompleteService = new AutocompleteService(frecencyData);
111
+ this.instanceRegistry = instanceRegistry;
112
+ this.commitSuggestions = createCommitSuggestionService({
113
+ isSystemActive: () => isAnyExecutionActive(this.sessions.values(), executorCache.values()),
114
+ broadcast: (suggestion) => {
115
+ this.broadcastToAll({ type: 'gitCommitSuggestion', tabId: GIT_VIEW_TAB_ID, data: suggestion });
116
+ notifySdkGitCommitSuggestion(suggestion);
117
+ },
118
+ });
119
+ if (browserHost)
120
+ this.setBrowserHost(browserHost);
121
+ installSdkAgentHost(this);
122
+ installSdkSessionHost(this);
123
+ installSdkQualityReviewHost(this);
124
+ installSdkBrowserHost(this);
125
+ installSdkComposerHost(this);
126
+ installSdkGitHost(this);
127
+ installSdkTerminalHost(this);
128
+ process.on('exit', () => {
129
+ if (this.frecencySaveTimer) {
130
+ clearTimeout(this.frecencySaveTimer);
131
+ this.saveFrecencyData();
132
+ }
133
+ });
134
+ }
135
+ setBrowserHost(host) {
136
+ if (this.browserHost === host)
137
+ return;
138
+ this.clearBrowserHost();
139
+ this.browserHost = host;
140
+ setAgentBrowserHostAttached(true);
141
+ const onSupervisorError = (err) => {
142
+ console.warn(`[wsHandler] browser host non-fatal supervisor error: ${err.message}`);
143
+ captureException(err, { context: 'wsHandler.browserHost.error' });
144
+ };
145
+ host.on('error', onSupervisorError);
146
+ this.browserHostBroadcastDispose = attachBrowserHostBroadcast(host, {
147
+ send: (ws, response) => this.send(ws, response),
148
+ broadcastToAll: (response) => this.broadcastToAll(response),
149
+ });
150
+ this.browserHostErrorDispose = () => host.off('error', onSupervisorError);
151
+ const onLeaseExpired = (event) => {
152
+ let owner = null;
153
+ try {
154
+ const driverState = getBrowserDriverState();
155
+ owner = driverState.getOwner(event.tabId);
156
+ driverState.clearOwner(event.tabId);
157
+ }
158
+ catch (err) {
159
+ captureException(err, { context: 'wsHandler.leaseExpired.clearOwner' });
160
+ }
161
+ const attributed = owner?.ownerAgentId === event.agentId ? owner : null;
162
+ void broadcastAgentTabState(host,
163
+ { broadcastToAll: (response) => this.broadcastToAll(response) }, event.agentId, attributed?.ownerAgentLabel ?? event.agentId, attributed?.ownerSessionId ? { sessionId: attributed.ownerSessionId } : {});
164
+ };
165
+ host.on('leaseExpired', onLeaseExpired);
166
+ this.browserHostLeaseExpiredDispose = () => host.off('leaseExpired', onLeaseExpired);
167
+ const driverState = getBrowserDriverState();
168
+ const onDriverState = (payload) => {
169
+ log('tx.driver_state', `tab=${payload.tabId.slice(0, 8)} state=${payload.state} mode=${payload.mode ?? '-'}`);
170
+ this.broadcastToAll({ type: 'browser_driver_state', data: payload });
171
+ };
172
+ driverState.on('change', onDriverState);
173
+ this.browserDriverStateDispose = () => driverState.off('change', onDriverState);
174
+ this.browserHostSdkDispose = attachSdkBrowserTransport(host, {
175
+ getWorkingDir: () => this.currentWorkingDir,
176
+ consent: {
177
+ listOwnerSessions: () => this.sessionRoles.listOwnerTierViewerIds(),
178
+ deliverToSession: (viewerId, frame) => {
179
+ const entry = this.sessionRoles.get(viewerId);
180
+ if (!entry)
181
+ return;
182
+ const out = { type: frame.type, data: frame.data };
183
+ out._target = viewerId;
184
+ this.send(entry.ws, out);
185
+ },
186
+ broadcast: (frame) => {
187
+ this.broadcastToAll({ type: frame.type, data: frame.data });
188
+ },
189
+ },
190
+ });
191
+ }
192
+ clearBrowserHost() {
193
+ setAgentBrowserHostAttached(false);
194
+ if (this.browserHostBroadcastDispose) {
195
+ this.browserHostBroadcastDispose();
196
+ this.browserHostBroadcastDispose = null;
197
+ }
198
+ if (this.browserHostLeaseExpiredDispose) {
199
+ this.browserHostLeaseExpiredDispose();
200
+ this.browserHostLeaseExpiredDispose = null;
201
+ }
202
+ if (this.browserHostErrorDispose) {
203
+ this.browserHostErrorDispose();
204
+ this.browserHostErrorDispose = null;
205
+ }
206
+ if (this.browserHostSdkDispose) {
207
+ this.browserHostSdkDispose();
208
+ this.browserHostSdkDispose = null;
209
+ }
210
+ if (this.browserDriverStateDispose) {
211
+ this.browserDriverStateDispose();
212
+ this.browserDriverStateDispose = null;
213
+ }
214
+ this.browserHost = null;
215
+ }
216
+ getRegistry(workingDir) {
217
+ if (!this.sessionRegistry) {
218
+ this.sessionRegistry = new SessionRegistry(workingDir);
219
+ }
220
+ return this.sessionRegistry;
221
+ }
222
+ getBrowserHost() {
223
+ return this.browserHost;
224
+ }
225
+ triggerAutoImplement(workingDir, boardId) {
226
+ maybeStartAutoImplement(this, workingDir, boardId);
227
+ }
228
+ setUsageReporter(reporter) {
229
+ this.usageReporter = reporter;
230
+ }
231
+ loadFrecencyData() {
232
+ try {
233
+ if (existsSync(this.frecencyPath)) {
234
+ const data = readFileSync(this.frecencyPath, 'utf-8');
235
+ return JSON.parse(data);
236
+ }
237
+ }
238
+ catch (error) {
239
+ console.error('[WebSocketImproviseHandler] Error loading frecency data:', error);
240
+ }
241
+ return {};
242
+ }
243
+ saveFrecencyData() {
244
+ try {
245
+ const dir = dirname(this.frecencyPath);
246
+ if (!existsSync(dir)) {
247
+ mkdirSync(dir, { recursive: true });
248
+ }
249
+ writeFileSync(this.frecencyPath, JSON.stringify(this.autocompleteService.getFrecencyData(), null, 2));
250
+ }
251
+ catch (error) {
252
+ console.error('[WebSocketImproviseHandler] Error saving frecency data:', error);
253
+ }
254
+ }
255
+ frecencySaveTimer = null;
256
+ recordFileSelection(filePath) {
257
+ this.autocompleteService.recordFileSelection(filePath);
258
+ if (!this.frecencySaveTimer) {
259
+ this.frecencySaveTimer = setTimeout(() => {
260
+ this.frecencySaveTimer = null;
261
+ this.saveFrecencyData();
262
+ }, 2000);
263
+ }
264
+ }
265
+ handleConnection(ws, workingDir) {
266
+ this.connections.set(ws, new Map());
267
+ this.allConnections.add(ws);
268
+ if (!ws._viewerId)
269
+ ws._viewerId = randomUUID();
270
+ if (workingDir)
271
+ this.currentWorkingDir = workingDir;
272
+ if (!this.gitHeadWatcher && workingDir) {
273
+ this.gitHeadWatcher = new GitHeadWatcher(workingDir, this);
274
+ this.gitHeadWatcher.start();
275
+ }
276
+ if (!this.skillsWatcher && workingDir) {
277
+ this.skillsWatcher = new SkillsWatcher(workingDir, this);
278
+ this.skillsWatcher.start();
279
+ }
280
+ if (!this.engineCredentialWatcher) {
281
+ this.engineCredentialWatcher = new EngineCredentialWatcher(this);
282
+ this.engineCredentialWatcher.start();
283
+ }
284
+ if (workingDir) {
285
+ getScheduler(workingDir, this).start();
286
+ getAgentScheduler(workingDir, this).start();
287
+ getQualityScheduler(workingDir).start();
288
+ }
289
+ if (!this.snippetRegistry && workingDir) {
290
+ const sources = defaultSnippetSources(workingDir);
291
+ this.snippetRegistry = new SnippetRegistry(sources);
292
+ this.snippetExpander = createExpander(this.snippetRegistry);
293
+ this.snippetWatcher = new SnippetWatcher(this.snippetRegistry, sources, () => {
294
+ this.broadcastToAll({ type: 'snippetListResult', data: buildSnippetListResultData(this.snippetRegistry) });
295
+ });
296
+ this.snippetWatcher.start();
297
+ }
298
+ if (this.browserHost) {
299
+ this.send(ws, {
300
+ type: 'browser_status',
301
+ data: this.browserHost.getStatus(),
302
+ });
303
+ }
304
+ }
305
+ async handleMessage(ws, message, workingDir) {
306
+ try {
307
+ const msg = JSON.parse(message);
308
+ if (isFileShareRequestType(msg.type) || msg._share !== undefined) {
309
+ handleFileShareMessage(this, ws, msg, workingDir);
310
+ return;
311
+ }
312
+ const tabId = msg.tabId || 'default';
313
+ const role = msg._role === undefined ? 'owner' : normalizeStoredRole(msg._role);
314
+ delete msg._role;
315
+ const explicitViewerStamp = msg._viewer !== undefined;
316
+ const viewerId = msg._viewer ?? ws._viewerId;
317
+ delete msg._viewer;
318
+ this.sessionRoles.noteMessage(viewerId, role, ws, { explicitViewerStamp, messageType: msg.type });
319
+ await this.dispatchMessage(ws, msg, tabId, workingDir, role, viewerId);
320
+ }
321
+ catch (error) {
322
+ console.error('[WebSocketImproviseHandler] Error handling message:', error);
323
+ captureException(error, { context: 'websocket.handleMessage' });
324
+ this.send(ws, {
325
+ type: 'error',
326
+ data: { message: error instanceof Error ? error.message : String(error) }
327
+ });
328
+ }
329
+ }
330
+ static DISPATCH = {
331
+ execute: 'session', cancel: 'session', getHistory: 'session', new: 'session', approve: 'session', reject: 'session', queueEnqueuePrompt: 'session', queueRemovePrompt: 'session', queueClearPrompts: 'session',
332
+ getTimeline: 'session',
333
+ getSessions: 'history', getSessionsCount: 'history', getSessionById: 'history', deleteSession: 'history', clearHistory: 'history', searchHistory: 'history',
334
+ exportSession: 'history', importSession: 'history',
335
+ autocomplete: 'file', readFile: 'file', recordSelection: 'file',
336
+ terminalInit: 'terminal', terminalReconnect: 'terminal', terminalList: 'terminal', terminalInput: 'terminal', terminalResize: 'terminal', terminalClose: 'terminal',
337
+ listDirectory: 'fileExplorer', writeFile: 'fileExplorer', createFile: 'fileExplorer', createDirectory: 'fileExplorer', deleteFile: 'fileExplorer', renameFile: 'fileExplorer', notifyFileOpened: 'fileExplorer', searchFileContents: 'fileExplorer', cancelSearch: 'fileExplorer', findDefinition: 'fileExplorer',
338
+ collabJoin: 'collab', collabLeave: 'collab', collabPush: 'collab', collabCursor: 'collab', collabFlush: 'collab',
339
+ gitStatus: 'git', gitStage: 'git', gitUnstage: 'git', gitCommit: 'git', gitCommitWithAI: 'git', gitPush: 'git', gitPull: 'git', gitLog: 'git', gitDiscoverRepos: 'git', gitSetDirectory: 'git', gitGetRemoteInfo: 'git', gitCreatePR: 'git', gitGeneratePRDescription: 'git', gitListBranches: 'git', gitCheckout: 'git', gitCreateBranch: 'git', gitDeleteBranch: 'git', gitDiff: 'git', gitShowCommit: 'git', gitCommitDiff: 'git', gitListTags: 'git', gitCreateTag: 'git', gitPushTag: 'git', gitWorktreeList: 'git', gitWorktreeCreate: 'git', gitWorktreeCreateAndAssign: 'git', gitWorktreeRemove: 'git', tabWorktreeSwitch: 'git', tabRepoWorktreeSwitch: 'git', gitWorktreePush: 'git', gitWorktreeCreatePR: 'git', gitMergePreview: 'git', gitWorktreeMerge: 'git', gitMergeAbort: 'git', gitMergeComplete: 'git', gitMergeStashPop: 'git', gitMergeDiscardBlockers: 'git',
340
+ planInit: 'plan', planGetState: 'plan', planListIssues: 'plan', planGetIssue: 'plan', planGetSprint: 'plan', planGetMilestone: 'plan', planUpdateIssue: 'plan', planDeleteIssue: 'plan', planScaffold: 'plan', planPrompt: 'plan', planExecute: 'plan', planExecuteEpic: 'plan', planPause: 'plan', planStop: 'plan', planResume: 'plan', planCreateBoard: 'plan', planUpdateBoard: 'plan', planArchiveBoard: 'plan', planRestoreBoard: 'plan', planGetBoard: 'plan', planGetBoardState: 'plan', planReorderBoards: 'plan', planSetActiveBoard: 'plan', planGetBoardArtifacts: 'plan', planExportBoard: 'plan', planImportBoard: 'plan', planDownloadBoard: 'plan', planInstantiateTemplate: 'plan', planCreateSprint: 'plan', planActivateSprint: 'plan', planCompleteSprint: 'plan', planGetSprintArtifacts: 'plan', chatToBoard: 'plan', queueEnqueueComposerPrompt: 'plan', queueRemoveComposerPrompt: 'plan', queueClearComposerPrompts: 'plan',
341
+ getPmTimeline: 'plan',
342
+ planScheduleCreate: 'schedule', planScheduleUpdate: 'schedule', planScheduleDelete: 'schedule', planScheduleList: 'schedule', planScheduleValidateSource: 'schedule',
343
+ agentScheduleCreate: 'agentSchedule', agentScheduleUpdate: 'agentSchedule', agentScheduleDelete: 'agentSchedule', agentScheduleList: 'agentSchedule',
344
+ fileUploadStart: 'fileUpload', fileUploadChunk: 'fileUpload', fileUploadComplete: 'fileUpload', fileUploadCancel: 'fileUpload',
345
+ fileDownloadStart: 'fileDownload', fileDownloadCancel: 'fileDownload',
346
+ sdkConnect: 'sdk', sdkRequest: 'sdk', sdkAgentRun: 'sdk', sdkPmWrite: 'sdk', sdkPmExecute: 'sdk', sdkPmSchedule: 'sdk', sdkFilesWrite: 'sdk', sdkStorageWrite: 'sdk', sdkCancel: 'sdk', sdkRevoke: 'sdk', sdkConsentDecision: 'sdk', sdkAgentSession: 'sdk', sdkAgentInteract: 'sdk', sdkGitWrite: 'sdk', sdkTerminalWrite: 'sdk', sdkToolsServe: 'sdk', sdkQualityWrite: 'sdk', sdkBrowserWrite: 'sdk', sdkInferenceRun: 'sdk', sdkEndpointsWrite: 'sdk',
347
+ appInstallLocal: 'apps', appServeStatus: 'apps', appStop: 'apps', appUpdate: 'apps', appGreenlightDecision: 'apps', appSpaceList: 'apps', appSpaceAdd: 'apps', appSpaceRemove: 'apps', appSpaceConfigure: 'apps',
348
+ browser_preflight: 'browser', browser_snapshot: 'browser', browser_resume_driver: 'browser',
349
+ browser_navigate: 'browser', browser_back: 'browser', browser_forward: 'browser', browser_reload: 'browser', browser_stop: 'browser',
350
+ browser_resize: 'browser', browser_set_bounds: 'browser', browser_set_device_profile: 'browser',
351
+ browser_input: 'browser',
352
+ browser_tab_open: 'browser', browser_tab_close: 'browser', browser_tab_select: 'browser',
353
+ browser_webrtc_answer: 'browser', browser_webrtc_ice: 'browser', browser_webrtc_ice_config: 'browser', browser_webrtc_state: 'browser', browser_webrtc_attach: 'browser',
354
+ browser_lease_release: 'browser', browser_agent_task: 'browser', browser_agent_approve: 'browser',
355
+ };
356
+ static APPS_READ_TYPES = new Set(['appServeStatus', 'appSpaceList']);
357
+ async dispatchMessage(ws, msg, tabId, workingDir, role, viewerId) {
358
+ if (await this.dispatchInlineMessage(ws, msg, tabId, workingDir, role))
359
+ return;
360
+ const domain = WebSocketImproviseHandler.DISPATCH[msg.type];
361
+ if (!domain) {
362
+ this.handleUnknownDomain(ws, msg, viewerId);
363
+ return;
364
+ }
365
+ if (msg.tabId && !hasScopeBinding(this, tabId)) {
366
+ restoreWorktreeFromRegistry(this, this.getRegistry(workingDir), tabId);
367
+ }
368
+ const effectiveDir = resolveScopeDir(this, tabId, workingDir);
369
+ return this.routeToDomainHandler(domain, ws, msg, tabId, workingDir, effectiveDir, role, viewerId);
370
+ }
371
+ handleUnknownDomain(ws, msg, viewerId) {
372
+ if (msg.type.startsWith('browser_')) {
373
+ console.warn(`[WebSocketImproviseHandler] Unrouted browser envelope: ${msg.type}`);
374
+ this.sendBrowserError(ws, 'unrouted_envelope', `CLI has no handler for envelope "${msg.type}". This is a wiring bug — please report.`, viewerId);
375
+ return;
376
+ }
377
+ if (msg.type.startsWith('plan')) {
378
+ console.warn(`[WebSocketImproviseHandler] Unrouted plan envelope: ${msg.type}`);
379
+ this.send(ws, {
380
+ type: 'planError',
381
+ data: { error: `This minion CLI has no handler for "${msg.type}". Update the minion CLI and try again.` },
382
+ });
383
+ return;
384
+ }
385
+ if (/^app[A-Z]/.test(msg.type)) {
386
+ console.warn(`[WebSocketImproviseHandler] Unrouted apps envelope: ${msg.type}`);
387
+ this.send(ws, {
388
+ type: 'appError',
389
+ data: {
390
+ operation: msg.type,
391
+ code: 'NOT_IMPLEMENTED',
392
+ message: `This minion CLI has no handler for "${msg.type}". Update the minion CLI and try again.`,
393
+ },
394
+ });
395
+ return;
396
+ }
397
+ console.warn(`[WebSocketImproviseHandler] Ignoring unknown message type: ${msg.type}`);
398
+ }
399
+ routeToDomainHandler(domain, ws, msg, tabId, workingDir, effectiveDir, role, viewerId) {
400
+ switch (domain) {
401
+ case 'session': return handleSessionMessage(this, ws, msg, tabId, role);
402
+ case 'history':
403
+ if (msg.type === 'importSession' && !can(role, 'execute')) {
404
+ this.sendPermissionDenied(ws, msg.type);
405
+ return;
406
+ }
407
+ return handleHistoryMessage(this, ws, msg, tabId, workingDir);
408
+ case 'file': return handleFileMessage(this, ws, msg, tabId, resolveFileDomainRoot(tabId, workingDir, effectiveDir), role);
409
+ case 'terminal':
410
+ if (!isViewOperation(msg.type) && !can(role, 'terminal')) {
411
+ this.sendPermissionDenied(ws, msg.type);
412
+ return;
413
+ }
414
+ return handleTerminalMessage(this, ws, msg, tabId, workingDir);
415
+ case 'fileExplorer': return handleFileExplorerMessage(this, ws, msg, tabId, effectiveDir, role);
416
+ case 'collab': return handleCollabMessage(this, ws, msg, effectiveDir, role, viewerId);
417
+ case 'git':
418
+ if (!isViewOperation(msg.type) && !can(role, 'git')) {
419
+ this.sendPermissionDenied(ws, msg.type);
420
+ return;
421
+ }
422
+ return handleGitMessage(this, ws, msg, tabId, workingDir);
423
+ case 'plan': return handlePlanMessage(this, ws, msg, tabId, workingDir, role);
424
+ case 'schedule': return handleScheduleMessage(this, ws, msg, tabId, workingDir, role);
425
+ case 'agentSchedule': return handleAgentScheduleMessage(this, ws, msg, workingDir, role);
426
+ case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId, effectiveDir, role);
427
+ case 'fileDownload': return this.handleFileDownloadMessage(ws, msg, tabId, effectiveDir);
428
+ case 'sdk': return handleSdkMessage(this, ws, msg, tabId, workingDir, role, viewerId);
429
+ case 'apps':
430
+ if (!WebSocketImproviseHandler.APPS_READ_TYPES.has(msg.type) && !can(role, 'editSettings')) {
431
+ this.sendPermissionDenied(ws, msg.type);
432
+ return;
433
+ }
434
+ return handleAppsMessage(this, ws, msg, workingDir);
435
+ case 'browser': return this.dispatchBrowserMessage(ws, msg, role, viewerId);
436
+ }
437
+ }
438
+ async dispatchInlineMessage(ws, msg, tabId, workingDir, role) {
439
+ if (await this.dispatchDelegatedMessage(ws, msg, tabId, workingDir, role))
440
+ return true;
441
+ switch (msg.type) {
442
+ case 'ping':
443
+ this.send(ws, { type: 'pong', tabId });
444
+ return true;
445
+ case 'resumeSession':
446
+ if (!msg.data?.historicalSessionId)
447
+ throw new Error('Historical session ID is required');
448
+ await resumeHistoricalSession(this, ws, tabId, workingDir, msg.data.historicalSessionId, msg.data);
449
+ return true;
450
+ case 'requestNotificationSummary':
451
+ if (!msg.data?.prompt || !msg.data?.output)
452
+ throw new Error('Prompt and output are required for notification summary');
453
+ await generateNotificationSummary(this, ws, tabId, msg.data.prompt, msg.data.output, workingDir);
454
+ return true;
455
+ case 'getSettings':
456
+ handleGetSettings(this, ws);
457
+ return true;
458
+ case 'updateSettings':
459
+ if (can(role, 'editSettings'))
460
+ handleUpdateSettings(this, ws, msg);
461
+ return true;
462
+ case 'getModelCatalog':
463
+ await handleGetModelCatalog(this, ws, msg);
464
+ return true;
465
+ case 'getSystemPromptProfiles':
466
+ case 'createSystemPrompt':
467
+ case 'readSystemPrompt':
468
+ case 'saveSystemPrompt':
469
+ case 'deleteSystemPrompt':
470
+ this.handleSystemPromptMessage(ws, msg, role);
471
+ return true;
472
+ case 'engineAuthStatus':
473
+ case 'setEngineApiKey':
474
+ case 'removeEngineApiKey':
475
+ case 'startEngineLogin':
476
+ case 'testEngineCredential':
477
+ if (can(role, 'editSettings'))
478
+ await handleEngineAuthMessage(this, ws, msg);
479
+ else
480
+ this.sendPermissionDenied(ws, msg.type);
481
+ return true;
482
+ case 'engineModelProfiles':
483
+ case 'setEngineModelProfile':
484
+ case 'removeEngineModelProfile':
485
+ if (can(role, 'editSettings'))
486
+ handleModelProfileMessage(this, ws, msg);
487
+ else
488
+ this.sendPermissionDenied(ws, msg.type);
489
+ return true;
490
+ case 'listSkills':
491
+ handleListSkills(this, ws, workingDir);
492
+ return true;
493
+ case 'snippetAutocomplete':
494
+ case 'snippetList':
495
+ handleSnippetMessage(this, ws, msg, tabId);
496
+ return true;
497
+ case 'snippetCreate':
498
+ if (can(role, 'editSettings'))
499
+ handleSnippetMessage(this, ws, msg, tabId);
500
+ return true;
501
+ case 'snippetGet':
502
+ case 'snippetUpdate':
503
+ if (can(role, 'editSettings'))
504
+ handleSnippetMessage(this, ws, msg, tabId);
505
+ return true;
506
+ case 'shutdownInstance':
507
+ this.handleShutdownInstance(ws, role, msg);
508
+ return true;
509
+ case 'restartInstance':
510
+ this.handleRestartInstance(ws, role, msg);
511
+ return true;
512
+ case 'askUserQuestionResponse':
513
+ if (can(role, 'execute'))
514
+ this.handleAskUserQuestionResponse(msg, tabId, ws);
515
+ return true;
516
+ default:
517
+ return false;
518
+ }
519
+ }
520
+ async dispatchDelegatedMessage(ws, msg, tabId, workingDir, role) {
521
+ if (await this.dispatchTabMessage(ws, msg, tabId, workingDir, role))
522
+ return true;
523
+ return false;
524
+ }
525
+ async dispatchTabMessage(ws, msg, tabId, workingDir, role) {
526
+ switch (msg.type) {
527
+ case 'initTab':
528
+ await initializeTab(this, ws, tabId, workingDir, msg.data?.tabName, msg.data);
529
+ return true;
530
+ case 'getActiveTabs':
531
+ handleGetActiveTabs(this, ws, workingDir);
532
+ return true;
533
+ case 'createTab':
534
+ await handleCreateTab(this, ws, workingDir, msg.data?.tabName, msg.data?.optimisticTabId);
535
+ return true;
536
+ case 'reorderTabs':
537
+ handleReorderTabs(this, ws, workingDir, msg.data?.tabOrder);
538
+ return true;
539
+ case 'syncTabMeta':
540
+ handleSyncTabMeta(this, ws, msg, tabId, workingDir);
541
+ return true;
542
+ case 'removeTab':
543
+ handleRemoveTab(this, ws, tabId, workingDir);
544
+ return true;
545
+ case 'markTabViewed':
546
+ handleMarkTabViewed(this, ws, tabId, workingDir);
547
+ return true;
548
+ case 'setTabEngine':
549
+ if (can(role, 'editSettings'))
550
+ handleSetTabEngine(this, ws, msg, tabId, workingDir);
551
+ return true;
552
+ default:
553
+ return false;
554
+ }
555
+ }
556
+ async dispatchBrowserMessage(ws, msg, role, viewerId) {
557
+ if (msg.type === 'browser_snapshot') {
558
+ this.handleBrowserSnapshotRequest(ws, viewerId);
559
+ return;
560
+ }
561
+ if (!can(role, 'driveBrowser')) {
562
+ if (msg.type !== 'browser_input')
563
+ log('drop', `type=${msg.type} reason=role role=${role}`);
564
+ return;
565
+ }
566
+ if (msg.type === 'browser_preflight') {
567
+ void this.handleBrowserPreflight(ws, viewerId);
568
+ return;
569
+ }
570
+ if (msg.type === 'browser_resume_driver') {
571
+ this.handleBrowserResumeDriver(msg);
572
+ return;
573
+ }
574
+ if (msg.type !== 'browser_input') {
575
+ log('dispatch', `type=${msg.type} viewer=${viewerId?.slice(0, 8) ?? '-'} stamped=${viewerId !== undefined && viewerId !== ws._viewerId} relay=${ws._isPlatformRelay === true} hostAttached=${this.browserHost !== null} hostState=${this.browserHost?.getStatus().state ?? 'n/a'}`);
576
+ }
577
+ if (!this.browserHost) {
578
+ this.sendBrowserHostUnavailable(ws, viewerId);
579
+ return;
580
+ }
581
+ const policy = browserHostPolicy(msg.type);
582
+ if (policy === 'start') {
583
+ await this.dispatchToReadyHost(ws, msg, role, this.browserHost, viewerId);
584
+ return;
585
+ }
586
+ const hostState = this.browserHost.getStatus().state;
587
+ if (policy === 'ifReady' && hostState !== 'ready') {
588
+ log('drop', `type=${msg.type} reason=session_frame_host_${hostState}`);
589
+ return;
590
+ }
591
+ await handleBrowserHostMessage(this.browserHost, ws, msg, undefined, this.browserResponseSink(), viewerId);
592
+ }
593
+ browserResponseSink() {
594
+ return {
595
+ send: (target, response) => this.send(target, response),
596
+ broadcastToAll: (response) => this.broadcastToAll(response),
597
+ };
598
+ }
599
+ async dispatchToReadyHost(ws, msg, role, host, viewerId) {
600
+ await this.ensureBrowserHostStarted(ws, viewerId);
601
+ const finalState = host.getStatus().state;
602
+ if (msg.type !== 'browser_input') {
603
+ log('dispatchToReadyHost', `type=${msg.type} finalState=${finalState}`);
604
+ }
605
+ if (finalState === 'ready') {
606
+ await handleBrowserHostMessage(host, ws, msg, can(role, 'driveBrowser') ? undefined : 'view', this.browserResponseSink(), viewerId);
607
+ return;
608
+ }
609
+ if (finalState === 'crashed') {
610
+ log.info('drop', `type=${msg.type} reason=crashed`);
611
+ return;
612
+ }
613
+ log.info('drop', `type=${msg.type} reason=unready state=${finalState}`);
614
+ this.sendBrowserError(ws, 'browser_host_unready', `Browser host did not reach ready (state=${finalState}); please retry.`, viewerId);
615
+ }
616
+ sendBrowserHostUnavailable(ws, viewerId) {
617
+ this.sendBrowserError(ws, 'browser_host_unavailable', 'Browser host is not configured on this CLI. Run a recent Minionry CLI build with the Electron host included.', viewerId);
618
+ }
619
+ browserHostStartPromise = null;
620
+ async ensureBrowserHostStarted(ws, viewerId) {
621
+ const host = this.browserHost;
622
+ if (!host)
623
+ return;
624
+ const state = host.getStatus().state;
625
+ log('ensureStarted.enter', `state=${state} inflight=${this.browserHostStartPromise !== null}`);
626
+ if (this.browserHostStartPromise) {
627
+ await this.joinInflightStart(ws, viewerId, host, this.browserHostStartPromise);
628
+ return;
629
+ }
630
+ if (state === 'ready') {
631
+ log('ensureStarted.alreadyReady', 'no-op');
632
+ return;
633
+ }
634
+ if (state === 'crashed') {
635
+ await this.spawnRecovery(ws, viewerId, host);
636
+ return;
637
+ }
638
+ await this.spawnNewStart(ws, viewerId, host, state);
639
+ }
640
+ async joinInflightStart(ws, viewerId, host, inflight) {
641
+ log('ensureStarted.awaitInflight', 'piggy-backing on in-flight start');
642
+ try {
643
+ await inflight;
644
+ log('ensureStarted.inflightResolved', `state=${host.getStatus().state}`);
645
+ }
646
+ catch (err) {
647
+ log.info('ensureStarted.inflightRejected', err instanceof Error ? err.message : String(err));
648
+ this.sendBrowserError(ws, 'browser_host_start_failed', err instanceof Error ? err.message : String(err), viewerId);
649
+ }
650
+ }
651
+ async spawnNewStart(ws, viewerId, host, fromState) {
652
+ log.info('ensureStarted.spawn', `state=${fromState} → starting`);
653
+ const startedAt = Date.now();
654
+ const startPromise = host.start({ orchestraId: 'default' });
655
+ this.browserHostStartPromise = startPromise;
656
+ try {
657
+ await startPromise;
658
+ log.info('ensureStarted.resolved', `state=${host.getStatus().state} elapsedMs=${Date.now() - startedAt}`);
659
+ }
660
+ catch (err) {
661
+ log.info('ensureStarted.rejected', `elapsedMs=${Date.now() - startedAt} err=${err instanceof Error ? err.message : String(err)}`);
662
+ captureException(err, { context: 'wsHandler.ensureBrowserHostStarted' });
663
+ this.sendBrowserError(ws, 'browser_host_start_failed', err instanceof Error ? err.message : String(err), viewerId);
664
+ }
665
+ finally {
666
+ if (this.browserHostStartPromise === startPromise) {
667
+ this.browserHostStartPromise = null;
668
+ }
669
+ }
670
+ }
671
+ async spawnRecovery(ws, viewerId, host) {
672
+ log.info('ensureStarted.recover', 'state=crashed → recovering');
673
+ const startedAt = Date.now();
674
+ const recoverPromise = host.recover();
675
+ this.browserHostStartPromise = recoverPromise;
676
+ try {
677
+ await recoverPromise;
678
+ log.info('ensureStarted.recovered', `state=${host.getStatus().state} elapsedMs=${Date.now() - startedAt}`);
679
+ }
680
+ catch (err) {
681
+ log.info('ensureStarted.recoverRejected', `elapsedMs=${Date.now() - startedAt} err=${err instanceof Error ? err.message : String(err)}`);
682
+ captureException(err, { context: 'wsHandler.ensureBrowserHostStarted.recover' });
683
+ this.sendBrowserError(ws, 'browser_host_start_failed', err instanceof Error ? err.message : String(err), viewerId);
684
+ }
685
+ finally {
686
+ if (this.browserHostStartPromise === recoverPromise) {
687
+ this.browserHostStartPromise = null;
688
+ }
689
+ }
690
+ }
691
+ handleBrowserResumeDriver(msg) {
692
+ try {
693
+ const tabId = (msg.data?.tabId ?? msg.tabId);
694
+ log('resumeDriver', `tab=${tabId?.slice(0, 8) ?? 'missing'}`);
695
+ if (!tabId)
696
+ return;
697
+ getBrowserDriverState().resume(tabId);
698
+ }
699
+ catch (err) {
700
+ captureException(err);
701
+ }
702
+ }
703
+ sendBrowserError(ws, code, message, viewerId) {
704
+ log.info('tx.browser_error', `code=${code} viewer=${viewerId?.slice(0, 8) ?? '-'} message=${message}`);
705
+ this.send(ws, addressToViewer({
706
+ type: 'browser_error',
707
+ data: { code, message },
708
+ }, ws, viewerId));
709
+ }
710
+ sendPermissionDenied(ws, operation) {
711
+ this.send(ws, {
712
+ type: 'permissionDenied',
713
+ data: { operation, message: 'Your role does not permit this action' },
714
+ });
715
+ }
716
+ handleSystemPromptMessage(ws, msg, role) {
717
+ if (msg.type === 'getSystemPromptProfiles') {
718
+ handleGetSystemPromptProfiles(this, ws);
719
+ return;
720
+ }
721
+ if (!can(role, 'editSettings'))
722
+ return;
723
+ switch (msg.type) {
724
+ case 'createSystemPrompt':
725
+ handleCreateSystemPrompt(this, ws, msg);
726
+ return;
727
+ case 'readSystemPrompt':
728
+ handleReadSystemPrompt(this, ws, msg);
729
+ return;
730
+ case 'saveSystemPrompt':
731
+ handleSaveSystemPrompt(this, ws, msg);
732
+ return;
733
+ case 'deleteSystemPrompt':
734
+ handleDeleteSystemPrompt(this, ws, msg);
735
+ return;
736
+ }
737
+ }
738
+ handleFileDownloadMessage(ws, msg, tabId, workingDir) {
739
+ if (!this.fileDownloadHandler) {
740
+ this.fileDownloadHandler = new FileDownloadHandler(workingDir);
741
+ }
742
+ const handler = this.fileDownloadHandler;
743
+ const send = this.send.bind(this);
744
+ switch (msg.type) {
745
+ case 'fileDownloadStart':
746
+ handler.handleDownloadStart(ws, send, tabId, msg.data);
747
+ break;
748
+ case 'fileDownloadCancel':
749
+ handler.handleDownloadCancel(ws, send, tabId, msg.data);
750
+ break;
751
+ }
752
+ }
753
+ handleFileUploadMessage(ws, msg, tabId, workingDir, role) {
754
+ if (!this.fileUploadHandler) {
755
+ this.fileUploadHandler = new FileUploadHandler(this);
756
+ }
757
+ const handler = this.fileUploadHandler;
758
+ const send = this.send.bind(this);
759
+ switch (msg.type) {
760
+ case 'fileUploadStart':
761
+ handler.handleUploadStart(ws, send, tabId, msg.data, workingDir, can(role, 'editFiles') ? undefined : 'view');
762
+ break;
763
+ case 'fileUploadChunk':
764
+ handler.handleUploadChunk(ws, send, tabId, msg.data);
765
+ break;
766
+ case 'fileUploadComplete':
767
+ handler.handleUploadComplete(ws, send, tabId, msg.data);
768
+ break;
769
+ case 'fileUploadCancel':
770
+ handler.handleUploadCancel(ws, send, tabId, msg.data);
771
+ break;
772
+ }
773
+ }
774
+ handleClose(ws) {
775
+ const tabMap = this.connections.get(ws);
776
+ if (tabMap) {
777
+ this.cleanupConnectionResources(tabMap);
778
+ }
779
+ this.connections.delete(ws);
780
+ this.allConnections.delete(ws);
781
+ cleanupTerminalSubscribers(this, ws);
782
+ this.sessionRoles.dropWs(ws);
783
+ if (ws._viewerId) {
784
+ teardownSdkForViewer(ws._viewerId);
785
+ }
786
+ if (ws._viewerId) {
787
+ this.collabSessions.dropViewer(ws._viewerId);
788
+ }
789
+ if (this.browserHost) {
790
+ try {
791
+ onBrowserWsContextClosed(this.browserHost, ws);
792
+ }
793
+ catch (err) {
794
+ captureException(err, { context: 'websocket.handleClose.browserDetach' });
795
+ }
796
+ }
797
+ if (this.allConnections.size === 0) {
798
+ teardownSdkOnTransportClose();
799
+ this.sessionRoles.clear();
800
+ this.disposeGlobalSingletons();
801
+ this.reapOrphanPtys();
802
+ }
803
+ }
804
+ handleTransportDetach(ws) {
805
+ const tabMap = this.connections.get(ws);
806
+ if (tabMap) {
807
+ for (const tabId of tabMap.keys()) {
808
+ const searchProcess = this.activeSearches.get(tabId);
809
+ if (searchProcess) {
810
+ try {
811
+ searchProcess.kill();
812
+ }
813
+ catch { }
814
+ this.activeSearches.delete(tabId);
815
+ }
816
+ }
817
+ }
818
+ this.connections.delete(ws);
819
+ this.allConnections.delete(ws);
820
+ cleanupTerminalSubscribers(this, ws);
821
+ this.sessionRoles.dropWs(ws);
822
+ if (this.browserHost) {
823
+ try {
824
+ onBrowserWsContextClosed(this.browserHost, ws);
825
+ }
826
+ catch (err) {
827
+ captureException(err, { context: 'websocket.handleTransportDetach.browserDetach' });
828
+ }
829
+ }
830
+ this.collabSessions.onTransportDetach();
831
+ }
832
+ handleBrowserViewerDisconnected(viewerId) {
833
+ teardownSdkForViewer(viewerId);
834
+ this.sessionRoles.dropViewer(viewerId);
835
+ this.collabSessions.dropViewer(viewerId);
836
+ if (!this.browserHost)
837
+ return;
838
+ try {
839
+ onBrowserViewerDisconnected(this.browserHost, viewerId);
840
+ }
841
+ catch (err) {
842
+ captureException(err, { context: 'websocket.handleBrowserViewerDisconnected' });
843
+ }
844
+ }
845
+ disposeGlobalSingletons() {
846
+ this.collabSessions.dispose('shutdown');
847
+ if (this.fileUploadHandler) {
848
+ this.fileUploadHandler.destroy();
849
+ this.fileUploadHandler = null;
850
+ }
851
+ if (this.fileDownloadHandler) {
852
+ this.fileDownloadHandler.destroy();
853
+ this.fileDownloadHandler = null;
854
+ }
855
+ if (this.gitHeadWatcher) {
856
+ this.gitHeadWatcher.stop();
857
+ this.gitHeadWatcher = null;
858
+ }
859
+ if (this.skillsWatcher) {
860
+ this.skillsWatcher.stop();
861
+ this.skillsWatcher = null;
862
+ }
863
+ if (this.engineCredentialWatcher) {
864
+ this.engineCredentialWatcher.stop();
865
+ this.engineCredentialWatcher = null;
866
+ }
867
+ if (this.snippetWatcher) {
868
+ this.snippetWatcher.stop();
869
+ this.snippetWatcher = null;
870
+ }
871
+ this.snippetRegistry = null;
872
+ this.snippetExpander = null;
873
+ }
874
+ reapOrphanPtys() {
875
+ const ptyManager = getPTYManager();
876
+ const activeTerminals = ptyManager.getActiveTerminals();
877
+ if (activeTerminals.length === 0)
878
+ return;
879
+ console.log(`[handler] No web subscribers — closing ${activeTerminals.length} orphan PTY${activeTerminals.length === 1 ? '' : 's'}`);
880
+ ptyManager.closeAll();
881
+ }
882
+ executingSessionSummaries() {
883
+ const tabBySession = new Map();
884
+ for (const tabMap of this.connections.values()) {
885
+ for (const [tabId, sessionId] of tabMap)
886
+ tabBySession.set(sessionId, tabId);
887
+ }
888
+ const executing = [];
889
+ for (const [sessionId, session] of this.sessions) {
890
+ if (!session.isExecuting)
891
+ continue;
892
+ executing.push({
893
+ sessionId,
894
+ ...(tabBySession.has(sessionId) ? { tabId: tabBySession.get(sessionId) } : {}),
895
+ ...(session.executionStartTimestamp ? { executionStartTimestamp: session.executionStartTimestamp } : {}),
896
+ });
897
+ }
898
+ return executing;
899
+ }
900
+ settleExecutingSessions(origin) {
901
+ let settled = 0;
902
+ for (const [sessionId, session] of this.sessions) {
903
+ try {
904
+ if (session.settleForLifecycle(origin)) {
905
+ settled++;
906
+ console.log(`[WebSocketImproviseHandler] Settled executing session ${sessionId} on ${origin}`);
907
+ }
908
+ }
909
+ catch (err) {
910
+ console.error(`[WebSocketImproviseHandler] Failed to settle session ${sessionId} on ${origin}:`, err);
911
+ }
912
+ }
913
+ return settled;
914
+ }
915
+ refuseIfExecuting(ws, msg, action) {
916
+ if (msg.data?.force === true)
917
+ return false;
918
+ const executing = this.executingSessionSummaries();
919
+ if (executing.length === 0)
920
+ return false;
921
+ console.log(`[WebSocketImproviseHandler] Refusing ${msg.type}: ${executing.length} session(s) executing (${executing.map((e) => e.sessionId).join(', ')})`);
922
+ this.send(ws, {
923
+ type: 'error',
924
+ data: {
925
+ code: 'executions_in_flight',
926
+ message: `${executing.length} agent session${executing.length === 1 ? ' is' : 's are'} still running — wait for ${executing.length === 1 ? 'it' : 'them'} to finish or stop ${executing.length === 1 ? 'it' : 'them'} first, then ${action} again.`,
927
+ executing,
928
+ },
929
+ });
930
+ return true;
931
+ }
932
+ cleanupConnectionResources(tabMap) {
933
+ const sessionIds = new Set(tabMap.values());
934
+ for (const sessionId of sessionIds) {
935
+ const session = this.sessions.get(sessionId);
936
+ if (!session)
937
+ continue;
938
+ if (session.isExecuting)
939
+ continue;
940
+ session.destroy();
941
+ this.sessions.delete(sessionId);
942
+ }
943
+ for (const tabId of tabMap.keys()) {
944
+ const searchProcess = this.activeSearches.get(tabId);
945
+ if (searchProcess) {
946
+ try {
947
+ searchProcess.kill();
948
+ }
949
+ catch { }
950
+ this.activeSearches.delete(tabId);
951
+ }
952
+ }
953
+ }
954
+ send(ws, response) {
955
+ try {
956
+ ws.send(JSON.stringify(response));
957
+ }
958
+ catch (error) {
959
+ console.error('[WebSocketImproviseHandler] Error sending message:', error);
960
+ }
961
+ }
962
+ broadcastToOthers(sender, response) {
963
+ for (const ws of this.allConnections) {
964
+ if (ws !== sender) {
965
+ this.send(ws, response);
966
+ }
967
+ }
968
+ }
969
+ broadcastToAll(response) {
970
+ for (const ws of this.allConnections) {
971
+ this.send(ws, response);
972
+ }
973
+ }
974
+ cleanupSession(sessionId) {
975
+ this.sessions.delete(sessionId);
976
+ }
977
+ handleAskUserQuestionResponse(msg, tabId, ws) {
978
+ const data = msg.data;
979
+ const toolUseId = typeof data?.toolUseId === 'string' ? data.toolUseId : '';
980
+ const answersIn = data?.answers;
981
+ if (!toolUseId)
982
+ return;
983
+ const answers = {};
984
+ if (answersIn && typeof answersIn === 'object' && !Array.isArray(answersIn)) {
985
+ for (const [k, v] of Object.entries(answersIn)) {
986
+ if (typeof v === 'string')
987
+ answers[k] = v;
988
+ }
989
+ }
990
+ const resolved = resolvePendingQuestion(toolUseId, answers);
991
+ if (resolved)
992
+ return;
993
+ if (Object.keys(answers).length === 0)
994
+ return;
995
+ const session = resolveTabSession(this, ws, tabId);
996
+ if (!session)
997
+ return;
998
+ session.executePrompt(buildLateAnswerPrompt(answers)).catch((err) => {
999
+ captureException(err, { context: 'wsHandler.askUserQuestion.lateReengage' });
1000
+ });
1001
+ }
1002
+ deniesEnvironmentScope(ws, msg) {
1003
+ const declared = parseDeclaredEnvironment(msg.data?.environment);
1004
+ const mine = resolveCliEnvironment();
1005
+ if (declared === mine)
1006
+ return false;
1007
+ console.log(`[WebSocketImproviseHandler] Rejecting ${msg.type} addressed to "${declared}" — this Space is "${mine}"`);
1008
+ this.send(ws, {
1009
+ type: 'error',
1010
+ data: {
1011
+ code: 'environment_mismatch',
1012
+ message: `This Space runs in the ${mine} environment and cannot be controlled from ${declared}.`,
1013
+ },
1014
+ });
1015
+ return true;
1016
+ }
1017
+ handleShutdownInstance(ws, role, msg) {
1018
+ if (role !== 'owner') {
1019
+ console.log('[WebSocketImproviseHandler] Rejecting shutdownInstance from non-owner');
1020
+ this.send(ws, {
1021
+ type: 'error',
1022
+ data: {
1023
+ code: 'forbidden',
1024
+ message: 'Only the owner can shut down this instance.'
1025
+ }
1026
+ });
1027
+ return;
1028
+ }
1029
+ if (this.deniesEnvironmentScope(ws, msg))
1030
+ return;
1031
+ if (this.refuseIfExecuting(ws, msg, 'shut down'))
1032
+ return;
1033
+ if (this.shutdownInProgress) {
1034
+ console.log('[WebSocketImproviseHandler] shutdownInstance already in progress — ignoring duplicate request');
1035
+ return;
1036
+ }
1037
+ this.shutdownInProgress = true;
1038
+ console.log('[WebSocketImproviseHandler] shutdownInstance requested by owner — broadcasting shuttingDown and exiting');
1039
+ this.collabSessions.flushAll('shutdown');
1040
+ this.settleExecutingSessions('shutdown');
1041
+ this.broadcastToAll({ type: 'shuttingDown', data: { reason: 'user-requested' } });
1042
+ setTimeout(() => {
1043
+ if (this.instanceRegistry) {
1044
+ this.instanceRegistry.unregister();
1045
+ }
1046
+ process.exit(0);
1047
+ }, 100);
1048
+ }
1049
+ handleRestartInstance(ws, role, msg) {
1050
+ if (role !== 'owner') {
1051
+ console.log('[WebSocketImproviseHandler] Rejecting restartInstance from non-owner');
1052
+ this.send(ws, {
1053
+ type: 'error',
1054
+ data: {
1055
+ code: 'forbidden',
1056
+ message: 'Only the owner can restart this instance.'
1057
+ }
1058
+ });
1059
+ return;
1060
+ }
1061
+ if (this.deniesEnvironmentScope(ws, msg))
1062
+ return;
1063
+ if (this.refuseIfExecuting(ws, msg, 'restart'))
1064
+ return;
1065
+ if (this.shutdownInProgress) {
1066
+ console.log('[WebSocketImproviseHandler] restartInstance ignored — shutdown/restart already in progress');
1067
+ return;
1068
+ }
1069
+ this.shutdownInProgress = true;
1070
+ const workingDirectory = this.instanceRegistry?.getCurrentInstance()?.workingDirectory ??
1071
+ process.env.MINIONRY_WORKING_DIR ??
1072
+ process.cwd();
1073
+ console.log(`[WebSocketImproviseHandler] restartInstance requested by owner — updating and relaunching minion in ${workingDirectory}`);
1074
+ this.collabSessions.flushAll('shutdown');
1075
+ this.settleExecutingSessions('restart');
1076
+ this.broadcastToAll({ type: 'shuttingDown', data: { reason: 'restart' } });
1077
+ setTimeout(() => {
1078
+ try {
1079
+ respawnInstance(workingDirectory, { update: true });
1080
+ }
1081
+ catch (err) {
1082
+ console.error('[WebSocketImproviseHandler] Failed to relaunch minion on restart:', err);
1083
+ captureException(err, { context: 'wsHandler.restartInstance.respawn' });
1084
+ }
1085
+ if (this.instanceRegistry) {
1086
+ this.instanceRegistry.unregister();
1087
+ }
1088
+ process.exit(0);
1089
+ }, 100);
1090
+ }
1091
+ async handleBrowserPreflight(ws, viewerId) {
1092
+ log('preflight.enter', `viewer=${viewerId?.slice(0, 8) ?? '-'}`);
1093
+ try {
1094
+ const { preflight: runPreflight } = await import('../browser/preflight.js');
1095
+ let result = await runPreflight();
1096
+ log.info('preflight.result', `ok=${result.ok} failing=${result.checks.filter(c => !c.ok).map(c => c.id).join(',') || '-'}`);
1097
+ if (result.checks.some((c) => c.id === 'electron' && !c.ok)) {
1098
+ const { ensureElectronProvisioned, isElectronProvisioning } = await import('../browser/electron-provision.js');
1099
+ log('preflight.provision', isElectronProvisioning() ? 'join-in-flight' : 'start');
1100
+ this.replyPreflight(ws, viewerId, result, { chromiumInstalling: true });
1101
+ try {
1102
+ const outcome = await ensureElectronProvisioned((line) => log('provision', line));
1103
+ log('preflight.provision.done', `ok=${outcome.ok} action=${outcome.action}`);
1104
+ }
1105
+ catch (err) {
1106
+ log('preflight.provision.error', err instanceof Error ? err.message : String(err));
1107
+ }
1108
+ result = await runPreflight();
1109
+ log.info('preflight.reresult', `ok=${result.ok} failing=${result.checks.filter(c => !c.ok).map(c => c.id).join(',') || '-'}`);
1110
+ }
1111
+ this.deliverPreflight(ws, viewerId, result);
1112
+ }
1113
+ catch (err) {
1114
+ log.error('preflight.threw', err);
1115
+ this.send(ws, addressToViewer({
1116
+ type: 'browser_preflight_result',
1117
+ data: {
1118
+ ok: false,
1119
+ message: err instanceof Error ? err.message : String(err),
1120
+ },
1121
+ }, ws, viewerId));
1122
+ }
1123
+ }
1124
+ deliverPreflight(ws, viewerId, result) {
1125
+ const host = this.browserHost;
1126
+ if (host)
1127
+ this.sendBrowserSnapshot(ws, viewerId, host);
1128
+ this.replyPreflight(ws, viewerId, result, { chromiumInstalling: false });
1129
+ if (!host)
1130
+ return;
1131
+ void this.sendAgentRosterSnapshot(ws, viewerId, host);
1132
+ if (result.ok && host.getStatus().state === 'crashed') {
1133
+ log('preflight.autoRecover', 'state=crashed && result.ok → spawnRecovery');
1134
+ void this.spawnRecovery(ws, viewerId, host);
1135
+ }
1136
+ }
1137
+ handleBrowserSnapshotRequest(ws, viewerId) {
1138
+ const host = this.browserHost;
1139
+ if (!host) {
1140
+ log('snapshot.noHost', `viewer=${viewerId?.slice(0, 8) ?? '-'}`);
1141
+ return;
1142
+ }
1143
+ this.sendBrowserSnapshot(ws, viewerId, host);
1144
+ void this.sendAgentRosterSnapshot(ws, viewerId, host);
1145
+ }
1146
+ sendBrowserSnapshot(ws, viewerId, host) {
1147
+ const roster = host.getTabsSnapshot();
1148
+ const status = host.getStatus();
1149
+ const driverState = getBrowserDriverState();
1150
+ const driving = [];
1151
+ for (const tab of roster.tabs) {
1152
+ const snapshot = driverState.getSnapshot(tab.id);
1153
+ if (snapshot)
1154
+ driving.push(snapshot);
1155
+ }
1156
+ log.info('snapshot.tx', `viewer=${viewerId?.slice(0, 8) ?? '-'} tabs=${roster.tabs.length} current=${roster.currentTabId?.slice(0, 8) ?? 'null'} host=${status.state} driverStates=${driving.length}`);
1157
+ const unicast = (type, data) => {
1158
+ this.send(ws, addressToViewer({ type, data }, ws, viewerId));
1159
+ };
1160
+ unicast('browser_state_update', { tabs: roster.tabs, currentTabId: roster.currentTabId });
1161
+ unicast('browser_status', status);
1162
+ for (const snapshot of driving)
1163
+ unicast('browser_driver_state', snapshot);
1164
+ }
1165
+ async sendAgentRosterSnapshot(ws, viewerId, host) {
1166
+ if (host.getStatus().state !== 'ready')
1167
+ return;
1168
+ try {
1169
+ const leases = await host.listLeases();
1170
+ const driverState = getBrowserDriverState();
1171
+ const toDevice = {
1172
+ broadcastToAll: (response) => this.send(ws, addressToViewer(response, ws, viewerId)),
1173
+ };
1174
+ for (const agentId of new Set(leases.map((lease) => lease.agentId))) {
1175
+ const owner = leases
1176
+ .filter((lease) => lease.agentId === agentId)
1177
+ .map((lease) => driverState.getOwner(lease.tabId))
1178
+ .find((candidate) => candidate?.ownerAgentId === agentId);
1179
+ const run = activeBrowserAgentTarget(agentId);
1180
+ await broadcastAgentTabState(host, toDevice, agentId, owner?.ownerAgentLabel ?? run?.label ?? agentId, {
1181
+ ...(run?.engine ? { engine: run.engine } : {}),
1182
+ ...(run?.model ? { model: run.model } : {}),
1183
+ ...(owner?.ownerSessionId ? { sessionId: owner.ownerSessionId } : {}),
1184
+ });
1185
+ }
1186
+ }
1187
+ catch (err) {
1188
+ log.warn('snapshot.agentRoster.failed', err instanceof Error ? err.message : String(err));
1189
+ }
1190
+ }
1191
+ replyPreflight(ws, viewerId, result, opts) {
1192
+ this.send(ws, addressToViewer({
1193
+ type: 'browser_preflight_result',
1194
+ data: toBrowserPreflightPayload(result, opts),
1195
+ }, ws, viewerId));
1196
+ }
1197
+ }