@xopcai/xopc 0.0.85 → 0.0.87
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/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/feishu/src/adapters/cli-login.js +3 -3
- package/dist/extensions/feishu/src/adapters/cli-login.js.map +1 -1
- package/dist/extensions/telegram/src/delivery-chat-id.d.ts +1 -1
- package/dist/extensions/telegram/src/delivery-chat-id.js +1 -1
- package/dist/extensions/telegram/src/delivery-chat-id.js.map +1 -1
- package/dist/extensions/telegram/src/routing-integration.js +1 -0
- package/dist/extensions/telegram/src/routing-integration.js.map +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +2 -2
- package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -1
- package/dist/extensions/weixin/src/api/api.js +2 -2
- package/dist/extensions/weixin/src/api/api.js.map +1 -1
- package/dist/extensions/weixin/src/auth/accounts.js +12 -12
- package/dist/extensions/weixin/src/auth/accounts.js.map +1 -1
- package/dist/extensions/weixin/src/delivery-to.js +2 -2
- package/dist/extensions/weixin/src/delivery-to.js.map +1 -1
- package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
- package/dist/extensions/weixin/src/messaging/debug-mode.js.map +1 -1
- package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
- package/dist/extensions/weixin/src/messaging/inbound.js.map +1 -1
- package/dist/extensions/weixin/src/storage/sync-buf.js +4 -4
- package/dist/extensions/weixin/src/storage/sync-buf.js.map +1 -1
- package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
- package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
- package/dist/gateway/static/root/assets/agents-BEAbXpuP.js +222 -0
- package/dist/gateway/static/root/assets/{apps-page-D7v7649T.js → apps-page-Dg8R-Szf.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-nCaMb0a7.js → channels-settings-yohw9YSu.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-status-swr-C1gZBcJV.js → channels-status-swr-BSHqqCF1.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-api-CoYK0hlm.js → cron-api-0h_QT8U3.js} +1 -1
- package/dist/gateway/static/root/assets/{cron-page-DeGo-Vjc.js → cron-page-BkfKFfFk.js} +1 -1
- package/dist/gateway/static/root/assets/{dist-DaK4dsss.js → dist-Cmjp2APP.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-BZngZWbO.js → extension-debug-page-CFa9z_1N.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-D6JSyV27.js → extension-page-BI8eaTPq.js} +1 -1
- package/dist/gateway/static/root/assets/extension-settings-page-x4BB7q1X.js +1 -0
- package/dist/gateway/static/root/assets/{fetch-B2MYHbWg.js → fetch-DRqwef_Q.js} +1 -1
- package/dist/gateway/static/root/assets/{field-primitives-Zzl22MvN.js → field-primitives-BiNHBo2Y.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-BtIcpG0O.js → heartbeat-config-api-ZRb8qhuz.js} +1 -1
- package/dist/gateway/static/root/assets/{index-D4vM3-P7.js → index-Cu7bKuUi.js} +96 -94
- package/dist/gateway/static/root/assets/index-a5gWIdZQ.css +1 -0
- package/dist/gateway/static/root/assets/{logs-page-_d4UJ-qQ.js → logs-page-BFZ8GgCv.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-5N4aF2Wk.js → sessions-page-CD7AfB-2.js} +1 -1
- package/dist/gateway/static/root/assets/settings-form-section-DiqqVs6m.js +1 -0
- package/dist/gateway/static/root/assets/settings-page-BBOjEQW3.js +3 -0
- package/dist/gateway/static/root/assets/{share-preview-page-D4EG_vM1.js → share-preview-page-n1Gprylk.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-sPAXhh8w.js → skills-page-CcN_gj--.js} +1 -1
- package/dist/gateway/static/root/assets/{theme-store-DryYl3qD.js → theme-store-CZOh1nT3.js} +1 -1
- package/dist/gateway/static/root/assets/url-Dd8Q7kZZ.js +3 -0
- package/dist/gateway/static/root/assets/{utils-CYO9eTCM.js → utils-CkWBfxs4.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-Ds51havm.js → voice-api-key-field-O6awz9hi.js} +1 -1
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-scope.d.ts +4 -0
- package/dist/src/agent/agent-scope.js +53 -10
- package/dist/src/agent/agent-scope.js.map +1 -1
- package/dist/src/agent/bootstrap/filter-bootstrap-files.js +2 -1
- package/dist/src/agent/bootstrap/filter-bootstrap-files.js.map +1 -1
- package/dist/src/agent/embedded/session-tool-result-guard.js +2 -1
- package/dist/src/agent/embedded/session-tool-result-guard.js.map +1 -1
- package/dist/src/agent/embedded/tool-result-truncation.js +2 -1
- package/dist/src/agent/embedded/tool-result-truncation.js.map +1 -1
- package/dist/src/agent/fallback/candidates.js +2 -2
- package/dist/src/agent/fallback/candidates.js.map +1 -1
- package/dist/src/agent/goals/persistent-goal-apis.d.ts +0 -2
- package/dist/src/agent/goals/persistent-goal-service.js +0 -1
- package/dist/src/agent/goals/persistent-goal-service.js.map +1 -1
- package/dist/src/agent/image/generation/normalization.js +2 -12
- package/dist/src/agent/image/generation/normalization.js.map +1 -1
- package/dist/src/agent/image/generation/provider-registry.d.ts +4 -8
- package/dist/src/agent/image/generation/provider-registry.js.map +1 -1
- package/dist/src/agent/image/generation/runtime.d.ts +2 -2
- package/dist/src/agent/image/generation/runtime.js.map +1 -1
- package/dist/src/agent/image/generation/types.d.ts +0 -18
- package/dist/src/agent/image/image-helpers.js +6 -1
- package/dist/src/agent/image/image-helpers.js.map +1 -1
- package/dist/src/agent/image/index.d.ts +1 -1
- package/dist/src/agent/inbound/inbound-loop.d.ts +5 -0
- package/dist/src/agent/inbound/inbound-loop.js +41 -10
- package/dist/src/agent/inbound/inbound-loop.js.map +1 -1
- package/dist/src/agent/inbound/turn-dispatcher.d.ts +4 -0
- package/dist/src/agent/inbound/turn-dispatcher.js +6 -4
- package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -1
- package/dist/src/agent/mcp/bundle-mcp-materialize.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-names.js +2 -1
- package/dist/src/agent/mcp/bundle-mcp-names.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js.map +1 -1
- package/dist/src/agent/mcp/mcp-transport-config.js +2 -1
- package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
- package/dist/src/agent/mcp/mcp-transport.js +2 -1
- package/dist/src/agent/mcp/mcp-transport.js.map +1 -1
- package/dist/src/agent/media-generation/runtime-shared.js +2 -9
- package/dist/src/agent/media-generation/runtime-shared.js.map +1 -1
- package/dist/src/agent/messaging/command-handler.d.ts +6 -0
- package/dist/src/agent/messaging/command-handler.js +5 -0
- package/dist/src/agent/messaging/command-handler.js.map +1 -1
- package/dist/src/agent/prompt/safety.d.ts +0 -7
- package/dist/src/agent/prompt/safety.js +1 -20
- package/dist/src/agent/prompt/safety.js.map +1 -1
- package/dist/src/agent/service/build-direct-message-content.js +1 -1
- package/dist/src/agent/service/build-direct-message-content.js.map +1 -1
- package/dist/src/agent/service/direct-turn-helpers.d.ts +3 -1
- package/dist/src/agent/service/direct-turn-helpers.js +6 -1
- package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
- package/dist/src/agent/service/process-direct-one-shot.d.ts +4 -0
- package/dist/src/agent/service/process-direct-one-shot.js +15 -2
- package/dist/src/agent/service/process-direct-one-shot.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.d.ts +4 -0
- package/dist/src/agent/service/process-direct-streaming.js +34 -4
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service/webchat-tts.js +1 -1
- package/dist/src/agent/service/webchat-tts.js.map +1 -1
- package/dist/src/agent/service.d.ts +8 -0
- package/dist/src/agent/service.js +21 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/tools/create-share-tool.js +27 -20
- package/dist/src/agent/tools/create-share-tool.js.map +1 -1
- package/dist/src/agent/tools/factory.js +1 -1
- package/dist/src/agent/tools/index.d.ts +0 -1
- package/dist/src/agent/tools/index.js +4 -5
- package/dist/src/agent/tools/shell.js +0 -13
- package/dist/src/agent/tools/shell.js.map +1 -1
- package/dist/src/agent/tools/workflow-tool.js +10 -4
- package/dist/src/agent/tools/workflow-tool.js.map +1 -1
- package/dist/src/agent/workflow/builtins/audit-repo.d.ts +5 -1
- package/dist/src/agent/workflow/builtins/audit-repo.js +52 -11
- package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -1
- package/dist/src/agent/workflow/builtins/debug-incident.d.ts +13 -0
- package/dist/src/agent/workflow/builtins/debug-incident.js +155 -0
- package/dist/src/agent/workflow/builtins/debug-incident.js.map +1 -0
- package/dist/src/agent/workflow/builtins/index.d.ts +3 -1
- package/dist/src/agent/workflow/builtins/index.js +11 -1
- package/dist/src/agent/workflow/builtins/index.js.map +1 -1
- package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +6 -1
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js +66 -30
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -1
- package/dist/src/agent/workflow/builtins/pr-review.d.ts +12 -0
- package/dist/src/agent/workflow/builtins/pr-review.js +156 -0
- package/dist/src/agent/workflow/builtins/pr-review.js.map +1 -0
- package/dist/src/agent/workflow/builtins/research.d.ts +5 -1
- package/dist/src/agent/workflow/builtins/research.js +37 -6
- package/dist/src/agent/workflow/builtins/research.js.map +1 -1
- package/dist/src/agent/workflow/catalog.d.ts +5 -0
- package/dist/src/agent/workflow/catalog.js +6 -2
- package/dist/src/agent/workflow/catalog.js.map +1 -1
- package/dist/src/agent/workflow/channel-capability.d.ts +3 -3
- package/dist/src/agent/workflow/index.d.ts +1 -1
- package/dist/src/agent/workflow/lint.d.ts +38 -0
- package/dist/src/agent/workflow/lint.js +74 -0
- package/dist/src/agent/workflow/lint.js.map +1 -0
- package/dist/src/agent/workflow/parser.js +13 -1
- package/dist/src/agent/workflow/parser.js.map +1 -1
- package/dist/src/agent/workflow/runtime.d.ts +3 -0
- package/dist/src/agent/workflow/runtime.js +76 -3
- package/dist/src/agent/workflow/runtime.js.map +1 -1
- package/dist/src/agent/workflow/types.d.ts +11 -1
- package/dist/src/browser/index.js +4 -4
- package/dist/src/browser/manager.d.ts +1 -3
- package/dist/src/browser/manager.js +0 -6
- package/dist/src/browser/manager.js.map +1 -1
- package/dist/src/browser/providers/browser-ext-install.d.ts +4 -4
- package/dist/src/browser/providers/browser-ext-install.js +38 -85
- package/dist/src/browser/providers/browser-ext-install.js.map +1 -1
- package/dist/src/browser/providers/cloakbrowser.d.ts +0 -5
- package/dist/src/browser/providers/cloakbrowser.js +2 -55
- package/dist/src/browser/providers/cloakbrowser.js.map +1 -1
- package/dist/src/channels/attachments/voice-stt-webchat.js +10 -8
- package/dist/src/channels/attachments/voice-stt-webchat.js.map +1 -1
- package/dist/src/channels/pairing/allow-from-file.js +9 -9
- package/dist/src/channels/pairing/allow-from-file.js.map +1 -1
- package/dist/src/channels/pairing/pairing-store.js +6 -6
- package/dist/src/channels/pairing/pairing-store.js.map +1 -1
- package/dist/src/chat-commands/builtins/session.js +1 -1
- package/dist/src/chat-commands/builtins/session.js.map +1 -1
- package/dist/src/chat-commands/builtins/tts.js +2 -2
- package/dist/src/chat-commands/builtins/tts.js.map +1 -1
- package/dist/src/chat-commands/builtins/workflow.js +7 -2
- package/dist/src/chat-commands/builtins/workflow.js.map +1 -1
- package/dist/src/chat-commands/context.d.ts +3 -0
- package/dist/src/chat-commands/context.js +21 -3
- package/dist/src/chat-commands/context.js.map +1 -1
- package/dist/src/chat-commands/session-key.d.ts +4 -37
- package/dist/src/chat-commands/session-key.js +49 -85
- package/dist/src/chat-commands/session-key.js.map +1 -1
- package/dist/src/chat-commands/types.d.ts +2 -0
- package/dist/src/cli/commands/agent/interactive.js +2 -2
- package/dist/src/cli/commands/agent/interactive.js.map +1 -1
- package/dist/src/cli/commands/agent/sessions.js +2 -2
- package/dist/src/cli/commands/agent/sessions.js.map +1 -1
- package/dist/src/cli/commands/agent.js +4 -5
- package/dist/src/cli/commands/agent.js.map +1 -1
- package/dist/src/cli/commands/channels.js +1 -5
- package/dist/src/cli/commands/channels.js.map +1 -1
- package/dist/src/cli/commands/gateway/lifecycle-core.js +1 -1
- package/dist/src/cli/commands/gateway/lifecycle-core.js.map +1 -1
- package/dist/src/cli/commands/gateway/logs.d.ts +9 -0
- package/dist/src/cli/commands/gateway/logs.js +50 -17
- package/dist/src/cli/commands/gateway/logs.js.map +1 -1
- package/dist/src/cli/commands/image.js +22 -21
- package/dist/src/cli/commands/image.js.map +1 -1
- package/dist/src/cli/commands/session/utils.js +2 -2
- package/dist/src/cli/commands/session/utils.js.map +1 -1
- package/dist/src/cli/commands/update.js +26 -46
- package/dist/src/cli/commands/update.js.map +1 -1
- package/dist/src/cli/utils/session.d.ts +0 -5
- package/dist/src/cli/utils/session.js +1 -6
- package/dist/src/cli/utils/session.js.map +1 -1
- package/dist/src/commands/agents.config.js +1 -1
- package/dist/src/commands/agents.config.js.map +1 -1
- package/dist/src/config/agent-profile.js +5 -27
- package/dist/src/config/agent-profile.js.map +1 -1
- package/dist/src/config/index.js +2 -2
- package/dist/src/config/model-input.js +2 -5
- package/dist/src/config/model-input.js.map +1 -1
- package/dist/src/config/schema.d.ts +201 -217
- package/dist/src/config/schema.js +54 -39
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/config/workspace-path-helpers.d.ts +1 -2
- package/dist/src/config/workspace-path-helpers.js.map +1 -1
- package/dist/src/daemon/install-plan.js +25 -1
- package/dist/src/daemon/install-plan.js.map +1 -1
- package/dist/src/daemon/launchd.d.ts +8 -0
- package/dist/src/daemon/launchd.js +5 -12
- package/dist/src/daemon/launchd.js.map +1 -1
- package/dist/src/daemon/schtasks.d.ts +25 -0
- package/dist/src/daemon/schtasks.js +166 -46
- package/dist/src/daemon/schtasks.js.map +1 -1
- package/dist/src/daemon/service.js +5 -4
- package/dist/src/daemon/service.js.map +1 -1
- package/dist/src/daemon/systemd.d.ts +6 -0
- package/dist/src/daemon/systemd.js +18 -3
- package/dist/src/daemon/systemd.js.map +1 -1
- package/dist/src/extensions/activation-context.js +0 -1
- package/dist/src/extensions/activation-context.js.map +1 -1
- package/dist/src/extensions/normalize-manifest.js +0 -1
- package/dist/src/extensions/normalize-manifest.js.map +1 -1
- package/dist/src/extensions/types/manifest.d.ts +0 -2
- package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
- package/dist/src/gateway/agent-builtin-tools.js +1 -0
- package/dist/src/gateway/agent-builtin-tools.js.map +1 -1
- package/dist/src/gateway/agents-admin.js +10 -2
- package/dist/src/gateway/agents-admin.js.map +1 -1
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/heartbeat/service.js.map +1 -1
- package/dist/src/gateway/hono/app.js +1 -1
- package/dist/src/gateway/hono/lib/agent-model.d.ts +18 -10
- package/dist/src/gateway/hono/lib/agent-model.js +24 -35
- package/dist/src/gateway/hono/lib/agent-model.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/lib/config-payload.js.map +1 -1
- package/dist/src/gateway/hono/lib/safe-voice-config.js +14 -53
- package/dist/src/gateway/hono/lib/safe-voice-config.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/agents.js +17 -5
- package/dist/src/gateway/hono/routes/config-patch/agents.js.map +1 -1
- package/dist/src/gateway/hono/routes/config-patch/channels.js +0 -11
- package/dist/src/gateway/hono/routes/config-patch/channels.js.map +1 -1
- package/dist/src/gateway/hono/routes/goals.js +1 -1
- package/dist/src/gateway/hono/routes/goals.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +28 -7
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/hono/routes/shares.js +14 -12
- package/dist/src/gateway/hono/routes/shares.js.map +1 -1
- package/dist/src/gateway/hono/routes/tunnel.js +1 -1
- package/dist/src/gateway/hono/routes/update.js +4 -2
- package/dist/src/gateway/hono/routes/update.js.map +1 -1
- package/dist/src/gateway/hono/sse.js +16 -33
- package/dist/src/gateway/hono/sse.js.map +1 -1
- package/dist/src/gateway/lock.js +10 -10
- package/dist/src/gateway/lock.js.map +1 -1
- package/dist/src/gateway/ports.js +6 -6
- package/dist/src/gateway/ports.js.map +1 -1
- package/dist/src/gateway/resolve-webchat-session-key.d.ts +19 -0
- package/dist/src/gateway/resolve-webchat-session-key.js +46 -0
- package/dist/src/gateway/resolve-webchat-session-key.js.map +1 -0
- package/dist/src/gateway/service/run-gateway-agent.js +27 -11
- package/dist/src/gateway/service/run-gateway-agent.js.map +1 -1
- package/dist/src/gateway/service/sessions-api.d.ts +3 -0
- package/dist/src/gateway/service/sessions-api.js +8 -0
- package/dist/src/gateway/service/sessions-api.js.map +1 -1
- package/dist/src/gateway/service.d.ts +0 -2
- package/dist/src/gateway/service.js +2 -7
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/session-reset-service.d.ts +20 -0
- package/dist/src/gateway/session-reset-service.js +54 -0
- package/dist/src/gateway/session-reset-service.js.map +1 -0
- package/dist/src/gateway/startup-readiness.d.ts +1 -1
- package/dist/src/gateway/startup-readiness.js +1 -0
- package/dist/src/gateway/startup-readiness.js.map +1 -1
- package/dist/src/infra/gateway-processes.js +2 -2
- package/dist/src/infra/gateway-processes.js.map +1 -1
- package/dist/src/infra/run-command.d.ts +16 -0
- package/dist/src/infra/run-command.js +67 -0
- package/dist/src/infra/run-command.js.map +1 -0
- package/dist/src/infra/update-global.d.ts +45 -0
- package/dist/src/infra/update-global.js +224 -0
- package/dist/src/infra/update-global.js.map +1 -0
- package/dist/src/mcp/channel-bridge.js +1 -1
- package/dist/src/mcp/channel-shared.js +2 -1
- package/dist/src/mcp/channel-shared.js.map +1 -1
- package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
- package/dist/src/providers/auth-runtime/auth-profile-store.js.map +1 -1
- package/dist/src/providers/auth-runtime/resolve-auth.js +1 -12
- package/dist/src/providers/auth-runtime/resolve-auth.js.map +1 -1
- package/dist/src/providers/auth-runtime/types.d.ts +6 -12
- package/dist/src/routing/agent-session-key.d.ts +58 -0
- package/dist/src/routing/agent-session-key.js +164 -0
- package/dist/src/routing/agent-session-key.js.map +1 -0
- package/dist/src/routing/index.d.ts +1 -1
- package/dist/src/routing/index.js +4 -2
- package/dist/src/routing/index.js.map +1 -1
- package/dist/src/routing/resolve-route.d.ts +15 -0
- package/dist/src/routing/resolve-route.js +41 -20
- package/dist/src/routing/resolve-route.js.map +1 -1
- package/dist/src/routing/resolve-tui-session-key.d.ts +25 -0
- package/dist/src/routing/resolve-tui-session-key.js +54 -0
- package/dist/src/routing/resolve-tui-session-key.js.map +1 -0
- package/dist/src/routing/session-key-utils.d.ts +24 -0
- package/dist/src/routing/session-key-utils.js +92 -0
- package/dist/src/routing/session-key-utils.js.map +1 -0
- package/dist/src/routing/session-key.d.ts +19 -49
- package/dist/src/routing/session-key.js +143 -116
- package/dist/src/routing/session-key.js.map +1 -1
- package/dist/src/session/index.d.ts +6 -0
- package/dist/src/session/index.js +7 -1
- package/dist/src/session/init-session-turn.d.ts +30 -0
- package/dist/src/session/init-session-turn.js +102 -0
- package/dist/src/session/init-session-turn.js.map +1 -0
- package/dist/src/session/lifecycle-timestamps.d.ts +8 -0
- package/dist/src/session/lifecycle-timestamps.js +16 -0
- package/dist/src/session/lifecycle-timestamps.js.map +1 -0
- package/dist/src/session/manager.d.ts +7 -1
- package/dist/src/session/manager.js +8 -1
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/parity/transcript-paths.js +2 -2
- package/dist/src/session/parity/transcript-paths.js.map +1 -1
- package/dist/src/session/parity/xopc-session-disk-entry.d.ts +6 -0
- package/dist/src/session/reset-policy.d.ts +32 -0
- package/dist/src/session/reset-policy.js +65 -0
- package/dist/src/session/reset-policy.js.map +1 -0
- package/dist/src/session/reset-triggers.d.ts +20 -0
- package/dist/src/session/reset-triggers.js +63 -0
- package/dist/src/session/reset-triggers.js.map +1 -0
- package/dist/src/session/reset-type.d.ts +12 -0
- package/dist/src/session/reset-type.js +25 -0
- package/dist/src/session/reset-type.js.map +1 -0
- package/dist/src/session/resolve-session.d.ts +30 -0
- package/dist/src/session/resolve-session.js +93 -0
- package/dist/src/session/resolve-session.js.map +1 -0
- package/dist/src/session/session-title.js +3 -2
- package/dist/src/session/session-title.js.map +1 -1
- package/dist/src/session/store.d.ts +11 -4
- package/dist/src/session/store.js +57 -6
- package/dist/src/session/store.js.map +1 -1
- package/dist/src/session/transcript-events.js +2 -1
- package/dist/src/session/transcript-events.js.map +1 -1
- package/dist/src/share/share-url.d.ts +33 -0
- package/dist/src/share/share-url.js +56 -14
- package/dist/src/share/share-url.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.js +4 -9
- package/dist/src/tui/backends/embedded-backend.js.map +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.js +1 -1
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
- package/dist/src/tui/components/chat-log.js +3 -3
- package/dist/src/tui/components/chat-log.js.map +1 -1
- package/dist/src/tui/theme.d.ts +0 -2
- package/dist/src/tui/theme.js +1 -3
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/tui-commands.d.ts +3 -0
- package/dist/src/tui/tui-commands.js +45 -10
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/tui/tui-keybindings-file.js +1 -21
- package/dist/src/tui/tui-keybindings-file.js.map +1 -1
- package/dist/src/tui/tui-session-actions.d.ts +28 -0
- package/dist/src/tui/tui-session-actions.js +88 -0
- package/dist/src/tui/tui-session-actions.js.map +1 -0
- package/dist/src/tui/tui.js +52 -47
- package/dist/src/tui/tui.js.map +1 -1
- package/dist/src/utils/string-coerce.d.ts +2 -0
- package/dist/src/utils/string-coerce.js +10 -1
- package/dist/src/utils/string-coerce.js.map +1 -1
- package/dist/src/voice/stt/config-slice.d.ts +2 -5
- package/dist/src/voice/stt/config-slice.js +5 -26
- package/dist/src/voice/stt/config-slice.js.map +1 -1
- package/dist/src/voice/stt/types.d.ts +1 -18
- package/dist/src/voice/stt/types.js +4 -2
- package/dist/src/voice/stt/types.js.map +1 -1
- package/dist/src/voice/tts/config-slice.d.ts +3 -7
- package/dist/src/voice/tts/config-slice.js +7 -38
- package/dist/src/voice/tts/config-slice.js.map +1 -1
- package/dist/src/voice/tts/merge-config.js +2 -48
- package/dist/src/voice/tts/merge-config.js.map +1 -1
- package/dist/src/voice/tts/providers/alibaba-speech.js +1 -1
- package/dist/src/voice/tts/providers/alibaba-speech.js.map +1 -1
- package/dist/src/voice/tts/types.d.ts +1 -29
- package/dist/src/voice/tts/types.js +19 -17
- package/dist/src/voice/tts/types.js.map +1 -1
- package/package.json +1 -4
- package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +0 -222
- package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +0 -1
- package/dist/gateway/static/root/assets/index-ew_2L2We.css +0 -1
- package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +0 -3
- package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +0 -3
- package/dist/src/agent/tools/browser-legacy-tools.d.ts +0 -17
- package/dist/src/agent/tools/browser-legacy-tools.js +0 -766
- package/dist/src/agent/tools/browser-legacy-tools.js.map +0 -1
|
@@ -43,6 +43,7 @@ async function runWorkflow(script, deps, options) {
|
|
|
43
43
|
const maxSubagents = Math.max(1, options.maxSubagents ?? DEFAULT_MAX_SUBAGENTS);
|
|
44
44
|
const limiter = createLimiter(concurrency);
|
|
45
45
|
const pendingAgentRuns = /* @__PURE__ */ new Set();
|
|
46
|
+
const phaseDefaultModels = buildPhaseModelMap(meta.phases);
|
|
46
47
|
const log = (message) => {
|
|
47
48
|
const text = String(message);
|
|
48
49
|
state.logs.push(text);
|
|
@@ -62,6 +63,22 @@ async function runWorkflow(script, deps, options) {
|
|
|
62
63
|
const throwIfAborted = () => {
|
|
63
64
|
if (options.signal?.aborted) throw new Error("workflow aborted");
|
|
64
65
|
};
|
|
66
|
+
const resolveAgentModel = (normalized, assignedPhase) => {
|
|
67
|
+
const realId = normalized.model?.trim();
|
|
68
|
+
if (realId) {
|
|
69
|
+
if (!deps.resolveModelId) throw new Error("workflow runtime missing resolveModelId; cannot resolve real model id");
|
|
70
|
+
return deps.resolveModelId(realId);
|
|
71
|
+
}
|
|
72
|
+
if (assignedPhase) {
|
|
73
|
+
const phaseRef = phaseDefaultModels.get(assignedPhase);
|
|
74
|
+
if (phaseRef) {
|
|
75
|
+
if (phaseRef.includes("/")) {
|
|
76
|
+
if (!deps.resolveModelId) return void 0;
|
|
77
|
+
return deps.resolveModelId(phaseRef);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
65
82
|
const agent = async (prompt, agentOptions = {}) => {
|
|
66
83
|
throwIfAborted();
|
|
67
84
|
if (budget.total !== null && budget.remaining() <= 0) throw new Error("workflow token budget exhausted");
|
|
@@ -82,6 +99,7 @@ async function runWorkflow(script, deps, options) {
|
|
|
82
99
|
});
|
|
83
100
|
try {
|
|
84
101
|
throwIfAborted();
|
|
102
|
+
const resolvedModel = resolveAgentModel(normalized, assignedPhase);
|
|
85
103
|
const result = await deps.runner.run(taskPrompt, {
|
|
86
104
|
label,
|
|
87
105
|
schema: normalized.schema,
|
|
@@ -89,7 +107,7 @@ async function runWorkflow(script, deps, options) {
|
|
|
89
107
|
maxIterations: normalized.maxIterations,
|
|
90
108
|
phase: assignedPhase,
|
|
91
109
|
signal: options.signal,
|
|
92
|
-
|
|
110
|
+
model: resolvedModel
|
|
93
111
|
});
|
|
94
112
|
throwIfAborted();
|
|
95
113
|
const status = result === null ? "error" : "done";
|
|
@@ -195,7 +213,7 @@ async function runWorkflow(script, deps, options) {
|
|
|
195
213
|
await Promise.allSettled([...pendingAgentRuns]);
|
|
196
214
|
} catch (e) {
|
|
197
215
|
await Promise.allSettled([...pendingAgentRuns]);
|
|
198
|
-
throw e;
|
|
216
|
+
throw rewriteScriptError(e);
|
|
199
217
|
}
|
|
200
218
|
assertStructuredCloneable(result, "workflow result");
|
|
201
219
|
return {
|
|
@@ -247,6 +265,12 @@ function createLimiter(limit) {
|
|
|
247
265
|
}
|
|
248
266
|
};
|
|
249
267
|
}
|
|
268
|
+
function buildPhaseModelMap(phases) {
|
|
269
|
+
const out = /* @__PURE__ */ new Map();
|
|
270
|
+
if (!phases) return out;
|
|
271
|
+
for (const p of phases) if (p && typeof p.title === "string" && typeof p.model === "string" && p.model.trim()) out.set(p.title, p.model.trim());
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
250
274
|
function requireString(value, name) {
|
|
251
275
|
if (typeof value !== "string") throw new TypeError(`${name} must be a string`);
|
|
252
276
|
return value;
|
|
@@ -267,11 +291,16 @@ function normalizeAgentOptions(value) {
|
|
|
267
291
|
if (maxIterations !== void 0) {
|
|
268
292
|
if (typeof maxIterations !== "number" || !Number.isFinite(maxIterations) || maxIterations < 1) throw new TypeError("agent maxIterations must be a positive number");
|
|
269
293
|
}
|
|
294
|
+
const modelStr = optionalString(options.model, "agent model");
|
|
295
|
+
if (modelStr !== void 0) {
|
|
296
|
+
const trimmed = modelStr.trim();
|
|
297
|
+
if (trimmed && !trimmed.includes("/")) throw new Error(`agent option 'model' must be a real model id in 'provider/model' form (got '${trimmed}')`);
|
|
298
|
+
}
|
|
270
299
|
return {
|
|
271
300
|
label: optionalString(options.label, "agent label"),
|
|
272
301
|
phase: optionalString(options.phase, "agent phase"),
|
|
273
302
|
schema: options.schema,
|
|
274
|
-
model:
|
|
303
|
+
model: modelStr,
|
|
275
304
|
toolset,
|
|
276
305
|
maxIterations
|
|
277
306
|
};
|
|
@@ -280,10 +309,54 @@ function assertStructuredCloneable(value, name) {
|
|
|
280
309
|
try {
|
|
281
310
|
structuredClone(value);
|
|
282
311
|
} catch (e) {
|
|
312
|
+
const promisePath = findPromisePath(value, "");
|
|
313
|
+
if (promisePath !== null) throw new Error(`${name} contains a Promise at \`${promisePath || "<root>"}\` — missing 'await' before parallel()/pipeline()/agent(). Async return auto-unwraps a bare Promise, but a Promise nested in an object/array is returned as-is.`);
|
|
283
314
|
const detail = e instanceof Error ? ` ${e.message}` : "";
|
|
284
315
|
throw new Error(`${name} must be structured-cloneable; did you forget to await agent(), parallel(), or pipeline()?${detail}`);
|
|
285
316
|
}
|
|
286
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Find the first Promise lurking in `value`, returning a JS-path like
|
|
320
|
+
* "results[0].pending". Returns null when no Promise is found. Bounded
|
|
321
|
+
* depth/breadth so it never explodes on weird user shapes.
|
|
322
|
+
*/
|
|
323
|
+
function findPromisePath(value, path, depth = 0) {
|
|
324
|
+
if (depth > 4) return null;
|
|
325
|
+
if (isThenable(value)) return path;
|
|
326
|
+
if (Array.isArray(value)) {
|
|
327
|
+
for (let i = 0; i < Math.min(value.length, 32); i++) {
|
|
328
|
+
const inner = findPromisePath(value[i], `${path}[${i}]`, depth + 1);
|
|
329
|
+
if (inner !== null) return inner;
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
if (value !== null && typeof value === "object") {
|
|
334
|
+
let count = 0;
|
|
335
|
+
for (const [key, child] of Object.entries(value)) {
|
|
336
|
+
if (count++ > 32) break;
|
|
337
|
+
const inner = findPromisePath(child, path ? `${path}.${key}` : key, depth + 1);
|
|
338
|
+
if (inner !== null) return inner;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
function isThenable(value) {
|
|
344
|
+
return value !== null && typeof value === "object" && typeof value.then === "function";
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Rewrite TypeErrors that look like "called .map on a Promise" with an
|
|
348
|
+
* await-shaped hint. Static lint catches the obvious form at parse time;
|
|
349
|
+
* this is the safety net for cases lint can't see (dynamic indirection,
|
|
350
|
+
* values stashed in helper closures).
|
|
351
|
+
*/
|
|
352
|
+
const PROMISE_METHOD_ERROR = /\.(map|filter|forEach|flat|flatMap|find|some|every|reduce|join|length|then)\b is not a function/;
|
|
353
|
+
function rewriteScriptError(error) {
|
|
354
|
+
const asError = error;
|
|
355
|
+
if (!(asError?.name === "TypeError" && typeof asError.message === "string")) return error instanceof Error ? error : new Error(String(error));
|
|
356
|
+
const message = asError.message;
|
|
357
|
+
if (!PROMISE_METHOD_ERROR.test(message)) return error instanceof Error ? error : new Error(message);
|
|
358
|
+
return /* @__PURE__ */ new Error(`${message}\n\nHint: parallel()/pipeline()/agent() return a Promise — 'await' the call before using its result.\n ❌ const r = parallel(...); r.map(...)\n ✅ const r = await parallel(...); r.map(...)`);
|
|
359
|
+
}
|
|
287
360
|
function defaultAgentLabel(phase, index) {
|
|
288
361
|
return phase ? `${phase} agent ${index}` : `agent ${index}`;
|
|
289
362
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","names":[],"sources":["../../../../src/agent/workflow/runtime.ts"],"sourcesContent":["/**\n * Sandboxed workflow runtime.\n *\n * Parses the script via {@link parseWorkflowScript} (which strips the `meta`\n * export), wraps the remaining body in an async IIFE, and runs it inside a\n * Node `vm` context with a curated set of globals:\n *\n * - `agent(prompt, opts)` — spawns a subagent through the injected\n * {@link SubagentRunner} and returns its result (string, or schema-validated\n * object). Failures resolve to `null`.\n * - `parallel(thunks)` — concurrent fan-out; thunks (not promises!) so the\n * limiter sees each agent() call.\n * - `pipeline(items, ...stages)` — per-item sequential stages, items run\n * concurrently (no stage barrier). Each stage receives\n * `(prevResult, originalItem, index)`. A stage that throws drops that item\n * to `null` and skips remaining stages.\n * - `phase(title)` — marks the current phase; surfaces through `onPhase`.\n * - `log(message)` — appends to the workflow log.\n * - `budget` — `{ total, spent(), remaining() }` for self-pacing scripts.\n * - `args`, `cwd`, `process.cwd()`.\n *\n * The runtime is the only code that touches `vm`. It exposes no IO surface,\n * carries no LLM dependency, and is fully driven by injected callbacks — that\n * means the workflow tool, tests, and any future runner share one runtime.\n */\n\nimport { availableParallelism } from 'node:os';\nimport { createContext, Script } from 'node:vm';\n\nimport { parseWorkflowScript } from './parser.js';\nimport type {\n AgentScriptOptions,\n SubagentRunner,\n WorkflowRunOptions,\n WorkflowRunResult,\n WorkflowSnapshot,\n WorkflowAgentStatus,\n} from './types.js';\n\nconst DEFAULT_CONCURRENCY_FLOOR = 1;\nconst DEFAULT_CONCURRENCY_CEILING = 16;\nconst DEFAULT_MAX_SUBAGENTS = 1000;\n\ninterface RuntimeState {\n currentPhase?: string;\n logs: string[];\n phases: string[];\n agentCount: number;\n spent: number;\n}\n\nexport interface RunWorkflowDeps {\n runner: SubagentRunner;\n}\n\nexport async function runWorkflow<T = unknown>(\n script: string,\n deps: RunWorkflowDeps,\n options: WorkflowRunOptions,\n): Promise<WorkflowRunResult<T>> {\n const started = Date.now();\n const { meta, body } = parseWorkflowScript(script);\n\n const state: RuntimeState = { logs: [], phases: [], agentCount: 0, spent: 0 };\n const concurrency = clampConcurrency(options.concurrency);\n const maxSubagents = Math.max(1, options.maxSubagents ?? DEFAULT_MAX_SUBAGENTS);\n const limiter = createLimiter(concurrency);\n const pendingAgentRuns = new Set<Promise<unknown>>();\n\n const log = (message: unknown) => {\n const text = String(message);\n state.logs.push(text);\n options.onLog?.(text);\n };\n\n const phase = (title: unknown) => {\n const text = requireString(title, 'phase title');\n state.currentPhase = text;\n if (!state.phases.includes(text)) state.phases.push(text);\n options.onPhase?.(text);\n };\n\n const budget = Object.freeze({\n total: options.tokenBudget ?? null,\n spent: () => state.spent,\n remaining: () =>\n options.tokenBudget == null\n ? Number.POSITIVE_INFINITY\n : Math.max(0, options.tokenBudget - state.spent),\n });\n\n const throwIfAborted = () => {\n if (options.signal?.aborted) throw new Error('workflow aborted');\n };\n\n const agent = async (prompt: unknown, agentOptions: unknown = {}) => {\n throwIfAborted();\n if (budget.total !== null && budget.remaining() <= 0) {\n throw new Error('workflow token budget exhausted');\n }\n if (state.agentCount >= maxSubagents) {\n throw new Error(`workflow agent quota exhausted (max ${maxSubagents})`);\n }\n\n const taskPrompt = requireString(prompt, 'agent prompt');\n const normalized = normalizeAgentOptions(agentOptions);\n const assignedPhase = normalized.phase ?? state.currentPhase;\n const requestedLabel = normalized.label?.trim();\n\n const runPromise = limiter(async () => {\n // Counter increments inside the limiter — id reflects start order, not enqueue order.\n state.agentCount += 1;\n const id = state.agentCount;\n const label = requestedLabel || defaultAgentLabel(assignedPhase, id);\n options.onAgentStart?.({ id, label, phase: assignedPhase, prompt: taskPrompt });\n\n try {\n throwIfAborted();\n const result = await deps.runner.run<unknown>(taskPrompt, {\n label,\n schema: normalized.schema,\n allowedToolNames: normalized.toolset,\n maxIterations: normalized.maxIterations,\n phase: assignedPhase,\n signal: options.signal,\n instructions: normalized.model\n ? `The parent workflow requested model \"${normalized.model}\".`\n : undefined,\n });\n throwIfAborted();\n\n const status: WorkflowAgentStatus = result === null ? 'error' : 'done';\n state.spent += estimateTokens(result);\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result, status });\n return result;\n } catch (e) {\n if (options.signal?.aborted) {\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result: null, status: 'skipped' });\n throw e;\n }\n const message = e instanceof Error ? e.message : String(e);\n log(`agent ${label} failed: ${message}`);\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result: null, status: 'error' });\n return null;\n }\n });\n\n pendingAgentRuns.add(runPromise);\n // `then` (not `finally`) keeps the bookkeeping promise from re-throwing into\n // the unhandled-rejection channel when `runPromise` rejects on abort.\n runPromise.then(\n () => pendingAgentRuns.delete(runPromise),\n () => pendingAgentRuns.delete(runPromise),\n );\n return runPromise;\n };\n\n const parallel = async (thunks: unknown) => {\n throwIfAborted();\n if (!Array.isArray(thunks)) throw new TypeError('parallel() expects an array of functions');\n for (const t of thunks) {\n if (typeof t !== 'function') {\n throw new TypeError(\n 'parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)',\n );\n }\n }\n return Promise.all(\n thunks.map(async (thunk, index) => {\n try {\n return await (thunk as () => unknown)();\n } catch (e) {\n if (options.signal?.aborted) throw e;\n const message = e instanceof Error ? e.message : String(e);\n log(`parallel[${index}] failed: ${message}`);\n return null;\n }\n }),\n );\n };\n\n const pipeline = async (items: unknown, ...stages: Array<unknown>) => {\n throwIfAborted();\n if (!Array.isArray(items)) {\n throw new TypeError('pipeline() expects an array as the first argument');\n }\n for (const stage of stages) {\n if (typeof stage !== 'function') {\n throw new TypeError(\n 'pipeline() stages must be functions: pipeline(items, item => ..., result => ...)',\n );\n }\n }\n const typedStages = stages as Array<\n (prev: unknown, original: unknown, index: number) => unknown\n >;\n return Promise.all(\n items.map(async (item, index) => {\n let value: unknown = item;\n for (const stage of typedStages) {\n try {\n throwIfAborted();\n value = await stage(value, item, index);\n throwIfAborted();\n } catch (e) {\n if (options.signal?.aborted) throw e;\n const message = e instanceof Error ? e.message : String(e);\n log(`pipeline[${index}] failed: ${message}`);\n return null;\n }\n }\n return value;\n }),\n );\n };\n\n const context = createContext({\n agent,\n parallel,\n pipeline,\n log,\n phase,\n args: options.args,\n cwd: options.cwd,\n process: Object.freeze({ cwd: () => options.cwd }),\n budget,\n console: {\n log,\n info: log,\n warn: (m: unknown) => log(`[warn] ${String(m)}`),\n error: (m: unknown) => log(`[error] ${String(m)}`),\n },\n JSON,\n Math,\n Array,\n Object,\n String,\n Number,\n Boolean,\n Set,\n Map,\n Promise,\n });\n\n const wrapped = `(async () => {\\n${body}\\n})()`;\n const script$ = new Script(wrapped, { filename: `${meta.name}.workflow.js` });\n\n let result: unknown;\n try {\n result = await script$.runInContext(context);\n // Wait for any agent() runs the script forgot to await before declaring success.\n await Promise.allSettled([...pendingAgentRuns]);\n } catch (e) {\n // Drain pending agent calls before propagating, so the snapshot reflects final state.\n await Promise.allSettled([...pendingAgentRuns]);\n throw e;\n }\n\n assertStructuredCloneable(result, 'workflow result');\n\n return {\n meta,\n result: result as T,\n logs: state.logs,\n phases: state.phases,\n agentCount: state.agentCount,\n durationMs: Date.now() - started,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Initial snapshot helper — kept here so the runtime is the single source of\n// truth for \"what a fresh snapshot looks like for this workflow\".\n// ---------------------------------------------------------------------------\n\nexport function emptySnapshotFor(name: string, description?: string): WorkflowSnapshot {\n return {\n name,\n description,\n phases: [],\n logs: [],\n agents: [],\n agentCount: 0,\n runningCount: 0,\n doneCount: 0,\n errorCount: 0,\n skippedCount: 0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\nfunction clampConcurrency(requested?: number): number {\n if (typeof requested === 'number' && Number.isFinite(requested) && requested >= 1) {\n return Math.min(Math.floor(requested), DEFAULT_CONCURRENCY_CEILING);\n }\n let cpu = DEFAULT_CONCURRENCY_CEILING;\n try {\n cpu = availableParallelism();\n } catch {\n // Some sandboxes throw; fall back to the ceiling.\n }\n return Math.max(DEFAULT_CONCURRENCY_FLOOR, Math.min(DEFAULT_CONCURRENCY_CEILING, cpu - 2));\n}\n\nfunction createLimiter(limit: number) {\n let active = 0;\n const queue: Array<() => void> = [];\n const next = () => {\n active -= 1;\n const resume = queue.shift();\n if (resume) resume();\n };\n return async <T>(fn: () => Promise<T>): Promise<T> => {\n if (active >= limit) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n active += 1;\n try {\n return await fn();\n } finally {\n next();\n }\n };\n}\n\nfunction requireString(value: unknown, name: string): string {\n if (typeof value !== 'string') throw new TypeError(`${name} must be a string`);\n return value;\n}\n\nfunction optionalString(value: unknown, name: string): string | undefined {\n if (value === undefined) return undefined;\n return requireString(value, name);\n}\n\nfunction normalizeAgentOptions(value: unknown): AgentScriptOptions {\n if (value === undefined || value === null) return {};\n if (typeof value !== 'object') throw new TypeError('agent options must be an object');\n const options = value as AgentScriptOptions;\n const toolset = options.toolset;\n if (toolset !== undefined) {\n if (!Array.isArray(toolset) || toolset.some((t) => typeof t !== 'string')) {\n throw new TypeError('agent toolset must be an array of strings');\n }\n }\n const maxIterations = options.maxIterations;\n if (maxIterations !== undefined) {\n if (typeof maxIterations !== 'number' || !Number.isFinite(maxIterations) || maxIterations < 1) {\n throw new TypeError('agent maxIterations must be a positive number');\n }\n }\n return {\n label: optionalString(options.label, 'agent label'),\n phase: optionalString(options.phase, 'agent phase'),\n schema: options.schema,\n model: optionalString(options.model, 'agent model'),\n toolset,\n maxIterations,\n };\n}\n\nfunction assertStructuredCloneable(value: unknown, name: string): void {\n try {\n structuredClone(value);\n } catch (e) {\n const detail = e instanceof Error ? ` ${e.message}` : '';\n throw new Error(\n `${name} must be structured-cloneable; did you forget to await agent(), parallel(), or pipeline()?${detail}`,\n );\n }\n}\n\nfunction defaultAgentLabel(phase: string | undefined, index: number): string {\n return phase ? `${phase} agent ${index}` : `agent ${index}`;\n}\n\nfunction estimateTokens(value: unknown): number {\n if (value === null || value === undefined) return 0;\n try {\n return Math.ceil(JSON.stringify(value).length / 4);\n } catch {\n return Math.ceil(String(value).length / 4);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runtime.js","names":[],"sources":["../../../../src/agent/workflow/runtime.ts"],"sourcesContent":["/**\n * Sandboxed workflow runtime.\n *\n * Parses the script via {@link parseWorkflowScript} (which strips the `meta`\n * export), wraps the remaining body in an async IIFE, and runs it inside a\n * Node `vm` context with a curated set of globals:\n *\n * - `agent(prompt, opts)` — spawns a subagent through the injected\n * {@link SubagentRunner} and returns its result (string, or schema-validated\n * object). Failures resolve to `null`.\n * - `parallel(thunks)` — concurrent fan-out; thunks (not promises!) so the\n * limiter sees each agent() call.\n * - `pipeline(items, ...stages)` — per-item sequential stages, items run\n * concurrently (no stage barrier). Each stage receives\n * `(prevResult, originalItem, index)`. A stage that throws drops that item\n * to `null` and skips remaining stages.\n * - `phase(title)` — marks the current phase; surfaces through `onPhase`.\n * - `log(message)` — appends to the workflow log.\n * - `budget` — `{ total, spent(), remaining() }` for self-pacing scripts.\n * - `args`, `cwd`, `process.cwd()`.\n *\n * The runtime is the only code that touches `vm`. It exposes no IO surface,\n * carries no LLM dependency, and is fully driven by injected callbacks — that\n * means the workflow tool, tests, and any future runner share one runtime.\n */\n\nimport { availableParallelism } from 'node:os';\nimport { createContext, Script } from 'node:vm';\n\nimport type { Api, Model } from '@earendil-works/pi-ai';\n\nimport { parseWorkflowScript } from './parser.js';\nimport type {\n AgentScriptOptions,\n SubagentRunner,\n WorkflowMetaPhase,\n WorkflowRunOptions,\n WorkflowRunResult,\n WorkflowSnapshot,\n WorkflowAgentStatus,\n} from './types.js';\n\nconst DEFAULT_CONCURRENCY_FLOOR = 1;\nconst DEFAULT_CONCURRENCY_CEILING = 16;\nconst DEFAULT_MAX_SUBAGENTS = 1000;\n\ninterface RuntimeState {\n currentPhase?: string;\n logs: string[];\n phases: string[];\n agentCount: number;\n spent: number;\n}\n\nexport interface RunWorkflowDeps {\n runner: SubagentRunner;\n /** Resolve a real model id (must contain `/`) to a {@link Model}. Throws on unknown id. */\n resolveModelId?: (modelId: string) => Model<Api>;\n}\n\nexport async function runWorkflow<T = unknown>(\n script: string,\n deps: RunWorkflowDeps,\n options: WorkflowRunOptions,\n): Promise<WorkflowRunResult<T>> {\n const started = Date.now();\n const { meta, body } = parseWorkflowScript(script);\n\n const state: RuntimeState = { logs: [], phases: [], agentCount: 0, spent: 0 };\n const concurrency = clampConcurrency(options.concurrency);\n const maxSubagents = Math.max(1, options.maxSubagents ?? DEFAULT_MAX_SUBAGENTS);\n const limiter = createLimiter(concurrency);\n const pendingAgentRuns = new Set<Promise<unknown>>();\n const phaseDefaultModels = buildPhaseModelMap(meta.phases);\n\n const log = (message: unknown) => {\n const text = String(message);\n state.logs.push(text);\n options.onLog?.(text);\n };\n\n const phase = (title: unknown) => {\n const text = requireString(title, 'phase title');\n state.currentPhase = text;\n if (!state.phases.includes(text)) state.phases.push(text);\n options.onPhase?.(text);\n };\n\n const budget = Object.freeze({\n total: options.tokenBudget ?? null,\n spent: () => state.spent,\n remaining: () =>\n options.tokenBudget == null\n ? Number.POSITIVE_INFINITY\n : Math.max(0, options.tokenBudget - state.spent),\n });\n\n const throwIfAborted = () => {\n if (options.signal?.aborted) throw new Error('workflow aborted');\n };\n\n const resolveAgentModel = (\n normalized: AgentScriptOptions,\n assignedPhase: string | undefined,\n ): Model<Api> | undefined => {\n const realId = normalized.model?.trim();\n if (realId) {\n if (!deps.resolveModelId) {\n throw new Error('workflow runtime missing resolveModelId; cannot resolve real model id');\n }\n return deps.resolveModelId(realId);\n }\n if (assignedPhase) {\n const phaseRef = phaseDefaultModels.get(assignedPhase);\n if (phaseRef) {\n if (phaseRef.includes('/')) {\n if (!deps.resolveModelId) return undefined;\n return deps.resolveModelId(phaseRef);\n }\n }\n }\n return undefined;\n };\n\n const agent = async (prompt: unknown, agentOptions: unknown = {}) => {\n throwIfAborted();\n if (budget.total !== null && budget.remaining() <= 0) {\n throw new Error('workflow token budget exhausted');\n }\n if (state.agentCount >= maxSubagents) {\n throw new Error(`workflow agent quota exhausted (max ${maxSubagents})`);\n }\n\n const taskPrompt = requireString(prompt, 'agent prompt');\n const normalized = normalizeAgentOptions(agentOptions);\n const assignedPhase = normalized.phase ?? state.currentPhase;\n const requestedLabel = normalized.label?.trim();\n\n const runPromise = limiter(async () => {\n // Counter increments inside the limiter — id reflects start order, not enqueue order.\n state.agentCount += 1;\n const id = state.agentCount;\n const label = requestedLabel || defaultAgentLabel(assignedPhase, id);\n options.onAgentStart?.({ id, label, phase: assignedPhase, prompt: taskPrompt });\n\n try {\n throwIfAborted();\n const resolvedModel = resolveAgentModel(normalized, assignedPhase);\n const result = await deps.runner.run<unknown>(taskPrompt, {\n label,\n schema: normalized.schema,\n allowedToolNames: normalized.toolset,\n maxIterations: normalized.maxIterations,\n phase: assignedPhase,\n signal: options.signal,\n model: resolvedModel,\n });\n throwIfAborted();\n\n const status: WorkflowAgentStatus = result === null ? 'error' : 'done';\n state.spent += estimateTokens(result);\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result, status });\n return result;\n } catch (e) {\n if (options.signal?.aborted) {\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result: null, status: 'skipped' });\n throw e;\n }\n const message = e instanceof Error ? e.message : String(e);\n log(`agent ${label} failed: ${message}`);\n options.onAgentEnd?.({ id, label, phase: assignedPhase, result: null, status: 'error' });\n return null;\n }\n });\n\n pendingAgentRuns.add(runPromise);\n // `then` (not `finally`) keeps the bookkeeping promise from re-throwing into\n // the unhandled-rejection channel when `runPromise` rejects on abort.\n runPromise.then(\n () => pendingAgentRuns.delete(runPromise),\n () => pendingAgentRuns.delete(runPromise),\n );\n return runPromise;\n };\n\n const parallel = async (thunks: unknown) => {\n throwIfAborted();\n if (!Array.isArray(thunks)) throw new TypeError('parallel() expects an array of functions');\n for (const t of thunks) {\n if (typeof t !== 'function') {\n throw new TypeError(\n 'parallel() expects an array of functions, not promises. Wrap each call: () => agent(...)',\n );\n }\n }\n return Promise.all(\n thunks.map(async (thunk, index) => {\n try {\n return await (thunk as () => unknown)();\n } catch (e) {\n if (options.signal?.aborted) throw e;\n const message = e instanceof Error ? e.message : String(e);\n log(`parallel[${index}] failed: ${message}`);\n return null;\n }\n }),\n );\n };\n\n const pipeline = async (items: unknown, ...stages: Array<unknown>) => {\n throwIfAborted();\n if (!Array.isArray(items)) {\n throw new TypeError('pipeline() expects an array as the first argument');\n }\n for (const stage of stages) {\n if (typeof stage !== 'function') {\n throw new TypeError(\n 'pipeline() stages must be functions: pipeline(items, item => ..., result => ...)',\n );\n }\n }\n const typedStages = stages as Array<\n (prev: unknown, original: unknown, index: number) => unknown\n >;\n return Promise.all(\n items.map(async (item, index) => {\n let value: unknown = item;\n for (const stage of typedStages) {\n try {\n throwIfAborted();\n value = await stage(value, item, index);\n throwIfAborted();\n } catch (e) {\n if (options.signal?.aborted) throw e;\n const message = e instanceof Error ? e.message : String(e);\n log(`pipeline[${index}] failed: ${message}`);\n return null;\n }\n }\n return value;\n }),\n );\n };\n\n const context = createContext({\n agent,\n parallel,\n pipeline,\n log,\n phase,\n args: options.args,\n cwd: options.cwd,\n process: Object.freeze({ cwd: () => options.cwd }),\n budget,\n console: {\n log,\n info: log,\n warn: (m: unknown) => log(`[warn] ${String(m)}`),\n error: (m: unknown) => log(`[error] ${String(m)}`),\n },\n JSON,\n Math,\n Array,\n Object,\n String,\n Number,\n Boolean,\n Set,\n Map,\n Promise,\n });\n\n const wrapped = `(async () => {\\n${body}\\n})()`;\n const script$ = new Script(wrapped, { filename: `${meta.name}.workflow.js` });\n\n let result: unknown;\n try {\n result = await script$.runInContext(context);\n // Wait for any agent() runs the script forgot to await before declaring success.\n await Promise.allSettled([...pendingAgentRuns]);\n } catch (e) {\n // Drain pending agent calls before propagating, so the snapshot reflects final state.\n await Promise.allSettled([...pendingAgentRuns]);\n throw rewriteScriptError(e);\n }\n\n assertStructuredCloneable(result, 'workflow result');\n\n return {\n meta,\n result: result as T,\n logs: state.logs,\n phases: state.phases,\n agentCount: state.agentCount,\n durationMs: Date.now() - started,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Initial snapshot helper — kept here so the runtime is the single source of\n// truth for \"what a fresh snapshot looks like for this workflow\".\n// ---------------------------------------------------------------------------\n\nexport function emptySnapshotFor(name: string, description?: string): WorkflowSnapshot {\n return {\n name,\n description,\n phases: [],\n logs: [],\n agents: [],\n agentCount: 0,\n runningCount: 0,\n doneCount: 0,\n errorCount: 0,\n skippedCount: 0,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internals\n// ---------------------------------------------------------------------------\n\nfunction clampConcurrency(requested?: number): number {\n if (typeof requested === 'number' && Number.isFinite(requested) && requested >= 1) {\n return Math.min(Math.floor(requested), DEFAULT_CONCURRENCY_CEILING);\n }\n let cpu = DEFAULT_CONCURRENCY_CEILING;\n try {\n cpu = availableParallelism();\n } catch {\n // Some sandboxes throw; fall back to the ceiling.\n }\n return Math.max(DEFAULT_CONCURRENCY_FLOOR, Math.min(DEFAULT_CONCURRENCY_CEILING, cpu - 2));\n}\n\nfunction createLimiter(limit: number) {\n let active = 0;\n const queue: Array<() => void> = [];\n const next = () => {\n active -= 1;\n const resume = queue.shift();\n if (resume) resume();\n };\n return async <T>(fn: () => Promise<T>): Promise<T> => {\n if (active >= limit) {\n await new Promise<void>((resolve) => queue.push(resolve));\n }\n active += 1;\n try {\n return await fn();\n } finally {\n next();\n }\n };\n}\n\nfunction buildPhaseModelMap(phases: WorkflowMetaPhase[] | undefined): Map<string, string> {\n const out = new Map<string, string>();\n if (!phases) return out;\n for (const p of phases) {\n if (p && typeof p.title === 'string' && typeof p.model === 'string' && p.model.trim()) {\n out.set(p.title, p.model.trim());\n }\n }\n return out;\n}\n\nfunction requireString(value: unknown, name: string): string {\n if (typeof value !== 'string') throw new TypeError(`${name} must be a string`);\n return value;\n}\n\nfunction optionalString(value: unknown, name: string): string | undefined {\n if (value === undefined) return undefined;\n return requireString(value, name);\n}\n\nfunction normalizeAgentOptions(value: unknown): AgentScriptOptions {\n if (value === undefined || value === null) return {};\n if (typeof value !== 'object') throw new TypeError('agent options must be an object');\n const options = value as AgentScriptOptions;\n const toolset = options.toolset;\n if (toolset !== undefined) {\n if (!Array.isArray(toolset) || toolset.some((t) => typeof t !== 'string')) {\n throw new TypeError('agent toolset must be an array of strings');\n }\n }\n const maxIterations = options.maxIterations;\n if (maxIterations !== undefined) {\n if (typeof maxIterations !== 'number' || !Number.isFinite(maxIterations) || maxIterations < 1) {\n throw new TypeError('agent maxIterations must be a positive number');\n }\n }\n const modelStr = optionalString(options.model, 'agent model');\n if (modelStr !== undefined) {\n const trimmed = modelStr.trim();\n if (trimmed && !trimmed.includes('/')) {\n throw new Error(\n `agent option 'model' must be a real model id in 'provider/model' form (got '${trimmed}')`,\n );\n }\n }\n return {\n label: optionalString(options.label, 'agent label'),\n phase: optionalString(options.phase, 'agent phase'),\n schema: options.schema,\n model: modelStr,\n toolset,\n maxIterations,\n };\n}\n\nfunction assertStructuredCloneable(value: unknown, name: string): void {\n try {\n structuredClone(value);\n } catch (e) {\n const promisePath = findPromisePath(value, '');\n if (promisePath !== null) {\n throw new Error(\n `${name} contains a Promise at \\`${promisePath || '<root>'}\\` — missing 'await' before parallel()/pipeline()/agent(). ` +\n `Async return auto-unwraps a bare Promise, but a Promise nested in an object/array is returned as-is.`,\n );\n }\n const detail = e instanceof Error ? ` ${e.message}` : '';\n throw new Error(\n `${name} must be structured-cloneable; did you forget to await agent(), parallel(), or pipeline()?${detail}`,\n );\n }\n}\n\n/**\n * Find the first Promise lurking in `value`, returning a JS-path like\n * \"results[0].pending\". Returns null when no Promise is found. Bounded\n * depth/breadth so it never explodes on weird user shapes.\n */\nfunction findPromisePath(value: unknown, path: string, depth = 0): string | null {\n if (depth > 4) return null;\n if (isThenable(value)) return path;\n if (Array.isArray(value)) {\n for (let i = 0; i < Math.min(value.length, 32); i++) {\n const inner = findPromisePath(value[i], `${path}[${i}]`, depth + 1);\n if (inner !== null) return inner;\n }\n return null;\n }\n if (value !== null && typeof value === 'object') {\n let count = 0;\n for (const [key, child] of Object.entries(value as Record<string, unknown>)) {\n if (count++ > 32) break;\n const inner = findPromisePath(child, path ? `${path}.${key}` : key, depth + 1);\n if (inner !== null) return inner;\n }\n }\n return null;\n}\n\nfunction isThenable(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { then?: unknown }).then === 'function'\n );\n}\n\n/**\n * Rewrite TypeErrors that look like \"called .map on a Promise\" with an\n * await-shaped hint. Static lint catches the obvious form at parse time;\n * this is the safety net for cases lint can't see (dynamic indirection,\n * values stashed in helper closures).\n */\nconst PROMISE_METHOD_ERROR =\n /\\.(map|filter|forEach|flat|flatMap|find|some|every|reduce|join|length|then)\\b is not a function/;\n\nfunction rewriteScriptError(error: unknown): Error {\n // VM-context errors aren't `instanceof` host classes (the vm has its own\n // global constructors), so check by name + duck-typing message.\n const asError = error as { name?: string; message?: string } | null | undefined;\n const isTypeError = asError?.name === 'TypeError' && typeof asError.message === 'string';\n if (!isTypeError) {\n return error instanceof Error ? error : new Error(String(error));\n }\n const message = (asError as { message: string }).message;\n if (!PROMISE_METHOD_ERROR.test(message)) {\n return error instanceof Error ? error : new Error(message);\n }\n return new Error(\n `${message}\\n\\nHint: parallel()/pipeline()/agent() return a Promise — 'await' the call before using its result.\\n` +\n ` ❌ const r = parallel(...); r.map(...)\\n` +\n ` ✅ const r = await parallel(...); r.map(...)`,\n );\n}\n\nfunction defaultAgentLabel(phase: string | undefined, index: number): string {\n return phase ? `${phase} agent ${index}` : `agent ${index}`;\n}\n\nfunction estimateTokens(value: unknown): number {\n if (value === null || value === undefined) return 0;\n try {\n return Math.ceil(JSON.stringify(value).length / 4);\n } catch {\n return Math.ceil(String(value).length / 4);\n }\n}\n"],"mappings":"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"}
|
|
@@ -24,11 +24,19 @@ export interface WorkflowMetaPhase {
|
|
|
24
24
|
detail?: string;
|
|
25
25
|
model?: string;
|
|
26
26
|
}
|
|
27
|
+
export interface WorkflowMetaEstimatedAgents {
|
|
28
|
+
min: number;
|
|
29
|
+
max: number;
|
|
30
|
+
}
|
|
27
31
|
export interface WorkflowMeta {
|
|
28
32
|
name: string;
|
|
29
33
|
description: string;
|
|
30
34
|
whenToUse?: string;
|
|
31
35
|
phases?: WorkflowMetaPhase[];
|
|
36
|
+
/** Discovery tags, e.g. `['code-review', 'planning']`. */
|
|
37
|
+
tags?: string[];
|
|
38
|
+
/** Rough subagent count range for UX / cost hints. */
|
|
39
|
+
estimatedAgents?: WorkflowMetaEstimatedAgents;
|
|
32
40
|
}
|
|
33
41
|
export type WorkflowAgentStatus = 'queued' | 'running' | 'done' | 'error' | 'skipped';
|
|
34
42
|
export interface WorkflowAgentSnapshot {
|
|
@@ -94,7 +102,9 @@ export interface AgentScriptOptions {
|
|
|
94
102
|
label?: string;
|
|
95
103
|
phase?: string;
|
|
96
104
|
schema?: JsonSchema;
|
|
97
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* Real model id (e.g. `openai/gpt-4o-mini`) — must contain `/`.
|
|
107
|
+
*/
|
|
98
108
|
model?: string;
|
|
99
109
|
/** Subagent tool allowlist override (forwarded to the runner). */
|
|
100
110
|
toolset?: string[];
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { resolveBrowserCommandTimeoutMs } from "./browser-command-timeout.js";
|
|
2
|
-
import { BrowserBackSchema, BrowserCdpSchema, BrowserClickSchema, BrowserCloseSchema, BrowserConsoleSchema, BrowserDialogSchema, BrowserGetImagesSchema, BrowserNavigateSchema, BrowserPressSchema, BrowserScreenshotSchema, BrowserScrollSchema, BrowserSnapshotSchema, BrowserTypeSchema, BrowserVisionSchema } from "./schemas.js";
|
|
3
|
-
import { assertBrowserUrlAllowed, checkPostRedirectUrl, containsApiKeyPattern, isAlwaysBlockedUrl } from "./url-policy.js";
|
|
4
|
-
import { snapshotSummaryHeader, truncateSnapshotAtBoundary } from "./snapshot-helpers.js";
|
|
5
1
|
import { BrowserManager } from "./manager.js";
|
|
6
2
|
import { resolveBrowserBackendFromConfig } from "./backend-from-config.js";
|
|
3
|
+
import { resolveBrowserCommandTimeoutMs } from "./browser-command-timeout.js";
|
|
7
4
|
import { BrowserNotReadyError, buildBrowserSetupDeepLink, checkBrowserReadiness } from "./readiness.js";
|
|
5
|
+
import { assertBrowserUrlAllowed, checkPostRedirectUrl, containsApiKeyPattern, isAlwaysBlockedUrl } from "./url-policy.js";
|
|
8
6
|
import { CdpSupervisor } from "./cdp-supervisor.js";
|
|
7
|
+
import { snapshotSummaryHeader, truncateSnapshotAtBoundary } from "./snapshot-helpers.js";
|
|
9
8
|
import { checkBotDetection, cleanupOrphanProcesses, startTracing, stopTracing } from "./session-lifecycle.js";
|
|
9
|
+
import { BrowserBackSchema, BrowserCdpSchema, BrowserClickSchema, BrowserCloseSchema, BrowserConsoleSchema, BrowserDialogSchema, BrowserGetImagesSchema, BrowserNavigateSchema, BrowserPressSchema, BrowserScreenshotSchema, BrowserScrollSchema, BrowserSnapshotSchema, BrowserTypeSchema, BrowserVisionSchema } from "./schemas.js";
|
|
10
10
|
import { generateMousePath, generateScrollPlan, generateTypingPlan, humanizedClick, humanizedFill, humanizedPress, humanizedScroll, resolveHumanConfig } from "./humanize.js";
|
|
11
11
|
import { WEBDRIVER_OVERRIDE_SCRIPT, buildLocalStealthArgs, buildStealthArgs, generateFingerprintSeed, makeExecutable, removeQuarantineAttr } from "./stealth.js";
|
|
12
12
|
import { createBrowserActionRegistry } from "./actions/registry.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Page } from 'playwright-core';
|
|
2
2
|
import type { BrowserBackend } from './providers/types.js';
|
|
3
3
|
import type { ExtensionBrowserProvider } from './providers/extension.js';
|
|
4
4
|
export interface BrowserManagerOptions {
|
|
@@ -37,8 +37,6 @@ export declare class BrowserManager {
|
|
|
37
37
|
* Extension mode does not create a Playwright {@link BrowserContext}.
|
|
38
38
|
*/
|
|
39
39
|
ensureConnected(): Promise<void>;
|
|
40
|
-
/** @deprecated Use {@link ensureConnected}. */
|
|
41
|
-
ensureBrowser(): Promise<BrowserContext>;
|
|
42
40
|
private _launchLocal;
|
|
43
41
|
private _connectViaCdp;
|
|
44
42
|
private _connectViaCloud;
|
|
@@ -118,12 +118,6 @@ var BrowserManager = class {
|
|
|
118
118
|
this.activeBackendMode = backend.mode;
|
|
119
119
|
if (this.browser) this._wireBrowserLifecycle(this.browser);
|
|
120
120
|
}
|
|
121
|
-
/** @deprecated Use {@link ensureConnected}. */
|
|
122
|
-
async ensureBrowser() {
|
|
123
|
-
await this.ensureConnected();
|
|
124
|
-
if (!this.context) throw new Error("ensureBrowser: no Playwright context (extension backend uses ensureConnected + registry bridge)");
|
|
125
|
-
return this.context;
|
|
126
|
-
}
|
|
127
121
|
async _launchLocal(headless) {
|
|
128
122
|
const pw = await loadPlaywrightCoreModule();
|
|
129
123
|
const chromium = pw.chromium ?? pw.default?.chromium;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.js","names":[],"sources":["../../../src/browser/manager.ts"],"sourcesContent":["import type { Browser, BrowserContext, Page } from 'playwright-core';\n\nimport { createLogger } from '../utils/logger.js';\n\nimport { loadPlaywrightCoreModule } from './providers/playwright-doctor.js';\nimport type { BrowserBackend, CloudBrowserProvider, CloudBrowserProviderConfig, ExtensionConnectionConfig } from './providers/types.js';\nimport type { ExtensionBrowserProvider } from './providers/extension.js';\n\nconst log = createLogger('browser-manager');\n\nconst MAX_PAGES = 3;\nconst PAGE_IDLE_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface BrowserManagerOptions {\n getHeadless: () => boolean;\n /** Backend connection mode. Default: local Playwright. */\n getBackend?: () => BrowserBackend;\n}\n\n/**\n * Multi-backend browser manager — supports local Playwright, direct CDP, and cloud providers.\n *\n * One browser context shared by all sessions; one {@link Page} per task/session key (max {@link MAX_PAGES}).\n * Backend is selected at first connection based on {@link BrowserManagerOptions.getBackend}.\n */\nexport class BrowserManager {\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private cloudProvider: CloudBrowserProvider | null = null;\n private extensionProvider: ExtensionBrowserProvider | null = null;\n private extensionRelease: (() => Promise<void>) | null = null;\n private cloakChildProcess: import('node:child_process').ChildProcess | null = null;\n private cloakTempProfileDir: string | null = null;\n private pages = new Map<string, { page: Page; lastUsed: number }>();\n private readonly options: BrowserManagerOptions;\n private activeBackendMode: BrowserBackend['mode'] | null = null;\n\n constructor(options: BrowserManagerOptions) {\n this.options = options;\n }\n\n /** Current backend mode (null if not yet connected). */\n get backendMode(): string | null {\n return this.activeBackendMode;\n }\n\n private evictIdlePages(): void {\n const now = Date.now();\n for (const [id, entry] of this.pages) {\n if (entry.page.isClosed() || now - entry.lastUsed > PAGE_IDLE_TIMEOUT_MS) {\n void entry.page.close().catch(() => {});\n this.pages.delete(id);\n log.debug({ taskId: id }, 'Evicted idle or closed browser page');\n }\n }\n }\n\n private _isPlaywrightConnectionAlive(): boolean {\n if (!this.browser || !this.context) return false;\n try {\n if (typeof this.browser.isConnected === 'function' && !this.browser.isConnected()) {\n return false;\n }\n return true;\n } catch {\n return false;\n }\n }\n\n private _wireBrowserLifecycle(browser: Browser): void {\n browser.on('disconnected', () => {\n log.warn({ mode: this.activeBackendMode }, 'Browser disconnected — clearing stale session');\n this._clearPlaywrightSessionRefs();\n });\n }\n\n /** Drop Playwright handles without tearing down extension bridge. */\n private _clearPlaywrightSessionRefs(): void {\n this.pages.clear();\n this.context = null;\n this.browser = null;\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n if (this.cloudProvider) {\n void this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n }\n\n private async _resetStalePlaywrightSession(): Promise<void> {\n for (const [, entry] of this.pages) {\n await entry.page.close().catch(() => {});\n }\n this.pages.clear();\n\n if (this.cloudProvider) {\n await this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n\n if (this.cloakChildProcess || this.cloakTempProfileDir) {\n const { cleanupCloakBrowser } = await import('./providers/cloakbrowser.js');\n await cleanupCloakBrowser(this.cloakChildProcess, this.cloakTempProfileDir).catch(() => {});\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n }\n\n await this.context?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n this.context = null;\n this.browser = null;\n }\n\n /**\n * Ensure Playwright context or Chrome Extension provider is ready.\n * Extension mode does not create a Playwright {@link BrowserContext}.\n */\n async ensureConnected(): Promise<void> {\n if (this.extensionProvider) return;\n\n if (this.context && this._isPlaywrightConnectionAlive()) {\n return;\n }\n\n if (this.context || this.browser) {\n log.warn({ mode: this.activeBackendMode }, 'Browser session unavailable — reconnecting');\n await this._resetStalePlaywrightSession();\n }\n\n const backend = this.options.getBackend?.() ?? { mode: 'local' as const, headless: false };\n\n switch (backend.mode) {\n case 'cdp':\n await this._connectViaCdp(backend.config.wsEndpoint);\n break;\n case 'cloud':\n await this._connectViaCloud(backend.config);\n break;\n case 'extension':\n await this._connectViaExtension(backend.config);\n break;\n case 'cloakbrowser':\n await this._connectViaCloakBrowser(backend.config);\n break;\n case 'local':\n default:\n await this._launchLocal(backend.mode === 'local' ? backend.headless : this.options.getHeadless() === true);\n break;\n }\n\n this.activeBackendMode = backend.mode;\n if (this.browser) {\n this._wireBrowserLifecycle(this.browser);\n }\n }\n\n /** @deprecated Use {@link ensureConnected}. */\n async ensureBrowser(): Promise<BrowserContext> {\n await this.ensureConnected();\n if (!this.context) {\n throw new Error('ensureBrowser: no Playwright context (extension backend uses ensureConnected + registry bridge)');\n }\n return this.context;\n }\n\n private async _launchLocal(headless: boolean): Promise<void> {\n const pw = await loadPlaywrightCoreModule();\n const chromium = pw.chromium ?? (pw as { default?: { chromium?: (typeof pw)['chromium'] } }).default?.chromium;\n if (!chromium?.launch) {\n throw new Error(\n 'playwright-core did not expose chromium (try reinstall: pnpm install playwright-core; install browser: npx playwright install chromium)',\n );\n }\n this.browser = await chromium.launch({\n headless,\n ...(headless ? { channel: 'chromium' } : {}),\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n this.context = await this.browser.newContext({\n viewport: { width: 1280, height: 720 },\n userAgent:\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n });\n log.info({ headless, mode: 'local' }, 'Browser launched (local)');\n }\n\n private async _connectViaCdp(wsEndpoint: string): Promise<void> {\n const pw = await loadPlaywrightCoreModule();\n const chromium = pw.chromium ?? (pw as { default?: { chromium?: (typeof pw)['chromium'] } }).default?.chromium;\n if (!chromium?.connectOverCDP) {\n throw new Error('playwright-core does not support connectOverCDP');\n }\n this.browser = await chromium.connectOverCDP(wsEndpoint);\n const contexts = this.browser.contexts();\n this.context = contexts.length > 0 ? contexts[0] : await this.browser.newContext();\n log.info({ mode: 'cdp', wsEndpoint }, 'Browser connected (CDP)');\n }\n\n private async _connectViaCloud(config: CloudBrowserProviderConfig): Promise<void> {\n const { BrowserbaseProvider } = await import('./providers/browserbase.js');\n const { BrowserUseProvider } = await import('./providers/browser-use.js');\n\n const provider = config.type === 'browserbase'\n ? new BrowserbaseProvider(config)\n : new BrowserUseProvider(config);\n\n const { browser, context } = await provider.connect();\n this.browser = browser;\n this.context = context;\n this.cloudProvider = provider;\n log.info({ mode: 'cloud', provider: config.type }, `Browser connected (${config.type})`);\n }\n\n private async _connectViaExtension(config?: ExtensionConnectionConfig): Promise<void> {\n const { acquireExtensionBrowserServer } = await import('./providers/extension-ws-acquire.js');\n const { provider, release } = await acquireExtensionBrowserServer(config);\n this.extensionProvider = provider;\n this.extensionRelease = release;\n log.info({ port: config?.port ?? 19820 }, 'Extension WS server ready, waiting for Chrome Extension...');\n await provider.waitForConnection();\n // Extension mode does not use Playwright — context stays null.\n // The action registry dispatches directly via extensionProvider.sendCommand().\n log.info({ mode: 'extension' }, 'Browser connected (Chrome Extension)');\n }\n\n private async _connectViaCloakBrowser(config?: import('./providers/types.js').CloakBrowserConfig): Promise<void> {\n const { launchCloakBrowser } = await import('./providers/cloakbrowser.js');\n const result = await launchCloakBrowser(config);\n if (!result.browser || !result.context) {\n throw new Error('BrowserManager: CloakBrowser launch did not return a Playwright connection');\n }\n this.browser = result.browser;\n this.context = result.context;\n this.cloakChildProcess = result.childProcess;\n this.cloakTempProfileDir = result.temporaryProfileDir;\n log.info({ mode: 'cloakbrowser' }, 'Browser connected (CloakBrowser)');\n }\n\n async getPage(taskId: string): Promise<Page> {\n if (this.extensionProvider) {\n throw new Error('BrowserManager.getPage is not used in Chrome Extension backend mode');\n }\n\n this.evictIdlePages();\n await this.ensureConnected();\n\n const existing = this.pages.get(taskId);\n if (existing && !existing.page.isClosed()) {\n existing.lastUsed = Date.now();\n return existing.page;\n }\n if (existing) {\n this.pages.delete(taskId);\n }\n\n if (this.pages.size >= MAX_PAGES) {\n const oldest = [...this.pages.entries()].sort((a, b) => a[1].lastUsed - b[1].lastUsed)[0];\n if (oldest) {\n await oldest[1].page.close().catch(() => {});\n this.pages.delete(oldest[0]);\n }\n }\n\n const ctx = this.context;\n if (!ctx) {\n throw new Error('BrowserManager: Playwright context missing after ensureConnected');\n }\n const page = await ctx.newPage();\n this.pages.set(taskId, { page, lastUsed: Date.now() });\n return page;\n }\n\n async closePage(taskId: string): Promise<void> {\n if (this.extensionProvider) {\n return;\n }\n const entry = this.pages.get(taskId);\n if (entry) {\n await entry.page.close().catch(() => {});\n this.pages.delete(taskId);\n }\n }\n\n /** Get the extension provider (only available in extension mode). */\n getExtensionProvider(): ExtensionBrowserProvider | null {\n return this.extensionProvider;\n }\n\n async shutdown(): Promise<void> {\n for (const [, entry] of this.pages) {\n await entry.page.close().catch(() => {});\n }\n this.pages.clear();\n\n if (this.cloudProvider) {\n await this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n\n if (this.extensionRelease) {\n await this.extensionRelease().catch(() => {});\n this.extensionRelease = null;\n }\n this.extensionProvider = null;\n\n // CloakBrowser cleanup — kill child process and remove temp profile\n if (this.cloakChildProcess || this.cloakTempProfileDir) {\n const { cleanupCloakBrowser } = await import('./providers/cloakbrowser.js');\n await cleanupCloakBrowser(this.cloakChildProcess, this.cloakTempProfileDir).catch(() => {});\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n }\n\n await this.context?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n this.context = null;\n this.browser = null;\n this.activeBackendMode = null;\n log.info('Browser shut down');\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"manager.js","names":[],"sources":["../../../src/browser/manager.ts"],"sourcesContent":["import type { Browser, BrowserContext, Page } from 'playwright-core';\n\nimport { createLogger } from '../utils/logger.js';\n\nimport { loadPlaywrightCoreModule } from './providers/playwright-doctor.js';\nimport type { BrowserBackend, CloudBrowserProvider, CloudBrowserProviderConfig, ExtensionConnectionConfig } from './providers/types.js';\nimport type { ExtensionBrowserProvider } from './providers/extension.js';\n\nconst log = createLogger('browser-manager');\n\nconst MAX_PAGES = 3;\nconst PAGE_IDLE_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport interface BrowserManagerOptions {\n getHeadless: () => boolean;\n /** Backend connection mode. Default: local Playwright. */\n getBackend?: () => BrowserBackend;\n}\n\n/**\n * Multi-backend browser manager — supports local Playwright, direct CDP, and cloud providers.\n *\n * One browser context shared by all sessions; one {@link Page} per task/session key (max {@link MAX_PAGES}).\n * Backend is selected at first connection based on {@link BrowserManagerOptions.getBackend}.\n */\nexport class BrowserManager {\n private browser: Browser | null = null;\n private context: BrowserContext | null = null;\n private cloudProvider: CloudBrowserProvider | null = null;\n private extensionProvider: ExtensionBrowserProvider | null = null;\n private extensionRelease: (() => Promise<void>) | null = null;\n private cloakChildProcess: import('node:child_process').ChildProcess | null = null;\n private cloakTempProfileDir: string | null = null;\n private pages = new Map<string, { page: Page; lastUsed: number }>();\n private readonly options: BrowserManagerOptions;\n private activeBackendMode: BrowserBackend['mode'] | null = null;\n\n constructor(options: BrowserManagerOptions) {\n this.options = options;\n }\n\n /** Current backend mode (null if not yet connected). */\n get backendMode(): string | null {\n return this.activeBackendMode;\n }\n\n private evictIdlePages(): void {\n const now = Date.now();\n for (const [id, entry] of this.pages) {\n if (entry.page.isClosed() || now - entry.lastUsed > PAGE_IDLE_TIMEOUT_MS) {\n void entry.page.close().catch(() => {});\n this.pages.delete(id);\n log.debug({ taskId: id }, 'Evicted idle or closed browser page');\n }\n }\n }\n\n private _isPlaywrightConnectionAlive(): boolean {\n if (!this.browser || !this.context) return false;\n try {\n if (typeof this.browser.isConnected === 'function' && !this.browser.isConnected()) {\n return false;\n }\n return true;\n } catch {\n return false;\n }\n }\n\n private _wireBrowserLifecycle(browser: Browser): void {\n browser.on('disconnected', () => {\n log.warn({ mode: this.activeBackendMode }, 'Browser disconnected — clearing stale session');\n this._clearPlaywrightSessionRefs();\n });\n }\n\n /** Drop Playwright handles without tearing down extension bridge. */\n private _clearPlaywrightSessionRefs(): void {\n this.pages.clear();\n this.context = null;\n this.browser = null;\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n if (this.cloudProvider) {\n void this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n }\n\n private async _resetStalePlaywrightSession(): Promise<void> {\n for (const [, entry] of this.pages) {\n await entry.page.close().catch(() => {});\n }\n this.pages.clear();\n\n if (this.cloudProvider) {\n await this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n\n if (this.cloakChildProcess || this.cloakTempProfileDir) {\n const { cleanupCloakBrowser } = await import('./providers/cloakbrowser.js');\n await cleanupCloakBrowser(this.cloakChildProcess, this.cloakTempProfileDir).catch(() => {});\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n }\n\n await this.context?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n this.context = null;\n this.browser = null;\n }\n\n /**\n * Ensure Playwright context or Chrome Extension provider is ready.\n * Extension mode does not create a Playwright {@link BrowserContext}.\n */\n async ensureConnected(): Promise<void> {\n if (this.extensionProvider) return;\n\n if (this.context && this._isPlaywrightConnectionAlive()) {\n return;\n }\n\n if (this.context || this.browser) {\n log.warn({ mode: this.activeBackendMode }, 'Browser session unavailable — reconnecting');\n await this._resetStalePlaywrightSession();\n }\n\n const backend = this.options.getBackend?.() ?? { mode: 'local' as const, headless: false };\n\n switch (backend.mode) {\n case 'cdp':\n await this._connectViaCdp(backend.config.wsEndpoint);\n break;\n case 'cloud':\n await this._connectViaCloud(backend.config);\n break;\n case 'extension':\n await this._connectViaExtension(backend.config);\n break;\n case 'cloakbrowser':\n await this._connectViaCloakBrowser(backend.config);\n break;\n case 'local':\n default:\n await this._launchLocal(backend.mode === 'local' ? backend.headless : this.options.getHeadless() === true);\n break;\n }\n\n this.activeBackendMode = backend.mode;\n if (this.browser) {\n this._wireBrowserLifecycle(this.browser);\n }\n }\n\n private async _launchLocal(headless: boolean): Promise<void> {\n const pw = await loadPlaywrightCoreModule();\n const chromium = pw.chromium ?? (pw as { default?: { chromium?: (typeof pw)['chromium'] } }).default?.chromium;\n if (!chromium?.launch) {\n throw new Error(\n 'playwright-core did not expose chromium (try reinstall: pnpm install playwright-core; install browser: npx playwright install chromium)',\n );\n }\n this.browser = await chromium.launch({\n headless,\n ...(headless ? { channel: 'chromium' } : {}),\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n this.context = await this.browser.newContext({\n viewport: { width: 1280, height: 720 },\n userAgent:\n 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',\n });\n log.info({ headless, mode: 'local' }, 'Browser launched (local)');\n }\n\n private async _connectViaCdp(wsEndpoint: string): Promise<void> {\n const pw = await loadPlaywrightCoreModule();\n const chromium = pw.chromium ?? (pw as { default?: { chromium?: (typeof pw)['chromium'] } }).default?.chromium;\n if (!chromium?.connectOverCDP) {\n throw new Error('playwright-core does not support connectOverCDP');\n }\n this.browser = await chromium.connectOverCDP(wsEndpoint);\n const contexts = this.browser.contexts();\n this.context = contexts.length > 0 ? contexts[0] : await this.browser.newContext();\n log.info({ mode: 'cdp', wsEndpoint }, 'Browser connected (CDP)');\n }\n\n private async _connectViaCloud(config: CloudBrowserProviderConfig): Promise<void> {\n const { BrowserbaseProvider } = await import('./providers/browserbase.js');\n const { BrowserUseProvider } = await import('./providers/browser-use.js');\n\n const provider = config.type === 'browserbase'\n ? new BrowserbaseProvider(config)\n : new BrowserUseProvider(config);\n\n const { browser, context } = await provider.connect();\n this.browser = browser;\n this.context = context;\n this.cloudProvider = provider;\n log.info({ mode: 'cloud', provider: config.type }, `Browser connected (${config.type})`);\n }\n\n private async _connectViaExtension(config?: ExtensionConnectionConfig): Promise<void> {\n const { acquireExtensionBrowserServer } = await import('./providers/extension-ws-acquire.js');\n const { provider, release } = await acquireExtensionBrowserServer(config);\n this.extensionProvider = provider;\n this.extensionRelease = release;\n log.info({ port: config?.port ?? 19820 }, 'Extension WS server ready, waiting for Chrome Extension...');\n await provider.waitForConnection();\n // Extension mode does not use Playwright — context stays null.\n // The action registry dispatches directly via extensionProvider.sendCommand().\n log.info({ mode: 'extension' }, 'Browser connected (Chrome Extension)');\n }\n\n private async _connectViaCloakBrowser(config?: import('./providers/types.js').CloakBrowserConfig): Promise<void> {\n const { launchCloakBrowser } = await import('./providers/cloakbrowser.js');\n const result = await launchCloakBrowser(config);\n if (!result.browser || !result.context) {\n throw new Error('BrowserManager: CloakBrowser launch did not return a Playwright connection');\n }\n this.browser = result.browser;\n this.context = result.context;\n this.cloakChildProcess = result.childProcess;\n this.cloakTempProfileDir = result.temporaryProfileDir;\n log.info({ mode: 'cloakbrowser' }, 'Browser connected (CloakBrowser)');\n }\n\n async getPage(taskId: string): Promise<Page> {\n if (this.extensionProvider) {\n throw new Error('BrowserManager.getPage is not used in Chrome Extension backend mode');\n }\n\n this.evictIdlePages();\n await this.ensureConnected();\n\n const existing = this.pages.get(taskId);\n if (existing && !existing.page.isClosed()) {\n existing.lastUsed = Date.now();\n return existing.page;\n }\n if (existing) {\n this.pages.delete(taskId);\n }\n\n if (this.pages.size >= MAX_PAGES) {\n const oldest = [...this.pages.entries()].sort((a, b) => a[1].lastUsed - b[1].lastUsed)[0];\n if (oldest) {\n await oldest[1].page.close().catch(() => {});\n this.pages.delete(oldest[0]);\n }\n }\n\n const ctx = this.context;\n if (!ctx) {\n throw new Error('BrowserManager: Playwright context missing after ensureConnected');\n }\n const page = await ctx.newPage();\n this.pages.set(taskId, { page, lastUsed: Date.now() });\n return page;\n }\n\n async closePage(taskId: string): Promise<void> {\n if (this.extensionProvider) {\n return;\n }\n const entry = this.pages.get(taskId);\n if (entry) {\n await entry.page.close().catch(() => {});\n this.pages.delete(taskId);\n }\n }\n\n /** Get the extension provider (only available in extension mode). */\n getExtensionProvider(): ExtensionBrowserProvider | null {\n return this.extensionProvider;\n }\n\n async shutdown(): Promise<void> {\n for (const [, entry] of this.pages) {\n await entry.page.close().catch(() => {});\n }\n this.pages.clear();\n\n if (this.cloudProvider) {\n await this.cloudProvider.disconnect().catch(() => {});\n this.cloudProvider = null;\n }\n\n if (this.extensionRelease) {\n await this.extensionRelease().catch(() => {});\n this.extensionRelease = null;\n }\n this.extensionProvider = null;\n\n // CloakBrowser cleanup — kill child process and remove temp profile\n if (this.cloakChildProcess || this.cloakTempProfileDir) {\n const { cleanupCloakBrowser } = await import('./providers/cloakbrowser.js');\n await cleanupCloakBrowser(this.cloakChildProcess, this.cloakTempProfileDir).catch(() => {});\n this.cloakChildProcess = null;\n this.cloakTempProfileDir = null;\n }\n\n await this.context?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n this.context = null;\n this.browser = null;\n this.activeBackendMode = null;\n log.info('Browser shut down');\n }\n}\n"],"mappings":";;;;aAEkD;AAMlD,MAAM,MAAM,aAAa,kBAAkB;AAE3C,MAAM,YAAY;AAClB,MAAM,uBAAuB,MAAS;;;;;;;AActC,IAAa,iBAAb,MAA4B;CAC1B,UAAkC;CAClC,UAAyC;CACzC,gBAAqD;CACrD,oBAA6D;CAC7D,mBAAyD;CACzD,oBAA8E;CAC9E,sBAA6C;CAC7C,wBAAgB,IAAI,KAA+C;CACnE;CACA,oBAA2D;CAE3D,YAAY,SAAgC;AAC1C,OAAK,UAAU;;;CAIjB,IAAI,cAA6B;AAC/B,SAAO,KAAK;;CAGd,iBAA+B;EAC7B,MAAM,MAAM,KAAK,KAAK;AACtB,OAAK,MAAM,CAAC,IAAI,UAAU,KAAK,MAC7B,KAAI,MAAM,KAAK,UAAU,IAAI,MAAM,MAAM,WAAW,sBAAsB;AACnE,SAAM,KAAK,OAAO,CAAC,YAAY,GAAG;AACvC,QAAK,MAAM,OAAO,GAAG;AACrB,OAAI,MAAM,EAAE,QAAQ,IAAI,EAAE,sCAAsC;;;CAKtE,+BAAgD;AAC9C,MAAI,CAAC,KAAK,WAAW,CAAC,KAAK,QAAS,QAAO;AAC3C,MAAI;AACF,OAAI,OAAO,KAAK,QAAQ,gBAAgB,cAAc,CAAC,KAAK,QAAQ,aAAa,CAC/E,QAAO;AAET,UAAO;UACD;AACN,UAAO;;;CAIX,sBAA8B,SAAwB;AACpD,UAAQ,GAAG,sBAAsB;AAC/B,OAAI,KAAK,EAAE,MAAM,KAAK,mBAAmB,EAAE,gDAAgD;AAC3F,QAAK,6BAA6B;IAClC;;;CAIJ,8BAA4C;AAC1C,OAAK,MAAM,OAAO;AAClB,OAAK,UAAU;AACf,OAAK,UAAU;AACf,OAAK,oBAAoB;AACzB,OAAK,sBAAsB;AAC3B,MAAI,KAAK,eAAe;AACjB,QAAK,cAAc,YAAY,CAAC,YAAY,GAAG;AACpD,QAAK,gBAAgB;;;CAIzB,MAAc,+BAA8C;AAC1D,OAAK,MAAM,GAAG,UAAU,KAAK,MAC3B,OAAM,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG;AAE1C,OAAK,MAAM,OAAO;AAElB,MAAI,KAAK,eAAe;AACtB,SAAM,KAAK,cAAc,YAAY,CAAC,YAAY,GAAG;AACrD,QAAK,gBAAgB;;AAGvB,MAAI,KAAK,qBAAqB,KAAK,qBAAqB;GACtD,MAAM,EAAE,wBAAwB,MAAM,OAAO;AAC7C,SAAM,oBAAoB,KAAK,mBAAmB,KAAK,oBAAoB,CAAC,YAAY,GAAG;AAC3F,QAAK,oBAAoB;AACzB,QAAK,sBAAsB;;AAG7B,QAAM,KAAK,SAAS,OAAO,CAAC,YAAY,GAAG;AAC3C,QAAM,KAAK,SAAS,OAAO,CAAC,YAAY,GAAG;AAC3C,OAAK,UAAU;AACf,OAAK,UAAU;;;;;;CAOjB,MAAM,kBAAiC;AACrC,MAAI,KAAK,kBAAmB;AAE5B,MAAI,KAAK,WAAW,KAAK,8BAA8B,CACrD;AAGF,MAAI,KAAK,WAAW,KAAK,SAAS;AAChC,OAAI,KAAK,EAAE,MAAM,KAAK,mBAAmB,EAAE,6CAA6C;AACxF,SAAM,KAAK,8BAA8B;;EAG3C,MAAM,UAAU,KAAK,QAAQ,cAAc,IAAI;GAAE,MAAM;GAAkB,UAAU;GAAO;AAE1F,UAAQ,QAAQ,MAAhB;GACE,KAAK;AACH,UAAM,KAAK,eAAe,QAAQ,OAAO,WAAW;AACpD;GACF,KAAK;AACH,UAAM,KAAK,iBAAiB,QAAQ,OAAO;AAC3C;GACF,KAAK;AACH,UAAM,KAAK,qBAAqB,QAAQ,OAAO;AAC/C;GACF,KAAK;AACH,UAAM,KAAK,wBAAwB,QAAQ,OAAO;AAClD;GAEF;AACE,UAAM,KAAK,aAAa,QAAQ,SAAS,UAAU,QAAQ,WAAW,KAAK,QAAQ,aAAa,KAAK,KAAK;AAC1G;;AAGJ,OAAK,oBAAoB,QAAQ;AACjC,MAAI,KAAK,QACP,MAAK,sBAAsB,KAAK,QAAQ;;CAI5C,MAAc,aAAa,UAAkC;EAC3D,MAAM,KAAK,MAAM,0BAA0B;EAC3C,MAAM,WAAW,GAAG,YAAa,GAA4D,SAAS;AACtG,MAAI,CAAC,UAAU,OACb,OAAM,IAAI,MACR,0IACD;AAEH,OAAK,UAAU,MAAM,SAAS,OAAO;GACnC;GACA,GAAI,WAAW,EAAE,SAAS,YAAY,GAAG,EAAE;GAC3C,MAAM,CAAC,gBAAgB,2BAA2B;GACnD,CAAC;AACF,OAAK,UAAU,MAAM,KAAK,QAAQ,WAAW;GAC3C,UAAU;IAAE,OAAO;IAAM,QAAQ;IAAK;GACtC,WACE;GACH,CAAC;AACF,MAAI,KAAK;GAAE;GAAU,MAAM;GAAS,EAAE,2BAA2B;;CAGnE,MAAc,eAAe,YAAmC;EAC9D,MAAM,KAAK,MAAM,0BAA0B;EAC3C,MAAM,WAAW,GAAG,YAAa,GAA4D,SAAS;AACtG,MAAI,CAAC,UAAU,eACb,OAAM,IAAI,MAAM,kDAAkD;AAEpE,OAAK,UAAU,MAAM,SAAS,eAAe,WAAW;EACxD,MAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,OAAK,UAAU,SAAS,SAAS,IAAI,SAAS,KAAK,MAAM,KAAK,QAAQ,YAAY;AAClF,MAAI,KAAK;GAAE,MAAM;GAAO;GAAY,EAAE,0BAA0B;;CAGlE,MAAc,iBAAiB,QAAmD;EAChF,MAAM,EAAE,wBAAwB,MAAM,OAAO;EAC7C,MAAM,EAAE,uBAAuB,MAAM,OAAO;EAE5C,MAAM,WAAW,OAAO,SAAS,gBAC7B,IAAI,oBAAoB,OAAO,GAC/B,IAAI,mBAAmB,OAAO;EAElC,MAAM,EAAE,SAAS,YAAY,MAAM,SAAS,SAAS;AACrD,OAAK,UAAU;AACf,OAAK,UAAU;AACf,OAAK,gBAAgB;AACrB,MAAI,KAAK;GAAE,MAAM;GAAS,UAAU,OAAO;GAAM,EAAE,sBAAsB,OAAO,KAAK,GAAG;;CAG1F,MAAc,qBAAqB,QAAmD;EACpF,MAAM,EAAE,kCAAkC,MAAM,OAAO;EACvD,MAAM,EAAE,UAAU,YAAY,MAAM,8BAA8B,OAAO;AACzE,OAAK,oBAAoB;AACzB,OAAK,mBAAmB;AACxB,MAAI,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,EAAE,6DAA6D;AACvG,QAAM,SAAS,mBAAmB;AAGlC,MAAI,KAAK,EAAE,MAAM,aAAa,EAAE,uCAAuC;;CAGzE,MAAc,wBAAwB,QAA2E;EAC/G,MAAM,EAAE,uBAAuB,MAAM,OAAO;EAC5C,MAAM,SAAS,MAAM,mBAAmB,OAAO;AAC/C,MAAI,CAAC,OAAO,WAAW,CAAC,OAAO,QAC7B,OAAM,IAAI,MAAM,6EAA6E;AAE/F,OAAK,UAAU,OAAO;AACtB,OAAK,UAAU,OAAO;AACtB,OAAK,oBAAoB,OAAO;AAChC,OAAK,sBAAsB,OAAO;AAClC,MAAI,KAAK,EAAE,MAAM,gBAAgB,EAAE,mCAAmC;;CAGxE,MAAM,QAAQ,QAA+B;AAC3C,MAAI,KAAK,kBACP,OAAM,IAAI,MAAM,sEAAsE;AAGxF,OAAK,gBAAgB;AACrB,QAAM,KAAK,iBAAiB;EAE5B,MAAM,WAAW,KAAK,MAAM,IAAI,OAAO;AACvC,MAAI,YAAY,CAAC,SAAS,KAAK,UAAU,EAAE;AACzC,YAAS,WAAW,KAAK,KAAK;AAC9B,UAAO,SAAS;;AAElB,MAAI,SACF,MAAK,MAAM,OAAO,OAAO;AAG3B,MAAI,KAAK,MAAM,QAAQ,WAAW;GAChC,MAAM,SAAS,CAAC,GAAG,KAAK,MAAM,SAAS,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,GAAG,WAAW,EAAE,GAAG,SAAS,CAAC;AACvF,OAAI,QAAQ;AACV,UAAM,OAAO,GAAG,KAAK,OAAO,CAAC,YAAY,GAAG;AAC5C,SAAK,MAAM,OAAO,OAAO,GAAG;;;EAIhC,MAAM,MAAM,KAAK;AACjB,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,mEAAmE;EAErF,MAAM,OAAO,MAAM,IAAI,SAAS;AAChC,OAAK,MAAM,IAAI,QAAQ;GAAE;GAAM,UAAU,KAAK,KAAK;GAAE,CAAC;AACtD,SAAO;;CAGT,MAAM,UAAU,QAA+B;AAC7C,MAAI,KAAK,kBACP;EAEF,MAAM,QAAQ,KAAK,MAAM,IAAI,OAAO;AACpC,MAAI,OAAO;AACT,SAAM,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG;AACxC,QAAK,MAAM,OAAO,OAAO;;;;CAK7B,uBAAwD;AACtD,SAAO,KAAK;;CAGd,MAAM,WAA0B;AAC9B,OAAK,MAAM,GAAG,UAAU,KAAK,MAC3B,OAAM,MAAM,KAAK,OAAO,CAAC,YAAY,GAAG;AAE1C,OAAK,MAAM,OAAO;AAElB,MAAI,KAAK,eAAe;AACtB,SAAM,KAAK,cAAc,YAAY,CAAC,YAAY,GAAG;AACrD,QAAK,gBAAgB;;AAGvB,MAAI,KAAK,kBAAkB;AACzB,SAAM,KAAK,kBAAkB,CAAC,YAAY,GAAG;AAC7C,QAAK,mBAAmB;;AAE1B,OAAK,oBAAoB;AAGzB,MAAI,KAAK,qBAAqB,KAAK,qBAAqB;GACtD,MAAM,EAAE,wBAAwB,MAAM,OAAO;AAC7C,SAAM,oBAAoB,KAAK,mBAAmB,KAAK,oBAAoB,CAAC,YAAY,GAAG;AAC3F,QAAK,oBAAoB;AACzB,QAAK,sBAAsB;;AAG7B,QAAM,KAAK,SAAS,OAAO,CAAC,YAAY,GAAG;AAC3C,QAAM,KAAK,SAAS,OAAO,CAAC,YAAY,GAAG;AAC3C,OAAK,UAAU;AACf,OAAK,UAAU;AACf,OAAK,oBAAoB;AACzB,MAAI,KAAK,oBAAoB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext
|
|
3
|
-
*
|
|
2
|
+
* Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext/.
|
|
3
|
+
* Fixed path — gateway upgrades overwrite in place so Chrome sideload paths stay stable.
|
|
4
4
|
*/
|
|
5
5
|
export declare const BROWSER_EXT_REQUIRED_FILES: readonly ["manifest.json", "popup.html", "dist/background.js", "dist/content.js", "dist/popup.js"];
|
|
6
6
|
export type BrowserExtBundledFrom = 'npm-dist' | 'git-dev' | 'electron-asar' | 'env-override';
|
|
@@ -32,8 +32,8 @@ export interface EnsureBrowserExtResult {
|
|
|
32
32
|
}
|
|
33
33
|
/** Validate a directory contains a loadable extension tree. */
|
|
34
34
|
export declare function validateBrowserExtLayout(dir: string): boolean;
|
|
35
|
-
/** Resolve the installed extension directory (
|
|
36
|
-
export declare function resolveInstalledExtensionPath(cacheDir: string,
|
|
35
|
+
/** Resolve the installed extension directory (fixed `browser-ext/` root). */
|
|
36
|
+
export declare function resolveInstalledExtensionPath(cacheDir: string, _meta: BrowserExtInstallMeta | null): string | null;
|
|
37
37
|
/**
|
|
38
38
|
* Resolve the bundled extension source directory (read-only).
|
|
39
39
|
*/
|
|
@@ -6,14 +6,14 @@ import { init_paths, resolveBinDir } from "../../config/paths.js";
|
|
|
6
6
|
import { init_write_file_atomic, writeTextAtomic } from "../../infra/write-file-atomic.js";
|
|
7
7
|
import { resolvePackageRoot } from "../../infra/update-check.js";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { readFile, readdir, rm } from "node:fs/promises";
|
|
11
11
|
import { spawn } from "node:child_process";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
//#region src/browser/providers/browser-ext-install.ts
|
|
14
14
|
/**
|
|
15
|
-
* Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext
|
|
16
|
-
*
|
|
15
|
+
* Install bundled Chrome extension artifacts into {resolveBinDir()}/browser-ext/.
|
|
16
|
+
* Fixed path — gateway upgrades overwrite in place so Chrome sideload paths stay stable.
|
|
17
17
|
*/
|
|
18
18
|
init_package_version();
|
|
19
19
|
init_paths();
|
|
@@ -22,9 +22,13 @@ init_logger();
|
|
|
22
22
|
init_cache_dir_policy();
|
|
23
23
|
const log = createLogger("BrowserExtInstall");
|
|
24
24
|
const META_FILENAME = ".meta.json";
|
|
25
|
-
const LEGACY_CURRENT_LINK = "current";
|
|
26
25
|
const STAGING_MAX_AGE_MS = 3600 * 1e3;
|
|
27
|
-
const
|
|
26
|
+
const INSTALLED_ARTIFACT_NAMES = [
|
|
27
|
+
"manifest.json",
|
|
28
|
+
"popup.html",
|
|
29
|
+
"dist",
|
|
30
|
+
"icons"
|
|
31
|
+
];
|
|
28
32
|
const BROWSER_EXT_REQUIRED_FILES = [
|
|
29
33
|
"manifest.json",
|
|
30
34
|
"popup.html",
|
|
@@ -64,19 +68,10 @@ function browserExtRoot(cacheDir) {
|
|
|
64
68
|
function resolveMetaPath(cacheDir) {
|
|
65
69
|
return join(browserExtRoot(cacheDir), META_FILENAME);
|
|
66
70
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
function resolveInstalledExtensionPath(cacheDir, meta) {
|
|
72
|
-
if (meta?.installPath && validateBrowserExtLayout(meta.installPath)) return meta.installPath;
|
|
73
|
-
const expectedDir = resolveVersionDir(cacheDir, PACKAGE_VERSION);
|
|
74
|
-
if (validateBrowserExtLayout(expectedDir)) return expectedDir;
|
|
75
|
-
const legacyCurrent = join(browserExtRoot(cacheDir), LEGACY_CURRENT_LINK);
|
|
76
|
-
if (existsSync(legacyCurrent)) try {
|
|
77
|
-
const real = realpathSync(legacyCurrent);
|
|
78
|
-
if (validateBrowserExtLayout(real)) return real;
|
|
79
|
-
} catch {}
|
|
71
|
+
/** Resolve the installed extension directory (fixed `browser-ext/` root). */
|
|
72
|
+
function resolveInstalledExtensionPath(cacheDir, _meta) {
|
|
73
|
+
const root = browserExtRoot(cacheDir);
|
|
74
|
+
if (validateBrowserExtLayout(root)) return root;
|
|
80
75
|
return null;
|
|
81
76
|
}
|
|
82
77
|
function walkAncestorsForGitDevBundled(start) {
|
|
@@ -150,55 +145,23 @@ async function cleanupStaleStaging(root) {
|
|
|
150
145
|
} catch {}
|
|
151
146
|
}
|
|
152
147
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
148
|
+
function removeInstalledArtifacts(root) {
|
|
149
|
+
for (const name of INSTALLED_ARTIFACT_NAMES) {
|
|
150
|
+
const full = join(root, name);
|
|
151
|
+
if (existsSync(full)) rmSync(full, {
|
|
152
|
+
recursive: true,
|
|
153
|
+
force: true
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function promoteStagingToRoot(stagingDir, root) {
|
|
158
|
+
removeInstalledArtifacts(root);
|
|
159
|
+
for (const name of readdirSync(stagingDir)) renameSync(join(stagingDir, name), join(root, name));
|
|
160
|
+
rmSync(stagingDir, {
|
|
158
161
|
recursive: true,
|
|
159
162
|
force: true
|
|
160
163
|
});
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
async function cleanupSiblingVersionDirs(root, keepVersion) {
|
|
164
|
-
if (!existsSync(root)) return;
|
|
165
|
-
let entries;
|
|
166
|
-
try {
|
|
167
|
-
entries = await readdir(root);
|
|
168
|
-
} catch {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
for (const name of entries) {
|
|
172
|
-
if (name === META_FILENAME || name.startsWith(".")) continue;
|
|
173
|
-
if (name === LEGACY_CURRENT_LINK) {
|
|
174
|
-
try {
|
|
175
|
-
await rm(join(root, name), {
|
|
176
|
-
recursive: true,
|
|
177
|
-
force: true
|
|
178
|
-
});
|
|
179
|
-
} catch (err) {
|
|
180
|
-
log.warn({
|
|
181
|
-
err,
|
|
182
|
-
name
|
|
183
|
-
}, "Failed to remove legacy browser extension path");
|
|
184
|
-
}
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
if (!VERSION_DIR_RE.test(name)) continue;
|
|
188
|
-
if (name === keepVersion) continue;
|
|
189
|
-
try {
|
|
190
|
-
await rm(join(root, name), {
|
|
191
|
-
recursive: true,
|
|
192
|
-
force: true
|
|
193
|
-
});
|
|
194
|
-
log.info({ version: name }, "Removed old browser extension version directory");
|
|
195
|
-
} catch (err) {
|
|
196
|
-
log.warn({
|
|
197
|
-
err,
|
|
198
|
-
version: name
|
|
199
|
-
}, "Failed to remove old browser extension version");
|
|
200
|
-
}
|
|
201
|
-
}
|
|
164
|
+
if (!validateBrowserExtLayout(root)) throw new Error("Bundled browser extension copy failed validation");
|
|
202
165
|
}
|
|
203
166
|
/** Copy one bundled file (read/write works when src is inside Electron app.asar). */
|
|
204
167
|
function copyBundledFile(src, dest) {
|
|
@@ -273,45 +236,35 @@ async function ensureBrowserExtensionArtifacts(opts) {
|
|
|
273
236
|
installedPath,
|
|
274
237
|
meta
|
|
275
238
|
});
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
copied: false
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
const versionDir = join(root, versionKey);
|
|
287
|
-
const stagingDir = join(root, `.staging-${versionKey}-${process.pid}`);
|
|
239
|
+
const extensionDir = root;
|
|
240
|
+
if (!needsRefresh && installedPath) return {
|
|
241
|
+
extensionDir: installedPath,
|
|
242
|
+
xopcVersion: PACKAGE_VERSION,
|
|
243
|
+
copied: false
|
|
244
|
+
};
|
|
245
|
+
const stagingDir = join(root, `.staging-${process.pid}`);
|
|
288
246
|
if (existsSync(stagingDir)) rmSync(stagingDir, {
|
|
289
247
|
recursive: true,
|
|
290
248
|
force: true
|
|
291
249
|
});
|
|
292
250
|
copyBundledTree(bundled.dir, stagingDir);
|
|
293
|
-
|
|
294
|
-
recursive: true,
|
|
295
|
-
force: true
|
|
296
|
-
});
|
|
297
|
-
renameSync(stagingDir, versionDir);
|
|
298
|
-
await cleanupSiblingVersionDirs(root, versionKey);
|
|
251
|
+
promoteStagingToRoot(stagingDir, root);
|
|
299
252
|
const nextMeta = {
|
|
300
253
|
xopcVersion: PACKAGE_VERSION,
|
|
301
254
|
manifestVersion: bundledManifestVersion,
|
|
302
255
|
source: "bundled",
|
|
303
256
|
bundledFrom: bundled.bundledFrom,
|
|
304
257
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
305
|
-
installPath:
|
|
258
|
+
installPath: extensionDir
|
|
306
259
|
};
|
|
307
260
|
await writeTextAtomic(resolveMetaPath(cacheDir), JSON.stringify(nextMeta, null, 2));
|
|
308
261
|
log.info({
|
|
309
|
-
extensionDir
|
|
262
|
+
extensionDir,
|
|
310
263
|
xopcVersion: PACKAGE_VERSION,
|
|
311
264
|
bundledFrom: bundled.bundledFrom
|
|
312
265
|
}, "Browser extension artifacts installed");
|
|
313
266
|
return {
|
|
314
|
-
extensionDir
|
|
267
|
+
extensionDir,
|
|
315
268
|
xopcVersion: PACKAGE_VERSION,
|
|
316
269
|
copied: true
|
|
317
270
|
};
|