@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.
- package/LICENSE +14 -0
- package/PRIVACY.md +357 -0
- package/README.md +57 -0
- package/THIRD_PARTY_NOTICES.md +245 -0
- package/bin/branding.js +4 -0
- package/bin/commands/browser-install.js +483 -0
- package/bin/commands/create-sandbox.js +542 -0
- package/bin/commands/fingerprint.js +101 -0
- package/bin/commands/login.js +400 -0
- package/bin/commands/logout.js +93 -0
- package/bin/commands/minions.js +211 -0
- package/bin/commands/sandboxes.js +309 -0
- package/bin/commands/status.js +298 -0
- package/bin/commands/whoami.js +153 -0
- package/bin/launch-env.js +35 -0
- package/bin/machine-identity.js +221 -0
- package/bin/machine-layout.js +200 -0
- package/bin/minion.js +839 -0
- package/bin/platform-endpoints.js +8 -0
- package/bin/postinstall.js +52 -0
- package/bin/pty-doctor.js +326 -0
- package/bin/pty-locator.js +110 -0
- package/bin/pty-provision.js +186 -0
- package/bin/sandbox/Dockerfile +78 -0
- package/bin/sandbox/entrypoint.sh +14 -0
- package/bin/sandbox/install-repo.sh +53 -0
- package/bin/sandbox/seccomp-userns.json +883 -0
- package/bin/sandbox-config.js +306 -0
- package/dist/bin/machine-identity.js +221 -0
- package/dist/bin/machine-layout.js +200 -0
- package/dist/electron-host/agent-lease.js +141 -0
- package/dist/electron-host/dom-driver.js +642 -0
- package/dist/electron-host/ipc-protocol.js +89 -0
- package/dist/electron-host/js-driver.js +216 -0
- package/dist/electron-host/view-manager.js +254 -0
- package/dist/server/cli/engine-resume.js +49 -0
- package/dist/server/cli/eta-estimator.js +278 -0
- package/dist/server/cli/headless/background-activity.js +109 -0
- package/dist/server/cli/headless/claude-invoker-process.js +614 -0
- package/dist/server/cli/headless/claude-invoker-stall.js +150 -0
- package/dist/server/cli/headless/claude-invoker-stream.js +555 -0
- package/dist/server/cli/headless/claude-invoker-tools.js +148 -0
- package/dist/server/cli/headless/claude-invoker.js +151 -0
- package/dist/server/cli/headless/engine-availability.js +92 -0
- package/dist/server/cli/headless/engine-notice.js +185 -0
- package/dist/server/cli/headless/haiku-assessments.js +293 -0
- package/dist/server/cli/headless/headless-logger.js +75 -0
- package/dist/server/cli/headless/index.js +9 -0
- package/dist/server/cli/headless/mcp-config.js +314 -0
- package/dist/server/cli/headless/native-timeout-detector.js +72 -0
- package/dist/server/cli/headless/output-utils.js +71 -0
- package/dist/server/cli/headless/prompt-utils.js +82 -0
- package/dist/server/cli/headless/resilient-runner.js +326 -0
- package/dist/server/cli/headless/retry-strategies.js +292 -0
- package/dist/server/cli/headless/runner.js +245 -0
- package/dist/server/cli/headless/stall-assessor.js +295 -0
- package/dist/server/cli/headless/tool-watchdog.js +447 -0
- package/dist/server/cli/headless/types.js +18 -0
- package/dist/server/cli/history-portability.js +85 -0
- package/dist/server/cli/improvisation-attachments.js +99 -0
- package/dist/server/cli/improvisation-history-store.js +121 -0
- package/dist/server/cli/improvisation-movements.js +137 -0
- package/dist/server/cli/improvisation-output-queue.js +34 -0
- package/dist/server/cli/improvisation-retry.js +8 -0
- package/dist/server/cli/improvisation-session-manager.js +1016 -0
- package/dist/server/cli/improvisation-types.js +31 -0
- package/dist/server/cli/plan-lifecycle.js +72 -0
- package/dist/server/cli/prompt-builders.js +371 -0
- package/dist/server/cli/retry/retry-best-result.js +28 -0
- package/dist/server/cli/retry/retry-context-loss.js +75 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js +213 -0
- package/dist/server/cli/retry/retry-resume-strategy.js +20 -0
- package/dist/server/cli/retry/retry-runner-factory.js +92 -0
- package/dist/server/cli/retry/retry-tool-results.js +22 -0
- package/dist/server/cli/retry/retry-types.js +3 -0
- package/dist/server/engines/EngineEvent.js +57 -0
- package/dist/server/engines/claude/ClaudeCodeEngine.js +316 -0
- package/dist/server/engines/claude/claude-command.js +32 -0
- package/dist/server/engines/codex/CodexEngine.js +469 -0
- package/dist/server/engines/codex/codex-auth.js +203 -0
- package/dist/server/engines/codex/codex-invoker.js +205 -0
- package/dist/server/engines/codex/codex-provision.js +215 -0
- package/dist/server/engines/credentials/credential-probe.js +88 -0
- package/dist/server/engines/credentials/model-profiles.js +249 -0
- package/dist/server/engines/credentials/provider-credentials.js +146 -0
- package/dist/server/engines/engine-event-stream.js +52 -0
- package/dist/server/engines/execution/engine-session.js +380 -0
- package/dist/server/engines/factory.js +42 -0
- package/dist/server/engines/hermes/HermesEngine.js +628 -0
- package/dist/server/engines/hermes/hermes-auth.js +159 -0
- package/dist/server/engines/hermes/hermes-gateway-home.js +271 -0
- package/dist/server/engines/hermes/hermes-invoker.js +350 -0
- package/dist/server/engines/hermes/hermes-provision.js +318 -0
- package/dist/server/engines/read-only.js +20 -0
- package/dist/server/engines/types.js +27 -0
- package/dist/server/handlers/company-execution-registry.js +67 -0
- package/dist/server/handlers/company-handlers.js +514 -0
- package/dist/server/handlers/company-triggers.js +189 -0
- package/dist/server/handlers/company-types.js +3 -0
- package/dist/server/index.js +244 -0
- package/dist/server/mcp/app-tools-bridge.js +110 -0
- package/dist/server/mcp/app-tools-mcp-server.js +99 -0
- package/dist/server/mcp/bouncer-integration.js +744 -0
- package/dist/server/mcp/browser-mcp-server.js +915 -0
- package/dist/server/mcp/browser-redaction.js +103 -0
- package/dist/server/mcp/classifier/BouncerClassifier.js +3 -0
- package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +212 -0
- package/dist/server/mcp/classifier/factory.js +124 -0
- package/dist/server/mcp/classifier/health.js +54 -0
- package/dist/server/mcp/classifier/telemetry.js +0 -0
- package/dist/server/mcp/company-patterns.js +104 -0
- package/dist/server/mcp/department-profiles.js +133 -0
- package/dist/server/mcp/quality-bridge.js +153 -0
- package/dist/server/mcp/quality-mcp-server.js +105 -0
- package/dist/server/mcp/security-analysis.js +384 -0
- package/dist/server/mcp/security-audit.js +152 -0
- package/dist/server/mcp/security-patterns.js +314 -0
- package/dist/server/mcp/server.js +194 -0
- package/dist/server/mcp/test-utils/agent-simulator.js +301 -0
- package/dist/server/mcp/test-utils/e2e-agent.js +103 -0
- package/dist/server/mcp/test-utils/e2e-capture.js +92 -0
- package/dist/server/mcp/test-utils/e2e-client.js +116 -0
- package/dist/server/mcp/test-utils/e2e-fixtures.js +87 -0
- package/dist/server/mcp/test-utils/e2e-gating.js +8 -0
- package/dist/server/mcp/test-utils/e2e-harness.js +8 -0
- package/dist/server/mcp/test-utils/e2e-scenarios.js +105 -0
- package/dist/server/routes/files.js +18 -0
- package/dist/server/routes/improvise.js +64 -0
- package/dist/server/routes/index.js +7 -0
- package/dist/server/routes/instances.js +41 -0
- package/dist/server/routes/internal.js +900 -0
- package/dist/server/routes/notifications.js +110 -0
- package/dist/server/server-setup.js +282 -0
- package/dist/server/services/agent-push.js +148 -0
- package/dist/server/services/analytics.js +197 -0
- package/dist/server/services/apps/build.js +249 -0
- package/dist/server/services/apps/greenlight.js +199 -0
- package/dist/server/services/apps/install.js +101 -0
- package/dist/server/services/apps/layout.js +35 -0
- package/dist/server/services/apps/serve.js +665 -0
- package/dist/server/services/apps/space-apps.js +248 -0
- package/dist/server/services/auth.js +48 -0
- package/dist/server/services/browser/chrome-sandbox.js +245 -0
- package/dist/server/services/browser/chromium-launch-spec.js +51 -0
- package/dist/server/services/browser/claude-reasoner.js +669 -0
- package/dist/server/services/browser/debug-log.js +3 -0
- package/dist/server/services/browser/driver-state.js +546 -0
- package/dist/server/services/browser/electron-provision.js +256 -0
- package/dist/server/services/browser/host-internal.js +245 -0
- package/dist/server/services/browser/host-mock.js +334 -0
- package/dist/server/services/browser/host-types.js +3 -0
- package/dist/server/services/browser/host.js +1067 -0
- package/dist/server/services/browser/index.js +4 -0
- package/dist/server/services/browser/ipc-client.js +354 -0
- package/dist/server/services/browser/pipewire-sink.js +199 -0
- package/dist/server/services/browser/preflight.js +590 -0
- package/dist/server/services/browser/reasoning-agent.js +659 -0
- package/dist/server/services/browser/sandbox-policy.js +169 -0
- package/dist/server/services/browser/stderr-noise-filter.js +26 -0
- package/dist/server/services/browser/telemetry.js +107 -0
- package/dist/server/services/browser/xvfb-supervisor.js +139 -0
- package/dist/server/services/browser-frame-policy.js +56 -0
- package/dist/server/services/client-id.js +32 -0
- package/dist/server/services/collab/registry.js +408 -0
- package/dist/server/services/collab/session.js +489 -0
- package/dist/server/services/collab/text-diff.js +28 -0
- package/dist/server/services/collab/types.js +19 -0
- package/dist/server/services/e2ee-group-key.js +197 -0
- package/dist/server/services/e2ee-identity.js +203 -0
- package/dist/server/services/file-explorer-ops.js +328 -0
- package/dist/server/services/file-transfer-zip.js +188 -0
- package/dist/server/services/files.js +111 -0
- package/dist/server/services/git/branch-ops.js +62 -0
- package/dist/server/services/git/command.js +68 -0
- package/dist/server/services/git/commit-message.js +74 -0
- package/dist/server/services/git/diff-ops.js +56 -0
- package/dist/server/services/git/haiku.js +76 -0
- package/dist/server/services/git/head-watcher.js +226 -0
- package/dist/server/services/git/index.js +17 -0
- package/dist/server/services/git/log-ops.js +98 -0
- package/dist/server/services/git/merge-ops.js +314 -0
- package/dist/server/services/git/parsers.js +401 -0
- package/dist/server/services/git/remote-ops.js +192 -0
- package/dist/server/services/git/status-ops.js +105 -0
- package/dist/server/services/git/suggestion-ops.js +80 -0
- package/dist/server/services/git/tag-ops.js +36 -0
- package/dist/server/services/git/types.js +4 -0
- package/dist/server/services/git/worktree-ops.js +114 -0
- package/dist/server/services/instances.js +337 -0
- package/dist/server/services/model-catalog.js +247 -0
- package/dist/server/services/observability/posthog.js +153 -0
- package/dist/server/services/pathUtils.js +114 -0
- package/dist/server/services/plan/agent-loader.js +97 -0
- package/dist/server/services/plan/agent-manager.js +363 -0
- package/dist/server/services/plan/agent-resolver.js +74 -0
- package/dist/server/services/plan/agents/assess-stall.md +33 -0
- package/dist/server/services/plan/agents/browser-planner.md +53 -0
- package/dist/server/services/plan/agents/browser-reasoner.md +114 -0
- package/dist/server/services/plan/agents/check-injection.md +95 -0
- package/dist/server/services/plan/agents/classify-error.md +30 -0
- package/dist/server/services/plan/agents/code-review.md +142 -0
- package/dist/server/services/plan/agents/commit-message.md +28 -0
- package/dist/server/services/plan/agents/detect-context-loss.md +30 -0
- package/dist/server/services/plan/agents/execute-issue.md +58 -0
- package/dist/server/services/plan/agents/fix-quality.md +25 -0
- package/dist/server/services/plan/agents/plan-coordinator.md +72 -0
- package/dist/server/services/plan/agents/pr-description.md +29 -0
- package/dist/server/services/plan/agents/retry-task.md +27 -0
- package/dist/server/services/plan/agents/review-code.md +38 -0
- package/dist/server/services/plan/agents/review-quality.md +45 -0
- package/dist/server/services/plan/agents/triage-findings.md +78 -0
- package/dist/server/services/plan/agents/verify-review.md +57 -0
- package/dist/server/services/plan/approval-evaluator.js +143 -0
- package/dist/server/services/plan/board-archive.js +102 -0
- package/dist/server/services/plan/board-config.js +108 -0
- package/dist/server/services/plan/board-export.js +214 -0
- package/dist/server/services/plan/composer-prompt.js +115 -0
- package/dist/server/services/plan/composer.js +664 -0
- package/dist/server/services/plan/config-installer.js +103 -0
- package/dist/server/services/plan/dependency-resolver.js +137 -0
- package/dist/server/services/plan/executor.js +696 -0
- package/dist/server/services/plan/front-matter.js +43 -0
- package/dist/server/services/plan/issue-classification.js +10 -0
- package/dist/server/services/plan/issue-effort.js +182 -0
- package/dist/server/services/plan/issue-loader.js +35 -0
- package/dist/server/services/plan/issue-prompt-builder.js +152 -0
- package/dist/server/services/plan/issue-retry.js +42 -0
- package/dist/server/services/plan/issue-writer.js +102 -0
- package/dist/server/services/plan/metrics-collector.js +272 -0
- package/dist/server/services/plan/output-manager.js +77 -0
- package/dist/server/services/plan/parser-core.js +435 -0
- package/dist/server/services/plan/parser-migration.js +108 -0
- package/dist/server/services/plan/parser.js +258 -0
- package/dist/server/services/plan/plan-runner-factory.js +68 -0
- package/dist/server/services/plan/pm-timeline.js +320 -0
- package/dist/server/services/plan/progress-log.js +101 -0
- package/dist/server/services/plan/prompt-builder.js +142 -0
- package/dist/server/services/plan/quality-delta.js +274 -0
- package/dist/server/services/plan/readiness-planner.js +34 -0
- package/dist/server/services/plan/review-gate.js +409 -0
- package/dist/server/services/plan/review-report.js +134 -0
- package/dist/server/services/plan/review-verdict-stream.js +161 -0
- package/dist/server/services/plan/state-reconciler.js +238 -0
- package/dist/server/services/plan/template-diff.js +175 -0
- package/dist/server/services/plan/template-instantiator.js +516 -0
- package/dist/server/services/plan/template-manifest.js +56 -0
- package/dist/server/services/plan/template-types.js +3 -0
- package/dist/server/services/plan/trigger-evaluator.js +211 -0
- package/dist/server/services/plan/types.js +3 -0
- package/dist/server/services/plan/watcher.js +73 -0
- package/dist/server/services/platform-credentials.js +56 -0
- package/dist/server/services/platform-e2ee.js +421 -0
- package/dist/server/services/platform-envelope.js +102 -0
- package/dist/server/services/platform-heartbeat.js +88 -0
- package/dist/server/services/platform-reconnect.js +60 -0
- package/dist/server/services/platform-token-lifecycle.js +95 -0
- package/dist/server/services/platform-url.js +17 -0
- package/dist/server/services/platform.js +656 -0
- package/dist/server/services/runtime-info.js +9 -0
- package/dist/server/services/schedule/agent-schedule-store.js +232 -0
- package/dist/server/services/schedule/agent-scheduler.js +323 -0
- package/dist/server/services/schedule/daily-timing.js +105 -0
- package/dist/server/services/schedule/quality-schedule-launch.js +11 -0
- package/dist/server/services/schedule/quality-schedule-store.js +211 -0
- package/dist/server/services/schedule/quality-scheduler.js +215 -0
- package/dist/server/services/schedule/run-bootstrap.js +329 -0
- package/dist/server/services/schedule/schedule-store.js +238 -0
- package/dist/server/services/schedule/schedule-wire.js +77 -0
- package/dist/server/services/schedule/scheduler.js +310 -0
- package/dist/server/services/schedule/template-source.js +97 -0
- package/dist/server/services/sdk/agent-sessions.js +264 -0
- package/dist/server/services/sdk/agents.js +557 -0
- package/dist/server/services/sdk/app-endpoints.js +48 -0
- package/dist/server/services/sdk/app-tools.js +303 -0
- package/dist/server/services/sdk/audit.js +48 -0
- package/dist/server/services/sdk/browser-transport.js +188 -0
- package/dist/server/services/sdk/browser.js +605 -0
- package/dist/server/services/sdk/composer.js +129 -0
- package/dist/server/services/sdk/connect.js +273 -0
- package/dist/server/services/sdk/consent-cooldown.js +31 -0
- package/dist/server/services/sdk/endpoint-url.js +141 -0
- package/dist/server/services/sdk/endpoints.js +314 -0
- package/dist/server/services/sdk/file-change-notify.js +36 -0
- package/dist/server/services/sdk/files-app.js +163 -0
- package/dist/server/services/sdk/files-search.js +131 -0
- package/dist/server/services/sdk/files-transfer.js +279 -0
- package/dist/server/services/sdk/files-workspace.js +233 -0
- package/dist/server/services/sdk/git.js +520 -0
- package/dist/server/services/sdk/grant-store.js +318 -0
- package/dist/server/services/sdk/host-ops.js +84 -0
- package/dist/server/services/sdk/inference-claude.js +214 -0
- package/dist/server/services/sdk/inference.js +247 -0
- package/dist/server/services/sdk/live-registry.js +42 -0
- package/dist/server/services/sdk/local-app-bindings.js +71 -0
- package/dist/server/services/sdk/pipeline.js +190 -0
- package/dist/server/services/sdk/pm-execute.js +228 -0
- package/dist/server/services/sdk/pm-planning-tap.js +29 -0
- package/dist/server/services/sdk/pm-schedule.js +165 -0
- package/dist/server/services/sdk/pm.js +634 -0
- package/dist/server/services/sdk/quality-change-notify.js +20 -0
- package/dist/server/services/sdk/quality-schedule.js +165 -0
- package/dist/server/services/sdk/quality.js +602 -0
- package/dist/server/services/sdk/rate-limits.js +198 -0
- package/dist/server/services/sdk/registry.js +597 -0
- package/dist/server/services/sdk/revoke.js +86 -0
- package/dist/server/services/sdk/run-profile.js +41 -0
- package/dist/server/services/sdk/sandbox.js +89 -0
- package/dist/server/services/sdk/storage.js +110 -0
- package/dist/server/services/sdk/streams.js +147 -0
- package/dist/server/services/sdk/terminal.js +201 -0
- package/dist/server/services/sdk/test-utils/workshop-browser-app.js +466 -0
- package/dist/server/services/sentry.js +99 -0
- package/dist/server/services/settings.js +374 -0
- package/dist/server/services/snippets/expander.js +87 -0
- package/dist/server/services/snippets/index.js +6 -0
- package/dist/server/services/snippets/loader.js +119 -0
- package/dist/server/services/snippets/parser.js +52 -0
- package/dist/server/services/snippets/types.js +3 -0
- package/dist/server/services/snippets/watcher.js +60 -0
- package/dist/server/services/space-layout.js +80 -0
- package/dist/server/services/system-prompts.js +219 -0
- package/dist/server/services/terminal/pty-manager.js +206 -0
- package/dist/server/services/terminal/pty-utils.js +150 -0
- package/dist/server/services/timeline/assembler.js +890 -0
- package/dist/server/services/timeline/contract.js +196 -0
- package/dist/server/services/timeline/index.js +4 -0
- package/dist/server/services/websocket/agent-schedule-handlers.js +217 -0
- package/dist/server/services/websocket/apps-handlers.js +399 -0
- package/dist/server/services/websocket/ask-user-question-bridge.js +112 -0
- package/dist/server/services/websocket/autocomplete-index.js +415 -0
- package/dist/server/services/websocket/autocomplete-match.js +243 -0
- package/dist/server/services/websocket/autocomplete.js +407 -0
- package/dist/server/services/websocket/browser-agent-runtime.js +208 -0
- package/dist/server/services/websocket/browser-device-profile.js +46 -0
- package/dist/server/services/websocket/browser-handlers.js +377 -0
- package/dist/server/services/websocket/browser-host-broadcast.js +179 -0
- package/dist/server/services/websocket/browser-ice-config.js +121 -0
- package/dist/server/services/websocket/browser-payloads.js +222 -0
- package/dist/server/services/websocket/browser-viewer-lifecycle.js +84 -0
- package/dist/server/services/websocket/browser-viewer-registry.js +118 -0
- package/dist/server/services/websocket/collab-broadcast.js +11 -0
- package/dist/server/services/websocket/collab-handlers.js +116 -0
- package/dist/server/services/websocket/commit-suggestion-service.js +132 -0
- package/dist/server/services/websocket/debug-log.js +38 -0
- package/dist/server/services/websocket/dispatch-util.js +10 -0
- package/dist/server/services/websocket/engine-auth-handlers.js +323 -0
- package/dist/server/services/websocket/engine-credential-watcher.js +83 -0
- package/dist/server/services/websocket/engine-profile-handlers.js +92 -0
- package/dist/server/services/websocket/file-definition-handlers.js +180 -0
- package/dist/server/services/websocket/file-download-handler.js +291 -0
- package/dist/server/services/websocket/file-explorer-handlers.js +291 -0
- package/dist/server/services/websocket/file-search-handlers.js +267 -0
- package/dist/server/services/websocket/file-share-handlers.js +328 -0
- package/dist/server/services/websocket/file-transfer-http.js +361 -0
- package/dist/server/services/websocket/file-upload-handler.js +267 -0
- package/dist/server/services/websocket/file-utils.js +346 -0
- package/dist/server/services/websocket/git-branch-handlers.js +90 -0
- package/dist/server/services/websocket/git-diff-handlers.js +59 -0
- package/dist/server/services/websocket/git-handlers.js +212 -0
- package/dist/server/services/websocket/git-head-watcher.js +85 -0
- package/dist/server/services/websocket/git-log-handlers.js +104 -0
- package/dist/server/services/websocket/git-pr-handlers.js +56 -0
- package/dist/server/services/websocket/git-tag-handlers.js +53 -0
- package/dist/server/services/websocket/git-utils.js +11 -0
- package/dist/server/services/websocket/git-worktree-handlers.js +521 -0
- package/dist/server/services/websocket/handler-context.js +113 -0
- package/dist/server/services/websocket/handler.js +1197 -0
- package/dist/server/services/websocket/index.js +5 -0
- package/dist/server/services/websocket/movement-written-files.js +26 -0
- package/dist/server/services/websocket/msg-id-tracker.js +48 -0
- package/dist/server/services/websocket/plan-board-handlers.js +410 -0
- package/dist/server/services/websocket/plan-execution-handlers.js +244 -0
- package/dist/server/services/websocket/plan-handlers.js +67 -0
- package/dist/server/services/websocket/plan-helpers.js +161 -0
- package/dist/server/services/websocket/plan-issue-handlers.js +168 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js +190 -0
- package/dist/server/services/websocket/plan-transfer-handlers.js +310 -0
- package/dist/server/services/websocket/pm-timeline-broadcast.js +28 -0
- package/dist/server/services/websocket/post-session-scan.js +132 -0
- package/dist/server/services/websocket/prompt-queue-handlers.js +96 -0
- package/dist/server/services/websocket/quality-agent-cost.js +229 -0
- package/dist/server/services/websocket/quality-agent-notes.js +113 -0
- package/dist/server/services/websocket/quality-change-activity.js +187 -0
- package/dist/server/services/websocket/quality-complexity.js +345 -0
- package/dist/server/services/websocket/quality-dependencies.js +445 -0
- package/dist/server/services/websocket/quality-duplication.js +162 -0
- package/dist/server/services/websocket/quality-eta.js +65 -0
- package/dist/server/services/websocket/quality-fingerprint.js +104 -0
- package/dist/server/services/websocket/quality-grading.js +575 -0
- package/dist/server/services/websocket/quality-handlers.js +48 -0
- package/dist/server/services/websocket/quality-linting.js +270 -0
- package/dist/server/services/websocket/quality-operations.js +54 -0
- package/dist/server/services/websocket/quality-persistence.js +410 -0
- package/dist/server/services/websocket/quality-review-agent.js +526 -0
- package/dist/server/services/websocket/quality-run.js +220 -0
- package/dist/server/services/websocket/quality-secrets.js +0 -0
- package/dist/server/services/websocket/quality-service.js +775 -0
- package/dist/server/services/websocket/quality-tools.js +383 -0
- package/dist/server/services/websocket/quality-triage-agent.js +101 -0
- package/dist/server/services/websocket/quality-types.js +149 -0
- package/dist/server/services/websocket/recommended-answers.js +28 -0
- package/dist/server/services/websocket/schedule-handlers.js +197 -0
- package/dist/server/services/websocket/sdk-agent-host.js +569 -0
- package/dist/server/services/websocket/sdk-browser-host.js +459 -0
- package/dist/server/services/websocket/sdk-composer-host.js +10 -0
- package/dist/server/services/websocket/sdk-git-host.js +115 -0
- package/dist/server/services/websocket/sdk-handlers.js +242 -0
- package/dist/server/services/websocket/sdk-pm-execution-host.js +63 -0
- package/dist/server/services/websocket/sdk-pm-schedule-host.js +21 -0
- package/dist/server/services/websocket/sdk-quality-schedule-host.js +34 -0
- package/dist/server/services/websocket/sdk-terminal-host.js +140 -0
- package/dist/server/services/websocket/session-handlers.js +565 -0
- package/dist/server/services/websocket/session-history.js +335 -0
- package/dist/server/services/websocket/session-initialization.js +315 -0
- package/dist/server/services/websocket/session-registry.js +340 -0
- package/dist/server/services/websocket/session-role-directory.js +39 -0
- package/dist/server/services/websocket/settings-handlers.js +276 -0
- package/dist/server/services/websocket/skill-handlers.js +324 -0
- package/dist/server/services/websocket/skill-watcher.js +83 -0
- package/dist/server/services/websocket/snippet-handlers.js +261 -0
- package/dist/server/services/websocket/tab-broadcast.js +35 -0
- package/dist/server/services/websocket/tab-event-buffer.js +110 -0
- package/dist/server/services/websocket/tab-event-replay.js +47 -0
- package/dist/server/services/websocket/tab-handlers.js +211 -0
- package/dist/server/services/websocket/terminal-handlers.js +268 -0
- package/dist/server/services/websocket/types.js +96 -0
- package/dist/server/types/company-messages.js +102 -0
- package/dist/server/utils/agent-manager.js +202 -0
- package/dist/server/utils/console-timestamps.js +24 -0
- package/dist/server/utils/file-store.js +112 -0
- package/dist/server/utils/paths.js +28 -0
- package/dist/server/utils/port-manager.js +43 -0
- package/dist/server/utils/port.js +204 -0
- package/electron-host/dist/agent-lease.js +141 -0
- package/electron-host/dist/capture-recovery.js +77 -0
- package/electron-host/dist/codec-policy.js +37 -0
- package/electron-host/dist/controller-channel.js +60 -0
- package/electron-host/dist/controller-handlers.js +153 -0
- package/electron-host/dist/device-emulator.js +106 -0
- package/electron-host/dist/dom-driver.js +642 -0
- package/electron-host/dist/domain-registry.js +159 -0
- package/electron-host/dist/domain-validators.js +308 -0
- package/electron-host/dist/editable-focus-reporter.js +143 -0
- package/electron-host/dist/fake-peer.test-util.js +105 -0
- package/electron-host/dist/host-log.js +31 -0
- package/electron-host/dist/host-wire.js +11 -0
- package/electron-host/dist/input-router.js +421 -0
- package/electron-host/dist/ipc-protocol.js +89 -0
- package/electron-host/dist/ipc-server.js +193 -0
- package/electron-host/dist/js-driver.js +216 -0
- package/electron-host/dist/main-guards.js +70 -0
- package/electron-host/dist/main.js +716 -0
- package/electron-host/dist/roster-sync.js +87 -0
- package/electron-host/dist/rpc-context.js +1 -0
- package/electron-host/dist/rpc-tab-methods.js +138 -0
- package/electron-host/dist/rpc-webrtc-methods.js +121 -0
- package/electron-host/dist/screen-capture-cache.js +147 -0
- package/electron-host/dist/sdk-injector.js +166 -0
- package/electron-host/dist/sdp.js +102 -0
- package/electron-host/dist/session-surface.js +78 -0
- package/electron-host/dist/surface-sizing.js +59 -0
- package/electron-host/dist/view-manager.js +254 -0
- package/electron-host/dist/viewer-registry.js +46 -0
- package/electron-host/dist/viewer-session.js +288 -0
- package/electron-host/dist/webrtc-channels.js +14 -0
- package/electron-host/dist/webrtc-controller.js +57 -0
- package/electron-host/dist/webrtc-ice-filter.js +13 -0
- package/electron-host/dist/webrtc-peer.js +45 -0
- package/electron-host/package.json +12 -0
- package/electron-host/tsconfig.json +23 -0
- package/electron-host/webrtc-controller-preload.cjs +3 -0
- package/electron-host/webrtc-controller.html +14 -0
- package/package.json +85 -0
- package/vendor/e2ee/LICENSE +14 -0
- package/vendor/e2ee/package.json +14 -0
- package/vendor/e2ee/src/index.js +310 -0
|
@@ -0,0 +1,915 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
// Copyright (c) 2025-present Minionry, Inc.
|
|
3
|
+
// SPDX-License-Identifier: LicenseRef-Minionry-Software
|
|
4
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
5
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
+
export const BROWSER_MCP_SERVER_NAME = 'minionry-browser';
|
|
8
|
+
export const HEARTBEAT_INTERVAL_MS = 10_000;
|
|
9
|
+
export const DEFAULT_RPC_TIMEOUT_MS = 15_000;
|
|
10
|
+
export const RPC_DEADLINE_SLACK_MS = 5_000;
|
|
11
|
+
export function rpcDeadlineMs(params) {
|
|
12
|
+
const own = params?.timeoutMs;
|
|
13
|
+
if (typeof own !== 'number' || !Number.isFinite(own) || own <= 0)
|
|
14
|
+
return DEFAULT_RPC_TIMEOUT_MS;
|
|
15
|
+
return Math.max(DEFAULT_RPC_TIMEOUT_MS, own + RPC_DEADLINE_SLACK_MS);
|
|
16
|
+
}
|
|
17
|
+
export class LeaseManager {
|
|
18
|
+
client;
|
|
19
|
+
log;
|
|
20
|
+
setIntervalFn;
|
|
21
|
+
clearIntervalFn;
|
|
22
|
+
heartbeatIntervalMs;
|
|
23
|
+
owned = new Set();
|
|
24
|
+
heartbeatHandle = null;
|
|
25
|
+
heartbeatTicks = 0;
|
|
26
|
+
lastDroppedTabs = [];
|
|
27
|
+
constructor(deps) {
|
|
28
|
+
this.client = deps.client;
|
|
29
|
+
this.log = deps.log ?? ((m) => console.error(m));
|
|
30
|
+
this.setIntervalFn = deps.setIntervalFn ?? ((handler, ms) => setInterval(handler, ms));
|
|
31
|
+
this.clearIntervalFn = deps.clearIntervalFn ?? clearInterval;
|
|
32
|
+
this.heartbeatIntervalMs = deps.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS;
|
|
33
|
+
}
|
|
34
|
+
ownedTabs() {
|
|
35
|
+
return [...this.owned];
|
|
36
|
+
}
|
|
37
|
+
trackForTest(tabId) {
|
|
38
|
+
this.owned.add(tabId);
|
|
39
|
+
}
|
|
40
|
+
async ensure(tabId) {
|
|
41
|
+
if (this.owned.has(tabId)) {
|
|
42
|
+
const hb = await this.client.call('agent.tab.heartbeat', { tabIds: [tabId] });
|
|
43
|
+
throwIfErr(hb);
|
|
44
|
+
const stillLeased = extractStillLeased(hb);
|
|
45
|
+
if (!stillLeased.includes(tabId)) {
|
|
46
|
+
this.owned.delete(tabId);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const lease = await this.client.call('agent.tab.lease', { tabId });
|
|
53
|
+
if (lease.body.error) {
|
|
54
|
+
const code = lease.body.error.code;
|
|
55
|
+
if (code === 'tab_already_leased') {
|
|
56
|
+
const owner = lease.body.error.data
|
|
57
|
+
?.currentOwner;
|
|
58
|
+
throw new TabAlreadyLeasedError(tabId, owner);
|
|
59
|
+
}
|
|
60
|
+
throw new BrowserOpError(code, lease.body.error.message, lease.body.error.data);
|
|
61
|
+
}
|
|
62
|
+
this.owned.add(tabId);
|
|
63
|
+
}
|
|
64
|
+
async release(tabId) {
|
|
65
|
+
this.owned.delete(tabId);
|
|
66
|
+
try {
|
|
67
|
+
await this.client.call('agent.tab.release', { tabId });
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
this.log(`[BrowserMcp] release(${tabId}) failed: ${stringifyError(err)}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
start() {
|
|
74
|
+
if (this.heartbeatHandle)
|
|
75
|
+
return;
|
|
76
|
+
this.heartbeatHandle = this.setIntervalFn(() => {
|
|
77
|
+
void this.tick();
|
|
78
|
+
}, this.heartbeatIntervalMs);
|
|
79
|
+
}
|
|
80
|
+
async stop() {
|
|
81
|
+
if (this.heartbeatHandle) {
|
|
82
|
+
this.clearIntervalFn(this.heartbeatHandle);
|
|
83
|
+
this.heartbeatHandle = null;
|
|
84
|
+
}
|
|
85
|
+
const tabs = this.ownedTabs();
|
|
86
|
+
await Promise.all(tabs.map((t) => this.release(t)));
|
|
87
|
+
}
|
|
88
|
+
async tick() {
|
|
89
|
+
this.heartbeatTicks += 1;
|
|
90
|
+
if (this.owned.size === 0) {
|
|
91
|
+
this.lastDroppedTabs = [];
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const tabIds = this.ownedTabs();
|
|
95
|
+
try {
|
|
96
|
+
const res = await this.client.call('agent.tab.heartbeat', { tabIds });
|
|
97
|
+
if (res.body.error) {
|
|
98
|
+
this.log(`[BrowserMcp] heartbeat error: ${res.body.error.message}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const stillLeased = extractStillLeased(res);
|
|
102
|
+
const dropped = tabIds.filter((t) => !stillLeased.includes(t));
|
|
103
|
+
this.lastDroppedTabs = dropped;
|
|
104
|
+
for (const t of dropped) {
|
|
105
|
+
this.owned.delete(t);
|
|
106
|
+
this.log(`[BrowserMcp] lost lease on tab ${t}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
this.log(`[BrowserMcp] heartbeat threw: ${stringifyError(err)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export class BrowserOpError extends Error {
|
|
115
|
+
code;
|
|
116
|
+
data;
|
|
117
|
+
constructor(code, message, data) {
|
|
118
|
+
super(message);
|
|
119
|
+
this.name = 'BrowserOpError';
|
|
120
|
+
this.code = code;
|
|
121
|
+
this.data = data;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export class TabAlreadyLeasedError extends BrowserOpError {
|
|
125
|
+
tabId;
|
|
126
|
+
currentOwner;
|
|
127
|
+
constructor(tabId, currentOwner) {
|
|
128
|
+
super('tab_already_leased', `Tab ${tabId} is already leased by another agent`, { currentOwner });
|
|
129
|
+
this.name = 'TabAlreadyLeasedError';
|
|
130
|
+
this.tabId = tabId;
|
|
131
|
+
this.currentOwner = currentOwner;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function throwIfErr(res) {
|
|
135
|
+
if (res.body.error) {
|
|
136
|
+
throw new BrowserOpError(res.body.error.code, res.body.error.message, res.body.error.data);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function extractStillLeased(res) {
|
|
140
|
+
const r = res.body.result;
|
|
141
|
+
return Array.isArray(r?.stillLeased)
|
|
142
|
+
? r.stillLeased.filter((t) => typeof t === 'string')
|
|
143
|
+
: [];
|
|
144
|
+
}
|
|
145
|
+
function stringifyError(err) {
|
|
146
|
+
if (err instanceof Error)
|
|
147
|
+
return err.message;
|
|
148
|
+
return String(err);
|
|
149
|
+
}
|
|
150
|
+
function ok(text, extra) {
|
|
151
|
+
return { content: [{ type: 'text', text }], ...(extra ? { _meta: extra } : {}) };
|
|
152
|
+
}
|
|
153
|
+
function okJson(payload, extra) {
|
|
154
|
+
return {
|
|
155
|
+
content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
|
|
156
|
+
...(extra ? { _meta: extra } : {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function fail(code, message, data) {
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: 'text', text: `${code}: ${message}` }],
|
|
162
|
+
isError: true,
|
|
163
|
+
_meta: { errorCode: code, ...(data !== undefined ? { data } : {}) },
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function resolveTabId(args, ctx) {
|
|
167
|
+
const t = args.tabId;
|
|
168
|
+
if (typeof t === 'string' && t)
|
|
169
|
+
return t;
|
|
170
|
+
if (ctx.defaultTabId)
|
|
171
|
+
return ctx.defaultTabId;
|
|
172
|
+
throw new BrowserOpError('tabId_required', 'no tabId provided and no MINIONRY_DEFAULT_TAB_ID configured');
|
|
173
|
+
}
|
|
174
|
+
function navigationTabId(args, ctx) {
|
|
175
|
+
const named = args.tabId;
|
|
176
|
+
if (typeof named === 'string' && named)
|
|
177
|
+
return named;
|
|
178
|
+
return ctx.defaultTabId;
|
|
179
|
+
}
|
|
180
|
+
async function navigateLeased(ctx, args, method, params = {}) {
|
|
181
|
+
const tabId = navigationTabId(args, ctx);
|
|
182
|
+
if (tabId === undefined) {
|
|
183
|
+
await rpc(ctx, method, params);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
await withLease(ctx, tabId, () => rpc(ctx, method, { ...params, tabId }));
|
|
187
|
+
}
|
|
188
|
+
async function withLease(ctx, tabId, fn) {
|
|
189
|
+
await ctx.leases.ensure(tabId);
|
|
190
|
+
return fn();
|
|
191
|
+
}
|
|
192
|
+
async function rpc(ctx, method, params) {
|
|
193
|
+
const res = await ctx.client.call(method, params, { timeoutMs: rpcDeadlineMs(params) });
|
|
194
|
+
throwIfErr(res);
|
|
195
|
+
return res.body.result;
|
|
196
|
+
}
|
|
197
|
+
const tools = [
|
|
198
|
+
{
|
|
199
|
+
name: 'browser_snapshot',
|
|
200
|
+
description: 'Capture an accessibility-tree snapshot of the current page. Returns a tree of nodes with stable refs that other browser_* tools accept.',
|
|
201
|
+
inputSchema: {
|
|
202
|
+
type: 'object',
|
|
203
|
+
properties: { tabId: { type: 'string' } },
|
|
204
|
+
},
|
|
205
|
+
handler: async (ctx, args) => {
|
|
206
|
+
const tabId = resolveTabId(args, ctx);
|
|
207
|
+
const snap = await withLease(ctx, tabId, () => rpc(ctx, 'dom.snapshot', { tabId }));
|
|
208
|
+
return okJson(snap);
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'browser_click',
|
|
213
|
+
description: 'Click an element identified by its snapshot ref.',
|
|
214
|
+
inputSchema: {
|
|
215
|
+
type: 'object',
|
|
216
|
+
required: ['ref'],
|
|
217
|
+
properties: {
|
|
218
|
+
tabId: { type: 'string' },
|
|
219
|
+
ref: { type: 'string' },
|
|
220
|
+
button: { type: 'string', enum: ['left', 'right', 'middle'] },
|
|
221
|
+
clickCount: { type: 'number' },
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
handler: async (ctx, args) => {
|
|
225
|
+
const tabId = resolveTabId(args, ctx);
|
|
226
|
+
const ref = String(args.ref ?? '');
|
|
227
|
+
if (!ref)
|
|
228
|
+
return fail('invalid_params', 'ref is required');
|
|
229
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.click', {
|
|
230
|
+
tabId,
|
|
231
|
+
ref,
|
|
232
|
+
...(args.button ? { button: args.button } : {}),
|
|
233
|
+
...(typeof args.clickCount === 'number' ? { clickCount: args.clickCount } : {}),
|
|
234
|
+
}));
|
|
235
|
+
return ok(`Clicked ${ref}`);
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'browser_type',
|
|
240
|
+
description: 'Focus an input by ref and type the given text. Set `submit:true` to press Enter at the end.',
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: 'object',
|
|
243
|
+
required: ['ref', 'text'],
|
|
244
|
+
properties: {
|
|
245
|
+
tabId: { type: 'string' },
|
|
246
|
+
ref: { type: 'string' },
|
|
247
|
+
text: { type: 'string' },
|
|
248
|
+
submit: { type: 'boolean' },
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
handler: async (ctx, args) => {
|
|
252
|
+
const tabId = resolveTabId(args, ctx);
|
|
253
|
+
const ref = String(args.ref ?? '');
|
|
254
|
+
const text = typeof args.text === 'string' ? args.text : '';
|
|
255
|
+
if (!ref)
|
|
256
|
+
return fail('invalid_params', 'ref is required');
|
|
257
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.type', {
|
|
258
|
+
tabId,
|
|
259
|
+
ref,
|
|
260
|
+
text,
|
|
261
|
+
submit: args.submit === true,
|
|
262
|
+
}));
|
|
263
|
+
return ok(`Typed into ${ref}`);
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: 'browser_hover',
|
|
268
|
+
description: 'Move the mouse over an element identified by its snapshot ref.',
|
|
269
|
+
inputSchema: {
|
|
270
|
+
type: 'object',
|
|
271
|
+
required: ['ref'],
|
|
272
|
+
properties: { tabId: { type: 'string' }, ref: { type: 'string' } },
|
|
273
|
+
},
|
|
274
|
+
handler: async (ctx, args) => {
|
|
275
|
+
const tabId = resolveTabId(args, ctx);
|
|
276
|
+
const ref = String(args.ref ?? '');
|
|
277
|
+
if (!ref)
|
|
278
|
+
return fail('invalid_params', 'ref is required');
|
|
279
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.hover', { tabId, ref }));
|
|
280
|
+
return ok(`Hovered ${ref}`);
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'browser_press_key',
|
|
285
|
+
description: 'Press a single key (e.g. "Enter", "Tab", "a"). Triggers keydown + keyup.',
|
|
286
|
+
inputSchema: {
|
|
287
|
+
type: 'object',
|
|
288
|
+
required: ['key'],
|
|
289
|
+
properties: { tabId: { type: 'string' }, key: { type: 'string' } },
|
|
290
|
+
},
|
|
291
|
+
handler: async (ctx, args) => {
|
|
292
|
+
const tabId = resolveTabId(args, ctx);
|
|
293
|
+
const key = String(args.key ?? '');
|
|
294
|
+
if (!key)
|
|
295
|
+
return fail('invalid_params', 'key is required');
|
|
296
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.press_key', { tabId, key }));
|
|
297
|
+
return ok(`Pressed ${key}`);
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: 'browser_select_option',
|
|
302
|
+
description: 'Select one or more values from a <select> element identified by ref.',
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: 'object',
|
|
305
|
+
required: ['ref', 'values'],
|
|
306
|
+
properties: {
|
|
307
|
+
tabId: { type: 'string' },
|
|
308
|
+
ref: { type: 'string' },
|
|
309
|
+
values: { type: 'array', items: { type: 'string' } },
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
handler: async (ctx, args) => {
|
|
313
|
+
const tabId = resolveTabId(args, ctx);
|
|
314
|
+
const ref = String(args.ref ?? '');
|
|
315
|
+
const values = Array.isArray(args.values)
|
|
316
|
+
? args.values.filter((v) => typeof v === 'string')
|
|
317
|
+
: null;
|
|
318
|
+
if (!ref)
|
|
319
|
+
return fail('invalid_params', 'ref is required');
|
|
320
|
+
if (!values)
|
|
321
|
+
return fail('invalid_params', 'values must be string[]');
|
|
322
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.select_option', {
|
|
323
|
+
tabId,
|
|
324
|
+
ref,
|
|
325
|
+
values,
|
|
326
|
+
}));
|
|
327
|
+
return ok(`Selected ${values.length} value(s) on ${ref}`);
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: 'browser_fill_form',
|
|
332
|
+
description: 'Fill multiple form fields atomically. Each field is { ref, value }.',
|
|
333
|
+
inputSchema: {
|
|
334
|
+
type: 'object',
|
|
335
|
+
required: ['fields'],
|
|
336
|
+
properties: {
|
|
337
|
+
tabId: { type: 'string' },
|
|
338
|
+
fields: {
|
|
339
|
+
type: 'array',
|
|
340
|
+
items: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
required: ['ref', 'value'],
|
|
343
|
+
properties: { ref: { type: 'string' }, value: { type: 'string' } },
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
handler: async (ctx, args) => {
|
|
349
|
+
const tabId = resolveTabId(args, ctx);
|
|
350
|
+
const fields = Array.isArray(args.fields) ? args.fields : null;
|
|
351
|
+
if (!fields)
|
|
352
|
+
return fail('invalid_params', 'fields[] is required');
|
|
353
|
+
const norm = [];
|
|
354
|
+
for (const f of fields) {
|
|
355
|
+
if (!f || typeof f !== 'object')
|
|
356
|
+
return fail('invalid_params', 'field is not an object');
|
|
357
|
+
const ref = f.ref;
|
|
358
|
+
const value = f.value;
|
|
359
|
+
if (typeof ref !== 'string' || typeof value !== 'string') {
|
|
360
|
+
return fail('invalid_params', 'each field needs string ref + value');
|
|
361
|
+
}
|
|
362
|
+
norm.push({ ref, value });
|
|
363
|
+
}
|
|
364
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.fill_form', {
|
|
365
|
+
tabId,
|
|
366
|
+
fields: norm,
|
|
367
|
+
}));
|
|
368
|
+
return ok(`Filled ${norm.length} field(s)`);
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'browser_drag',
|
|
373
|
+
description: 'Drag from one element ref to another (mouseDown + move + mouseUp).',
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: 'object',
|
|
376
|
+
required: ['fromRef', 'toRef'],
|
|
377
|
+
properties: {
|
|
378
|
+
tabId: { type: 'string' },
|
|
379
|
+
fromRef: { type: 'string' },
|
|
380
|
+
toRef: { type: 'string' },
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
handler: async (ctx, args) => {
|
|
384
|
+
const tabId = resolveTabId(args, ctx);
|
|
385
|
+
const fromRef = String(args.fromRef ?? '');
|
|
386
|
+
const toRef = String(args.toRef ?? '');
|
|
387
|
+
if (!fromRef || !toRef)
|
|
388
|
+
return fail('invalid_params', 'fromRef and toRef are required');
|
|
389
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.drag_drop', {
|
|
390
|
+
tabId,
|
|
391
|
+
fromRef,
|
|
392
|
+
toRef,
|
|
393
|
+
}));
|
|
394
|
+
return ok(`Dragged ${fromRef} -> ${toRef}`);
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
name: 'browser_file_upload',
|
|
399
|
+
description: 'Attach one or more files (by absolute path) to a file input identified by ref.',
|
|
400
|
+
inputSchema: {
|
|
401
|
+
type: 'object',
|
|
402
|
+
required: ['ref', 'paths'],
|
|
403
|
+
properties: {
|
|
404
|
+
tabId: { type: 'string' },
|
|
405
|
+
ref: { type: 'string' },
|
|
406
|
+
paths: { type: 'array', items: { type: 'string' } },
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
handler: async (ctx, args) => {
|
|
410
|
+
const tabId = resolveTabId(args, ctx);
|
|
411
|
+
const ref = String(args.ref ?? '');
|
|
412
|
+
const paths = Array.isArray(args.paths)
|
|
413
|
+
? args.paths.filter((p) => typeof p === 'string')
|
|
414
|
+
: null;
|
|
415
|
+
if (!ref)
|
|
416
|
+
return fail('invalid_params', 'ref is required');
|
|
417
|
+
if (!paths || paths.length === 0)
|
|
418
|
+
return fail('invalid_params', 'paths must be non-empty string[]');
|
|
419
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.file_upload', {
|
|
420
|
+
tabId,
|
|
421
|
+
ref,
|
|
422
|
+
paths,
|
|
423
|
+
}));
|
|
424
|
+
return ok(`Attached ${paths.length} file(s) to ${ref}`);
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: 'browser_handle_dialog',
|
|
429
|
+
description: 'Respond to a pending JavaScript dialog (alert / confirm / prompt). Set accept:true to accept, false to dismiss.',
|
|
430
|
+
inputSchema: {
|
|
431
|
+
type: 'object',
|
|
432
|
+
required: ['accept'],
|
|
433
|
+
properties: {
|
|
434
|
+
tabId: { type: 'string' },
|
|
435
|
+
accept: { type: 'boolean' },
|
|
436
|
+
promptText: { type: 'string' },
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
handler: async (ctx, args) => {
|
|
440
|
+
const tabId = resolveTabId(args, ctx);
|
|
441
|
+
const accept = args.accept === true;
|
|
442
|
+
const promptText = typeof args.promptText === 'string' ? args.promptText : undefined;
|
|
443
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.handle_dialog', {
|
|
444
|
+
tabId,
|
|
445
|
+
accept,
|
|
446
|
+
...(promptText !== undefined ? { promptText } : {}),
|
|
447
|
+
}));
|
|
448
|
+
return ok(accept ? 'Dialog accepted' : 'Dialog dismissed');
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: 'browser_wait_for',
|
|
453
|
+
description: 'Block until the given condition becomes true. condition is either {type:"expression", expr:"…"} or {type:"navigation"}.',
|
|
454
|
+
inputSchema: {
|
|
455
|
+
type: 'object',
|
|
456
|
+
required: ['condition'],
|
|
457
|
+
properties: {
|
|
458
|
+
tabId: { type: 'string' },
|
|
459
|
+
condition: { type: 'object' },
|
|
460
|
+
timeoutMs: { type: 'number' },
|
|
461
|
+
pollIntervalMs: { type: 'number' },
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
handler: async (ctx, args) => {
|
|
465
|
+
const tabId = resolveTabId(args, ctx);
|
|
466
|
+
const condition = args.condition;
|
|
467
|
+
if (!condition || typeof condition !== 'object') {
|
|
468
|
+
return fail('invalid_params', 'condition is required');
|
|
469
|
+
}
|
|
470
|
+
const timeoutMs = typeof args.timeoutMs === 'number' ? args.timeoutMs : 5_000;
|
|
471
|
+
await withLease(ctx, tabId, () => rpc(ctx, 'dom.wait_for', {
|
|
472
|
+
tabId,
|
|
473
|
+
timeoutMs,
|
|
474
|
+
...(typeof args.pollIntervalMs === 'number'
|
|
475
|
+
? { pollIntervalMs: args.pollIntervalMs }
|
|
476
|
+
: {}),
|
|
477
|
+
condition,
|
|
478
|
+
}));
|
|
479
|
+
return ok('wait_for satisfied');
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: 'browser_take_screenshot',
|
|
484
|
+
description: 'Capture a screenshot of the current viewport (or full page). Returns a PNG/JPEG image inline.',
|
|
485
|
+
inputSchema: {
|
|
486
|
+
type: 'object',
|
|
487
|
+
properties: {
|
|
488
|
+
tabId: { type: 'string' },
|
|
489
|
+
fullPage: { type: 'boolean' },
|
|
490
|
+
format: { type: 'string', enum: ['png', 'jpeg'] },
|
|
491
|
+
quality: { type: 'number' },
|
|
492
|
+
clip: {
|
|
493
|
+
type: 'object',
|
|
494
|
+
properties: {
|
|
495
|
+
x: { type: 'number' },
|
|
496
|
+
y: { type: 'number' },
|
|
497
|
+
width: { type: 'number' },
|
|
498
|
+
height: { type: 'number' },
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
handler: async (ctx, args) => {
|
|
504
|
+
const tabId = resolveTabId(args, ctx);
|
|
505
|
+
const res = await withLease(ctx, tabId, () => rpc(ctx, 'dom.screenshot', {
|
|
506
|
+
tabId,
|
|
507
|
+
...(args.fullPage === true ? { fullPage: true } : {}),
|
|
508
|
+
...(args.format ? { format: args.format } : {}),
|
|
509
|
+
...(typeof args.quality === 'number' ? { quality: args.quality } : {}),
|
|
510
|
+
...(args.clip && typeof args.clip === 'object' ? { clip: args.clip } : {}),
|
|
511
|
+
}));
|
|
512
|
+
return {
|
|
513
|
+
content: [
|
|
514
|
+
{
|
|
515
|
+
type: 'image',
|
|
516
|
+
data: res.data,
|
|
517
|
+
mimeType: res.format === 'jpeg' ? 'image/jpeg' : 'image/png',
|
|
518
|
+
},
|
|
519
|
+
],
|
|
520
|
+
};
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: 'browser_console_messages',
|
|
525
|
+
description: 'Return console messages buffered for this tab. Pass `since:<seq>` to get only newer entries.',
|
|
526
|
+
inputSchema: {
|
|
527
|
+
type: 'object',
|
|
528
|
+
properties: {
|
|
529
|
+
tabId: { type: 'string' },
|
|
530
|
+
since: { type: 'number' },
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
handler: async (ctx, args) => {
|
|
534
|
+
const tabId = resolveTabId(args, ctx);
|
|
535
|
+
const since = typeof args.since === 'number' ? args.since : undefined;
|
|
536
|
+
const out = await withLease(ctx, tabId, () => rpc(ctx, 'dom.console', {
|
|
537
|
+
tabId,
|
|
538
|
+
...(since !== undefined ? { since } : {}),
|
|
539
|
+
}));
|
|
540
|
+
return okJson(out);
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
name: 'browser_network_request',
|
|
545
|
+
description: 'Return the buffered network entry for `requestId` (one row from the per-tab network log). Use browser_network_requests for the full list.',
|
|
546
|
+
inputSchema: {
|
|
547
|
+
type: 'object',
|
|
548
|
+
required: ['requestId'],
|
|
549
|
+
properties: {
|
|
550
|
+
tabId: { type: 'string' },
|
|
551
|
+
requestId: { type: 'string' },
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
handler: async (ctx, args) => {
|
|
555
|
+
const tabId = resolveTabId(args, ctx);
|
|
556
|
+
const requestId = String(args.requestId ?? '');
|
|
557
|
+
if (!requestId)
|
|
558
|
+
return fail('invalid_params', 'requestId is required');
|
|
559
|
+
const entries = await withLease(ctx, tabId, () => rpc(ctx, 'dom.network', { tabId }));
|
|
560
|
+
const match = entries.find((e) => e.requestId === requestId);
|
|
561
|
+
if (!match) {
|
|
562
|
+
return fail('not_found', `no network entry for requestId ${requestId}`);
|
|
563
|
+
}
|
|
564
|
+
return okJson(match);
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: 'browser_network_requests',
|
|
569
|
+
description: 'Return network requests buffered for this tab. Pass `since:<seq>` to get only newer entries.',
|
|
570
|
+
inputSchema: {
|
|
571
|
+
type: 'object',
|
|
572
|
+
properties: {
|
|
573
|
+
tabId: { type: 'string' },
|
|
574
|
+
since: { type: 'number' },
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
handler: async (ctx, args) => {
|
|
578
|
+
const tabId = resolveTabId(args, ctx);
|
|
579
|
+
const since = typeof args.since === 'number' ? args.since : undefined;
|
|
580
|
+
const out = await withLease(ctx, tabId, () => rpc(ctx, 'dom.network', {
|
|
581
|
+
tabId,
|
|
582
|
+
...(since !== undefined ? { since } : {}),
|
|
583
|
+
}));
|
|
584
|
+
return okJson(out);
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
name: 'browser_navigate',
|
|
589
|
+
description: 'Navigate your tab (tabId, else this session\'s tab) to the given URL. The browser bouncer enforces the allowlist.',
|
|
590
|
+
inputSchema: {
|
|
591
|
+
type: 'object',
|
|
592
|
+
required: ['url'],
|
|
593
|
+
properties: { url: { type: 'string' }, tabId: { type: 'string' } },
|
|
594
|
+
},
|
|
595
|
+
handler: async (ctx, args) => {
|
|
596
|
+
const url = String(args.url ?? '');
|
|
597
|
+
if (!url)
|
|
598
|
+
return fail('invalid_params', 'url is required');
|
|
599
|
+
await navigateLeased(ctx, args, 'view.navigate', { url });
|
|
600
|
+
return ok(`Navigated to ${url}`);
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
name: 'browser_back',
|
|
605
|
+
description: 'Navigate your tab back one entry in history.',
|
|
606
|
+
inputSchema: { type: 'object', properties: { tabId: { type: 'string' } } },
|
|
607
|
+
handler: async (ctx, args) => {
|
|
608
|
+
await navigateLeased(ctx, args, 'view.back');
|
|
609
|
+
return ok('Navigated back');
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: 'browser_forward',
|
|
614
|
+
description: 'Navigate your tab forward one entry in history.',
|
|
615
|
+
inputSchema: { type: 'object', properties: { tabId: { type: 'string' } } },
|
|
616
|
+
handler: async (ctx, args) => {
|
|
617
|
+
await navigateLeased(ctx, args, 'view.forward');
|
|
618
|
+
return ok('Navigated forward');
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
name: 'browser_reload',
|
|
623
|
+
description: 'Reload your tab.',
|
|
624
|
+
inputSchema: { type: 'object', properties: { tabId: { type: 'string' } } },
|
|
625
|
+
handler: async (ctx, args) => {
|
|
626
|
+
await navigateLeased(ctx, args, 'view.reload');
|
|
627
|
+
return ok('Reloaded');
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
name: 'browser_tabs',
|
|
632
|
+
description: 'List tabs the current agent owns leases on, with their cached metadata (url, title, favicon).',
|
|
633
|
+
inputSchema: { type: 'object', properties: {} },
|
|
634
|
+
handler: async (ctx) => {
|
|
635
|
+
const leases = await rpc(ctx, 'agent.tab.list', {});
|
|
636
|
+
const tabIds = leases.map((l) => l.tabId);
|
|
637
|
+
const tabs = await rpc(ctx, 'tab.list', { tabIds });
|
|
638
|
+
return okJson({ leases, tabs });
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: 'browser_tab_new',
|
|
643
|
+
description: 'Open a new tab at `url` (defaults to about:blank) and return its tabId.',
|
|
644
|
+
inputSchema: {
|
|
645
|
+
type: 'object',
|
|
646
|
+
properties: { url: { type: 'string' } },
|
|
647
|
+
},
|
|
648
|
+
handler: async (ctx, args) => {
|
|
649
|
+
const url = typeof args.url === 'string' && args.url ? args.url : 'about:blank';
|
|
650
|
+
const res = await rpc(ctx, 'tab.open', { url });
|
|
651
|
+
await ctx.leases.ensure(res.id);
|
|
652
|
+
return okJson({ tabId: res.id });
|
|
653
|
+
},
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: 'browser_tab_close',
|
|
657
|
+
description: 'Close a tab by id. Releases the lease if the caller held one.',
|
|
658
|
+
inputSchema: {
|
|
659
|
+
type: 'object',
|
|
660
|
+
properties: { tabId: { type: 'string' } },
|
|
661
|
+
},
|
|
662
|
+
handler: async (ctx, args) => {
|
|
663
|
+
const tabId = resolveTabId(args, ctx);
|
|
664
|
+
await rpc(ctx, 'tab.close', { id: tabId });
|
|
665
|
+
await ctx.leases.release(tabId);
|
|
666
|
+
return ok(`Closed tab ${tabId}`);
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: 'browser_tab_select',
|
|
671
|
+
description: 'Switch focus to the given tab id. Auto-leases on first use.',
|
|
672
|
+
inputSchema: {
|
|
673
|
+
type: 'object',
|
|
674
|
+
required: ['tabId'],
|
|
675
|
+
properties: { tabId: { type: 'string' } },
|
|
676
|
+
},
|
|
677
|
+
handler: async (ctx, args) => {
|
|
678
|
+
const tabId = String(args.tabId ?? '');
|
|
679
|
+
if (!tabId)
|
|
680
|
+
return fail('invalid_params', 'tabId is required');
|
|
681
|
+
await ctx.leases.ensure(tabId);
|
|
682
|
+
await rpc(ctx, 'tab.select', { id: tabId });
|
|
683
|
+
return ok(`Selected tab ${tabId}`);
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: 'browser_evaluate',
|
|
688
|
+
description: 'Evaluate a JS expression in the tab. `world:"isolated"` (default) keeps the page DOM untouched; `world:"main"` uses the page realm.',
|
|
689
|
+
inputSchema: {
|
|
690
|
+
type: 'object',
|
|
691
|
+
required: ['expr'],
|
|
692
|
+
properties: {
|
|
693
|
+
tabId: { type: 'string' },
|
|
694
|
+
expr: { type: 'string' },
|
|
695
|
+
world: { type: 'string', enum: ['main', 'isolated'] },
|
|
696
|
+
timeoutMs: { type: 'number' },
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
handler: async (ctx, args) => {
|
|
700
|
+
const tabId = resolveTabId(args, ctx);
|
|
701
|
+
const expr = String(args.expr ?? '');
|
|
702
|
+
if (!expr)
|
|
703
|
+
return fail('invalid_params', 'expr is required');
|
|
704
|
+
const world = args.world === 'main' ? 'main' : 'isolated';
|
|
705
|
+
const out = await withLease(ctx, tabId, () => rpc(ctx, 'js.evaluate', {
|
|
706
|
+
tabId,
|
|
707
|
+
expr,
|
|
708
|
+
world,
|
|
709
|
+
...(typeof args.timeoutMs === 'number' ? { timeoutMs: args.timeoutMs } : {}),
|
|
710
|
+
}));
|
|
711
|
+
return okJson(out);
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
name: 'browser_run_code_unsafe',
|
|
716
|
+
description: 'Evaluate JS in the main world. Equivalent to browser_evaluate with world:"main"; surfaced separately so the Bouncer can apply stricter review.',
|
|
717
|
+
inputSchema: {
|
|
718
|
+
type: 'object',
|
|
719
|
+
required: ['code'],
|
|
720
|
+
properties: {
|
|
721
|
+
tabId: { type: 'string' },
|
|
722
|
+
code: { type: 'string' },
|
|
723
|
+
timeoutMs: { type: 'number' },
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
handler: async (ctx, args) => {
|
|
727
|
+
const tabId = resolveTabId(args, ctx);
|
|
728
|
+
const expr = String(args.code ?? '');
|
|
729
|
+
if (!expr)
|
|
730
|
+
return fail('invalid_params', 'code is required');
|
|
731
|
+
const out = await withLease(ctx, tabId, () => rpc(ctx, 'js.evaluate', {
|
|
732
|
+
tabId,
|
|
733
|
+
expr,
|
|
734
|
+
world: 'main',
|
|
735
|
+
...(typeof args.timeoutMs === 'number' ? { timeoutMs: args.timeoutMs } : {}),
|
|
736
|
+
}));
|
|
737
|
+
return okJson(out);
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
];
|
|
741
|
+
export const TOOLS = tools;
|
|
742
|
+
export async function callTool(ctx, name, args) {
|
|
743
|
+
const tool = TOOLS.find((t) => t.name === name);
|
|
744
|
+
if (!tool) {
|
|
745
|
+
return fail('unknown_tool', `no such browser tool: ${name}`);
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
return await tool.handler(ctx, args);
|
|
749
|
+
}
|
|
750
|
+
catch (err) {
|
|
751
|
+
if (err instanceof TabAlreadyLeasedError) {
|
|
752
|
+
return fail(err.code, err.message, { currentOwner: err.currentOwner });
|
|
753
|
+
}
|
|
754
|
+
if (err instanceof BrowserOpError) {
|
|
755
|
+
return fail(err.code, err.message, err.data);
|
|
756
|
+
}
|
|
757
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
758
|
+
ctx.log(`[BrowserMcp] tool ${name} threw: ${message}`);
|
|
759
|
+
return fail('internal_error', message);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
export function createBrowserMcpServer(deps) {
|
|
763
|
+
const log = deps.log ?? ((m) => console.error(m));
|
|
764
|
+
const leases = new LeaseManager({
|
|
765
|
+
client: deps.client,
|
|
766
|
+
log,
|
|
767
|
+
...(deps.setIntervalFn ? { setIntervalFn: deps.setIntervalFn } : {}),
|
|
768
|
+
...(deps.clearIntervalFn ? { clearIntervalFn: deps.clearIntervalFn } : {}),
|
|
769
|
+
...(deps.heartbeatIntervalMs !== undefined
|
|
770
|
+
? { heartbeatIntervalMs: deps.heartbeatIntervalMs }
|
|
771
|
+
: {}),
|
|
772
|
+
});
|
|
773
|
+
const toolCtx = {
|
|
774
|
+
client: deps.client,
|
|
775
|
+
leases,
|
|
776
|
+
...(deps.defaultTabId ? { defaultTabId: deps.defaultTabId } : {}),
|
|
777
|
+
log,
|
|
778
|
+
};
|
|
779
|
+
const server = new Server({ name: BROWSER_MCP_SERVER_NAME, version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
780
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
781
|
+
return {
|
|
782
|
+
tools: TOOLS.map((t) => ({
|
|
783
|
+
name: t.name,
|
|
784
|
+
description: t.description,
|
|
785
|
+
inputSchema: t.inputSchema,
|
|
786
|
+
})),
|
|
787
|
+
};
|
|
788
|
+
});
|
|
789
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
790
|
+
const name = request.params.name;
|
|
791
|
+
const args = (request.params.arguments ?? {});
|
|
792
|
+
const result = await callTool(toolCtx, name, args);
|
|
793
|
+
return result;
|
|
794
|
+
});
|
|
795
|
+
leases.start();
|
|
796
|
+
return {
|
|
797
|
+
server,
|
|
798
|
+
leases,
|
|
799
|
+
async shutdown() {
|
|
800
|
+
await leases.stop();
|
|
801
|
+
},
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
export function createFetchBrowserOpClient(opts) {
|
|
805
|
+
const url = `http://127.0.0.1:${opts.port}/internal/browser/op`;
|
|
806
|
+
const fetchImpl = opts.fetchFn ?? fetch;
|
|
807
|
+
const defaultTimeoutMs = opts.timeoutMs ?? DEFAULT_RPC_TIMEOUT_MS;
|
|
808
|
+
return {
|
|
809
|
+
async call(method, params, callOpts) {
|
|
810
|
+
const ac = new AbortController();
|
|
811
|
+
const timer = setTimeout(() => ac.abort(), callOpts?.timeoutMs ?? defaultTimeoutMs);
|
|
812
|
+
try {
|
|
813
|
+
const res = await fetchImpl(url, {
|
|
814
|
+
method: 'POST',
|
|
815
|
+
headers: {
|
|
816
|
+
'content-type': 'application/json',
|
|
817
|
+
'x-minionry-bouncer-secret': opts.bouncerSecret,
|
|
818
|
+
'x-minionry-capability-token': opts.capabilityToken,
|
|
819
|
+
},
|
|
820
|
+
body: JSON.stringify({ method, params: params ?? {} }),
|
|
821
|
+
signal: ac.signal,
|
|
822
|
+
});
|
|
823
|
+
const status = res.status;
|
|
824
|
+
let body;
|
|
825
|
+
try {
|
|
826
|
+
body = (await res.json());
|
|
827
|
+
}
|
|
828
|
+
catch {
|
|
829
|
+
body = {
|
|
830
|
+
error: {
|
|
831
|
+
code: 'invalid_response',
|
|
832
|
+
message: `non-JSON response (status ${status})`,
|
|
833
|
+
},
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
return { status, body };
|
|
837
|
+
}
|
|
838
|
+
catch (err) {
|
|
839
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
840
|
+
return {
|
|
841
|
+
status: 0,
|
|
842
|
+
body: { error: { code: 'transport_error', message } },
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
finally {
|
|
846
|
+
clearTimeout(timer);
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
export function readBootEnv(env) {
|
|
852
|
+
if (!env.MINIONRY_PORT) {
|
|
853
|
+
throw new Error('MINIONRY_PORT env var is required');
|
|
854
|
+
}
|
|
855
|
+
if (!env.MINIONRY_BOUNCER_SECRET) {
|
|
856
|
+
throw new Error('MINIONRY_BOUNCER_SECRET env var is required');
|
|
857
|
+
}
|
|
858
|
+
if (!env.MINIONRY_CAPABILITY_TOKEN) {
|
|
859
|
+
throw new Error('MINIONRY_CAPABILITY_TOKEN env var is required');
|
|
860
|
+
}
|
|
861
|
+
const port = Number(env.MINIONRY_PORT);
|
|
862
|
+
if (!Number.isFinite(port) || port <= 0) {
|
|
863
|
+
throw new Error(`MINIONRY_PORT must be a positive number (got "${env.MINIONRY_PORT}")`);
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
port,
|
|
867
|
+
bouncerSecret: env.MINIONRY_BOUNCER_SECRET,
|
|
868
|
+
capabilityToken: env.MINIONRY_CAPABILITY_TOKEN,
|
|
869
|
+
...(env.MINIONRY_DEFAULT_TAB_ID ? { defaultTabId: env.MINIONRY_DEFAULT_TAB_ID } : {}),
|
|
870
|
+
...(env.MINIONRY_AGENT_LABEL ? { agentLabel: env.MINIONRY_AGENT_LABEL } : {}),
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
async function main() {
|
|
874
|
+
const cfg = readBootEnv(process.env);
|
|
875
|
+
const client = createFetchBrowserOpClient({
|
|
876
|
+
port: cfg.port,
|
|
877
|
+
bouncerSecret: cfg.bouncerSecret,
|
|
878
|
+
capabilityToken: cfg.capabilityToken,
|
|
879
|
+
});
|
|
880
|
+
const handle = createBrowserMcpServer({
|
|
881
|
+
client,
|
|
882
|
+
...(cfg.defaultTabId ? { defaultTabId: cfg.defaultTabId } : {}),
|
|
883
|
+
});
|
|
884
|
+
const transport = new StdioServerTransport();
|
|
885
|
+
await handle.server.connect(transport);
|
|
886
|
+
console.error(`[BrowserMcp] connected on stdio${cfg.agentLabel ? ` (${cfg.agentLabel})` : ''} — ${TOOLS.length} tools`);
|
|
887
|
+
const shutdown = async (reason) => {
|
|
888
|
+
console.error(`[BrowserMcp] shutting down: ${reason}`);
|
|
889
|
+
try {
|
|
890
|
+
await handle.shutdown();
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
console.error(`[BrowserMcp] shutdown error: ${stringifyError(err)}`);
|
|
894
|
+
}
|
|
895
|
+
process.exit(0);
|
|
896
|
+
};
|
|
897
|
+
process.stdin.on('end', () => {
|
|
898
|
+
void shutdown('stdin EOF (parent closed)');
|
|
899
|
+
});
|
|
900
|
+
process.stdin.on('close', () => {
|
|
901
|
+
void shutdown('stdin closed');
|
|
902
|
+
});
|
|
903
|
+
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
904
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
905
|
+
}
|
|
906
|
+
const isMain = typeof process !== 'undefined' &&
|
|
907
|
+
Array.isArray(process.argv) &&
|
|
908
|
+
process.argv[1] &&
|
|
909
|
+
/browser-mcp-server\.(t|j)s$/.test(process.argv[1]);
|
|
910
|
+
if (isMain) {
|
|
911
|
+
main().catch((err) => {
|
|
912
|
+
console.error(`[BrowserMcp] fatal: ${stringifyError(err)}`);
|
|
913
|
+
process.exit(1);
|
|
914
|
+
});
|
|
915
|
+
}
|