opendevbrowser 0.0.28 → 0.0.29
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/README.md +2 -2
- package/dist/accessibility-snapshot-PA6NWNS7.js +39 -0
- package/dist/accessibility-snapshot-PA6NWNS7.js.map +1 -0
- package/dist/active-window-YNYTIPZN.js +37 -0
- package/dist/active-window-YNYTIPZN.js.map +1 -0
- package/dist/annotate-STYHXZYJ.js +205 -0
- package/dist/annotate-STYHXZYJ.js.map +1 -0
- package/dist/artifacts-KJ6RNDO2.js +120 -0
- package/dist/artifacts-KJ6RNDO2.js.map +1 -0
- package/dist/attr-GHFZZ4SA.js +84 -0
- package/dist/attr-GHFZZ4SA.js.map +1 -0
- package/dist/browser/ops-client.d.ts +1 -0
- package/dist/browser/ops-client.d.ts.map +1 -1
- package/dist/canvas-54FBOEGP.js +309 -0
- package/dist/canvas-54FBOEGP.js.map +1 -0
- package/dist/capture-desktop-SNABC24E.js +38 -0
- package/dist/capture-desktop-SNABC24E.js.map +1 -0
- package/dist/capture-window-UJSB5AMP.js +40 -0
- package/dist/capture-window-UJSB5AMP.js.map +1 -0
- package/dist/check-ST5UQ2F5.js +71 -0
- package/dist/check-ST5UQ2F5.js.map +1 -0
- package/dist/checked-IEMWI5CU.js +71 -0
- package/dist/checked-IEMWI5CU.js.map +1 -0
- package/dist/chunk-2CG4SW3E.js +64 -0
- package/dist/chunk-2CG4SW3E.js.map +1 -0
- package/dist/chunk-2SIMIPLY.js +67 -0
- package/dist/chunk-2SIMIPLY.js.map +1 -0
- package/dist/chunk-37VSRUW4.js +141 -0
- package/dist/chunk-37VSRUW4.js.map +1 -0
- package/dist/chunk-5SWZDVOW.js +144 -0
- package/dist/chunk-5SWZDVOW.js.map +1 -0
- package/dist/chunk-6PVZ2ABC.js +429 -0
- package/dist/chunk-6PVZ2ABC.js.map +1 -0
- package/dist/chunk-7GVOUZMQ.js +64 -0
- package/dist/chunk-7GVOUZMQ.js.map +1 -0
- package/dist/chunk-7THCPS52.js +84 -0
- package/dist/chunk-7THCPS52.js.map +1 -0
- package/dist/chunk-ASMHEEKY.js +10 -0
- package/dist/chunk-ASMHEEKY.js.map +1 -0
- package/dist/chunk-DBF5OKH3.js +111 -0
- package/dist/chunk-DBF5OKH3.js.map +1 -0
- package/dist/chunk-DW4TX7MU.js +54 -0
- package/dist/chunk-DW4TX7MU.js.map +1 -0
- package/dist/chunk-IPE7TF2P.js +54 -0
- package/dist/chunk-IPE7TF2P.js.map +1 -0
- package/dist/chunk-IQTJHXZJ.js +126 -0
- package/dist/chunk-IQTJHXZJ.js.map +1 -0
- package/dist/chunk-J47N77VG.js +2969 -0
- package/dist/chunk-J47N77VG.js.map +1 -0
- package/dist/chunk-JZXD6FWR.js +25 -0
- package/dist/chunk-JZXD6FWR.js.map +1 -0
- package/dist/{chunk-QVWOPIZJ.js → chunk-KDSNXS6N.js} +75 -149
- package/dist/chunk-KDSNXS6N.js.map +1 -0
- package/dist/chunk-KZ2IXVQT.js +219 -0
- package/dist/chunk-KZ2IXVQT.js.map +1 -0
- package/dist/chunk-LBPELU7L.js +3649 -0
- package/dist/chunk-LBPELU7L.js.map +1 -0
- package/dist/chunk-MX3NFLCE.js +940 -0
- package/dist/chunk-MX3NFLCE.js.map +1 -0
- package/dist/chunk-N44UXKIB.js +26 -0
- package/dist/chunk-N44UXKIB.js.map +1 -0
- package/dist/chunk-OW5HMYMI.js +19 -0
- package/dist/chunk-OW5HMYMI.js.map +1 -0
- package/dist/chunk-OYNLAZQU.js +838 -0
- package/dist/chunk-OYNLAZQU.js.map +1 -0
- package/dist/chunk-PDPJN2OP.js +17 -0
- package/dist/chunk-PDPJN2OP.js.map +1 -0
- package/dist/chunk-RCZZGGJS.js +226 -0
- package/dist/chunk-RCZZGGJS.js.map +1 -0
- package/dist/chunk-RJNI3BHT.js +1 -0
- package/dist/chunk-RPXWUCQQ.js +112 -0
- package/dist/chunk-RPXWUCQQ.js.map +1 -0
- package/dist/chunk-S5KZQJJI.js +107 -0
- package/dist/chunk-S5KZQJJI.js.map +1 -0
- package/dist/{chunk-T3VVHJTK.js → chunk-S6S2UP6U.js} +1074 -1459
- package/dist/chunk-S6S2UP6U.js.map +1 -0
- package/dist/{chunk-I5ZCOZZV.js → chunk-SXAGSEKZ.js} +1202 -9561
- package/dist/chunk-SXAGSEKZ.js.map +1 -0
- package/dist/chunk-T4GMCW6Z.js +46 -0
- package/dist/chunk-T4GMCW6Z.js.map +1 -0
- package/dist/chunk-WHQZBUNY.js +982 -0
- package/dist/chunk-WHQZBUNY.js.map +1 -0
- package/dist/chunk-WOXBLP7V.js +610 -0
- package/dist/chunk-WOXBLP7V.js.map +1 -0
- package/dist/cli/commands/inspiredesign.d.ts.map +1 -1
- package/dist/cli/commands/macro-resolve.d.ts +4 -1
- package/dist/cli/commands/macro-resolve.d.ts.map +1 -1
- package/dist/cli/commands/product-video.d.ts.map +1 -1
- package/dist/cli/commands/research.d.ts.map +1 -1
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/shopping.d.ts.map +1 -1
- package/dist/cli/commands/workflow-output.d.ts +2 -0
- package/dist/cli/commands/workflow-output.d.ts.map +1 -0
- package/dist/cli/daemon-commands.d.ts.map +1 -1
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/index.js +204 -8123
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/installers/postinstall-skill-sync.js +2 -1
- package/dist/cli/installers/postinstall-skill-sync.js.map +1 -1
- package/dist/cli/remote-relay.d.ts.map +1 -1
- package/dist/click-TENZA3Y6.js +81 -0
- package/dist/click-TENZA3Y6.js.map +1 -0
- package/dist/clone-component-STH5AR6M.js +82 -0
- package/dist/clone-component-STH5AR6M.js.map +1 -0
- package/dist/clone-page-BSTWAPAJ.js +69 -0
- package/dist/clone-page-BSTWAPAJ.js.map +1 -0
- package/dist/close-CEMMAAM7.js +63 -0
- package/dist/close-CEMMAAM7.js.map +1 -0
- package/dist/close-QCWUNRAI.js +63 -0
- package/dist/close-QCWUNRAI.js.map +1 -0
- package/dist/connect-J3RVSEZF.js +107 -0
- package/dist/connect-J3RVSEZF.js.map +1 -0
- package/dist/console-poll-HL7BVIVX.js +76 -0
- package/dist/console-poll-HL7BVIVX.js.map +1 -0
- package/dist/cookie-import-WMUCIIHN.js +177 -0
- package/dist/cookie-import-WMUCIIHN.js.map +1 -0
- package/dist/cookie-list-PB2N4RPH.js +117 -0
- package/dist/cookie-list-PB2N4RPH.js.map +1 -0
- package/dist/daemon-5KSVMGN4.js +194 -0
- package/dist/daemon-5KSVMGN4.js.map +1 -0
- package/dist/daemon-fingerprint.json +1 -1
- package/dist/debug-trace-snapshot-RK7KDXA5.js +136 -0
- package/dist/debug-trace-snapshot-RK7KDXA5.js.map +1 -0
- package/dist/dialog-P6P4U7XE.js +75 -0
- package/dist/dialog-P6P4U7XE.js.map +1 -0
- package/dist/disconnect-32F7IDIP.js +58 -0
- package/dist/disconnect-32F7IDIP.js.map +1 -0
- package/dist/enabled-A6C6ZM2O.js +71 -0
- package/dist/enabled-A6C6ZM2O.js.map +1 -0
- package/dist/extension-extractor-GKWSFHPN.js +11 -0
- package/dist/extension-extractor-GKWSFHPN.js.map +1 -0
- package/dist/global-D6WLWBXA.js +56 -0
- package/dist/global-D6WLWBXA.js.map +1 -0
- package/dist/goto-ULTSABDM.js +98 -0
- package/dist/goto-ULTSABDM.js.map +1 -0
- package/dist/help-EKKKEDL5.js +491 -0
- package/dist/help-EKKKEDL5.js.map +1 -0
- package/dist/hover-UF2ZUMTQ.js +71 -0
- package/dist/hover-UF2ZUMTQ.js.map +1 -0
- package/dist/html-B6TX7GK7.js +84 -0
- package/dist/html-B6TX7GK7.js.map +1 -0
- package/dist/index.js +68 -34
- package/dist/index.js.map +1 -1
- package/dist/inspector-6S5FKUZQ.js +62 -0
- package/dist/inspector-6S5FKUZQ.js.map +1 -0
- package/dist/inspector-audit-ARGEGOS7.js +84 -0
- package/dist/inspector-audit-ARGEGOS7.js.map +1 -0
- package/dist/inspector-plan-CSG5HZOC.js +69 -0
- package/dist/inspector-plan-CSG5HZOC.js.map +1 -0
- package/dist/inspiredesign-7VRMMZN4.js +234 -0
- package/dist/inspiredesign-7VRMMZN4.js.map +1 -0
- package/dist/install-autostart-output-5DOMKCQL.js +41 -0
- package/dist/install-autostart-output-5DOMKCQL.js.map +1 -0
- package/dist/install-autostart-reconciliation-NHKOFYTD.js +73 -0
- package/dist/install-autostart-reconciliation-NHKOFYTD.js.map +1 -0
- package/dist/launch-REYCIR3Z.js +225 -0
- package/dist/launch-REYCIR3Z.js.map +1 -0
- package/dist/list-NPRXRQY2.js +51 -0
- package/dist/list-NPRXRQY2.js.map +1 -0
- package/dist/list-STYD2ZWA.js +54 -0
- package/dist/list-STYD2ZWA.js.map +1 -0
- package/dist/local-HXJLUUNT.js +54 -0
- package/dist/local-HXJLUUNT.js.map +1 -0
- package/dist/macro-resolve-ZIJZ65QI.js +253 -0
- package/dist/macro-resolve-ZIJZ65QI.js.map +1 -0
- package/dist/macros/execute-runtime.d.ts +3 -1
- package/dist/macros/execute-runtime.d.ts.map +1 -1
- package/dist/macros/execute.d.ts +2 -0
- package/dist/macros/execute.d.ts.map +1 -1
- package/dist/native-UPLVQ2SG.js +22 -0
- package/dist/native-UPLVQ2SG.js.map +1 -0
- package/dist/network-poll-HLDOSC72.js +76 -0
- package/dist/network-poll-HLDOSC72.js.map +1 -0
- package/dist/new-HXLLN6UT.js +69 -0
- package/dist/new-HXLLN6UT.js.map +1 -0
- package/dist/onboarding-metadata-7E3KLYSZ.js +27 -0
- package/dist/onboarding-metadata-7E3KLYSZ.js.map +1 -0
- package/dist/open-KDR25LQZ.js +81 -0
- package/dist/open-KDR25LQZ.js.map +1 -0
- package/dist/opendevbrowser.js +68 -34
- package/dist/opendevbrowser.js.map +1 -1
- package/dist/perf-EM6SWFJ6.js +58 -0
- package/dist/perf-EM6SWFJ6.js.map +1 -0
- package/dist/pointer-down-ZYWRZNCH.js +55 -0
- package/dist/pointer-down-ZYWRZNCH.js.map +1 -0
- package/dist/pointer-drag-LVEAVJO4.js +54 -0
- package/dist/pointer-drag-LVEAVJO4.js.map +1 -0
- package/dist/pointer-move-7SRKUS66.js +52 -0
- package/dist/pointer-move-7SRKUS66.js.map +1 -0
- package/dist/pointer-up-KLDBSK37.js +55 -0
- package/dist/pointer-up-KLDBSK37.js.map +1 -0
- package/dist/press-UIIXFTD7.js +83 -0
- package/dist/press-UIIXFTD7.js.map +1 -0
- package/dist/product-video-PYOXJVAI.js +235 -0
- package/dist/product-video-PYOXJVAI.js.map +1 -0
- package/dist/providers/artifacts.d.ts +0 -2
- package/dist/providers/artifacts.d.ts.map +1 -1
- package/dist/providers/blocker.d.ts.map +1 -1
- package/dist/providers/bounded-map.d.ts +2 -0
- package/dist/providers/bounded-map.d.ts.map +1 -0
- package/dist/providers/community/index.d.ts.map +1 -1
- package/dist/providers/constraint.d.ts.map +1 -1
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/renderer.d.ts.map +1 -1
- package/dist/providers/research-compiler.d.ts +1 -1
- package/dist/providers/research-compiler.d.ts.map +1 -1
- package/dist/providers/research-executor.d.ts.map +1 -1
- package/dist/providers/runtime-factory.d.ts.map +1 -1
- package/dist/providers/shared/traversal-url.d.ts +3 -0
- package/dist/providers/shared/traversal-url.d.ts.map +1 -1
- package/dist/providers/shopping/index.d.ts.map +1 -1
- package/dist/providers/social/search-quality.d.ts.map +1 -1
- package/dist/providers/workflow-handoff.d.ts +4 -0
- package/dist/providers/workflow-handoff.d.ts.map +1 -1
- package/dist/providers/workflows.d.ts.map +1 -1
- package/dist/{providers-QF2RFB4J.js → providers-4YY2BLXG.js} +19 -14
- package/dist/providers-4YY2BLXG.js.map +1 -0
- package/dist/public-surface/generated-manifest.d.ts +2 -2
- package/dist/public-surface/generated-manifest.d.ts.map +1 -1
- package/dist/public-surface/source.d.ts +2 -2
- package/dist/public-surface/source.d.ts.map +1 -1
- package/dist/relay/protocol.d.ts +3 -1
- package/dist/relay/protocol.d.ts.map +1 -1
- package/dist/relay/relay-server.d.ts +6 -0
- package/dist/relay/relay-server.d.ts.map +1 -1
- package/dist/research-CKXMJ2DK.js +295 -0
- package/dist/research-CKXMJ2DK.js.map +1 -0
- package/dist/review-7HWJPZOD.js +48 -0
- package/dist/review-7HWJPZOD.js.map +1 -0
- package/dist/review-desktop-2IBJHFB5.js +54 -0
- package/dist/review-desktop-2IBJHFB5.js.map +1 -0
- package/dist/rpc-3HGIEJUO.js +159 -0
- package/dist/rpc-3HGIEJUO.js.map +1 -0
- package/dist/run-ADRYI3MS.js +180 -0
- package/dist/run-ADRYI3MS.js.map +1 -0
- package/dist/screencast-start-DTLUHD5H.js +67 -0
- package/dist/screencast-start-DTLUHD5H.js.map +1 -0
- package/dist/screencast-stop-54C5LRSS.js +59 -0
- package/dist/screencast-stop-54C5LRSS.js.map +1 -0
- package/dist/screenshot-HOAKR7P7.js +68 -0
- package/dist/screenshot-HOAKR7P7.js.map +1 -0
- package/dist/scroll-IAOO5COY.js +84 -0
- package/dist/scroll-IAOO5COY.js.map +1 -0
- package/dist/scroll-into-view-RKWSLAPH.js +71 -0
- package/dist/scroll-into-view-RKWSLAPH.js.map +1 -0
- package/dist/select-IGD3T6X4.js +86 -0
- package/dist/select-IGD3T6X4.js.map +1 -0
- package/dist/serve-7X4INUCU.js +498 -0
- package/dist/serve-7X4INUCU.js.map +1 -0
- package/dist/shopping-FC6DRW76.js +273 -0
- package/dist/shopping-FC6DRW76.js.map +1 -0
- package/dist/skill-lifecycle-5UAZGKSN.js +89 -0
- package/dist/skill-lifecycle-5UAZGKSN.js.map +1 -0
- package/dist/skills-NSXDX6YM.js +26 -0
- package/dist/skills-NSXDX6YM.js.map +1 -0
- package/dist/snapshot-X22GG324.js +113 -0
- package/dist/snapshot-X22GG324.js.map +1 -0
- package/dist/status-SP55LMNW.js +132 -0
- package/dist/status-SP55LMNW.js.map +1 -0
- package/dist/status-VH2WXIDG.js +35 -0
- package/dist/status-VH2WXIDG.js.map +1 -0
- package/dist/status-capabilities-YBERLRRA.js +57 -0
- package/dist/status-capabilities-YBERLRRA.js.map +1 -0
- package/dist/text-6TB5WNLI.js +84 -0
- package/dist/text-6TB5WNLI.js.map +1 -0
- package/dist/tools/macro_resolve.d.ts.map +1 -1
- package/dist/type-3UI3TQH3.js +94 -0
- package/dist/type-3UI3TQH3.js.map +1 -0
- package/dist/uncheck-5L3D2D4U.js +71 -0
- package/dist/uncheck-5L3D2D4U.js.map +1 -0
- package/dist/uninstall-KYKGJAX7.js +91 -0
- package/dist/uninstall-KYKGJAX7.js.map +1 -0
- package/dist/update-SMXPYGXS.js +305 -0
- package/dist/update-SMXPYGXS.js.map +1 -0
- package/dist/update-skill-modes-BVX7IVMW.js +38 -0
- package/dist/update-skill-modes-BVX7IVMW.js.map +1 -0
- package/dist/upload-YG4J2EMI.js +56 -0
- package/dist/upload-YG4J2EMI.js.map +1 -0
- package/dist/use-V3LGFP3K.js +63 -0
- package/dist/use-V3LGFP3K.js.map +1 -0
- package/dist/value-3247D57X.js +71 -0
- package/dist/value-3247D57X.js.map +1 -0
- package/dist/visible-A7HEV36U.js +71 -0
- package/dist/visible-A7HEV36U.js.map +1 -0
- package/dist/wait-UZPP4Y4R.js +109 -0
- package/dist/wait-UZPP4Y4R.js.map +1 -0
- package/dist/windows-76TR3AIP.js +37 -0
- package/dist/windows-76TR3AIP.js.map +1 -0
- package/extension/dist/background.js +99 -22
- package/extension/dist/ops/ops-runtime.js +85 -7
- package/extension/dist/ops/ops-session-store.js +3 -0
- package/extension/dist/ops/target-session-coordinator.js +3 -0
- package/extension/dist/services/CDPRouter.js +9 -0
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/opendevbrowser-best-practices/SKILL.md +8 -6
- package/skills/opendevbrowser-best-practices/artifacts/skill-runtime-surface-matrix.md +1 -1
- package/skills/opendevbrowser-best-practices/scripts/odb-workflow.sh +3 -2
- package/skills/opendevbrowser-best-practices/scripts/validator-fixture-cli.sh +39 -2
- package/skills/opendevbrowser-research/SKILL.md +64 -12
- package/skills/opendevbrowser-research/artifacts/research-workflows.md +56 -19
- package/skills/opendevbrowser-research/assets/templates/compact.md +31 -5
- package/skills/opendevbrowser-research/assets/templates/context.json +52 -1
- package/skills/opendevbrowser-research/assets/templates/report.md +57 -4
- package/skills/opendevbrowser-research/examples/sample-input.json +1 -1
- package/skills/opendevbrowser-research/examples/sample-output.md +27 -2
- package/skills/opendevbrowser-research/scripts/run-research.sh +2 -6
- package/skills/opendevbrowser-research/scripts/validate-skill-assets.sh +115 -1
- package/dist/chunk-I5ZCOZZV.js.map +0 -1
- package/dist/chunk-QVWOPIZJ.js.map +0 -1
- package/dist/chunk-T3VVHJTK.js.map +0 -1
- /package/dist/{providers-QF2RFB4J.js.map → chunk-RJNI3BHT.js.map} +0 -0
|
@@ -0,0 +1,3649 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SCREENCAST_RETENTION_MS,
|
|
3
|
+
buildBrowserReviewResult,
|
|
4
|
+
buildLoopbackSessionRelayEndpoint,
|
|
5
|
+
classifySessionRelayEndpoint,
|
|
6
|
+
createOpenDevBrowserCore,
|
|
7
|
+
readCookiesFromSource,
|
|
8
|
+
resolveBundledProviderRuntime,
|
|
9
|
+
resolveSessionRelayRoute
|
|
10
|
+
} from "./chunk-SXAGSEKZ.js";
|
|
11
|
+
import {
|
|
12
|
+
loadGlobalConfig,
|
|
13
|
+
resolveConfig
|
|
14
|
+
} from "./chunk-MX3NFLCE.js";
|
|
15
|
+
import {
|
|
16
|
+
generateSecureToken
|
|
17
|
+
} from "./chunk-ASMHEEKY.js";
|
|
18
|
+
import {
|
|
19
|
+
buildMacroResolveSuccessHandoff,
|
|
20
|
+
detectSocialSearchShell,
|
|
21
|
+
isChallengeAutomationMode,
|
|
22
|
+
runInspiredesignWorkflow,
|
|
23
|
+
runProductVideoWorkflow,
|
|
24
|
+
runResearchWorkflow,
|
|
25
|
+
runShoppingWorkflow
|
|
26
|
+
} from "./chunk-S6S2UP6U.js";
|
|
27
|
+
import {
|
|
28
|
+
buildBlockerArtifacts,
|
|
29
|
+
classifyBlockerSignal,
|
|
30
|
+
redactSensitive
|
|
31
|
+
} from "./chunk-WHQZBUNY.js";
|
|
32
|
+
|
|
33
|
+
// src/cli/utils/http.ts
|
|
34
|
+
var DEFAULT_HTTP_TIMEOUT_MS = 5e3;
|
|
35
|
+
function isAbortError(error) {
|
|
36
|
+
if (!error || typeof error !== "object") return false;
|
|
37
|
+
return "name" in error && error.name === "AbortError";
|
|
38
|
+
}
|
|
39
|
+
var resolveTimeoutMs = (timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) => {
|
|
40
|
+
return Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_HTTP_TIMEOUT_MS;
|
|
41
|
+
};
|
|
42
|
+
var createTimeoutError = (timeoutMs) => {
|
|
43
|
+
return new Error(`Request timed out after ${timeoutMs}ms`);
|
|
44
|
+
};
|
|
45
|
+
var createTimedSignal = (timeoutMs, upstreamSignal) => {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
48
|
+
let removeAbortListener;
|
|
49
|
+
if (upstreamSignal) {
|
|
50
|
+
if (upstreamSignal.aborted) {
|
|
51
|
+
controller.abort(upstreamSignal.reason);
|
|
52
|
+
} else {
|
|
53
|
+
const onAbort = () => controller.abort(upstreamSignal.reason);
|
|
54
|
+
upstreamSignal.addEventListener("abort", onAbort, { once: true });
|
|
55
|
+
removeAbortListener = () => upstreamSignal.removeEventListener("abort", onAbort);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
signal: controller.signal,
|
|
60
|
+
dispose: () => {
|
|
61
|
+
clearTimeout(timeoutId);
|
|
62
|
+
removeAbortListener?.();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
var cancelResponseBody = (response) => {
|
|
67
|
+
try {
|
|
68
|
+
const cancelResult = response.body?.cancel?.();
|
|
69
|
+
if (cancelResult instanceof Promise) {
|
|
70
|
+
void cancelResult.catch(() => {
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var readResponseBodyWithTimeout = async (response, signal, timeoutMs, reader) => {
|
|
77
|
+
let bodyCancelled = false;
|
|
78
|
+
const cancelBody = () => {
|
|
79
|
+
if (bodyCancelled) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
bodyCancelled = true;
|
|
83
|
+
cancelResponseBody(response);
|
|
84
|
+
};
|
|
85
|
+
if (signal.aborted) {
|
|
86
|
+
cancelBody();
|
|
87
|
+
throw createTimeoutError(timeoutMs);
|
|
88
|
+
}
|
|
89
|
+
let removeAbortListener;
|
|
90
|
+
const abortPromise = new Promise((_, reject) => {
|
|
91
|
+
const onAbort = () => {
|
|
92
|
+
cancelBody();
|
|
93
|
+
reject(createTimeoutError(timeoutMs));
|
|
94
|
+
};
|
|
95
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
96
|
+
removeAbortListener = () => signal.removeEventListener("abort", onAbort);
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
return await Promise.race([reader(), abortPromise]);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (signal.aborted || isAbortError(error)) {
|
|
102
|
+
cancelBody();
|
|
103
|
+
throw createTimeoutError(timeoutMs);
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
} finally {
|
|
107
|
+
removeAbortListener?.();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
async function fetchWithTimeout(input, init = {}, timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) {
|
|
111
|
+
const resolvedTimeout = resolveTimeoutMs(timeoutMs);
|
|
112
|
+
const timedSignal = createTimedSignal(resolvedTimeout, init?.signal ?? void 0);
|
|
113
|
+
try {
|
|
114
|
+
return await fetch(input, { ...init, signal: timedSignal.signal });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (isAbortError(error) || timedSignal.signal.aborted) {
|
|
117
|
+
throw createTimeoutError(resolvedTimeout);
|
|
118
|
+
}
|
|
119
|
+
throw error;
|
|
120
|
+
} finally {
|
|
121
|
+
timedSignal.dispose();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function fetchWithTimeoutContext(input, init = {}, timeoutMs = DEFAULT_HTTP_TIMEOUT_MS) {
|
|
125
|
+
const resolvedTimeout = resolveTimeoutMs(timeoutMs);
|
|
126
|
+
const timedSignal = createTimedSignal(resolvedTimeout, init?.signal ?? void 0);
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetch(input, { ...init, signal: timedSignal.signal });
|
|
129
|
+
return {
|
|
130
|
+
response,
|
|
131
|
+
signal: timedSignal.signal,
|
|
132
|
+
timeoutMs: resolvedTimeout,
|
|
133
|
+
dispose: timedSignal.dispose
|
|
134
|
+
};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
timedSignal.dispose();
|
|
137
|
+
if (isAbortError(error) || timedSignal.signal.aborted) {
|
|
138
|
+
throw createTimeoutError(resolvedTimeout);
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function readResponseTextWithTimeout(response, signal, timeoutMs) {
|
|
144
|
+
return await readResponseBodyWithTimeout(response, signal, timeoutMs, () => response.text());
|
|
145
|
+
}
|
|
146
|
+
async function readResponseJsonWithTimeout(response, signal, timeoutMs) {
|
|
147
|
+
return await readResponseBodyWithTimeout(response, signal, timeoutMs, () => response.json());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/cli/daemon.ts
|
|
151
|
+
import { createServer } from "http";
|
|
152
|
+
import { createHash, timingSafeEqual } from "crypto";
|
|
153
|
+
import { mkdirSync, readFileSync, writeFileSync, unlinkSync, existsSync } from "fs";
|
|
154
|
+
import { homedir } from "os";
|
|
155
|
+
import { basename, dirname, join, resolve } from "path";
|
|
156
|
+
import { fileURLToPath } from "url";
|
|
157
|
+
|
|
158
|
+
// src/cli/daemon-commands.ts
|
|
159
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
160
|
+
|
|
161
|
+
// src/browser/session-inspector.ts
|
|
162
|
+
import { randomUUID } from "crypto";
|
|
163
|
+
async function inspectSession(handle, options) {
|
|
164
|
+
const [session, targets, traceRaw] = await Promise.all([
|
|
165
|
+
handle.status(options.sessionId),
|
|
166
|
+
handle.listTargets(options.sessionId, options.includeUrls ?? true),
|
|
167
|
+
handle.debugTraceSnapshot(options.sessionId, {
|
|
168
|
+
sinceConsoleSeq: options.sinceConsoleSeq,
|
|
169
|
+
sinceNetworkSeq: options.sinceNetworkSeq,
|
|
170
|
+
sinceExceptionSeq: options.sinceExceptionSeq,
|
|
171
|
+
max: options.max ?? 25,
|
|
172
|
+
requestId: options.requestId
|
|
173
|
+
})
|
|
174
|
+
]);
|
|
175
|
+
const trace = asRecord(traceRaw);
|
|
176
|
+
const tracePage = asRecord(trace.page);
|
|
177
|
+
const traceMeta = asRecord(trace.meta);
|
|
178
|
+
const traceConsole = summarizeConsole(readChannel(trace, "console"));
|
|
179
|
+
const traceNetwork = summarizeNetwork(readChannel(trace, "network"));
|
|
180
|
+
const traceException = summarizeException(readChannel(trace, "exception"));
|
|
181
|
+
const blockerState = readBlockerState(traceMeta, session.meta);
|
|
182
|
+
const relay = options.relayStatus ? summarizeRelay(options.relayStatus) : null;
|
|
183
|
+
const healthState = deriveHealthState({
|
|
184
|
+
mode: session.mode,
|
|
185
|
+
blockerState,
|
|
186
|
+
dialogOpen: session.meta?.dialog?.open === true,
|
|
187
|
+
relay,
|
|
188
|
+
activeTargetId: targets.activeTargetId,
|
|
189
|
+
consoleErrors: traceConsole.errorCount,
|
|
190
|
+
networkFailures: traceNetwork.failureCount,
|
|
191
|
+
exceptionCount: traceException.eventCount
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
session,
|
|
195
|
+
relay,
|
|
196
|
+
targets: {
|
|
197
|
+
activeTargetId: targets.activeTargetId,
|
|
198
|
+
count: targets.targets.length,
|
|
199
|
+
items: targets.targets
|
|
200
|
+
},
|
|
201
|
+
console: traceConsole,
|
|
202
|
+
network: traceNetwork,
|
|
203
|
+
exception: traceException,
|
|
204
|
+
proofArtifact: {
|
|
205
|
+
source: "debug_trace_snapshot",
|
|
206
|
+
requestId: getString(trace.requestId) ?? options.requestId ?? null,
|
|
207
|
+
generatedAt: getString(trace.generatedAt) ?? null,
|
|
208
|
+
blockerState,
|
|
209
|
+
...getString(tracePage.url) || session.url ? { url: getString(tracePage.url) ?? session.url } : {},
|
|
210
|
+
...getString(tracePage.title) || session.title ? { title: getString(tracePage.title) ?? session.title } : {}
|
|
211
|
+
},
|
|
212
|
+
healthState,
|
|
213
|
+
suggestedNextAction: deriveSuggestedNextAction({
|
|
214
|
+
mode: session.mode,
|
|
215
|
+
dialogOpen: session.meta?.dialog?.open === true,
|
|
216
|
+
blockerState,
|
|
217
|
+
relay,
|
|
218
|
+
activeTargetId: targets.activeTargetId,
|
|
219
|
+
consoleErrors: traceConsole.errorCount,
|
|
220
|
+
networkFailures: traceNetwork.failureCount,
|
|
221
|
+
exceptionCount: traceException.eventCount
|
|
222
|
+
})
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
async function buildCorrelatedAuditBundle(args) {
|
|
226
|
+
const requestId = args.requestId ?? randomUUID();
|
|
227
|
+
const sessionInspector = await inspectSession(args.handle, {
|
|
228
|
+
sessionId: args.browserSessionId,
|
|
229
|
+
includeUrls: args.includeUrls,
|
|
230
|
+
sinceConsoleSeq: args.sinceConsoleSeq,
|
|
231
|
+
sinceNetworkSeq: args.sinceNetworkSeq,
|
|
232
|
+
sinceExceptionSeq: args.sinceExceptionSeq,
|
|
233
|
+
max: args.max,
|
|
234
|
+
requestId,
|
|
235
|
+
relayStatus: args.relayStatus
|
|
236
|
+
});
|
|
237
|
+
return {
|
|
238
|
+
bundleId: randomUUID(),
|
|
239
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
240
|
+
browserSessionId: args.browserSessionId,
|
|
241
|
+
...typeof args.targetId !== "undefined" ? { targetId: args.targetId } : {},
|
|
242
|
+
observationId: args.observation.observationId,
|
|
243
|
+
requestId: sessionInspector.proofArtifact.requestId ?? requestId,
|
|
244
|
+
...args.challengePlan.challengeId ? { challengeId: args.challengePlan.challengeId } : {},
|
|
245
|
+
desktop: args.observation,
|
|
246
|
+
review: args.review,
|
|
247
|
+
sessionInspector,
|
|
248
|
+
challengePlan: args.challengePlan
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function readChannel(trace, channelName) {
|
|
252
|
+
const channels = asRecord(trace.channels);
|
|
253
|
+
const channel = asRecord(channels[channelName]);
|
|
254
|
+
const events = asArray(channel.events);
|
|
255
|
+
return {
|
|
256
|
+
events,
|
|
257
|
+
nextSeq: getNumber(channel.nextSeq),
|
|
258
|
+
truncated: getBoolean(channel.truncated) ?? false
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function summarizeException(channel) {
|
|
262
|
+
const events = channel.events.map((entry) => {
|
|
263
|
+
const record = asRecord(entry);
|
|
264
|
+
const message = [
|
|
265
|
+
getString(record.text),
|
|
266
|
+
getString(record.message),
|
|
267
|
+
getString(record.value)
|
|
268
|
+
].find((value) => typeof value === "string" && value.trim().length > 0);
|
|
269
|
+
const url = getString(record.url) ?? getString(record.sourceURL) ?? void 0;
|
|
270
|
+
const line = getNumber(record.lineNumber) ?? getNumber(record.line) ?? void 0;
|
|
271
|
+
const column = getNumber(record.columnNumber) ?? getNumber(record.column) ?? void 0;
|
|
272
|
+
if (!message && !url && typeof line !== "number" && typeof column !== "number") {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
message: message?.trim() ?? "Unhandled exception",
|
|
277
|
+
...url ? { url } : {},
|
|
278
|
+
...typeof line === "number" ? { line } : {},
|
|
279
|
+
...typeof column === "number" ? { column } : {}
|
|
280
|
+
};
|
|
281
|
+
}).filter((entry) => entry !== null);
|
|
282
|
+
return {
|
|
283
|
+
eventCount: channel.events.length,
|
|
284
|
+
nextSeq: channel.nextSeq,
|
|
285
|
+
truncated: channel.truncated,
|
|
286
|
+
latest: events.slice(-3).reverse()
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function summarizeConsole(channel) {
|
|
290
|
+
const events = channel.events.map((entry) => {
|
|
291
|
+
const record = asRecord(entry);
|
|
292
|
+
const message = [
|
|
293
|
+
getString(record.text),
|
|
294
|
+
getString(record.message),
|
|
295
|
+
getString(record.value)
|
|
296
|
+
].find((value) => typeof value === "string" && value.trim().length > 0);
|
|
297
|
+
return {
|
|
298
|
+
level: (getString(record.level) ?? getString(record.type) ?? "log").toLowerCase(),
|
|
299
|
+
message: message?.trim() ?? ""
|
|
300
|
+
};
|
|
301
|
+
}).filter((entry) => entry.message.length > 0);
|
|
302
|
+
const latest = events.slice(-3).reverse().map((entry) => ({
|
|
303
|
+
level: entry.level,
|
|
304
|
+
message: entry.message
|
|
305
|
+
}));
|
|
306
|
+
return {
|
|
307
|
+
eventCount: channel.events.length,
|
|
308
|
+
nextSeq: channel.nextSeq,
|
|
309
|
+
truncated: channel.truncated,
|
|
310
|
+
errorCount: events.filter((entry) => entry.level.includes("error")).length,
|
|
311
|
+
warningCount: events.filter((entry) => entry.level.includes("warn")).length,
|
|
312
|
+
latest
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function summarizeNetwork(channel) {
|
|
316
|
+
const normalized = channel.events.map((entry) => {
|
|
317
|
+
const record = asRecord(entry);
|
|
318
|
+
return {
|
|
319
|
+
status: getNumber(record.status) ?? void 0,
|
|
320
|
+
method: getString(record.method) ?? void 0,
|
|
321
|
+
url: getString(record.url) ?? void 0,
|
|
322
|
+
error: getString(record.errorText) ?? getString(record.error) ?? void 0
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
const failures = normalized.filter((entry) => typeof entry.status === "number" && entry.status >= 400 || typeof entry.error === "string");
|
|
326
|
+
return {
|
|
327
|
+
eventCount: channel.events.length,
|
|
328
|
+
nextSeq: channel.nextSeq,
|
|
329
|
+
truncated: channel.truncated,
|
|
330
|
+
failureCount: failures.length,
|
|
331
|
+
latestFailures: failures.slice(-3).reverse()
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function summarizeRelay(relay) {
|
|
335
|
+
return {
|
|
336
|
+
running: relay.running,
|
|
337
|
+
...typeof relay.port === "number" ? { port: relay.port } : {},
|
|
338
|
+
extensionConnected: relay.extensionConnected,
|
|
339
|
+
extensionHandshakeComplete: relay.extensionHandshakeComplete,
|
|
340
|
+
annotationConnected: relay.annotationConnected,
|
|
341
|
+
opsConnected: relay.opsConnected,
|
|
342
|
+
canvasConnected: relay.canvasConnected,
|
|
343
|
+
cdpConnected: relay.cdpConnected,
|
|
344
|
+
pairingRequired: relay.pairingRequired,
|
|
345
|
+
health: relay.health
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function deriveHealthState(input) {
|
|
349
|
+
if (input.blockerState === "active" || input.dialogOpen || input.activeTargetId === null || input.mode === "extension" && input.relay && !input.relay.extensionHandshakeComplete && input.relay.extensionConnected) {
|
|
350
|
+
return "blocked";
|
|
351
|
+
}
|
|
352
|
+
if (input.relay && !input.relay.health.ok || input.consoleErrors > 0 || input.networkFailures > 0 || input.exceptionCount > 0) {
|
|
353
|
+
return "warning";
|
|
354
|
+
}
|
|
355
|
+
return "ok";
|
|
356
|
+
}
|
|
357
|
+
function deriveSuggestedNextAction(input) {
|
|
358
|
+
if (input.dialogOpen) {
|
|
359
|
+
return "Handle the open dialog before continuing any page interaction.";
|
|
360
|
+
}
|
|
361
|
+
if (input.mode === "extension" && input.relay && !input.relay.extensionHandshakeComplete) {
|
|
362
|
+
return "Re-establish a clean daemon-extension handshake: open the extension popup, click Connect again, confirm `status --daemon` shows ext=on and handshake=on, then retry the next page action.";
|
|
363
|
+
}
|
|
364
|
+
if (input.blockerState === "active") {
|
|
365
|
+
return "Resolve the active blocker or challenge before issuing more page actions.";
|
|
366
|
+
}
|
|
367
|
+
if (input.activeTargetId === null) {
|
|
368
|
+
return "Create or select a target before continuing the next automation step.";
|
|
369
|
+
}
|
|
370
|
+
if (input.consoleErrors > 0 || input.networkFailures > 0 || input.exceptionCount > 0) {
|
|
371
|
+
return "Inspect the summarized trace failures, fix the page instability, then rerun snapshot or review.";
|
|
372
|
+
}
|
|
373
|
+
return "Capture snapshot or review and continue the normal snapshot -> action -> snapshot loop.";
|
|
374
|
+
}
|
|
375
|
+
function readBlockerState(traceMeta, sessionMeta) {
|
|
376
|
+
const raw = getString(traceMeta.blockerState) ?? sessionMeta?.blockerState ?? "clear";
|
|
377
|
+
return raw === "active" || raw === "resolving" ? raw : "clear";
|
|
378
|
+
}
|
|
379
|
+
function asRecord(value) {
|
|
380
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
381
|
+
}
|
|
382
|
+
function asArray(value) {
|
|
383
|
+
return Array.isArray(value) ? value : [];
|
|
384
|
+
}
|
|
385
|
+
function getString(value) {
|
|
386
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
387
|
+
}
|
|
388
|
+
function getNumber(value) {
|
|
389
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
390
|
+
}
|
|
391
|
+
function getBoolean(value) {
|
|
392
|
+
return typeof value === "boolean" ? value : null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/inspiredesign/capture.ts
|
|
396
|
+
var INSPIREDESIGN_CAPTURE_TIMEOUT_MS = 3e4;
|
|
397
|
+
var INSPIREDESIGN_CAPTURE_MAX_CHARS = 12e3;
|
|
398
|
+
var ACTIVE_SESSION_COOKIE_REUSE_UNAVAILABLE_MESSAGE = "Deep capture only honors configured provider cookie sources; active session cookies are not reused.";
|
|
399
|
+
var DOM_CAPTURE_HELPER_UNAVAILABLE_MESSAGE = "DOM capture helper unavailable in this execution lane.";
|
|
400
|
+
var SNAPSHOT_CAPTURE_EMPTY_MESSAGE = "Snapshot capture returned empty content.";
|
|
401
|
+
var CLONE_CAPTURE_EMPTY_MESSAGE = "Clone capture returned empty component and CSS previews.";
|
|
402
|
+
var DOM_CAPTURE_EMPTY_MESSAGE = "DOM capture returned empty HTML.";
|
|
403
|
+
var SKIPPED_AFTER_TRANSPORT_TIMEOUT_SUFFIX = "transport timeout.";
|
|
404
|
+
var createRemainingCaptureTimeout = (timeoutMs) => {
|
|
405
|
+
const startedAtMs = Date.now();
|
|
406
|
+
let firstRead = true;
|
|
407
|
+
return () => {
|
|
408
|
+
if (firstRead) {
|
|
409
|
+
firstRead = false;
|
|
410
|
+
return timeoutMs;
|
|
411
|
+
}
|
|
412
|
+
return Math.max(1, timeoutMs - Math.max(0, Date.now() - startedAtMs));
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
var clampInspiredesignCaptureTimeout = (timeoutMs) => {
|
|
416
|
+
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) return INSPIREDESIGN_CAPTURE_TIMEOUT_MS;
|
|
417
|
+
return Math.max(1, Math.min(timeoutMs, INSPIREDESIGN_CAPTURE_TIMEOUT_MS));
|
|
418
|
+
};
|
|
419
|
+
function sanitizeInspiredesignCaptureText(value) {
|
|
420
|
+
if (value === void 0) return void 0;
|
|
421
|
+
const redacted = redactSensitive(value);
|
|
422
|
+
return typeof redacted === "string" ? redacted : value;
|
|
423
|
+
}
|
|
424
|
+
var buildCaptureAttempt = (status, detail) => {
|
|
425
|
+
const sanitizedDetail = sanitizeInspiredesignCaptureText(detail);
|
|
426
|
+
return sanitizedDetail ? { status, detail: sanitizedDetail } : { status };
|
|
427
|
+
};
|
|
428
|
+
var detailFromCaptureError = (error, fallback) => {
|
|
429
|
+
return error instanceof Error ? error.message : fallback;
|
|
430
|
+
};
|
|
431
|
+
var isTransportTimeoutError = (error, detail) => {
|
|
432
|
+
if (error instanceof Error) {
|
|
433
|
+
const code = error.code;
|
|
434
|
+
if (error.name === "TimeoutError" || code === "ETIMEDOUT" || code === "ERR_HTTP_REQUEST_TIMEOUT") {
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return /\btimed out after \d+ms\b/i.test(detail) || /exceeded timeout budget\./i.test(detail);
|
|
439
|
+
};
|
|
440
|
+
var isIgnorableNetworkIdleWaitError = (error) => {
|
|
441
|
+
if (!(error instanceof Error)) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
return /timed out|timeout/i.test(error.message);
|
|
445
|
+
};
|
|
446
|
+
var buildSkippedAfterTransportTimeoutAttempt = (label) => {
|
|
447
|
+
return buildCaptureAttempt("skipped", `Skipped after ${label} ${SKIPPED_AFTER_TRANSPORT_TIMEOUT_SUFFIX}`);
|
|
448
|
+
};
|
|
449
|
+
var hasUsableCaptureText = (value) => {
|
|
450
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
451
|
+
};
|
|
452
|
+
var resolveInspiredesignCaptureCookiePolicy = (options) => {
|
|
453
|
+
if (options.cookiePolicyOverride) return options.cookiePolicyOverride;
|
|
454
|
+
return options.useCookies === false ? "off" : "auto";
|
|
455
|
+
};
|
|
456
|
+
var withCaptureDeadline = async (promise, timeoutMs, label) => {
|
|
457
|
+
let clearDeadline = () => {
|
|
458
|
+
};
|
|
459
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
460
|
+
const handle = setTimeout(() => reject(new Error(`Deep capture ${label} exceeded timeout budget.`)), timeoutMs);
|
|
461
|
+
clearDeadline = () => clearTimeout(handle);
|
|
462
|
+
});
|
|
463
|
+
try {
|
|
464
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
465
|
+
} finally {
|
|
466
|
+
clearDeadline();
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
var verifyRequiredCaptureCookies = async (manager, sessionId, url, importState, timeoutMs) => {
|
|
470
|
+
const cookies = await withCaptureDeadline(
|
|
471
|
+
manager.cookieList(sessionId, [url], void 0, timeoutMs),
|
|
472
|
+
timeoutMs,
|
|
473
|
+
"cookie verification"
|
|
474
|
+
);
|
|
475
|
+
if (cookies.count > 0) return;
|
|
476
|
+
if (!importState.sourceConfigured) {
|
|
477
|
+
throw new Error(ACTIVE_SESSION_COOKIE_REUSE_UNAVAILABLE_MESSAGE);
|
|
478
|
+
}
|
|
479
|
+
const sourceDetail = importState.sourceMessage ? ` ${importState.sourceMessage}` : "";
|
|
480
|
+
throw new Error(`Deep capture requires observable cookies from the configured provider cookie source for the requested URL.${sourceDetail}`);
|
|
481
|
+
};
|
|
482
|
+
var importConfiguredCaptureCookies = async (manager, sessionId, source, timeoutMs) => {
|
|
483
|
+
if (!source || typeof manager.cookieImport !== "function") {
|
|
484
|
+
return {
|
|
485
|
+
sourceConfigured: Boolean(source),
|
|
486
|
+
sourceAvailable: false
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
const loaded = await readCookiesFromSource(source);
|
|
490
|
+
if (loaded.cookies.length === 0) {
|
|
491
|
+
return {
|
|
492
|
+
sourceConfigured: true,
|
|
493
|
+
sourceAvailable: loaded.available,
|
|
494
|
+
sourceMessage: loaded.message
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
await withCaptureDeadline(
|
|
498
|
+
manager.cookieImport(sessionId, loaded.cookies, false, void 0, timeoutMs),
|
|
499
|
+
timeoutMs,
|
|
500
|
+
"cookie import"
|
|
501
|
+
);
|
|
502
|
+
return {
|
|
503
|
+
sourceConfigured: true,
|
|
504
|
+
sourceAvailable: loaded.available,
|
|
505
|
+
sourceMessage: loaded.message
|
|
506
|
+
};
|
|
507
|
+
};
|
|
508
|
+
var captureSnapshotArtifact = async (manager, sessionId, remainingTimeoutMs) => {
|
|
509
|
+
try {
|
|
510
|
+
const snapshot = await withCaptureDeadline(
|
|
511
|
+
manager.snapshot(
|
|
512
|
+
sessionId,
|
|
513
|
+
"actionables",
|
|
514
|
+
INSPIREDESIGN_CAPTURE_MAX_CHARS,
|
|
515
|
+
void 0,
|
|
516
|
+
void 0,
|
|
517
|
+
remainingTimeoutMs()
|
|
518
|
+
),
|
|
519
|
+
remainingTimeoutMs(),
|
|
520
|
+
"snapshot capture"
|
|
521
|
+
);
|
|
522
|
+
const content = sanitizeInspiredesignCaptureText(snapshot.content) ?? "";
|
|
523
|
+
if (!hasUsableCaptureText(content)) {
|
|
524
|
+
return {
|
|
525
|
+
attempt: buildCaptureAttempt("failed", SNAPSHOT_CAPTURE_EMPTY_MESSAGE)
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
return {
|
|
529
|
+
attempt: buildCaptureAttempt("captured"),
|
|
530
|
+
snapshot: {
|
|
531
|
+
content,
|
|
532
|
+
refCount: snapshot.refCount,
|
|
533
|
+
warnings: snapshot.warnings ?? []
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
} catch (error) {
|
|
537
|
+
const detail = detailFromCaptureError(error, "Snapshot capture failed.");
|
|
538
|
+
return {
|
|
539
|
+
attempt: buildCaptureAttempt("failed", detail),
|
|
540
|
+
...isTransportTimeoutError(error, detail) ? { transportTimedOut: true } : {}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
var captureCloneArtifact = async (manager, sessionId, remainingTimeoutMs) => {
|
|
545
|
+
try {
|
|
546
|
+
const clone = await withCaptureDeadline(
|
|
547
|
+
manager.clonePage(sessionId, void 0, remainingTimeoutMs()),
|
|
548
|
+
remainingTimeoutMs(),
|
|
549
|
+
"clone capture"
|
|
550
|
+
);
|
|
551
|
+
const componentPreview = sanitizeInspiredesignCaptureText(clone.component) ?? "";
|
|
552
|
+
const cssPreview = sanitizeInspiredesignCaptureText(clone.css) ?? "";
|
|
553
|
+
if (!hasUsableCaptureText(componentPreview) && !hasUsableCaptureText(cssPreview)) {
|
|
554
|
+
return {
|
|
555
|
+
attempt: buildCaptureAttempt("failed", CLONE_CAPTURE_EMPTY_MESSAGE)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
attempt: buildCaptureAttempt("captured"),
|
|
560
|
+
clone: {
|
|
561
|
+
componentPreview,
|
|
562
|
+
cssPreview,
|
|
563
|
+
warnings: clone.warnings ?? []
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
} catch (error) {
|
|
567
|
+
const detail = detailFromCaptureError(error, "Clone capture failed.");
|
|
568
|
+
return {
|
|
569
|
+
attempt: buildCaptureAttempt("failed", detail),
|
|
570
|
+
...isTransportTimeoutError(error, detail) ? { transportTimedOut: true } : {}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
var captureDomArtifact = async (manager, sessionId, remainingTimeoutMs) => {
|
|
575
|
+
if (typeof manager.clonePageHtmlWithOptions !== "function") {
|
|
576
|
+
return {
|
|
577
|
+
attempt: buildCaptureAttempt("skipped", DOM_CAPTURE_HELPER_UNAVAILABLE_MESSAGE)
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
const dom = await withCaptureDeadline(
|
|
582
|
+
manager.clonePageHtmlWithOptions(sessionId, void 0, void 0, remainingTimeoutMs()),
|
|
583
|
+
remainingTimeoutMs(),
|
|
584
|
+
"DOM capture"
|
|
585
|
+
);
|
|
586
|
+
const outerHTML = sanitizeInspiredesignCaptureText(dom.html) ?? "";
|
|
587
|
+
if (!hasUsableCaptureText(outerHTML)) {
|
|
588
|
+
return {
|
|
589
|
+
attempt: buildCaptureAttempt("failed", DOM_CAPTURE_EMPTY_MESSAGE)
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
return {
|
|
593
|
+
attempt: buildCaptureAttempt("captured"),
|
|
594
|
+
dom: {
|
|
595
|
+
outerHTML,
|
|
596
|
+
truncated: false
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
} catch (error) {
|
|
600
|
+
const detail = detailFromCaptureError(error, "DOM capture failed.");
|
|
601
|
+
return {
|
|
602
|
+
attempt: buildCaptureAttempt("failed", detail),
|
|
603
|
+
...isTransportTimeoutError(error, detail) ? { transportTimedOut: true } : {}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
var buildCaptureEvidence = (snapshot, clone, dom) => {
|
|
608
|
+
const attempts = {
|
|
609
|
+
snapshot: snapshot.attempt,
|
|
610
|
+
clone: clone.attempt,
|
|
611
|
+
dom: dom.attempt
|
|
612
|
+
};
|
|
613
|
+
return {
|
|
614
|
+
...snapshot.snapshot ? { snapshot: snapshot.snapshot } : {},
|
|
615
|
+
...dom.dom ? { dom: dom.dom } : {},
|
|
616
|
+
...clone.clone ? { clone: clone.clone } : {},
|
|
617
|
+
attempts
|
|
618
|
+
};
|
|
619
|
+
};
|
|
620
|
+
var buildTransportTimeoutCaptureEvidence = (snapshot, clone, label) => {
|
|
621
|
+
const attempts = {
|
|
622
|
+
snapshot: snapshot.attempt,
|
|
623
|
+
clone: clone?.attempt ?? buildSkippedAfterTransportTimeoutAttempt(label),
|
|
624
|
+
dom: buildSkippedAfterTransportTimeoutAttempt(label)
|
|
625
|
+
};
|
|
626
|
+
return {
|
|
627
|
+
...snapshot.snapshot ? { snapshot: snapshot.snapshot } : {},
|
|
628
|
+
...clone?.clone ? { clone: clone.clone } : {},
|
|
629
|
+
attempts
|
|
630
|
+
};
|
|
631
|
+
};
|
|
632
|
+
var captureInspiredesignArtifacts = async (manager, sessionId, remainingTimeoutMs) => {
|
|
633
|
+
const snapshot = await captureSnapshotArtifact(manager, sessionId, remainingTimeoutMs);
|
|
634
|
+
if (snapshot.transportTimedOut) {
|
|
635
|
+
return buildTransportTimeoutCaptureEvidence(snapshot, void 0, "snapshot capture");
|
|
636
|
+
}
|
|
637
|
+
const clone = await captureCloneArtifact(manager, sessionId, remainingTimeoutMs);
|
|
638
|
+
if (clone.transportTimedOut) {
|
|
639
|
+
return buildTransportTimeoutCaptureEvidence(snapshot, clone, "clone capture");
|
|
640
|
+
}
|
|
641
|
+
const dom = await captureDomArtifact(manager, sessionId, remainingTimeoutMs);
|
|
642
|
+
return buildCaptureEvidence(snapshot, clone, dom);
|
|
643
|
+
};
|
|
644
|
+
async function captureInspiredesignReferenceFromManager(manager, url, options = {}) {
|
|
645
|
+
const cookiePolicy = resolveInspiredesignCaptureCookiePolicy(options);
|
|
646
|
+
const captureTimeoutMs = clampInspiredesignCaptureTimeout(options.timeoutMs);
|
|
647
|
+
const remainingTimeoutMs = createRemainingCaptureTimeout(captureTimeoutMs);
|
|
648
|
+
const launchTimeoutMs = remainingTimeoutMs();
|
|
649
|
+
const session = await withCaptureDeadline(
|
|
650
|
+
manager.launch({
|
|
651
|
+
headless: true,
|
|
652
|
+
startUrl: "about:blank",
|
|
653
|
+
persistProfile: false,
|
|
654
|
+
noExtension: true
|
|
655
|
+
}, launchTimeoutMs),
|
|
656
|
+
launchTimeoutMs,
|
|
657
|
+
"session launch"
|
|
658
|
+
);
|
|
659
|
+
try {
|
|
660
|
+
const importState = cookiePolicy === "off" ? { sourceConfigured: false, sourceAvailable: false } : await importConfiguredCaptureCookies(
|
|
661
|
+
manager,
|
|
662
|
+
session.sessionId,
|
|
663
|
+
options.cookieSource,
|
|
664
|
+
remainingTimeoutMs()
|
|
665
|
+
);
|
|
666
|
+
if (cookiePolicy === "required") {
|
|
667
|
+
await verifyRequiredCaptureCookies(
|
|
668
|
+
manager,
|
|
669
|
+
session.sessionId,
|
|
670
|
+
url,
|
|
671
|
+
importState,
|
|
672
|
+
remainingTimeoutMs()
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
manager.setSessionChallengeAutomationMode?.(session.sessionId, options.challengeAutomationMode);
|
|
676
|
+
const gotoTimeoutMs = remainingTimeoutMs();
|
|
677
|
+
await withCaptureDeadline(
|
|
678
|
+
manager.goto(session.sessionId, url, "load", gotoTimeoutMs),
|
|
679
|
+
gotoTimeoutMs,
|
|
680
|
+
"navigation"
|
|
681
|
+
);
|
|
682
|
+
const waitTimeoutMs = remainingTimeoutMs();
|
|
683
|
+
try {
|
|
684
|
+
await withCaptureDeadline(
|
|
685
|
+
manager.waitForLoad(session.sessionId, "networkidle", waitTimeoutMs),
|
|
686
|
+
waitTimeoutMs,
|
|
687
|
+
"network idle wait"
|
|
688
|
+
);
|
|
689
|
+
} catch (error) {
|
|
690
|
+
if (!isIgnorableNetworkIdleWaitError(error)) {
|
|
691
|
+
throw error;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return await captureInspiredesignArtifacts(manager, session.sessionId, remainingTimeoutMs);
|
|
695
|
+
} finally {
|
|
696
|
+
await manager.disconnect(session.sessionId, true).catch(() => void 0);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// src/macros/execute.ts
|
|
701
|
+
var buildRuntimePolicyOverrides = (overrides) => {
|
|
702
|
+
if (!overrides) {
|
|
703
|
+
return void 0;
|
|
704
|
+
}
|
|
705
|
+
const runtimePolicy = {
|
|
706
|
+
...overrides.browserMode ? { browserMode: overrides.browserMode } : {},
|
|
707
|
+
...typeof overrides.useCookies === "boolean" ? { useCookies: overrides.useCookies } : {},
|
|
708
|
+
...overrides.cookiePolicyOverride ? { cookiePolicyOverride: overrides.cookiePolicyOverride } : {}
|
|
709
|
+
};
|
|
710
|
+
return Object.keys(runtimePolicy).length > 0 ? runtimePolicy : void 0;
|
|
711
|
+
};
|
|
712
|
+
var isRecordValue = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
713
|
+
var requireMacroString = (value, label) => {
|
|
714
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
715
|
+
throw new Error(`Macro action missing ${label}`);
|
|
716
|
+
}
|
|
717
|
+
return value;
|
|
718
|
+
};
|
|
719
|
+
var optionalPositiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
720
|
+
var optionalBoolean = (value) => typeof value === "boolean" ? value : void 0;
|
|
721
|
+
var optionalJsonRecord = (value) => isRecordValue(value) ? value : void 0;
|
|
722
|
+
var optionalStringArray = (value) => {
|
|
723
|
+
if (!Array.isArray(value)) return void 0;
|
|
724
|
+
const parsed = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
725
|
+
return parsed.length > 0 ? parsed : void 0;
|
|
726
|
+
};
|
|
727
|
+
var normalizePlainText = (value) => typeof value === "string" ? value.replace(/\s+/g, " ").trim() : "";
|
|
728
|
+
var REDDIT_VERIFICATION_WALL_RE = /\b(?:please wait for verification|verify you are human|security check)\b/i;
|
|
729
|
+
var readTargetedSocialPlatform = (providerId) => {
|
|
730
|
+
switch (providerId) {
|
|
731
|
+
case "social/x":
|
|
732
|
+
return "x";
|
|
733
|
+
case "social/bluesky":
|
|
734
|
+
return "bluesky";
|
|
735
|
+
case "social/reddit":
|
|
736
|
+
return "reddit";
|
|
737
|
+
case "social/facebook":
|
|
738
|
+
return "facebook";
|
|
739
|
+
case "social/threads":
|
|
740
|
+
return "threads";
|
|
741
|
+
default:
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
var getMacroShellReason = (record, providerId, fallbackRetrievalPath) => {
|
|
746
|
+
const url = normalizePlainText(record?.url).toLowerCase();
|
|
747
|
+
const title = normalizePlainText(record?.title);
|
|
748
|
+
const content = normalizePlainText(record?.content);
|
|
749
|
+
const combined = `${title} ${content}`.trim().toLowerCase();
|
|
750
|
+
const retrievalPath = normalizePlainText(
|
|
751
|
+
typeof record?.attributes?.retrievalPath === "string" ? record.attributes.retrievalPath : fallbackRetrievalPath
|
|
752
|
+
).toLowerCase();
|
|
753
|
+
const extractionQuality = optionalJsonRecord(record?.attributes?.extractionQuality);
|
|
754
|
+
const contentChars = Number(extractionQuality?.contentChars ?? content.length);
|
|
755
|
+
const links = optionalStringArray(record?.attributes?.links) ?? [];
|
|
756
|
+
if (combined.includes("bots use duckduckgo too") || combined.includes("please complete the following challenge") || combined.includes("select all squares containing a duck")) {
|
|
757
|
+
return "challenge_shell";
|
|
758
|
+
}
|
|
759
|
+
const socialPlatform = readTargetedSocialPlatform(providerId);
|
|
760
|
+
if (socialPlatform) {
|
|
761
|
+
const socialShell = detectSocialSearchShell(socialPlatform, {
|
|
762
|
+
url,
|
|
763
|
+
title,
|
|
764
|
+
content,
|
|
765
|
+
links
|
|
766
|
+
});
|
|
767
|
+
if (socialShell) {
|
|
768
|
+
return socialShell.providerShell;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (url.includes("reddit.com") && REDDIT_VERIFICATION_WALL_RE.test(combined)) {
|
|
772
|
+
return "challenge_shell";
|
|
773
|
+
}
|
|
774
|
+
if (retrievalPath === "web:search:index" && (url.includes("duckduckgo.com") || title.toLowerCase().includes("duckduckgo"))) {
|
|
775
|
+
return "search_shell";
|
|
776
|
+
}
|
|
777
|
+
if (providerId === "web/default" && (retrievalPath === "web:fetch:url" || retrievalPath.startsWith("fetch:")) && contentChars > 0 && contentChars <= 8 && links.length >= 20) {
|
|
778
|
+
return "truncated_fetch_shell";
|
|
779
|
+
}
|
|
780
|
+
if (providerId === "social/youtube" && (url.includes("youtube.com/watch") && combined.includes("about press copyright contact us creators advertise developers terms privacy policy") || url.includes("developers.google.com/youtube") && combined.includes("google for developers skip to main content youtube"))) {
|
|
781
|
+
return "generic_shell";
|
|
782
|
+
}
|
|
783
|
+
return null;
|
|
784
|
+
};
|
|
785
|
+
var assertMacroExecutionQuality = (resolution, result) => {
|
|
786
|
+
if (!Array.isArray(result.records) || result.records.length === 0 || result.failures.length > 0) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
const providerId = typeof resolution.action.input.providerId === "string" && resolution.action.input.providerId.trim() ? resolution.action.input.providerId.trim() : resolution.provenance.provider;
|
|
790
|
+
const fallbackRetrievalPath = normalizePlainText(
|
|
791
|
+
optionalJsonRecord(result.meta?.provenance)?.retrievalPath
|
|
792
|
+
);
|
|
793
|
+
const reasons = result.records.map((record) => getMacroShellReason(record, providerId, fallbackRetrievalPath)).filter((reason) => typeof reason === "string");
|
|
794
|
+
if (reasons.length !== result.records.length) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
throw new Error(`Macro execution returned only shell records (${[...new Set(reasons)].join(",")}).`);
|
|
798
|
+
};
|
|
799
|
+
var partitionMacroExecutionRecords = (records, providerId, fallbackRetrievalPath) => {
|
|
800
|
+
const usable = [];
|
|
801
|
+
const shell = [];
|
|
802
|
+
for (const record of records) {
|
|
803
|
+
const reason = getMacroShellReason(record, providerId, fallbackRetrievalPath);
|
|
804
|
+
if (reason) {
|
|
805
|
+
shell.push({ record, reason });
|
|
806
|
+
} else {
|
|
807
|
+
usable.push(record);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return { usable, shell };
|
|
811
|
+
};
|
|
812
|
+
var prioritizeMacroExecutionRecords = (resolution, result) => {
|
|
813
|
+
if (!Array.isArray(result.records) || result.records.length <= 1) {
|
|
814
|
+
return result;
|
|
815
|
+
}
|
|
816
|
+
const providerId = typeof resolution.action.input.providerId === "string" && resolution.action.input.providerId.trim() ? resolution.action.input.providerId.trim() : resolution.provenance.provider;
|
|
817
|
+
const fallbackRetrievalPath = normalizePlainText(
|
|
818
|
+
optionalJsonRecord(result.meta?.provenance)?.retrievalPath
|
|
819
|
+
);
|
|
820
|
+
const { usable, shell } = partitionMacroExecutionRecords(result.records, providerId, fallbackRetrievalPath);
|
|
821
|
+
if (usable.length === 0 || shell.length === 0) {
|
|
822
|
+
return result;
|
|
823
|
+
}
|
|
824
|
+
return {
|
|
825
|
+
...result,
|
|
826
|
+
records: [
|
|
827
|
+
...usable,
|
|
828
|
+
...shell.map((entry) => entry.record)
|
|
829
|
+
]
|
|
830
|
+
};
|
|
831
|
+
};
|
|
832
|
+
var normalizeSearchInput = (input) => {
|
|
833
|
+
const query = requireMacroString(input.query, "search.query");
|
|
834
|
+
const limit = optionalPositiveInteger(input.limit);
|
|
835
|
+
const filters = optionalJsonRecord(input.filters);
|
|
836
|
+
return {
|
|
837
|
+
query,
|
|
838
|
+
...limit !== void 0 ? { limit } : {},
|
|
839
|
+
...filters ? { filters } : {}
|
|
840
|
+
};
|
|
841
|
+
};
|
|
842
|
+
var normalizeFetchInput = (input) => {
|
|
843
|
+
const url = requireMacroString(input.url, "fetch.url");
|
|
844
|
+
const filters = optionalJsonRecord(input.filters);
|
|
845
|
+
return {
|
|
846
|
+
url,
|
|
847
|
+
...filters ? { filters } : {}
|
|
848
|
+
};
|
|
849
|
+
};
|
|
850
|
+
var normalizeCrawlInput = (input) => {
|
|
851
|
+
const seedUrls = optionalStringArray(input.seedUrls);
|
|
852
|
+
if (!seedUrls || seedUrls.length === 0) {
|
|
853
|
+
throw new Error("Macro action missing crawl.seedUrls");
|
|
854
|
+
}
|
|
855
|
+
const strategy = input.strategy === "bfs" || input.strategy === "dfs" ? input.strategy : void 0;
|
|
856
|
+
const maxDepth = optionalPositiveInteger(input.maxDepth);
|
|
857
|
+
const maxPages = optionalPositiveInteger(input.maxPages);
|
|
858
|
+
const maxPerDomain = optionalPositiveInteger(input.maxPerDomain);
|
|
859
|
+
const filters = optionalJsonRecord(input.filters);
|
|
860
|
+
return {
|
|
861
|
+
seedUrls,
|
|
862
|
+
...strategy ? { strategy } : {},
|
|
863
|
+
...maxDepth !== void 0 ? { maxDepth } : {},
|
|
864
|
+
...maxPages !== void 0 ? { maxPages } : {},
|
|
865
|
+
...maxPerDomain !== void 0 ? { maxPerDomain } : {},
|
|
866
|
+
...filters ? { filters } : {}
|
|
867
|
+
};
|
|
868
|
+
};
|
|
869
|
+
var normalizePostInput = (input) => {
|
|
870
|
+
const target = requireMacroString(input.target, "post.target");
|
|
871
|
+
const content = requireMacroString(input.content, "post.content");
|
|
872
|
+
const mediaUrls = optionalStringArray(input.mediaUrls);
|
|
873
|
+
const confirm = optionalBoolean(input.confirm);
|
|
874
|
+
const riskAccepted = optionalBoolean(input.riskAccepted);
|
|
875
|
+
const metadata = optionalJsonRecord(input.metadata);
|
|
876
|
+
return {
|
|
877
|
+
target,
|
|
878
|
+
content,
|
|
879
|
+
...mediaUrls ? { mediaUrls } : {},
|
|
880
|
+
...confirm !== void 0 ? { confirm } : {},
|
|
881
|
+
...riskAccepted !== void 0 ? { riskAccepted } : {},
|
|
882
|
+
...metadata ? { metadata } : {}
|
|
883
|
+
};
|
|
884
|
+
};
|
|
885
|
+
var buildRunOptions = (resolution, overrides) => {
|
|
886
|
+
const source = resolution.action.source;
|
|
887
|
+
const providerId = typeof resolution.action.input.providerId === "string" && resolution.action.input.providerId.trim() ? resolution.action.input.providerId.trim() : void 0;
|
|
888
|
+
const runtimePolicy = buildRuntimePolicyOverrides(overrides);
|
|
889
|
+
return {
|
|
890
|
+
source,
|
|
891
|
+
...providerId ? { providerIds: [providerId] } : {},
|
|
892
|
+
...overrides?.challengeAutomationMode ? { challengeAutomationMode: overrides.challengeAutomationMode } : {},
|
|
893
|
+
...runtimePolicy ? { runtimePolicy } : {}
|
|
894
|
+
};
|
|
895
|
+
};
|
|
896
|
+
var executeMacroResolution = async (resolution, runtime, overrides) => {
|
|
897
|
+
const { operation, input } = resolution.action;
|
|
898
|
+
if (!isRecordValue(input)) {
|
|
899
|
+
throw new Error("Macro action input is invalid");
|
|
900
|
+
}
|
|
901
|
+
const options = buildRunOptions(resolution, overrides);
|
|
902
|
+
let result;
|
|
903
|
+
switch (operation) {
|
|
904
|
+
case "search":
|
|
905
|
+
result = await runtime.search(normalizeSearchInput(input), options);
|
|
906
|
+
break;
|
|
907
|
+
case "fetch":
|
|
908
|
+
result = await runtime.fetch(normalizeFetchInput(input), options);
|
|
909
|
+
break;
|
|
910
|
+
case "crawl":
|
|
911
|
+
result = await runtime.crawl(normalizeCrawlInput(input), options);
|
|
912
|
+
break;
|
|
913
|
+
case "post":
|
|
914
|
+
result = await runtime.post(normalizePostInput(input), options);
|
|
915
|
+
break;
|
|
916
|
+
default:
|
|
917
|
+
throw new Error(`Macro operation is not supported: ${operation}`);
|
|
918
|
+
}
|
|
919
|
+
const prioritized = prioritizeMacroExecutionRecords(resolution, result);
|
|
920
|
+
assertMacroExecutionQuality(resolution, prioritized);
|
|
921
|
+
return prioritized;
|
|
922
|
+
};
|
|
923
|
+
var shapeExecutionPayload = (result) => {
|
|
924
|
+
return {
|
|
925
|
+
records: result.records,
|
|
926
|
+
failures: result.failures,
|
|
927
|
+
metrics: result.metrics,
|
|
928
|
+
meta: {
|
|
929
|
+
ok: result.ok,
|
|
930
|
+
partial: result.partial,
|
|
931
|
+
sourceSelection: result.sourceSelection,
|
|
932
|
+
providerOrder: result.providerOrder,
|
|
933
|
+
trace: result.trace,
|
|
934
|
+
...result.meta?.tier ? { tier: result.meta.tier } : {},
|
|
935
|
+
...result.meta?.provenance ? { provenance: result.meta.provenance } : {},
|
|
936
|
+
...result.meta?.blocker ? { blocker: result.meta.blocker } : {},
|
|
937
|
+
...result.error ? { error: result.error } : {}
|
|
938
|
+
},
|
|
939
|
+
...result.diagnostics ? { diagnostics: result.diagnostics } : {}
|
|
940
|
+
};
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
// src/macros/execute-runtime.ts
|
|
944
|
+
var MACRO_TIMEOUT_MIN_MS = 1e3;
|
|
945
|
+
var MACRO_TIMEOUT_MAX_MS = 3e5;
|
|
946
|
+
var clampMacroRuntimeTimeout = (timeoutMs) => {
|
|
947
|
+
if (!Number.isFinite(timeoutMs ?? NaN)) {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
const parsed = Math.floor(timeoutMs);
|
|
951
|
+
return Math.max(MACRO_TIMEOUT_MIN_MS, Math.min(MACRO_TIMEOUT_MAX_MS, parsed));
|
|
952
|
+
};
|
|
953
|
+
var buildMacroRuntimeInit = (timeoutMs) => {
|
|
954
|
+
const macroTimeoutMs = clampMacroRuntimeTimeout(timeoutMs);
|
|
955
|
+
if (macroTimeoutMs === null) {
|
|
956
|
+
return void 0;
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
budgets: {
|
|
960
|
+
timeoutMs: {
|
|
961
|
+
search: macroTimeoutMs,
|
|
962
|
+
fetch: macroTimeoutMs,
|
|
963
|
+
crawl: macroTimeoutMs,
|
|
964
|
+
post: macroTimeoutMs
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
};
|
|
969
|
+
var executeMacroWithRuntime = async (args) => {
|
|
970
|
+
const runtime = args.runtime ?? resolveBundledProviderRuntime({
|
|
971
|
+
existingRuntime: args.existingRuntime,
|
|
972
|
+
config: args.config,
|
|
973
|
+
manager: args.manager,
|
|
974
|
+
browserFallbackPort: args.browserFallbackPort,
|
|
975
|
+
init: buildMacroRuntimeInit(args.timeoutMs)
|
|
976
|
+
});
|
|
977
|
+
return shapeExecutionPayload(
|
|
978
|
+
await executeMacroResolution(
|
|
979
|
+
args.resolution,
|
|
980
|
+
runtime,
|
|
981
|
+
args.challengeAutomationMode || args.browserMode || typeof args.useCookies === "boolean" || args.cookiePolicyOverride ? {
|
|
982
|
+
...args.browserMode ? { browserMode: args.browserMode } : {},
|
|
983
|
+
...typeof args.useCookies === "boolean" ? { useCookies: args.useCookies } : {},
|
|
984
|
+
...args.challengeAutomationMode ? { challengeAutomationMode: args.challengeAutomationMode } : {},
|
|
985
|
+
...args.cookiePolicyOverride ? { cookiePolicyOverride: args.cookiePolicyOverride } : {}
|
|
986
|
+
} : void 0
|
|
987
|
+
)
|
|
988
|
+
);
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
// src/cli/daemon-state.ts
|
|
992
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
993
|
+
var HUB_INSTANCE_ID = randomUUID2();
|
|
994
|
+
var BINDING_TTL_MS = 6e4;
|
|
995
|
+
var RENEW_INTERVAL_MS = 2e4;
|
|
996
|
+
var RENEW_GRACE_MS = RENEW_INTERVAL_MS * 2;
|
|
997
|
+
var RENEW_JITTER_MS = 2e3;
|
|
998
|
+
var WAIT_MAX_MS = 3e4;
|
|
999
|
+
var binding = null;
|
|
1000
|
+
var queue = [];
|
|
1001
|
+
var sessionLeases = /* @__PURE__ */ new Map();
|
|
1002
|
+
var screencastOwners = /* @__PURE__ */ new Map();
|
|
1003
|
+
var screencastOwnerCleanupTimers = /* @__PURE__ */ new Map();
|
|
1004
|
+
var getHubInstanceId = () => HUB_INSTANCE_ID;
|
|
1005
|
+
var nowMs = () => Date.now();
|
|
1006
|
+
var isExpired = (state) => nowMs() > state.expiresAt + RENEW_GRACE_MS;
|
|
1007
|
+
var computeRenewAfterMs = () => {
|
|
1008
|
+
const jitter = Math.floor((Math.random() * 2 - 1) * RENEW_JITTER_MS);
|
|
1009
|
+
return Math.max(1e3, RENEW_INTERVAL_MS + jitter);
|
|
1010
|
+
};
|
|
1011
|
+
var serializeBinding = (state) => ({
|
|
1012
|
+
bindingId: state.bindingId,
|
|
1013
|
+
expiresAt: new Date(state.expiresAt).toISOString(),
|
|
1014
|
+
ttlMs: BINDING_TTL_MS,
|
|
1015
|
+
renewAfterMs: computeRenewAfterMs()
|
|
1016
|
+
});
|
|
1017
|
+
var serializeQueue = (entry) => ({
|
|
1018
|
+
queued: true,
|
|
1019
|
+
position: queue.findIndex((item) => item.clientId === entry.clientId) + 1,
|
|
1020
|
+
waitUntil: new Date(entry.timeoutAt).toISOString(),
|
|
1021
|
+
waitMs: Math.max(0, entry.timeoutAt - nowMs())
|
|
1022
|
+
});
|
|
1023
|
+
var cleanupQueue = () => {
|
|
1024
|
+
const now = nowMs();
|
|
1025
|
+
queue = queue.filter((entry) => entry.timeoutAt > now);
|
|
1026
|
+
};
|
|
1027
|
+
var clearScreencastOwnerCleanup = (screencastId) => {
|
|
1028
|
+
const timer = screencastOwnerCleanupTimers.get(screencastId);
|
|
1029
|
+
if (!timer) {
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
clearTimeout(timer);
|
|
1033
|
+
screencastOwnerCleanupTimers.delete(screencastId);
|
|
1034
|
+
};
|
|
1035
|
+
var releaseTrackedScreencastOwner = (screencastId) => {
|
|
1036
|
+
clearScreencastOwnerCleanup(screencastId);
|
|
1037
|
+
screencastOwners.delete(screencastId);
|
|
1038
|
+
};
|
|
1039
|
+
var isExpiredScreencastOwner = (owner) => {
|
|
1040
|
+
return owner.completedAt !== null && owner.lastUsedAt + SCREENCAST_RETENTION_MS <= nowMs();
|
|
1041
|
+
};
|
|
1042
|
+
var scheduleScreencastOwnerCleanup = (owner) => {
|
|
1043
|
+
clearScreencastOwnerCleanup(owner.screencastId);
|
|
1044
|
+
if (owner.completedAt === null) {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const timer = setTimeout(() => {
|
|
1048
|
+
if (isExpiredScreencastOwner(owner) && screencastOwners.get(owner.screencastId) === owner) {
|
|
1049
|
+
screencastOwners.delete(owner.screencastId);
|
|
1050
|
+
}
|
|
1051
|
+
screencastOwnerCleanupTimers.delete(owner.screencastId);
|
|
1052
|
+
}, SCREENCAST_RETENTION_MS);
|
|
1053
|
+
timer.unref?.();
|
|
1054
|
+
screencastOwnerCleanupTimers.set(owner.screencastId, timer);
|
|
1055
|
+
};
|
|
1056
|
+
var cleanupScreencastOwners = () => {
|
|
1057
|
+
for (const [screencastId, owner] of screencastOwners.entries()) {
|
|
1058
|
+
if (isExpiredScreencastOwner(owner)) {
|
|
1059
|
+
releaseTrackedScreencastOwner(screencastId);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
var getQueueEntry = (clientId) => {
|
|
1064
|
+
return queue.find((entry) => entry.clientId === clientId) ?? null;
|
|
1065
|
+
};
|
|
1066
|
+
var enqueueClient = (clientId) => {
|
|
1067
|
+
cleanupQueue();
|
|
1068
|
+
const existing = getQueueEntry(clientId);
|
|
1069
|
+
if (existing) {
|
|
1070
|
+
return existing;
|
|
1071
|
+
}
|
|
1072
|
+
const entry = {
|
|
1073
|
+
clientId,
|
|
1074
|
+
requestedAt: nowMs(),
|
|
1075
|
+
timeoutAt: nowMs() + WAIT_MAX_MS
|
|
1076
|
+
};
|
|
1077
|
+
queue.push(entry);
|
|
1078
|
+
return entry;
|
|
1079
|
+
};
|
|
1080
|
+
var dequeueClient = (clientId) => {
|
|
1081
|
+
queue = queue.filter((entry) => entry.clientId !== clientId);
|
|
1082
|
+
};
|
|
1083
|
+
var maybeGrantBinding = (clientId) => {
|
|
1084
|
+
const existing = getBindingState();
|
|
1085
|
+
const now = nowMs();
|
|
1086
|
+
if (existing) {
|
|
1087
|
+
if (existing.clientId !== clientId) {
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
existing.expiresAt = now + BINDING_TTL_MS;
|
|
1091
|
+
existing.lastRenewedAt = now;
|
|
1092
|
+
return serializeBinding(existing);
|
|
1093
|
+
}
|
|
1094
|
+
cleanupQueue();
|
|
1095
|
+
const head = queue[0];
|
|
1096
|
+
if (head && head.clientId !== clientId) {
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
if (head && head.clientId === clientId) {
|
|
1100
|
+
queue.shift();
|
|
1101
|
+
}
|
|
1102
|
+
const state = {
|
|
1103
|
+
bindingId: randomUUID2(),
|
|
1104
|
+
clientId: clientId.trim(),
|
|
1105
|
+
expiresAt: now + BINDING_TTL_MS,
|
|
1106
|
+
lastRenewedAt: now
|
|
1107
|
+
};
|
|
1108
|
+
binding = state;
|
|
1109
|
+
return serializeBinding(state);
|
|
1110
|
+
};
|
|
1111
|
+
var getBindingState = () => {
|
|
1112
|
+
if (binding && isExpired(binding)) {
|
|
1113
|
+
binding = null;
|
|
1114
|
+
}
|
|
1115
|
+
cleanupQueue();
|
|
1116
|
+
return binding;
|
|
1117
|
+
};
|
|
1118
|
+
var clearBinding = () => {
|
|
1119
|
+
binding = null;
|
|
1120
|
+
queue = [];
|
|
1121
|
+
};
|
|
1122
|
+
var registerSessionLease = (sessionId, leaseId, clientId) => {
|
|
1123
|
+
if (!sessionId || !sessionId.trim()) {
|
|
1124
|
+
throw new Error("RELAY_SESSION_REQUIRED: sessionId is required");
|
|
1125
|
+
}
|
|
1126
|
+
if (!leaseId || !leaseId.trim()) {
|
|
1127
|
+
throw new Error("RELAY_LEASE_REQUIRED: leaseId is required");
|
|
1128
|
+
}
|
|
1129
|
+
if (!clientId || !clientId.trim()) {
|
|
1130
|
+
throw new Error("RELAY_CLIENT_ID_REQUIRED: clientId is required");
|
|
1131
|
+
}
|
|
1132
|
+
const lease = {
|
|
1133
|
+
sessionId,
|
|
1134
|
+
leaseId,
|
|
1135
|
+
clientId: clientId.trim(),
|
|
1136
|
+
createdAt: nowMs(),
|
|
1137
|
+
lastUsedAt: nowMs()
|
|
1138
|
+
};
|
|
1139
|
+
sessionLeases.set(sessionId, lease);
|
|
1140
|
+
return lease;
|
|
1141
|
+
};
|
|
1142
|
+
var getSessionLease = (sessionId) => {
|
|
1143
|
+
if (!sessionId || !sessionId.trim()) return null;
|
|
1144
|
+
return sessionLeases.get(sessionId) ?? null;
|
|
1145
|
+
};
|
|
1146
|
+
var releaseSessionLease = (sessionId) => {
|
|
1147
|
+
sessionLeases.delete(sessionId);
|
|
1148
|
+
};
|
|
1149
|
+
var releaseOwnedSessionLease = (sessionId, clientId, leaseId) => {
|
|
1150
|
+
if (!sessionId || !sessionId.trim()) return false;
|
|
1151
|
+
if (!clientId || !clientId.trim()) return false;
|
|
1152
|
+
const lease = sessionLeases.get(sessionId);
|
|
1153
|
+
if (!lease) return false;
|
|
1154
|
+
const normalizedClientId = clientId.trim();
|
|
1155
|
+
const normalizedLeaseId = leaseId?.trim() ?? "";
|
|
1156
|
+
if (lease.clientId !== normalizedClientId) return false;
|
|
1157
|
+
if (normalizedLeaseId && lease.leaseId !== normalizedLeaseId) return false;
|
|
1158
|
+
sessionLeases.delete(sessionId);
|
|
1159
|
+
return true;
|
|
1160
|
+
};
|
|
1161
|
+
var registerScreencastOwner = (sessionId, screencastId, clientId) => {
|
|
1162
|
+
if (!sessionId || !sessionId.trim()) {
|
|
1163
|
+
throw new Error("RELAY_SESSION_REQUIRED: sessionId is required");
|
|
1164
|
+
}
|
|
1165
|
+
if (!screencastId || !screencastId.trim()) {
|
|
1166
|
+
throw new Error("RELAY_SCREENCAST_REQUIRED: screencastId is required");
|
|
1167
|
+
}
|
|
1168
|
+
if (!clientId || !clientId.trim()) {
|
|
1169
|
+
throw new Error("RELAY_CLIENT_ID_REQUIRED: clientId is required");
|
|
1170
|
+
}
|
|
1171
|
+
cleanupScreencastOwners();
|
|
1172
|
+
const createdAt = nowMs();
|
|
1173
|
+
const owner = {
|
|
1174
|
+
screencastId: screencastId.trim(),
|
|
1175
|
+
sessionId: sessionId.trim(),
|
|
1176
|
+
clientId: clientId.trim(),
|
|
1177
|
+
createdAt,
|
|
1178
|
+
completedAt: null,
|
|
1179
|
+
lastUsedAt: createdAt
|
|
1180
|
+
};
|
|
1181
|
+
releaseTrackedScreencastOwner(owner.screencastId);
|
|
1182
|
+
screencastOwners.set(owner.screencastId, owner);
|
|
1183
|
+
return owner;
|
|
1184
|
+
};
|
|
1185
|
+
var getScreencastOwner = (screencastId) => {
|
|
1186
|
+
if (!screencastId || !screencastId.trim()) return null;
|
|
1187
|
+
cleanupScreencastOwners();
|
|
1188
|
+
return screencastOwners.get(screencastId) ?? null;
|
|
1189
|
+
};
|
|
1190
|
+
var touchScreencastOwner = (screencastId) => {
|
|
1191
|
+
const owner = getScreencastOwner(screencastId);
|
|
1192
|
+
if (!owner) {
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
owner.lastUsedAt = nowMs();
|
|
1196
|
+
scheduleScreencastOwnerCleanup(owner);
|
|
1197
|
+
return owner;
|
|
1198
|
+
};
|
|
1199
|
+
var completeScreencastOwner = (screencastId) => {
|
|
1200
|
+
const owner = getScreencastOwner(screencastId);
|
|
1201
|
+
if (!owner) {
|
|
1202
|
+
return null;
|
|
1203
|
+
}
|
|
1204
|
+
const completedAt = nowMs();
|
|
1205
|
+
owner.completedAt = completedAt;
|
|
1206
|
+
owner.lastUsedAt = completedAt;
|
|
1207
|
+
scheduleScreencastOwnerCleanup(owner);
|
|
1208
|
+
return owner;
|
|
1209
|
+
};
|
|
1210
|
+
var requireScreencastOwner = (sessionId, screencastId, clientId) => {
|
|
1211
|
+
if (!sessionId || !sessionId.trim()) {
|
|
1212
|
+
throw new Error("RELAY_SESSION_REQUIRED: sessionId is required");
|
|
1213
|
+
}
|
|
1214
|
+
if (!screencastId || !screencastId.trim()) {
|
|
1215
|
+
throw new Error("RELAY_SCREENCAST_REQUIRED: screencastId is required");
|
|
1216
|
+
}
|
|
1217
|
+
if (!clientId || !clientId.trim()) {
|
|
1218
|
+
throw new Error("RELAY_CLIENT_ID_REQUIRED: clientId is required");
|
|
1219
|
+
}
|
|
1220
|
+
const owner = getScreencastOwner(screencastId);
|
|
1221
|
+
if (!owner) {
|
|
1222
|
+
throw new Error("RELAY_SCREENCAST_OWNER_REQUIRED: No retained owner for completed screencast retrieval.");
|
|
1223
|
+
}
|
|
1224
|
+
const normalizedSessionId = sessionId.trim();
|
|
1225
|
+
const normalizedClientId = clientId.trim();
|
|
1226
|
+
if (owner.sessionId !== normalizedSessionId || owner.clientId !== normalizedClientId) {
|
|
1227
|
+
throw new Error("RELAY_SCREENCAST_OWNER_INVALID: Screencast does not match the current owner.");
|
|
1228
|
+
}
|
|
1229
|
+
touchScreencastOwner(screencastId);
|
|
1230
|
+
return owner;
|
|
1231
|
+
};
|
|
1232
|
+
var requireSessionLease = (sessionId, clientId, leaseId) => {
|
|
1233
|
+
if (!sessionId || !sessionId.trim()) {
|
|
1234
|
+
throw new Error("RELAY_SESSION_REQUIRED: sessionId is required");
|
|
1235
|
+
}
|
|
1236
|
+
if (!clientId || !clientId.trim()) {
|
|
1237
|
+
throw new Error("RELAY_CLIENT_ID_REQUIRED: clientId is required");
|
|
1238
|
+
}
|
|
1239
|
+
const lease = sessionLeases.get(sessionId);
|
|
1240
|
+
if (!lease) {
|
|
1241
|
+
throw new Error("RELAY_LEASE_REQUIRED: No active lease for session.");
|
|
1242
|
+
}
|
|
1243
|
+
const normalizedClientId = clientId.trim();
|
|
1244
|
+
const normalizedLeaseId = leaseId?.trim() ?? "";
|
|
1245
|
+
if (!normalizedLeaseId) {
|
|
1246
|
+
if (lease.clientId !== normalizedClientId) {
|
|
1247
|
+
throw new Error("RELAY_LEASE_INVALID: Lease does not match session owner.");
|
|
1248
|
+
}
|
|
1249
|
+
lease.lastUsedAt = nowMs();
|
|
1250
|
+
return lease;
|
|
1251
|
+
}
|
|
1252
|
+
if (lease.leaseId !== normalizedLeaseId || lease.clientId !== normalizedClientId) {
|
|
1253
|
+
throw new Error("RELAY_LEASE_INVALID: Lease does not match session owner.");
|
|
1254
|
+
}
|
|
1255
|
+
lease.lastUsedAt = nowMs();
|
|
1256
|
+
return lease;
|
|
1257
|
+
};
|
|
1258
|
+
var bindRelay = (clientId) => {
|
|
1259
|
+
if (!clientId || !clientId.trim()) {
|
|
1260
|
+
throw new Error("RELAY_CLIENT_ID_REQUIRED: clientId is required");
|
|
1261
|
+
}
|
|
1262
|
+
const result = maybeGrantBinding(clientId);
|
|
1263
|
+
if (result) {
|
|
1264
|
+
return result;
|
|
1265
|
+
}
|
|
1266
|
+
const entry = enqueueClient(clientId.trim());
|
|
1267
|
+
return serializeQueue(entry);
|
|
1268
|
+
};
|
|
1269
|
+
var waitForBinding = async (clientId, timeoutMs) => {
|
|
1270
|
+
if (!clientId || !clientId.trim()) {
|
|
1271
|
+
throw new Error("RELAY_CLIENT_ID_REQUIRED: clientId is required");
|
|
1272
|
+
}
|
|
1273
|
+
const entry = enqueueClient(clientId.trim());
|
|
1274
|
+
const deadline = timeoutMs ? Math.min(entry.timeoutAt, nowMs() + timeoutMs) : entry.timeoutAt;
|
|
1275
|
+
while (nowMs() <= deadline) {
|
|
1276
|
+
const result = maybeGrantBinding(clientId);
|
|
1277
|
+
if (result) {
|
|
1278
|
+
return result;
|
|
1279
|
+
}
|
|
1280
|
+
cleanupQueue();
|
|
1281
|
+
if (!getQueueEntry(clientId)) {
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
await new Promise((resolve2) => setTimeout(resolve2, 250));
|
|
1285
|
+
}
|
|
1286
|
+
dequeueClient(clientId);
|
|
1287
|
+
throw new Error("RELAY_WAIT_TIMEOUT: Timed out waiting for relay binding.");
|
|
1288
|
+
};
|
|
1289
|
+
var renewRelay = (clientId, bindingId) => {
|
|
1290
|
+
if (!clientId || !clientId.trim()) {
|
|
1291
|
+
throw new Error("RELAY_CLIENT_ID_REQUIRED: clientId is required");
|
|
1292
|
+
}
|
|
1293
|
+
if (!bindingId || !bindingId.trim()) {
|
|
1294
|
+
throw new Error("RELAY_BINDING_REQUIRED: bindingId is required");
|
|
1295
|
+
}
|
|
1296
|
+
const existing = getBindingState();
|
|
1297
|
+
if (!existing) {
|
|
1298
|
+
throw new Error("RELAY_BINDING_REQUIRED: No active binding to renew.");
|
|
1299
|
+
}
|
|
1300
|
+
if (existing.clientId !== clientId || existing.bindingId !== bindingId) {
|
|
1301
|
+
throw new Error("RELAY_BINDING_INVALID: Binding does not match the current owner.");
|
|
1302
|
+
}
|
|
1303
|
+
const now = nowMs();
|
|
1304
|
+
existing.expiresAt = now + BINDING_TTL_MS;
|
|
1305
|
+
existing.lastRenewedAt = now;
|
|
1306
|
+
return serializeBinding(existing);
|
|
1307
|
+
};
|
|
1308
|
+
var releaseRelay = (clientId, bindingId) => {
|
|
1309
|
+
if (!clientId || !clientId.trim()) {
|
|
1310
|
+
throw new Error("RELAY_CLIENT_ID_REQUIRED: clientId is required");
|
|
1311
|
+
}
|
|
1312
|
+
if (!bindingId || !bindingId.trim()) {
|
|
1313
|
+
throw new Error("RELAY_BINDING_REQUIRED: bindingId is required");
|
|
1314
|
+
}
|
|
1315
|
+
const existing = getBindingState();
|
|
1316
|
+
if (!existing) {
|
|
1317
|
+
return { released: false };
|
|
1318
|
+
}
|
|
1319
|
+
if (existing.clientId !== clientId || existing.bindingId !== bindingId) {
|
|
1320
|
+
throw new Error("RELAY_BINDING_INVALID: Binding does not match the current owner.");
|
|
1321
|
+
}
|
|
1322
|
+
binding = null;
|
|
1323
|
+
return { released: true };
|
|
1324
|
+
};
|
|
1325
|
+
var requireBinding = (clientId, bindingId) => {
|
|
1326
|
+
if (!clientId || !clientId.trim()) {
|
|
1327
|
+
throw new Error("RELAY_CLIENT_ID_REQUIRED: clientId is required");
|
|
1328
|
+
}
|
|
1329
|
+
const existing = getBindingState();
|
|
1330
|
+
if (!existing) {
|
|
1331
|
+
throw new Error("RELAY_BINDING_REQUIRED: Call relay.bind to acquire the relay binding.");
|
|
1332
|
+
}
|
|
1333
|
+
if (!bindingId || !bindingId.trim()) {
|
|
1334
|
+
throw new Error("RELAY_BINDING_REQUIRED: bindingId is required for relay operations.");
|
|
1335
|
+
}
|
|
1336
|
+
if (existing.clientId !== clientId || existing.bindingId !== bindingId) {
|
|
1337
|
+
throw new Error("RELAY_BINDING_INVALID: Binding does not match the current owner.");
|
|
1338
|
+
}
|
|
1339
|
+
return existing;
|
|
1340
|
+
};
|
|
1341
|
+
var getBindingDiagnostics = () => {
|
|
1342
|
+
const existing = getBindingState();
|
|
1343
|
+
if (!existing) return null;
|
|
1344
|
+
const expiresInMs = Math.max(0, existing.expiresAt - nowMs());
|
|
1345
|
+
return {
|
|
1346
|
+
bindingId: existing.bindingId,
|
|
1347
|
+
clientId: existing.clientId,
|
|
1348
|
+
expiresAt: new Date(existing.expiresAt).toISOString(),
|
|
1349
|
+
expiresInMs,
|
|
1350
|
+
queueLength: queue.length
|
|
1351
|
+
};
|
|
1352
|
+
};
|
|
1353
|
+
var getBindingRenewConfig = () => ({
|
|
1354
|
+
ttlMs: BINDING_TTL_MS,
|
|
1355
|
+
renewIntervalMs: RENEW_INTERVAL_MS,
|
|
1356
|
+
graceMs: RENEW_GRACE_MS,
|
|
1357
|
+
waitMaxMs: WAIT_MAX_MS
|
|
1358
|
+
});
|
|
1359
|
+
|
|
1360
|
+
// src/cli/daemon-commands.ts
|
|
1361
|
+
var createDaemonWorkflowRuntime = (core, options) => resolveBundledProviderRuntime({
|
|
1362
|
+
existingRuntime: core.providerRuntime,
|
|
1363
|
+
config: core.config,
|
|
1364
|
+
manager: core.manager,
|
|
1365
|
+
browserFallbackPort: core.browserFallbackPort,
|
|
1366
|
+
init: options?.init
|
|
1367
|
+
});
|
|
1368
|
+
async function handleDaemonCommand(core, request) {
|
|
1369
|
+
const params = request.params ?? {};
|
|
1370
|
+
const bindingId = optionalString(params.bindingId);
|
|
1371
|
+
try {
|
|
1372
|
+
return await (async () => {
|
|
1373
|
+
switch (request.name) {
|
|
1374
|
+
case "relay.status":
|
|
1375
|
+
return core.relay.status();
|
|
1376
|
+
case "relay.cdpUrl":
|
|
1377
|
+
return core.relay.getCdpUrl();
|
|
1378
|
+
case "relay.annotationUrl":
|
|
1379
|
+
return core.relay.getAnnotationUrl?.() ?? null;
|
|
1380
|
+
case "relay.opsUrl":
|
|
1381
|
+
return core.relay.getOpsUrl?.() ?? null;
|
|
1382
|
+
case "relay.canvasUrl":
|
|
1383
|
+
return core.relay.getCanvasUrl?.() ?? null;
|
|
1384
|
+
case "canvas.execute":
|
|
1385
|
+
return core.canvasManager.execute(
|
|
1386
|
+
requireString(params.command, "command"),
|
|
1387
|
+
requireRecord(params.params ?? {}, "params")
|
|
1388
|
+
);
|
|
1389
|
+
case "relay.bind": {
|
|
1390
|
+
const clientId = requireClientId(params);
|
|
1391
|
+
const binding2 = bindRelay(clientId);
|
|
1392
|
+
const relayStatus = core.relay.status();
|
|
1393
|
+
return {
|
|
1394
|
+
...binding2,
|
|
1395
|
+
hubInstanceId: getHubInstanceId(),
|
|
1396
|
+
relayInstanceId: relayStatus.instanceId,
|
|
1397
|
+
relayPort: relayStatus.port ?? null,
|
|
1398
|
+
bindingConfig: getBindingRenewConfig()
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
case "relay.wait": {
|
|
1402
|
+
const clientId = requireClientId(params);
|
|
1403
|
+
const timeoutMs = optionalNumber(params.timeoutMs, "timeoutMs");
|
|
1404
|
+
const binding2 = await waitForBinding(clientId, timeoutMs);
|
|
1405
|
+
const relayStatus = core.relay.status();
|
|
1406
|
+
return {
|
|
1407
|
+
...binding2,
|
|
1408
|
+
hubInstanceId: getHubInstanceId(),
|
|
1409
|
+
relayInstanceId: relayStatus.instanceId,
|
|
1410
|
+
relayPort: relayStatus.port ?? null,
|
|
1411
|
+
bindingConfig: getBindingRenewConfig()
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
case "relay.renew": {
|
|
1415
|
+
const clientId = requireClientId(params);
|
|
1416
|
+
const binding2 = renewRelay(clientId, requireString(bindingId, "bindingId"));
|
|
1417
|
+
const relayStatus = core.relay.status();
|
|
1418
|
+
return {
|
|
1419
|
+
...binding2,
|
|
1420
|
+
hubInstanceId: getHubInstanceId(),
|
|
1421
|
+
relayInstanceId: relayStatus.instanceId,
|
|
1422
|
+
relayPort: relayStatus.port ?? null,
|
|
1423
|
+
bindingConfig: getBindingRenewConfig()
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
case "relay.release": {
|
|
1427
|
+
const clientId = requireClientId(params);
|
|
1428
|
+
return releaseRelay(clientId, requireString(bindingId, "bindingId"));
|
|
1429
|
+
}
|
|
1430
|
+
case "session.launch":
|
|
1431
|
+
return launchWithRelay(core, params, requireClientId(params), bindingId);
|
|
1432
|
+
case "session.connect":
|
|
1433
|
+
return connectWithRelayRouting(core, params, requireClientId(params), bindingId);
|
|
1434
|
+
case "session.disconnect":
|
|
1435
|
+
return disconnectSession(core, params, requireClientId(params), bindingId);
|
|
1436
|
+
case "session.status":
|
|
1437
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1438
|
+
return core.manager.status(requireString(params.sessionId, "sessionId"));
|
|
1439
|
+
case "status.capabilities":
|
|
1440
|
+
if (typeof params.sessionId === "string") {
|
|
1441
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1442
|
+
}
|
|
1443
|
+
return runStatusCapabilities(core, params);
|
|
1444
|
+
case "session.inspect": {
|
|
1445
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1446
|
+
const inspector = requireSessionInspectorHandle(core);
|
|
1447
|
+
return inspectSession(inspector, {
|
|
1448
|
+
sessionId: requireString(params.sessionId, "sessionId"),
|
|
1449
|
+
includeUrls: optionalBoolean2(params.includeUrls) ?? true,
|
|
1450
|
+
sinceConsoleSeq: optionalNumber(params.sinceConsoleSeq, "sinceConsoleSeq") ?? void 0,
|
|
1451
|
+
sinceNetworkSeq: optionalNumber(params.sinceNetworkSeq, "sinceNetworkSeq") ?? void 0,
|
|
1452
|
+
sinceExceptionSeq: optionalNumber(params.sinceExceptionSeq, "sinceExceptionSeq") ?? void 0,
|
|
1453
|
+
max: optionalNumber(params.max, "max") ?? void 0,
|
|
1454
|
+
requestId: optionalString(params.requestId),
|
|
1455
|
+
relayStatus: core.relay.status()
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
case "session.inspectPlan":
|
|
1459
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1460
|
+
return runInspectChallengePlan(core, params);
|
|
1461
|
+
case "session.inspectAudit":
|
|
1462
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1463
|
+
return runInspectAudit(core, params);
|
|
1464
|
+
case "desktop.status":
|
|
1465
|
+
return core.desktopRuntime.status();
|
|
1466
|
+
case "desktop.windows.list":
|
|
1467
|
+
return core.desktopRuntime.listWindows(optionalString(params.reason));
|
|
1468
|
+
case "desktop.window.active":
|
|
1469
|
+
return core.desktopRuntime.activeWindow(optionalString(params.reason));
|
|
1470
|
+
case "desktop.capture.desktop":
|
|
1471
|
+
return core.desktopRuntime.captureDesktop({
|
|
1472
|
+
reason: requireString(params.reason, "reason")
|
|
1473
|
+
});
|
|
1474
|
+
case "desktop.capture.window":
|
|
1475
|
+
return core.desktopRuntime.captureWindow(
|
|
1476
|
+
requireString(params.windowId, "windowId"),
|
|
1477
|
+
{ reason: requireString(params.reason, "reason") }
|
|
1478
|
+
);
|
|
1479
|
+
case "desktop.accessibility.snapshot":
|
|
1480
|
+
return core.desktopRuntime.accessibilitySnapshot(
|
|
1481
|
+
requireString(params.reason, "reason"),
|
|
1482
|
+
optionalString(params.windowId)
|
|
1483
|
+
);
|
|
1484
|
+
case "annotate": {
|
|
1485
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1486
|
+
const sessionId = requireString(params.sessionId, "sessionId");
|
|
1487
|
+
const stored = optionalBoolean2(params.stored) ?? false;
|
|
1488
|
+
const transport = stored ? "relay" : requireAnnotationTransport(params.transport);
|
|
1489
|
+
if (transport === "relay" && !stored) {
|
|
1490
|
+
const status = await core.manager.status(sessionId);
|
|
1491
|
+
if (status.mode !== "extension") {
|
|
1492
|
+
throw new Error("Relay annotations require extension mode.");
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
const url = optionalString(params.url);
|
|
1496
|
+
const targetId = optionalString(params.targetId);
|
|
1497
|
+
const tabId = optionalNumber(params.tabId, "tabId");
|
|
1498
|
+
const screenshotMode = requireScreenshotMode(params.screenshotMode);
|
|
1499
|
+
const debug = optionalBoolean2(params.debug) ?? false;
|
|
1500
|
+
const context = optionalString(params.context);
|
|
1501
|
+
const includeScreenshots = optionalBoolean2(params.includeScreenshots) ?? true;
|
|
1502
|
+
const timeoutMs = optionalNumber(params.timeoutMs, "timeoutMs");
|
|
1503
|
+
return core.annotationManager.requestAnnotation({
|
|
1504
|
+
sessionId,
|
|
1505
|
+
transport,
|
|
1506
|
+
stored,
|
|
1507
|
+
includeScreenshots,
|
|
1508
|
+
targetId,
|
|
1509
|
+
tabId,
|
|
1510
|
+
url,
|
|
1511
|
+
screenshotMode,
|
|
1512
|
+
debug,
|
|
1513
|
+
context,
|
|
1514
|
+
timeoutMs
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
case "agent.inbox.enqueue":
|
|
1518
|
+
return core.agentInbox.enqueue({
|
|
1519
|
+
payload: requireAnnotationPayload(params.payload),
|
|
1520
|
+
source: requireAnnotationDispatchSource(params.source),
|
|
1521
|
+
label: optionalString(params.label) ?? "",
|
|
1522
|
+
explicitChatScopeKey: optionalString(params.chatScopeKey) ?? null
|
|
1523
|
+
});
|
|
1524
|
+
case "agent.inbox.peek": {
|
|
1525
|
+
const chatScopeKey = requireString(params.chatScopeKey, "chatScopeKey");
|
|
1526
|
+
return {
|
|
1527
|
+
chatScopeKey,
|
|
1528
|
+
activeScopes: core.agentInbox.listActiveScopes(),
|
|
1529
|
+
entries: core.agentInbox.peekScope(chatScopeKey)
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
case "agent.inbox.consume": {
|
|
1533
|
+
const chatScopeKey = requireString(params.chatScopeKey, "chatScopeKey");
|
|
1534
|
+
const entries = core.agentInbox.consumeScope(chatScopeKey);
|
|
1535
|
+
return {
|
|
1536
|
+
chatScopeKey,
|
|
1537
|
+
receiptIds: entries.map((entry) => entry.id),
|
|
1538
|
+
entries
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
case "agent.inbox.ack": {
|
|
1542
|
+
const receiptIds = requireStringArray(params.receiptIds, "receiptIds");
|
|
1543
|
+
core.agentInbox.acknowledge(receiptIds);
|
|
1544
|
+
return {
|
|
1545
|
+
ok: true,
|
|
1546
|
+
receiptIds
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
case "targets.list":
|
|
1550
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1551
|
+
return core.manager.listTargets(
|
|
1552
|
+
requireString(params.sessionId, "sessionId"),
|
|
1553
|
+
optionalBoolean2(params.includeUrls) ?? false
|
|
1554
|
+
);
|
|
1555
|
+
case "targets.use":
|
|
1556
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1557
|
+
return core.manager.useTarget(
|
|
1558
|
+
requireString(params.sessionId, "sessionId"),
|
|
1559
|
+
requireString(params.targetId, "targetId")
|
|
1560
|
+
);
|
|
1561
|
+
case "targets.new":
|
|
1562
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1563
|
+
return core.manager.newTarget(
|
|
1564
|
+
requireString(params.sessionId, "sessionId"),
|
|
1565
|
+
optionalString(params.url)
|
|
1566
|
+
);
|
|
1567
|
+
case "targets.close":
|
|
1568
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1569
|
+
await core.manager.closeTarget(
|
|
1570
|
+
requireString(params.sessionId, "sessionId"),
|
|
1571
|
+
requireString(params.targetId, "targetId")
|
|
1572
|
+
);
|
|
1573
|
+
return { ok: true };
|
|
1574
|
+
case "page.open":
|
|
1575
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1576
|
+
return core.manager.page(
|
|
1577
|
+
requireString(params.sessionId, "sessionId"),
|
|
1578
|
+
requireString(params.name, "name"),
|
|
1579
|
+
optionalString(params.url)
|
|
1580
|
+
);
|
|
1581
|
+
case "page.list":
|
|
1582
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1583
|
+
return core.manager.listPages(requireString(params.sessionId, "sessionId"));
|
|
1584
|
+
case "page.close":
|
|
1585
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1586
|
+
await core.manager.closePage(
|
|
1587
|
+
requireString(params.sessionId, "sessionId"),
|
|
1588
|
+
requireString(params.name, "name")
|
|
1589
|
+
);
|
|
1590
|
+
return { ok: true };
|
|
1591
|
+
case "nav.goto":
|
|
1592
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1593
|
+
{
|
|
1594
|
+
const targetId = optionalString(params.targetId);
|
|
1595
|
+
const sessionId = requireString(params.sessionId, "sessionId");
|
|
1596
|
+
return attachBlockerMetaForNavigation(
|
|
1597
|
+
core,
|
|
1598
|
+
sessionId,
|
|
1599
|
+
await core.manager.goto(
|
|
1600
|
+
sessionId,
|
|
1601
|
+
requireString(params.url, "url"),
|
|
1602
|
+
requireWaitUntil(params.waitUntil),
|
|
1603
|
+
optionalNumber(params.timeoutMs, "timeoutMs") ?? 3e4,
|
|
1604
|
+
void 0,
|
|
1605
|
+
targetId
|
|
1606
|
+
)
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
case "nav.wait":
|
|
1610
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1611
|
+
{
|
|
1612
|
+
const targetId = optionalString(params.targetId);
|
|
1613
|
+
const sessionId = requireString(params.sessionId, "sessionId");
|
|
1614
|
+
if (typeof params.ref === "string") {
|
|
1615
|
+
return attachBlockerMetaForNavigation(
|
|
1616
|
+
core,
|
|
1617
|
+
sessionId,
|
|
1618
|
+
await core.manager.waitForRef(
|
|
1619
|
+
sessionId,
|
|
1620
|
+
requireString(params.ref, "ref"),
|
|
1621
|
+
requireState(params.state),
|
|
1622
|
+
optionalNumber(params.timeoutMs, "timeoutMs") ?? 3e4,
|
|
1623
|
+
targetId
|
|
1624
|
+
)
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
return attachBlockerMetaForNavigation(
|
|
1628
|
+
core,
|
|
1629
|
+
sessionId,
|
|
1630
|
+
await core.manager.waitForLoad(
|
|
1631
|
+
sessionId,
|
|
1632
|
+
requireWaitUntil(params.until),
|
|
1633
|
+
optionalNumber(params.timeoutMs, "timeoutMs") ?? 3e4,
|
|
1634
|
+
targetId
|
|
1635
|
+
)
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
case "nav.snapshot":
|
|
1639
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1640
|
+
{
|
|
1641
|
+
const targetId = optionalString(params.targetId);
|
|
1642
|
+
return core.manager.snapshot(
|
|
1643
|
+
requireString(params.sessionId, "sessionId"),
|
|
1644
|
+
requireSnapshotMode(params.mode),
|
|
1645
|
+
optionalPositiveInteger2(params.maxChars, "maxChars") ?? 16e3,
|
|
1646
|
+
optionalString(params.cursor),
|
|
1647
|
+
targetId
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
case "nav.review":
|
|
1651
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1652
|
+
return buildBrowserReviewResult({
|
|
1653
|
+
manager: core.manager,
|
|
1654
|
+
sessionId: requireString(params.sessionId, "sessionId"),
|
|
1655
|
+
targetId: optionalString(params.targetId),
|
|
1656
|
+
maxChars: optionalPositiveInteger2(params.maxChars, "maxChars") ?? core.config.snapshot.maxChars,
|
|
1657
|
+
cursor: optionalString(params.cursor)
|
|
1658
|
+
});
|
|
1659
|
+
case "nav.reviewDesktop":
|
|
1660
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1661
|
+
return runReviewDesktop(core, params);
|
|
1662
|
+
case "interact.click":
|
|
1663
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1664
|
+
return core.manager.click(
|
|
1665
|
+
requireString(params.sessionId, "sessionId"),
|
|
1666
|
+
requireString(params.ref, "ref"),
|
|
1667
|
+
optionalString(params.targetId)
|
|
1668
|
+
);
|
|
1669
|
+
case "interact.hover":
|
|
1670
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1671
|
+
return core.manager.hover(
|
|
1672
|
+
requireString(params.sessionId, "sessionId"),
|
|
1673
|
+
requireString(params.ref, "ref"),
|
|
1674
|
+
optionalString(params.targetId)
|
|
1675
|
+
);
|
|
1676
|
+
case "interact.press":
|
|
1677
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1678
|
+
return core.manager.press(
|
|
1679
|
+
requireString(params.sessionId, "sessionId"),
|
|
1680
|
+
requireString(params.key, "key"),
|
|
1681
|
+
optionalString(params.ref),
|
|
1682
|
+
optionalString(params.targetId)
|
|
1683
|
+
);
|
|
1684
|
+
case "interact.check":
|
|
1685
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1686
|
+
return core.manager.check(
|
|
1687
|
+
requireString(params.sessionId, "sessionId"),
|
|
1688
|
+
requireString(params.ref, "ref"),
|
|
1689
|
+
optionalString(params.targetId)
|
|
1690
|
+
);
|
|
1691
|
+
case "interact.uncheck":
|
|
1692
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1693
|
+
return core.manager.uncheck(
|
|
1694
|
+
requireString(params.sessionId, "sessionId"),
|
|
1695
|
+
requireString(params.ref, "ref"),
|
|
1696
|
+
optionalString(params.targetId)
|
|
1697
|
+
);
|
|
1698
|
+
case "interact.type":
|
|
1699
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1700
|
+
return core.manager.type(
|
|
1701
|
+
requireString(params.sessionId, "sessionId"),
|
|
1702
|
+
requireString(params.ref, "ref"),
|
|
1703
|
+
requireString(params.text, "text"),
|
|
1704
|
+
optionalBoolean2(params.clear) ?? false,
|
|
1705
|
+
optionalBoolean2(params.submit) ?? false,
|
|
1706
|
+
optionalString(params.targetId)
|
|
1707
|
+
);
|
|
1708
|
+
case "interact.select":
|
|
1709
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1710
|
+
return core.manager.select(
|
|
1711
|
+
requireString(params.sessionId, "sessionId"),
|
|
1712
|
+
requireString(params.ref, "ref"),
|
|
1713
|
+
requireStringArray(params.values, "values"),
|
|
1714
|
+
optionalString(params.targetId)
|
|
1715
|
+
);
|
|
1716
|
+
case "interact.upload":
|
|
1717
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1718
|
+
return core.manager.upload(
|
|
1719
|
+
requireString(params.sessionId, "sessionId"),
|
|
1720
|
+
{
|
|
1721
|
+
ref: requireString(params.ref, "ref"),
|
|
1722
|
+
files: requireStringArray(params.files, "files"),
|
|
1723
|
+
...typeof params.targetId === "string" ? { targetId: params.targetId } : {}
|
|
1724
|
+
}
|
|
1725
|
+
);
|
|
1726
|
+
case "interact.scroll":
|
|
1727
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1728
|
+
return core.manager.scroll(
|
|
1729
|
+
requireString(params.sessionId, "sessionId"),
|
|
1730
|
+
optionalNumber(params.dy, "dy") ?? 0,
|
|
1731
|
+
optionalString(params.ref),
|
|
1732
|
+
optionalString(params.targetId)
|
|
1733
|
+
);
|
|
1734
|
+
case "interact.scrollIntoView":
|
|
1735
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1736
|
+
return core.manager.scrollIntoView(
|
|
1737
|
+
requireString(params.sessionId, "sessionId"),
|
|
1738
|
+
requireString(params.ref, "ref"),
|
|
1739
|
+
optionalString(params.targetId)
|
|
1740
|
+
);
|
|
1741
|
+
case "pointer.move":
|
|
1742
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1743
|
+
return core.manager.pointerMove(
|
|
1744
|
+
requireString(params.sessionId, "sessionId"),
|
|
1745
|
+
requireFiniteNumber(params.x, "x"),
|
|
1746
|
+
requireFiniteNumber(params.y, "y"),
|
|
1747
|
+
optionalString(params.targetId),
|
|
1748
|
+
optionalNumber(params.steps, "steps")
|
|
1749
|
+
);
|
|
1750
|
+
case "pointer.down":
|
|
1751
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1752
|
+
return core.manager.pointerDown(
|
|
1753
|
+
requireString(params.sessionId, "sessionId"),
|
|
1754
|
+
requireFiniteNumber(params.x, "x"),
|
|
1755
|
+
requireFiniteNumber(params.y, "y"),
|
|
1756
|
+
optionalString(params.targetId),
|
|
1757
|
+
optionalPointerButton(params.button),
|
|
1758
|
+
optionalNumber(params.clickCount, "clickCount") ?? 1
|
|
1759
|
+
);
|
|
1760
|
+
case "pointer.up":
|
|
1761
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1762
|
+
return core.manager.pointerUp(
|
|
1763
|
+
requireString(params.sessionId, "sessionId"),
|
|
1764
|
+
requireFiniteNumber(params.x, "x"),
|
|
1765
|
+
requireFiniteNumber(params.y, "y"),
|
|
1766
|
+
optionalString(params.targetId),
|
|
1767
|
+
optionalPointerButton(params.button),
|
|
1768
|
+
optionalNumber(params.clickCount, "clickCount") ?? 1
|
|
1769
|
+
);
|
|
1770
|
+
case "pointer.drag":
|
|
1771
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1772
|
+
return core.manager.drag(
|
|
1773
|
+
requireString(params.sessionId, "sessionId"),
|
|
1774
|
+
requirePointerPoint(params.from, "from"),
|
|
1775
|
+
requirePointerPoint(params.to, "to"),
|
|
1776
|
+
optionalString(params.targetId),
|
|
1777
|
+
optionalNumber(params.steps, "steps")
|
|
1778
|
+
);
|
|
1779
|
+
case "dom.getHtml":
|
|
1780
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1781
|
+
return core.manager.domGetHtml(
|
|
1782
|
+
requireString(params.sessionId, "sessionId"),
|
|
1783
|
+
requireString(params.ref, "ref"),
|
|
1784
|
+
optionalPositiveInteger2(params.maxChars, "maxChars") ?? 8e3,
|
|
1785
|
+
optionalString(params.targetId)
|
|
1786
|
+
);
|
|
1787
|
+
case "dom.getText":
|
|
1788
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1789
|
+
return core.manager.domGetText(
|
|
1790
|
+
requireString(params.sessionId, "sessionId"),
|
|
1791
|
+
requireString(params.ref, "ref"),
|
|
1792
|
+
optionalPositiveInteger2(params.maxChars, "maxChars") ?? 8e3,
|
|
1793
|
+
optionalString(params.targetId)
|
|
1794
|
+
);
|
|
1795
|
+
case "dom.getAttr":
|
|
1796
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1797
|
+
return core.manager.domGetAttr(
|
|
1798
|
+
requireString(params.sessionId, "sessionId"),
|
|
1799
|
+
requireString(params.ref, "ref"),
|
|
1800
|
+
requireString(params.name, "name"),
|
|
1801
|
+
optionalString(params.targetId)
|
|
1802
|
+
);
|
|
1803
|
+
case "dom.getValue":
|
|
1804
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1805
|
+
return core.manager.domGetValue(
|
|
1806
|
+
requireString(params.sessionId, "sessionId"),
|
|
1807
|
+
requireString(params.ref, "ref"),
|
|
1808
|
+
optionalString(params.targetId)
|
|
1809
|
+
);
|
|
1810
|
+
case "dom.isVisible":
|
|
1811
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1812
|
+
return core.manager.domIsVisible(
|
|
1813
|
+
requireString(params.sessionId, "sessionId"),
|
|
1814
|
+
requireString(params.ref, "ref"),
|
|
1815
|
+
optionalString(params.targetId)
|
|
1816
|
+
);
|
|
1817
|
+
case "dom.isEnabled":
|
|
1818
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1819
|
+
return core.manager.domIsEnabled(
|
|
1820
|
+
requireString(params.sessionId, "sessionId"),
|
|
1821
|
+
requireString(params.ref, "ref"),
|
|
1822
|
+
optionalString(params.targetId)
|
|
1823
|
+
);
|
|
1824
|
+
case "dom.isChecked":
|
|
1825
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1826
|
+
return core.manager.domIsChecked(
|
|
1827
|
+
requireString(params.sessionId, "sessionId"),
|
|
1828
|
+
requireString(params.ref, "ref"),
|
|
1829
|
+
optionalString(params.targetId)
|
|
1830
|
+
);
|
|
1831
|
+
case "export.clonePage":
|
|
1832
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1833
|
+
return core.manager.clonePage(
|
|
1834
|
+
requireString(params.sessionId, "sessionId"),
|
|
1835
|
+
optionalString(params.targetId)
|
|
1836
|
+
);
|
|
1837
|
+
case "export.clonePageHtml":
|
|
1838
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1839
|
+
if (typeof core.manager.clonePageHtmlWithOptions !== "function") {
|
|
1840
|
+
throw new Error("clonePageHtmlWithOptions unavailable in this execution lane.");
|
|
1841
|
+
}
|
|
1842
|
+
{
|
|
1843
|
+
const maxNodes = optionalPositiveInteger2(params.maxNodes, "maxNodes");
|
|
1844
|
+
return core.manager.clonePageHtmlWithOptions(
|
|
1845
|
+
requireString(params.sessionId, "sessionId"),
|
|
1846
|
+
optionalString(params.targetId),
|
|
1847
|
+
{
|
|
1848
|
+
...typeof maxNodes === "number" ? { maxNodes } : {},
|
|
1849
|
+
...typeof params.inlineStyles === "boolean" ? { inlineStyles: params.inlineStyles } : {}
|
|
1850
|
+
}
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
case "export.cloneComponent":
|
|
1854
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1855
|
+
return core.manager.cloneComponent(
|
|
1856
|
+
requireString(params.sessionId, "sessionId"),
|
|
1857
|
+
requireString(params.ref, "ref"),
|
|
1858
|
+
optionalString(params.targetId)
|
|
1859
|
+
);
|
|
1860
|
+
case "devtools.perf":
|
|
1861
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1862
|
+
return core.manager.perfMetrics(
|
|
1863
|
+
requireString(params.sessionId, "sessionId"),
|
|
1864
|
+
optionalString(params.targetId)
|
|
1865
|
+
);
|
|
1866
|
+
case "page.screenshot":
|
|
1867
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1868
|
+
return core.manager.screenshot(
|
|
1869
|
+
requireString(params.sessionId, "sessionId"),
|
|
1870
|
+
{
|
|
1871
|
+
...typeof params.path === "string" ? { path: params.path } : {},
|
|
1872
|
+
...typeof params.targetId === "string" ? { targetId: params.targetId } : {},
|
|
1873
|
+
...typeof params.ref === "string" ? { ref: params.ref } : {},
|
|
1874
|
+
...params.fullPage === true ? { fullPage: true } : {}
|
|
1875
|
+
}
|
|
1876
|
+
);
|
|
1877
|
+
case "page.screencast.start":
|
|
1878
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1879
|
+
const screencastOwnerClientId = requireClientId(params);
|
|
1880
|
+
const screencast = await core.manager.startScreencast(
|
|
1881
|
+
requireString(params.sessionId, "sessionId"),
|
|
1882
|
+
{
|
|
1883
|
+
targetId: optionalString(params.targetId),
|
|
1884
|
+
outputDir: optionalString(params.outputDir),
|
|
1885
|
+
intervalMs: optionalNumber(params.intervalMs, "intervalMs") ?? void 0,
|
|
1886
|
+
maxFrames: optionalNumber(params.maxFrames, "maxFrames") ?? void 0
|
|
1887
|
+
}
|
|
1888
|
+
);
|
|
1889
|
+
registerScreencastOwner(screencast.sessionId, screencast.screencastId, screencastOwnerClientId);
|
|
1890
|
+
core.manager.monitorScreencastCompletion?.(screencast.screencastId, () => {
|
|
1891
|
+
completeScreencastOwner(screencast.screencastId);
|
|
1892
|
+
});
|
|
1893
|
+
return screencast;
|
|
1894
|
+
case "page.screencast.stop":
|
|
1895
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1896
|
+
const screencastResult = await core.manager.stopScreencast(
|
|
1897
|
+
requireString(params.sessionId, "sessionId"),
|
|
1898
|
+
requireString(params.screencastId, "screencastId")
|
|
1899
|
+
);
|
|
1900
|
+
completeScreencastOwner(screencastResult.screencastId);
|
|
1901
|
+
return screencastResult;
|
|
1902
|
+
case "page.dialog":
|
|
1903
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1904
|
+
return core.manager.dialog(
|
|
1905
|
+
requireString(params.sessionId, "sessionId"),
|
|
1906
|
+
{
|
|
1907
|
+
...typeof params.targetId === "string" ? { targetId: params.targetId } : {},
|
|
1908
|
+
...typeof params.action === "string" ? { action: requireDialogAction(params.action) } : {},
|
|
1909
|
+
...typeof params.promptText === "string" ? { promptText: params.promptText } : {}
|
|
1910
|
+
}
|
|
1911
|
+
);
|
|
1912
|
+
case "devtools.consolePoll":
|
|
1913
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1914
|
+
return core.manager.consolePoll(
|
|
1915
|
+
requireString(params.sessionId, "sessionId"),
|
|
1916
|
+
optionalNonNegativeInteger(params.sinceSeq, "sinceSeq"),
|
|
1917
|
+
optionalPositiveInteger2(params.max, "max") ?? 50
|
|
1918
|
+
);
|
|
1919
|
+
case "devtools.networkPoll":
|
|
1920
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1921
|
+
return core.manager.networkPoll(
|
|
1922
|
+
requireString(params.sessionId, "sessionId"),
|
|
1923
|
+
optionalNonNegativeInteger(params.sinceSeq, "sinceSeq"),
|
|
1924
|
+
optionalPositiveInteger2(params.max, "max") ?? 50
|
|
1925
|
+
);
|
|
1926
|
+
case "devtools.debugTraceSnapshot": {
|
|
1927
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1928
|
+
const sessionId = requireString(params.sessionId, "sessionId");
|
|
1929
|
+
const manager = core.manager;
|
|
1930
|
+
const max = optionalNumber(params.max, "max") ?? 50;
|
|
1931
|
+
const requestId = optionalString(params.requestId);
|
|
1932
|
+
const sinceConsoleSeq = optionalNumber(params.sinceConsoleSeq, "sinceConsoleSeq");
|
|
1933
|
+
const sinceNetworkSeq = optionalNumber(params.sinceNetworkSeq, "sinceNetworkSeq");
|
|
1934
|
+
const sinceExceptionSeq = optionalNumber(params.sinceExceptionSeq, "sinceExceptionSeq");
|
|
1935
|
+
if (typeof manager.debugTraceSnapshot === "function") {
|
|
1936
|
+
return manager.debugTraceSnapshot(sessionId, {
|
|
1937
|
+
sinceConsoleSeq,
|
|
1938
|
+
sinceNetworkSeq,
|
|
1939
|
+
sinceExceptionSeq,
|
|
1940
|
+
max,
|
|
1941
|
+
requestId
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
const [page, consoleChannel, networkChannel] = await Promise.all([
|
|
1945
|
+
core.manager.status(sessionId),
|
|
1946
|
+
core.manager.consolePoll(sessionId, sinceConsoleSeq, max),
|
|
1947
|
+
core.manager.networkPoll(sessionId, sinceNetworkSeq, max)
|
|
1948
|
+
]);
|
|
1949
|
+
const exceptionChannel = typeof manager.exceptionPoll === "function" ? await manager.exceptionPoll(sessionId, sinceExceptionSeq, max) : { events: [], nextSeq: sinceExceptionSeq ?? 0 };
|
|
1950
|
+
const fallbackResult = {
|
|
1951
|
+
requestId: requestId ?? randomUUID3(),
|
|
1952
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1953
|
+
page,
|
|
1954
|
+
channels: {
|
|
1955
|
+
console: consoleChannel,
|
|
1956
|
+
network: networkChannel,
|
|
1957
|
+
exception: exceptionChannel
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
return attachBlockerMetaForTrace(core, fallbackResult);
|
|
1961
|
+
}
|
|
1962
|
+
case "session.cookieImport": {
|
|
1963
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
1964
|
+
const sessionId = requireString(params.sessionId, "sessionId");
|
|
1965
|
+
const manager = core.manager;
|
|
1966
|
+
const cookies = requireCookieArray(params.cookies, "cookies");
|
|
1967
|
+
const strict = optionalBoolean2(params.strict) ?? true;
|
|
1968
|
+
const requestId = optionalString(params.requestId) ?? randomUUID3();
|
|
1969
|
+
if (typeof manager.cookieImport === "function") {
|
|
1970
|
+
return manager.cookieImport(sessionId, cookies, strict, requestId);
|
|
1971
|
+
}
|
|
1972
|
+
const normalized = [];
|
|
1973
|
+
const rejected = [];
|
|
1974
|
+
cookies.forEach((cookie, index) => {
|
|
1975
|
+
const validation = validateCookieRecord(cookie);
|
|
1976
|
+
if (!validation.valid) {
|
|
1977
|
+
rejected.push({ index, reason: validation.reason });
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
normalized.push(validation.cookie);
|
|
1981
|
+
});
|
|
1982
|
+
if (strict && rejected.length > 0) {
|
|
1983
|
+
throw new Error(`Cookie import rejected ${rejected.length} entries.`);
|
|
1984
|
+
}
|
|
1985
|
+
if (normalized.length > 0) {
|
|
1986
|
+
const targetId = optionalString(params.targetId);
|
|
1987
|
+
await core.manager.withPage(sessionId, targetId ?? null, async (page) => {
|
|
1988
|
+
await page.context().addCookies(normalized);
|
|
1989
|
+
return void 0;
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
return {
|
|
1993
|
+
requestId,
|
|
1994
|
+
imported: normalized.length,
|
|
1995
|
+
rejected
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
case "session.cookieList": {
|
|
1999
|
+
await authorizeSessionCommand(core, params, request.name, bindingId);
|
|
2000
|
+
const sessionId = requireString(params.sessionId, "sessionId");
|
|
2001
|
+
const manager = core.manager;
|
|
2002
|
+
const urls = requireOptionalCookieUrlArray(params.urls, "urls");
|
|
2003
|
+
const requestId = optionalString(params.requestId) ?? randomUUID3();
|
|
2004
|
+
if (typeof manager.cookieList === "function") {
|
|
2005
|
+
return manager.cookieList(sessionId, urls, requestId);
|
|
2006
|
+
}
|
|
2007
|
+
const targetId = optionalString(params.targetId);
|
|
2008
|
+
const cookies = await core.manager.withPage(
|
|
2009
|
+
sessionId,
|
|
2010
|
+
targetId ?? null,
|
|
2011
|
+
async (page) => {
|
|
2012
|
+
const listed = urls ? await page.context().cookies(urls) : await page.context().cookies();
|
|
2013
|
+
return listed.map((cookie) => ({
|
|
2014
|
+
name: cookie.name,
|
|
2015
|
+
value: cookie.value,
|
|
2016
|
+
domain: cookie.domain,
|
|
2017
|
+
path: cookie.path,
|
|
2018
|
+
expires: cookie.expires,
|
|
2019
|
+
httpOnly: cookie.httpOnly,
|
|
2020
|
+
secure: cookie.secure,
|
|
2021
|
+
...cookie.sameSite ? { sameSite: cookie.sameSite } : {}
|
|
2022
|
+
}));
|
|
2023
|
+
}
|
|
2024
|
+
);
|
|
2025
|
+
return {
|
|
2026
|
+
requestId,
|
|
2027
|
+
cookies,
|
|
2028
|
+
count: cookies.length
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
case "macro.resolve": {
|
|
2032
|
+
const execute = optionalBoolean2(params.execute) ?? false;
|
|
2033
|
+
const browserMode = optionalWorkflowBrowserMode(params.browserMode);
|
|
2034
|
+
const useCookies = optionalBoolean2(params.useCookies);
|
|
2035
|
+
const challengeAutomationMode = optionalChallengeAutomationMode(params.challengeAutomationMode);
|
|
2036
|
+
const cookiePolicyOverride = optionalCookiePolicy(params.cookiePolicyOverride);
|
|
2037
|
+
if (!execute && browserMode) {
|
|
2038
|
+
throw new Error("browserMode requires execute=true for macro resolution");
|
|
2039
|
+
}
|
|
2040
|
+
if (!execute && typeof useCookies === "boolean") {
|
|
2041
|
+
throw new Error("useCookies requires execute=true for macro resolution");
|
|
2042
|
+
}
|
|
2043
|
+
if (!execute && challengeAutomationMode) {
|
|
2044
|
+
throw new Error("challengeAutomationMode requires execute=true for macro resolution");
|
|
2045
|
+
}
|
|
2046
|
+
if (!execute && cookiePolicyOverride) {
|
|
2047
|
+
throw new Error("cookiePolicyOverride requires execute=true for macro resolution");
|
|
2048
|
+
}
|
|
2049
|
+
return resolveMacroExpression(
|
|
2050
|
+
{
|
|
2051
|
+
expression: requireString(params.expression, "expression"),
|
|
2052
|
+
defaultProvider: optionalString(params.defaultProvider),
|
|
2053
|
+
includeCatalog: optionalBoolean2(params.includeCatalog) ?? false,
|
|
2054
|
+
execute,
|
|
2055
|
+
timeoutMs: optionalNumber(params.timeoutMs, "timeoutMs"),
|
|
2056
|
+
browserMode,
|
|
2057
|
+
useCookies,
|
|
2058
|
+
challengeAutomationMode,
|
|
2059
|
+
cookiePolicyOverride
|
|
2060
|
+
},
|
|
2061
|
+
core.config,
|
|
2062
|
+
core.manager,
|
|
2063
|
+
core.browserFallbackPort,
|
|
2064
|
+
core.providerRuntime
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
case "research.run":
|
|
2068
|
+
return runResearchWorkflow(
|
|
2069
|
+
createDaemonWorkflowRuntime(core),
|
|
2070
|
+
{
|
|
2071
|
+
topic: requireString(params.topic, "topic"),
|
|
2072
|
+
days: optionalNumber(params.days, "days"),
|
|
2073
|
+
from: optionalString(params.from),
|
|
2074
|
+
to: optionalString(params.to),
|
|
2075
|
+
sourceSelection: optionalProviderSelection(params.sourceSelection),
|
|
2076
|
+
sources: optionalProviderSources(params.sources),
|
|
2077
|
+
mode: optionalRenderMode(params.mode) ?? "compact",
|
|
2078
|
+
includeEngagement: optionalBoolean2(params.includeEngagement),
|
|
2079
|
+
limitPerSource: optionalNumber(params.limitPerSource, "limitPerSource"),
|
|
2080
|
+
timeoutMs: optionalNumber(params.timeoutMs, "timeoutMs"),
|
|
2081
|
+
outputDir: optionalString(params.outputDir),
|
|
2082
|
+
ttlHours: optionalNumber(params.ttlHours, "ttlHours"),
|
|
2083
|
+
browserMode: optionalWorkflowBrowserMode(params.browserMode),
|
|
2084
|
+
useCookies: optionalBoolean2(params.useCookies),
|
|
2085
|
+
challengeAutomationMode: optionalChallengeAutomationMode(params.challengeAutomationMode),
|
|
2086
|
+
cookiePolicyOverride: optionalCookiePolicy(params.cookiePolicyOverride)
|
|
2087
|
+
}
|
|
2088
|
+
);
|
|
2089
|
+
case "shopping.run":
|
|
2090
|
+
return runShoppingWorkflow(
|
|
2091
|
+
createDaemonWorkflowRuntime(core),
|
|
2092
|
+
{
|
|
2093
|
+
query: requireString(params.query, "query"),
|
|
2094
|
+
providers: optionalStringArray2(params.providers),
|
|
2095
|
+
budget: optionalNumber(params.budget, "budget"),
|
|
2096
|
+
region: optionalString(params.region),
|
|
2097
|
+
browserMode: optionalWorkflowBrowserMode(params.browserMode),
|
|
2098
|
+
sort: optionalShoppingSort(params.sort),
|
|
2099
|
+
mode: optionalRenderMode(params.mode) ?? "compact",
|
|
2100
|
+
timeoutMs: optionalNumber(params.timeoutMs, "timeoutMs"),
|
|
2101
|
+
outputDir: optionalString(params.outputDir),
|
|
2102
|
+
ttlHours: optionalNumber(params.ttlHours, "ttlHours"),
|
|
2103
|
+
useCookies: optionalBoolean2(params.useCookies),
|
|
2104
|
+
challengeAutomationMode: optionalChallengeAutomationMode(params.challengeAutomationMode),
|
|
2105
|
+
cookiePolicyOverride: optionalCookiePolicy(params.cookiePolicyOverride)
|
|
2106
|
+
}
|
|
2107
|
+
);
|
|
2108
|
+
case "inspiredesign.run": {
|
|
2109
|
+
const inspiredesignTimeoutMs = optionalNumber(params.timeoutMs, "timeoutMs");
|
|
2110
|
+
return runInspiredesignWorkflow(
|
|
2111
|
+
createDaemonWorkflowRuntime(core),
|
|
2112
|
+
{
|
|
2113
|
+
brief: requireString(params.brief, "brief"),
|
|
2114
|
+
urls: optionalStringArray2(params.urls),
|
|
2115
|
+
captureMode: optionalInspiredesignCaptureMode(params.captureMode),
|
|
2116
|
+
includePrototypeGuidance: optionalBoolean2(params.includePrototypeGuidance),
|
|
2117
|
+
mode: optionalRenderMode(params.mode) ?? "compact",
|
|
2118
|
+
timeoutMs: inspiredesignTimeoutMs,
|
|
2119
|
+
outputDir: optionalString(params.outputDir),
|
|
2120
|
+
ttlHours: optionalNumber(params.ttlHours, "ttlHours"),
|
|
2121
|
+
browserMode: optionalWorkflowBrowserMode(params.browserMode),
|
|
2122
|
+
useCookies: optionalBoolean2(params.useCookies),
|
|
2123
|
+
challengeAutomationMode: optionalChallengeAutomationMode(params.challengeAutomationMode),
|
|
2124
|
+
cookiePolicyOverride: optionalCookiePolicy(params.cookiePolicyOverride)
|
|
2125
|
+
},
|
|
2126
|
+
{
|
|
2127
|
+
captureReference: async (url, options) => captureInspiredesignReferenceFromManager(core.manager, url, {
|
|
2128
|
+
...options,
|
|
2129
|
+
cookieSource: core.config.providers?.cookieSource
|
|
2130
|
+
})
|
|
2131
|
+
}
|
|
2132
|
+
);
|
|
2133
|
+
}
|
|
2134
|
+
case "product.video.run": {
|
|
2135
|
+
const productVideoTimeoutMs = optionalNumber(params.timeoutMs, "timeoutMs");
|
|
2136
|
+
return runProductVideoWorkflow(
|
|
2137
|
+
createDaemonWorkflowRuntime(core),
|
|
2138
|
+
{
|
|
2139
|
+
product_url: optionalString(params.product_url),
|
|
2140
|
+
product_name: optionalString(params.product_name),
|
|
2141
|
+
provider_hint: optionalString(params.provider_hint),
|
|
2142
|
+
include_screenshots: optionalBoolean2(params.include_screenshots),
|
|
2143
|
+
include_all_images: optionalBoolean2(params.include_all_images),
|
|
2144
|
+
include_copy: optionalBoolean2(params.include_copy),
|
|
2145
|
+
output_dir: optionalString(params.output_dir),
|
|
2146
|
+
ttl_hours: optionalNumber(params.ttl_hours, "ttl_hours"),
|
|
2147
|
+
timeoutMs: productVideoTimeoutMs,
|
|
2148
|
+
browserMode: optionalWorkflowBrowserMode(params.browserMode),
|
|
2149
|
+
useCookies: optionalBoolean2(params.useCookies),
|
|
2150
|
+
challengeAutomationMode: optionalChallengeAutomationMode(params.challengeAutomationMode),
|
|
2151
|
+
cookiePolicyOverride: optionalCookiePolicy(params.cookiePolicyOverride)
|
|
2152
|
+
},
|
|
2153
|
+
{
|
|
2154
|
+
captureScreenshot: async (url, timeoutMs) => {
|
|
2155
|
+
const captureTimeoutMs = Math.max(1, Math.min(timeoutMs ?? 3e4, 3e4));
|
|
2156
|
+
const session = await core.manager.launch({
|
|
2157
|
+
headless: true,
|
|
2158
|
+
startUrl: "about:blank",
|
|
2159
|
+
// Capture sessions are ephemeral; avoid persisted profile lock contention.
|
|
2160
|
+
persistProfile: false
|
|
2161
|
+
});
|
|
2162
|
+
try {
|
|
2163
|
+
await core.manager.goto(session.sessionId, url, "load", captureTimeoutMs);
|
|
2164
|
+
const screenshot = await Promise.race([
|
|
2165
|
+
core.manager.screenshot(session.sessionId),
|
|
2166
|
+
new Promise((resolve2) => {
|
|
2167
|
+
setTimeout(() => resolve2(null), captureTimeoutMs);
|
|
2168
|
+
})
|
|
2169
|
+
]);
|
|
2170
|
+
if (!screenshot || typeof screenshot.base64 !== "string" || screenshot.base64.length === 0) return null;
|
|
2171
|
+
return Buffer.from(screenshot.base64, "base64");
|
|
2172
|
+
} catch {
|
|
2173
|
+
return null;
|
|
2174
|
+
} finally {
|
|
2175
|
+
await core.manager.disconnect(session.sessionId, true).catch(() => {
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
);
|
|
2181
|
+
}
|
|
2182
|
+
default:
|
|
2183
|
+
throw new Error(`Unknown daemon command: ${request.name}`);
|
|
2184
|
+
}
|
|
2185
|
+
})();
|
|
2186
|
+
} catch (error) {
|
|
2187
|
+
throw coerceDaemonSessionError(params, error);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
async function launchWithRelay(core, params, clientId, bindingId) {
|
|
2191
|
+
let relayStatus = core.relay.status();
|
|
2192
|
+
const extensionLegacy = optionalBoolean2(params.extensionLegacy) ?? false;
|
|
2193
|
+
let relayUrl = extensionLegacy ? core.relay.getCdpUrl() : core.relay.getOpsUrl?.() ?? null;
|
|
2194
|
+
const relayPort = core.config.relayPort;
|
|
2195
|
+
const noExtension = optionalBoolean2(params.noExtension) ?? false;
|
|
2196
|
+
const extensionOnly = optionalBoolean2(params.extensionOnly) ?? false;
|
|
2197
|
+
const waitForExtension = optionalBoolean2(params.waitForExtension) ?? false;
|
|
2198
|
+
const headlessExplicit = optionalBoolean2(params.headless) === true;
|
|
2199
|
+
if (headlessExplicit && !noExtension) {
|
|
2200
|
+
throw unsupportedModeError(
|
|
2201
|
+
"Extension mode does not support headless launches. Use --no-extension --headless for managed mode."
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
const managedExplicit = Boolean(noExtension || headlessExplicit);
|
|
2205
|
+
const managedHeadless = headlessExplicit ? true : false;
|
|
2206
|
+
const waitTimeoutMs = clampWaitTimeout(optionalNumber(params.waitTimeoutMs, "waitTimeoutMs") ?? 3e4);
|
|
2207
|
+
if (!managedExplicit && extensionLegacy) {
|
|
2208
|
+
requireBinding(clientId, bindingId);
|
|
2209
|
+
}
|
|
2210
|
+
if (waitForExtension && !managedExplicit) {
|
|
2211
|
+
const observedPort2 = resolveObservedPort(relayStatus, relayPort);
|
|
2212
|
+
const connected = await waitForRelayHandshake(core.relay, observedPort2, waitTimeoutMs);
|
|
2213
|
+
if (connected) {
|
|
2214
|
+
relayStatus = core.relay.status();
|
|
2215
|
+
relayUrl = extensionLegacy ? core.relay.getCdpUrl() ?? relayUrl : core.relay.getOpsUrl?.() ?? relayUrl;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
const observedPort = resolveObservedPort(relayStatus, relayPort);
|
|
2219
|
+
const shouldFetchObserved = !managedExplicit && (!relayUrl || !relayStatus.extensionHandshakeComplete);
|
|
2220
|
+
const observedStatus = shouldFetchObserved ? await fetchRelayObservedStatus(observedPort) : null;
|
|
2221
|
+
const matchingObservedStatus = getMatchingObservedRelayStatus(relayStatus, observedStatus);
|
|
2222
|
+
if (!relayUrl) {
|
|
2223
|
+
const fallbackPort = isValidPort(observedStatus?.port) ? observedStatus?.port : observedPort;
|
|
2224
|
+
relayUrl = fallbackPort ? buildLoopbackSessionRelayEndpoint(fallbackPort, { extensionLegacy }) : null;
|
|
2225
|
+
}
|
|
2226
|
+
const extensionReady = Boolean(
|
|
2227
|
+
relayUrl && (relayStatus.extensionHandshakeComplete || matchingObservedStatus?.extensionHandshakeComplete)
|
|
2228
|
+
);
|
|
2229
|
+
const extensionSocketConnected = Boolean(
|
|
2230
|
+
relayStatus.extensionConnected || matchingObservedStatus?.extensionConnected
|
|
2231
|
+
);
|
|
2232
|
+
const observedInstanceMismatch = Boolean(
|
|
2233
|
+
observedStatus && observedStatus.instanceId !== relayStatus.instanceId && (observedStatus.extensionConnected || observedStatus.extensionHandshakeComplete)
|
|
2234
|
+
);
|
|
2235
|
+
const handshakePending = Boolean(relayUrl && extensionSocketConnected && !extensionReady);
|
|
2236
|
+
const diagnostics = observedStatus ? `Diagnostics: relayPort=${observedPort ?? "?"} instance=${observedStatus.instanceId.slice(0, 8)} ext=${observedStatus.extensionConnected} handshake=${observedStatus.extensionHandshakeComplete} ops=${observedStatus.opsConnected} cdp=${observedStatus.cdpConnected}` : null;
|
|
2237
|
+
const missingReason = observedInstanceMismatch ? diagnostics ? `Extension not connected to the expected relay instance. ${diagnostics}` : "Extension not connected to the expected relay instance." : handshakePending ? diagnostics ? `Extension websocket connected but handshake incomplete. Re-establish a clean daemon-extension handshake. ${diagnostics}` : "Extension websocket connected but handshake incomplete. Re-establish a clean daemon-extension handshake." : diagnostics ? `Extension not connected. ${diagnostics}` : "Extension not connected.";
|
|
2238
|
+
if (extensionOnly && !extensionReady) {
|
|
2239
|
+
throw new Error(buildExtensionMissingMessage(missingReason));
|
|
2240
|
+
}
|
|
2241
|
+
if (!managedExplicit) {
|
|
2242
|
+
if (!extensionReady || !relayUrl) {
|
|
2243
|
+
throw new Error(buildExtensionMissingMessage(missingReason));
|
|
2244
|
+
}
|
|
2245
|
+
try {
|
|
2246
|
+
const startUrl = optionalString(params.startUrl);
|
|
2247
|
+
const result = startUrl ? await core.manager.connectRelay(relayUrl, { startUrl }) : await core.manager.connectRelay(relayUrl);
|
|
2248
|
+
const leaseId = extractLeaseId(result);
|
|
2249
|
+
if (result.mode === "extension" && !extensionLegacy && !leaseId) {
|
|
2250
|
+
throw new Error("[invalid_session] Extension relay session missing leaseId.");
|
|
2251
|
+
}
|
|
2252
|
+
if (result.mode === "extension" && leaseId) {
|
|
2253
|
+
registerSessionLease(result.sessionId, leaseId, clientId);
|
|
2254
|
+
}
|
|
2255
|
+
return { ...result, warnings: result.warnings ?? [], ...leaseId ? { leaseId } : {} };
|
|
2256
|
+
} catch (error) {
|
|
2257
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2258
|
+
const unauthorized = message.toLowerCase().includes("unauthorized") || message.includes("401");
|
|
2259
|
+
const relayLabel = extensionLegacy ? "/cdp" : "/ops";
|
|
2260
|
+
const reason = unauthorized ? `Extension relay connection failed: relay ${relayLabel} unauthorized (token mismatch).` : `Extension relay connection failed: ${message}`;
|
|
2261
|
+
throw new Error(buildExtensionMissingMessage(reason));
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
try {
|
|
2265
|
+
const result = await core.manager.launch({
|
|
2266
|
+
profile: optionalString(params.profile),
|
|
2267
|
+
headless: managedHeadless,
|
|
2268
|
+
startUrl: optionalString(params.startUrl),
|
|
2269
|
+
chromePath: optionalString(params.chromePath),
|
|
2270
|
+
flags: optionalStringArray2(params.flags),
|
|
2271
|
+
persistProfile: optionalBoolean2(params.persistProfile)
|
|
2272
|
+
});
|
|
2273
|
+
return { ...result, warnings: result.warnings ?? [] };
|
|
2274
|
+
} catch (error) {
|
|
2275
|
+
throw new Error(buildManagedFailureMessage(error));
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
async function connectWithRelayRouting(core, params, clientId, bindingId) {
|
|
2279
|
+
const wsEndpoint = optionalString(params.wsEndpoint);
|
|
2280
|
+
const extensionLegacy = optionalBoolean2(params.extensionLegacy) ?? false;
|
|
2281
|
+
const relayUrl = extensionLegacy ? core.relay.getCdpUrl() : core.relay.getOpsUrl?.() ?? null;
|
|
2282
|
+
const parsedRelayEndpoint = classifySessionRelayEndpoint(wsEndpoint);
|
|
2283
|
+
const resolvedRelayEndpoint = parsedRelayEndpoint ? resolveSessionRelayRoute(parsedRelayEndpoint, { extensionLegacy }) : null;
|
|
2284
|
+
if (resolvedRelayEndpoint && "code" in resolvedRelayEndpoint) {
|
|
2285
|
+
throw new Error("Legacy extension relay (/cdp) requires --extension-legacy.");
|
|
2286
|
+
}
|
|
2287
|
+
const relayEndpoint = relayUrl && wsEndpoint === relayUrl ? relayUrl : resolvedRelayEndpoint?.normalizedEndpoint ?? null;
|
|
2288
|
+
const hasExplicitCdp = Boolean(wsEndpoint || params.host || params.port);
|
|
2289
|
+
const headlessExplicit = optionalBoolean2(params.headless) === true;
|
|
2290
|
+
if (headlessExplicit && !hasExplicitCdp) {
|
|
2291
|
+
throw unsupportedModeError(
|
|
2292
|
+
"Extension mode does not support headless connect routing. Use launch --no-extension --headless or connect to an explicit CDP endpoint."
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
if (relayEndpoint || !hasExplicitCdp && relayUrl) {
|
|
2296
|
+
if (headlessExplicit) {
|
|
2297
|
+
throw unsupportedModeError(
|
|
2298
|
+
"Extension mode does not support headless connect routing. Use launch --no-extension --headless or connect to an explicit CDP endpoint."
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
if (extensionLegacy) {
|
|
2302
|
+
requireBinding(clientId, bindingId);
|
|
2303
|
+
}
|
|
2304
|
+
const startUrl = optionalString(params.startUrl);
|
|
2305
|
+
const result = startUrl ? await core.manager.connectRelay(relayEndpoint ?? relayUrl ?? "", { startUrl }) : await core.manager.connectRelay(relayEndpoint ?? relayUrl ?? "");
|
|
2306
|
+
const leaseId = extractLeaseId(result);
|
|
2307
|
+
if (result.mode === "extension" && !extensionLegacy && !leaseId) {
|
|
2308
|
+
throw new Error("[invalid_session] Extension relay session missing leaseId.");
|
|
2309
|
+
}
|
|
2310
|
+
if (result.mode === "extension" && leaseId) {
|
|
2311
|
+
registerSessionLease(result.sessionId, leaseId, clientId);
|
|
2312
|
+
}
|
|
2313
|
+
return { ...result, ...leaseId ? { leaseId } : {} };
|
|
2314
|
+
}
|
|
2315
|
+
if (!hasExplicitCdp) {
|
|
2316
|
+
throw new Error("Extension relay not available. Connect the extension or pass --cdp-port/--ws-endpoint.");
|
|
2317
|
+
}
|
|
2318
|
+
return core.manager.connect({
|
|
2319
|
+
wsEndpoint,
|
|
2320
|
+
host: optionalString(params.host),
|
|
2321
|
+
port: optionalNumber(params.port, "port"),
|
|
2322
|
+
startUrl: optionalString(params.startUrl)
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
async function disconnectSession(core, params, clientId, bindingId) {
|
|
2326
|
+
const sessionId = requireString(params.sessionId, "sessionId");
|
|
2327
|
+
let status = null;
|
|
2328
|
+
try {
|
|
2329
|
+
status = await core.manager.status(sessionId);
|
|
2330
|
+
} catch (error) {
|
|
2331
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2332
|
+
if (isIgnorableDisconnectStatusError(message)) {
|
|
2333
|
+
const lease = getSessionLease(sessionId);
|
|
2334
|
+
if (lease) {
|
|
2335
|
+
requireSessionLease(sessionId, clientId, optionalString(params.leaseId));
|
|
2336
|
+
} else if (bindingId) {
|
|
2337
|
+
requireBinding(clientId, bindingId);
|
|
2338
|
+
}
|
|
2339
|
+
releaseSessionLease(sessionId);
|
|
2340
|
+
if (bindingId) {
|
|
2341
|
+
releaseRelay(clientId, bindingId);
|
|
2342
|
+
return { ok: true, bindingReleased: true };
|
|
2343
|
+
}
|
|
2344
|
+
return { ok: true };
|
|
2345
|
+
}
|
|
2346
|
+
throw error;
|
|
2347
|
+
}
|
|
2348
|
+
if (status.mode === "extension") {
|
|
2349
|
+
const lease = getSessionLease(sessionId);
|
|
2350
|
+
if (lease) {
|
|
2351
|
+
requireSessionLease(sessionId, clientId, optionalString(params.leaseId));
|
|
2352
|
+
} else {
|
|
2353
|
+
requireBinding(clientId, bindingId);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
await core.manager.disconnect(sessionId, optionalBoolean2(params.closeBrowser) ?? false);
|
|
2357
|
+
releaseSessionLease(sessionId);
|
|
2358
|
+
if (status.mode === "extension" && bindingId) {
|
|
2359
|
+
releaseRelay(clientId, bindingId);
|
|
2360
|
+
return { ok: true, bindingReleased: true };
|
|
2361
|
+
}
|
|
2362
|
+
return { ok: true };
|
|
2363
|
+
}
|
|
2364
|
+
function requireSessionInspectorHandle(core) {
|
|
2365
|
+
const inspector = core.manager.createSessionInspector?.();
|
|
2366
|
+
if (!inspector) {
|
|
2367
|
+
throw new Error("Session inspector is unavailable for the current runtime.");
|
|
2368
|
+
}
|
|
2369
|
+
return inspector;
|
|
2370
|
+
}
|
|
2371
|
+
function readChallengeAutomationMode(params) {
|
|
2372
|
+
return optionalChallengeAutomationMode(params.challengeAutomationMode);
|
|
2373
|
+
}
|
|
2374
|
+
async function runReviewDesktop(core, params) {
|
|
2375
|
+
return core.automationCoordinator.reviewDesktop({
|
|
2376
|
+
browserSessionId: requireString(params.sessionId, "sessionId"),
|
|
2377
|
+
targetId: optionalString(params.targetId),
|
|
2378
|
+
reason: optionalString(params.reason),
|
|
2379
|
+
maxChars: optionalNumber(params.maxChars, "maxChars"),
|
|
2380
|
+
cursor: optionalString(params.cursor)
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
async function runInspectChallengePlan(core, params) {
|
|
2384
|
+
return core.automationCoordinator.inspectChallengePlan({
|
|
2385
|
+
browserSessionId: requireString(params.sessionId, "sessionId"),
|
|
2386
|
+
targetId: optionalString(params.targetId),
|
|
2387
|
+
runMode: readChallengeAutomationMode(params)
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
async function runInspectAudit(core, params) {
|
|
2391
|
+
const browserSessionId = requireString(params.sessionId, "sessionId");
|
|
2392
|
+
const targetId = optionalString(params.targetId);
|
|
2393
|
+
const review = await runReviewDesktop(core, params);
|
|
2394
|
+
const challengePlan = await core.automationCoordinator.inspectChallengePlan({
|
|
2395
|
+
browserSessionId,
|
|
2396
|
+
targetId,
|
|
2397
|
+
runMode: readChallengeAutomationMode(params)
|
|
2398
|
+
});
|
|
2399
|
+
return buildCorrelatedAuditBundle({
|
|
2400
|
+
handle: requireSessionInspectorHandle(core),
|
|
2401
|
+
browserSessionId,
|
|
2402
|
+
targetId,
|
|
2403
|
+
observation: review.observation,
|
|
2404
|
+
review: review.verification,
|
|
2405
|
+
challengePlan,
|
|
2406
|
+
includeUrls: optionalBoolean2(params.includeUrls) ?? void 0,
|
|
2407
|
+
sinceConsoleSeq: optionalNumber(params.sinceConsoleSeq, "sinceConsoleSeq"),
|
|
2408
|
+
sinceNetworkSeq: optionalNumber(params.sinceNetworkSeq, "sinceNetworkSeq"),
|
|
2409
|
+
sinceExceptionSeq: optionalNumber(params.sinceExceptionSeq, "sinceExceptionSeq"),
|
|
2410
|
+
max: optionalNumber(params.max, "max"),
|
|
2411
|
+
requestId: optionalString(params.requestId),
|
|
2412
|
+
relayStatus: core.relay.status()
|
|
2413
|
+
});
|
|
2414
|
+
}
|
|
2415
|
+
async function runStatusCapabilities(core, params) {
|
|
2416
|
+
return core.automationCoordinator.statusCapabilities({
|
|
2417
|
+
browserSessionId: optionalString(params.sessionId),
|
|
2418
|
+
targetId: optionalString(params.targetId),
|
|
2419
|
+
runMode: readChallengeAutomationMode(params)
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
async function authorizeSessionCommand(core, params, commandName, bindingId) {
|
|
2423
|
+
const sessionId = optionalString(params.sessionId);
|
|
2424
|
+
if (!sessionId) return;
|
|
2425
|
+
const clientId = requireClientId(params);
|
|
2426
|
+
const lease = getSessionLease(sessionId);
|
|
2427
|
+
if (lease) {
|
|
2428
|
+
requireSessionLease(sessionId, clientId, optionalString(params.leaseId));
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
let status;
|
|
2432
|
+
try {
|
|
2433
|
+
status = await core.manager.status(sessionId);
|
|
2434
|
+
} catch (error) {
|
|
2435
|
+
if (canStopCompletedScreencastWithoutLiveSession(commandName, error)) {
|
|
2436
|
+
requireScreencastOwner(
|
|
2437
|
+
sessionId,
|
|
2438
|
+
requireString(params.screencastId, "screencastId"),
|
|
2439
|
+
clientId
|
|
2440
|
+
);
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
throw error;
|
|
2444
|
+
}
|
|
2445
|
+
if (status.mode !== "extension") {
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
requireBinding(clientId, bindingId);
|
|
2449
|
+
}
|
|
2450
|
+
function extractLeaseId(result) {
|
|
2451
|
+
if (!result || typeof result !== "object") return void 0;
|
|
2452
|
+
const leaseId = result.leaseId;
|
|
2453
|
+
return typeof leaseId === "string" ? leaseId : void 0;
|
|
2454
|
+
}
|
|
2455
|
+
function buildExtensionMissingMessage(reason) {
|
|
2456
|
+
return [
|
|
2457
|
+
reason,
|
|
2458
|
+
"Connect the extension: open the Chrome extension popup and click Connect. If ext=on but handshake=off, click Connect again to re-establish a clean daemon-extension handshake, then retry.",
|
|
2459
|
+
"Tip: If the popup says Connected, it may be connected to a different relay instance/port than the daemon expects.",
|
|
2460
|
+
"Legend: ext=extension websocket, handshake=extension handshake, ops=active /ops client, cdp=active /cdp client, pairing=token required.",
|
|
2461
|
+
"",
|
|
2462
|
+
"Other options (explicit):",
|
|
2463
|
+
"- Managed (headed): npx opendevbrowser launch --no-extension",
|
|
2464
|
+
"- Managed (headless): npx opendevbrowser launch --no-extension --headless",
|
|
2465
|
+
"- Legacy extension relay: npx opendevbrowser launch --extension-legacy",
|
|
2466
|
+
"- CDPConnect (default port): npx opendevbrowser connect --cdp-port 9222",
|
|
2467
|
+
"- CDPConnect (explicit WS): npx opendevbrowser connect --ws-endpoint ws://127.0.0.1:9222/devtools/browser/<id>",
|
|
2468
|
+
"Note: CDPConnect requires Chrome started with --remote-debugging-port=9222."
|
|
2469
|
+
].join("\n");
|
|
2470
|
+
}
|
|
2471
|
+
function buildManagedFailureMessage(error) {
|
|
2472
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2473
|
+
const normalized = detail.toLowerCase();
|
|
2474
|
+
const profileLocked = normalized.includes("singletonlock") || normalized.includes("processsingleton") || normalized.includes("profile in use") || normalized.includes("already in use") || normalized.includes("user data directory is already in use") || normalized.includes("profile is locked");
|
|
2475
|
+
if (profileLocked) {
|
|
2476
|
+
return [
|
|
2477
|
+
`Managed session failed: ${detail}`,
|
|
2478
|
+
"",
|
|
2479
|
+
"Detected persisted profile lock (another Chrome process is using the same profile).",
|
|
2480
|
+
"Retry options (explicit):",
|
|
2481
|
+
"- Managed with a unique profile: npx opendevbrowser launch --no-extension --profile lock-safe-<timestamp>",
|
|
2482
|
+
"- Managed with a temporary profile: npx opendevbrowser launch --no-extension --persist-profile false",
|
|
2483
|
+
"- CDPConnect (default port): npx opendevbrowser connect --cdp-port 9222",
|
|
2484
|
+
"- CDPConnect (explicit WS): npx opendevbrowser connect --ws-endpoint ws://127.0.0.1:9222/devtools/browser/<id>"
|
|
2485
|
+
].join("\n");
|
|
2486
|
+
}
|
|
2487
|
+
return [
|
|
2488
|
+
`Managed session failed: ${detail}`,
|
|
2489
|
+
"",
|
|
2490
|
+
"Final option (explicit):",
|
|
2491
|
+
"- CDPConnect (default port): npx opendevbrowser connect --cdp-port 9222",
|
|
2492
|
+
"- CDPConnect (explicit WS): npx opendevbrowser connect --ws-endpoint ws://127.0.0.1:9222/devtools/browser/<id>"
|
|
2493
|
+
].join("\n");
|
|
2494
|
+
}
|
|
2495
|
+
function unsupportedModeError(message) {
|
|
2496
|
+
return new Error(`[unsupported_mode] ${message}`);
|
|
2497
|
+
}
|
|
2498
|
+
function coerceDaemonSessionError(params, error) {
|
|
2499
|
+
const sessionId = optionalString(params.sessionId);
|
|
2500
|
+
const clientId = optionalString(params.clientId);
|
|
2501
|
+
const baseError = error instanceof Error ? error : new Error(String(error ?? ""));
|
|
2502
|
+
if (!sessionId || !clientId) {
|
|
2503
|
+
return baseError;
|
|
2504
|
+
}
|
|
2505
|
+
if (!isStaleExtensionSessionError(baseError.message)) {
|
|
2506
|
+
return baseError;
|
|
2507
|
+
}
|
|
2508
|
+
const released = releaseOwnedSessionLease(sessionId, clientId, optionalString(params.leaseId));
|
|
2509
|
+
if (!released) {
|
|
2510
|
+
return baseError;
|
|
2511
|
+
}
|
|
2512
|
+
return new Error([
|
|
2513
|
+
`[relaunch_required] Extension session ${sessionId} is no longer valid.`,
|
|
2514
|
+
"Relaunch the extension-backed session and retry the command.",
|
|
2515
|
+
`Previous error: ${baseError.message}`
|
|
2516
|
+
].join(" "));
|
|
2517
|
+
}
|
|
2518
|
+
function isIgnorableDisconnectStatusError(message) {
|
|
2519
|
+
return message.includes("[invalid_session]") || message.includes("Unknown ops session") || message.includes("Ops client not connected");
|
|
2520
|
+
}
|
|
2521
|
+
function canStopCompletedScreencastWithoutLiveSession(commandName, error) {
|
|
2522
|
+
if (commandName !== "page.screencast.stop") {
|
|
2523
|
+
return false;
|
|
2524
|
+
}
|
|
2525
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2526
|
+
return isIgnorableDisconnectStatusError(message);
|
|
2527
|
+
}
|
|
2528
|
+
function isStaleExtensionSessionError(message) {
|
|
2529
|
+
return message.includes("[invalid_session]") || message.includes("Unknown sessionId:") || message.includes("Unknown ops session") || message.includes("[not_owner]") || message.includes("Client does not own session") || message.includes("Lease does not match session owner");
|
|
2530
|
+
}
|
|
2531
|
+
async function attachBlockerMetaForNavigation(core, sessionId, result) {
|
|
2532
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
2533
|
+
return result;
|
|
2534
|
+
}
|
|
2535
|
+
const record = result;
|
|
2536
|
+
const existingMeta = !Array.isArray(record.meta) && typeof record.meta === "object" && record.meta !== null ? record.meta : void 0;
|
|
2537
|
+
if (existingMeta && typeof existingMeta.blockerState === "string") {
|
|
2538
|
+
return result;
|
|
2539
|
+
}
|
|
2540
|
+
const fallbackStatus = await core.manager.status(sessionId);
|
|
2541
|
+
let networkEvents = { events: [] };
|
|
2542
|
+
try {
|
|
2543
|
+
const polled = await core.manager.networkPoll(
|
|
2544
|
+
sessionId,
|
|
2545
|
+
void 0,
|
|
2546
|
+
core.config.blockerArtifactCaps.maxNetworkEvents
|
|
2547
|
+
);
|
|
2548
|
+
if (polled && Array.isArray(polled.events)) {
|
|
2549
|
+
networkEvents = { events: polled.events };
|
|
2550
|
+
}
|
|
2551
|
+
} catch {
|
|
2552
|
+
}
|
|
2553
|
+
const blocker = classifyBlockerSignal({
|
|
2554
|
+
source: "navigation",
|
|
2555
|
+
url: typeof record.url === "string" ? record.url : fallbackStatus.url,
|
|
2556
|
+
finalUrl: typeof record.finalUrl === "string" ? record.finalUrl : fallbackStatus.url,
|
|
2557
|
+
title: fallbackStatus.title,
|
|
2558
|
+
status: typeof record.status === "number" ? record.status : findLatestStatus(networkEvents.events),
|
|
2559
|
+
networkHosts: extractHosts(networkEvents.events),
|
|
2560
|
+
threshold: core.config.blockerDetectionThreshold,
|
|
2561
|
+
promptGuardEnabled: core.config.security.promptInjectionGuard?.enabled ?? true
|
|
2562
|
+
});
|
|
2563
|
+
return {
|
|
2564
|
+
...record,
|
|
2565
|
+
meta: {
|
|
2566
|
+
...existingMeta ?? {},
|
|
2567
|
+
blockerState: blocker ? "active" : "clear",
|
|
2568
|
+
...blocker ? { blocker } : {}
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
function attachBlockerMetaForTrace(core, result) {
|
|
2573
|
+
const blocker = classifyBlockerSignal({
|
|
2574
|
+
source: "network",
|
|
2575
|
+
url: result.page.url,
|
|
2576
|
+
finalUrl: result.page.url,
|
|
2577
|
+
title: result.page.title,
|
|
2578
|
+
status: findLatestStatus(result.channels.network.events),
|
|
2579
|
+
networkHosts: extractHosts(result.channels.network.events),
|
|
2580
|
+
traceRequestId: result.requestId,
|
|
2581
|
+
threshold: core.config.blockerDetectionThreshold,
|
|
2582
|
+
promptGuardEnabled: core.config.security.promptInjectionGuard?.enabled ?? true
|
|
2583
|
+
});
|
|
2584
|
+
const blockerArtifacts = blocker ? buildBlockerArtifacts({
|
|
2585
|
+
networkEvents: result.channels.network.events,
|
|
2586
|
+
consoleEvents: result.channels.console.events,
|
|
2587
|
+
exceptionEvents: result.channels.exception.events,
|
|
2588
|
+
promptGuardEnabled: core.config.security.promptInjectionGuard?.enabled ?? true,
|
|
2589
|
+
caps: core.config.blockerArtifactCaps
|
|
2590
|
+
}) : void 0;
|
|
2591
|
+
return {
|
|
2592
|
+
...result,
|
|
2593
|
+
meta: {
|
|
2594
|
+
blockerState: blocker ? "active" : "clear",
|
|
2595
|
+
...blocker ? { blocker } : {},
|
|
2596
|
+
...blockerArtifacts ? { blockerArtifacts } : {}
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
function findLatestStatus(events) {
|
|
2601
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
2602
|
+
const status = events[index]?.status;
|
|
2603
|
+
if (typeof status === "number") return status;
|
|
2604
|
+
}
|
|
2605
|
+
return void 0;
|
|
2606
|
+
}
|
|
2607
|
+
function extractHosts(events) {
|
|
2608
|
+
const hosts = [];
|
|
2609
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2610
|
+
for (const event of events) {
|
|
2611
|
+
if (typeof event.url !== "string") continue;
|
|
2612
|
+
try {
|
|
2613
|
+
const host = new URL(event.url).hostname.toLowerCase();
|
|
2614
|
+
if (!host || seen.has(host)) continue;
|
|
2615
|
+
seen.add(host);
|
|
2616
|
+
hosts.push(host);
|
|
2617
|
+
} catch {
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
return hosts;
|
|
2621
|
+
}
|
|
2622
|
+
function requireString(value, label) {
|
|
2623
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
2624
|
+
throw new Error(`Missing ${label}`);
|
|
2625
|
+
}
|
|
2626
|
+
return value;
|
|
2627
|
+
}
|
|
2628
|
+
function requireRecord(value, label) {
|
|
2629
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2630
|
+
throw new Error(`Invalid ${label}`);
|
|
2631
|
+
}
|
|
2632
|
+
return value;
|
|
2633
|
+
}
|
|
2634
|
+
function requireClientId(params) {
|
|
2635
|
+
return requireString(params.clientId, "clientId");
|
|
2636
|
+
}
|
|
2637
|
+
function requireStringArray(value, label) {
|
|
2638
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
2639
|
+
throw new Error(`Invalid ${label}`);
|
|
2640
|
+
}
|
|
2641
|
+
return value;
|
|
2642
|
+
}
|
|
2643
|
+
function requireDialogAction(value) {
|
|
2644
|
+
if (value === "status" || value === "accept" || value === "dismiss") {
|
|
2645
|
+
return value;
|
|
2646
|
+
}
|
|
2647
|
+
throw new Error("Invalid action");
|
|
2648
|
+
}
|
|
2649
|
+
function requireCookieArray(value, label) {
|
|
2650
|
+
if (!Array.isArray(value)) {
|
|
2651
|
+
throw new Error(`Invalid ${label}`);
|
|
2652
|
+
}
|
|
2653
|
+
const parsed = [];
|
|
2654
|
+
for (const entry of value) {
|
|
2655
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
2656
|
+
throw new Error(`Invalid ${label}`);
|
|
2657
|
+
}
|
|
2658
|
+
const cookie = entry;
|
|
2659
|
+
if (typeof cookie.name !== "string" || typeof cookie.value !== "string") {
|
|
2660
|
+
throw new Error(`Invalid ${label}`);
|
|
2661
|
+
}
|
|
2662
|
+
if (typeof cookie.sameSite !== "undefined" && cookie.sameSite !== "Strict" && cookie.sameSite !== "Lax" && cookie.sameSite !== "None") {
|
|
2663
|
+
throw new Error(`Invalid ${label}`);
|
|
2664
|
+
}
|
|
2665
|
+
parsed.push({
|
|
2666
|
+
name: cookie.name,
|
|
2667
|
+
value: cookie.value,
|
|
2668
|
+
...typeof cookie.url === "string" ? { url: cookie.url } : {},
|
|
2669
|
+
...typeof cookie.domain === "string" ? { domain: cookie.domain } : {},
|
|
2670
|
+
...typeof cookie.path === "string" ? { path: cookie.path } : {},
|
|
2671
|
+
...typeof cookie.expires === "number" ? { expires: cookie.expires } : {},
|
|
2672
|
+
...typeof cookie.httpOnly === "boolean" ? { httpOnly: cookie.httpOnly } : {},
|
|
2673
|
+
...typeof cookie.secure === "boolean" ? { secure: cookie.secure } : {},
|
|
2674
|
+
...cookie.sameSite ? { sameSite: cookie.sameSite } : {}
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
return parsed;
|
|
2678
|
+
}
|
|
2679
|
+
function requireOptionalCookieUrlArray(value, label) {
|
|
2680
|
+
if (typeof value === "undefined") {
|
|
2681
|
+
return void 0;
|
|
2682
|
+
}
|
|
2683
|
+
if (!Array.isArray(value)) {
|
|
2684
|
+
throw new Error(`Invalid ${label}`);
|
|
2685
|
+
}
|
|
2686
|
+
const normalized = [];
|
|
2687
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2688
|
+
for (const entry of value) {
|
|
2689
|
+
if (typeof entry !== "string") {
|
|
2690
|
+
throw new Error(`Invalid ${label}`);
|
|
2691
|
+
}
|
|
2692
|
+
const trimmed = entry.trim();
|
|
2693
|
+
if (!trimmed) {
|
|
2694
|
+
throw new Error(`Invalid ${label}`);
|
|
2695
|
+
}
|
|
2696
|
+
let parsedUrl;
|
|
2697
|
+
try {
|
|
2698
|
+
parsedUrl = new URL(trimmed);
|
|
2699
|
+
} catch {
|
|
2700
|
+
throw new Error(`Invalid ${label}`);
|
|
2701
|
+
}
|
|
2702
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
2703
|
+
throw new Error(`Invalid ${label}`);
|
|
2704
|
+
}
|
|
2705
|
+
const normalizedUrl = parsedUrl.toString();
|
|
2706
|
+
if (seen.has(normalizedUrl)) {
|
|
2707
|
+
continue;
|
|
2708
|
+
}
|
|
2709
|
+
seen.add(normalizedUrl);
|
|
2710
|
+
normalized.push(normalizedUrl);
|
|
2711
|
+
}
|
|
2712
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
2713
|
+
}
|
|
2714
|
+
function validateCookieRecord(cookie) {
|
|
2715
|
+
const name = cookie.name?.trim();
|
|
2716
|
+
if (!name) {
|
|
2717
|
+
return { valid: false, reason: "Cookie name is required.", cookie };
|
|
2718
|
+
}
|
|
2719
|
+
if (!/^[^\s;=]+$/.test(name)) {
|
|
2720
|
+
return { valid: false, reason: `Invalid cookie name: ${cookie.name}.`, cookie };
|
|
2721
|
+
}
|
|
2722
|
+
if (typeof cookie.value !== "string" || /\r|\n|;/.test(cookie.value)) {
|
|
2723
|
+
return { valid: false, reason: `Invalid cookie value for ${name}.`, cookie };
|
|
2724
|
+
}
|
|
2725
|
+
const hasUrl = typeof cookie.url === "string" && cookie.url.trim().length > 0;
|
|
2726
|
+
const hasDomain = typeof cookie.domain === "string" && cookie.domain.trim().length > 0;
|
|
2727
|
+
if (!hasUrl && !hasDomain) {
|
|
2728
|
+
return { valid: false, reason: `Cookie ${name} requires url or domain.`, cookie };
|
|
2729
|
+
}
|
|
2730
|
+
let normalizedUrl;
|
|
2731
|
+
if (hasUrl) {
|
|
2732
|
+
try {
|
|
2733
|
+
const parsedUrl = new URL(cookie.url);
|
|
2734
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
2735
|
+
return { valid: false, reason: `Cookie ${name} url must be http(s).`, cookie };
|
|
2736
|
+
}
|
|
2737
|
+
normalizedUrl = parsedUrl.toString();
|
|
2738
|
+
} catch {
|
|
2739
|
+
return { valid: false, reason: `Cookie ${name} has invalid url.`, cookie };
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
let normalizedDomain;
|
|
2743
|
+
if (hasDomain) {
|
|
2744
|
+
normalizedDomain = String(cookie.domain).trim().toLowerCase();
|
|
2745
|
+
if (!/^\.?[a-z0-9.-]+$/.test(normalizedDomain) || normalizedDomain.includes("..")) {
|
|
2746
|
+
return { valid: false, reason: `Cookie ${name} has invalid domain.`, cookie };
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
const normalizedPath = typeof cookie.path === "string" ? cookie.path.trim() : void 0;
|
|
2750
|
+
if (typeof normalizedPath === "string" && !normalizedPath.startsWith("/")) {
|
|
2751
|
+
return { valid: false, reason: `Cookie ${name} path must start with '/'.`, cookie };
|
|
2752
|
+
}
|
|
2753
|
+
if (typeof cookie.expires !== "undefined") {
|
|
2754
|
+
if (!Number.isFinite(cookie.expires) || cookie.expires < -1) {
|
|
2755
|
+
return { valid: false, reason: `Cookie ${name} has invalid expires.`, cookie };
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
if (cookie.sameSite === "None" && cookie.secure !== true) {
|
|
2759
|
+
return { valid: false, reason: `Cookie ${name} with SameSite=None must set secure=true.`, cookie };
|
|
2760
|
+
}
|
|
2761
|
+
const normalizedCookie = {
|
|
2762
|
+
name,
|
|
2763
|
+
value: cookie.value,
|
|
2764
|
+
...typeof cookie.expires === "number" ? { expires: cookie.expires } : {},
|
|
2765
|
+
...typeof cookie.httpOnly === "boolean" ? { httpOnly: cookie.httpOnly } : {},
|
|
2766
|
+
...typeof cookie.secure === "boolean" ? { secure: cookie.secure } : {},
|
|
2767
|
+
...cookie.sameSite ? { sameSite: cookie.sameSite } : {}
|
|
2768
|
+
};
|
|
2769
|
+
if (normalizedDomain) {
|
|
2770
|
+
normalizedCookie.domain = normalizedDomain;
|
|
2771
|
+
normalizedCookie.path = normalizedPath ?? "/";
|
|
2772
|
+
} else if (normalizedUrl) {
|
|
2773
|
+
normalizedCookie.url = normalizedUrl;
|
|
2774
|
+
}
|
|
2775
|
+
return {
|
|
2776
|
+
valid: true,
|
|
2777
|
+
reason: "",
|
|
2778
|
+
cookie: normalizedCookie
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
function optionalString(value) {
|
|
2782
|
+
return typeof value === "string" ? value : void 0;
|
|
2783
|
+
}
|
|
2784
|
+
function optionalStringArray2(value) {
|
|
2785
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : void 0;
|
|
2786
|
+
}
|
|
2787
|
+
function optionalNumber(value, label) {
|
|
2788
|
+
if (typeof value === "undefined") {
|
|
2789
|
+
return void 0;
|
|
2790
|
+
}
|
|
2791
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2792
|
+
return value;
|
|
2793
|
+
}
|
|
2794
|
+
throw new Error(`Invalid ${label}`);
|
|
2795
|
+
}
|
|
2796
|
+
function optionalBoolean2(value) {
|
|
2797
|
+
return typeof value === "boolean" ? value : void 0;
|
|
2798
|
+
}
|
|
2799
|
+
function requireFiniteNumber(value, label) {
|
|
2800
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2801
|
+
return value;
|
|
2802
|
+
}
|
|
2803
|
+
throw new Error(`Invalid ${label}`);
|
|
2804
|
+
}
|
|
2805
|
+
function optionalPositiveInteger2(value, label) {
|
|
2806
|
+
if (typeof value === "undefined") {
|
|
2807
|
+
return void 0;
|
|
2808
|
+
}
|
|
2809
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
2810
|
+
return value;
|
|
2811
|
+
}
|
|
2812
|
+
throw new Error(`Invalid ${label}`);
|
|
2813
|
+
}
|
|
2814
|
+
function optionalNonNegativeInteger(value, label) {
|
|
2815
|
+
if (typeof value === "undefined") {
|
|
2816
|
+
return void 0;
|
|
2817
|
+
}
|
|
2818
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
|
|
2819
|
+
return value;
|
|
2820
|
+
}
|
|
2821
|
+
throw new Error(`Invalid ${label}`);
|
|
2822
|
+
}
|
|
2823
|
+
function requirePointerPoint(value, label) {
|
|
2824
|
+
const point = requireRecord(value, label);
|
|
2825
|
+
return {
|
|
2826
|
+
x: requireFiniteNumber(point.x, `${label}.x`),
|
|
2827
|
+
y: requireFiniteNumber(point.y, `${label}.y`)
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
function optionalPointerButton(value) {
|
|
2831
|
+
if (value === "middle" || value === "right") {
|
|
2832
|
+
return value;
|
|
2833
|
+
}
|
|
2834
|
+
return "left";
|
|
2835
|
+
}
|
|
2836
|
+
function optionalRenderMode(value) {
|
|
2837
|
+
if (typeof value === "undefined") return void 0;
|
|
2838
|
+
if (value === "compact" || value === "json" || value === "md" || value === "context" || value === "path") {
|
|
2839
|
+
return value;
|
|
2840
|
+
}
|
|
2841
|
+
throw new Error("Invalid mode");
|
|
2842
|
+
}
|
|
2843
|
+
function optionalProviderSelection(value) {
|
|
2844
|
+
if (typeof value === "undefined") return void 0;
|
|
2845
|
+
if (value === "auto" || value === "web" || value === "community" || value === "social" || value === "shopping" || value === "all") {
|
|
2846
|
+
return value;
|
|
2847
|
+
}
|
|
2848
|
+
throw new Error("Invalid sourceSelection");
|
|
2849
|
+
}
|
|
2850
|
+
function optionalProviderSources(value) {
|
|
2851
|
+
if (typeof value === "undefined") return void 0;
|
|
2852
|
+
if (!Array.isArray(value)) {
|
|
2853
|
+
throw new Error("Invalid sources");
|
|
2854
|
+
}
|
|
2855
|
+
const valid = value.every((entry) => entry === "web" || entry === "community" || entry === "social" || entry === "shopping");
|
|
2856
|
+
if (!valid) {
|
|
2857
|
+
throw new Error("Invalid sources");
|
|
2858
|
+
}
|
|
2859
|
+
return value;
|
|
2860
|
+
}
|
|
2861
|
+
function optionalCookiePolicy(value) {
|
|
2862
|
+
if (typeof value === "undefined") return void 0;
|
|
2863
|
+
if (value === "off" || value === "auto" || value === "required") {
|
|
2864
|
+
return value;
|
|
2865
|
+
}
|
|
2866
|
+
throw new Error("Invalid cookiePolicyOverride");
|
|
2867
|
+
}
|
|
2868
|
+
function optionalChallengeAutomationMode(value) {
|
|
2869
|
+
if (typeof value === "undefined") return void 0;
|
|
2870
|
+
if (isChallengeAutomationMode(value)) {
|
|
2871
|
+
return value;
|
|
2872
|
+
}
|
|
2873
|
+
throw new Error("Invalid challengeAutomationMode");
|
|
2874
|
+
}
|
|
2875
|
+
function optionalShoppingSort(value) {
|
|
2876
|
+
if (typeof value === "undefined") return void 0;
|
|
2877
|
+
if (value === "best_deal" || value === "lowest_price" || value === "highest_rating" || value === "fastest_shipping") {
|
|
2878
|
+
return value;
|
|
2879
|
+
}
|
|
2880
|
+
throw new Error("Invalid shopping sort");
|
|
2881
|
+
}
|
|
2882
|
+
function optionalWorkflowBrowserMode(value) {
|
|
2883
|
+
if (typeof value === "undefined") return void 0;
|
|
2884
|
+
if (value === "auto" || value === "extension" || value === "managed") {
|
|
2885
|
+
return value;
|
|
2886
|
+
}
|
|
2887
|
+
throw new Error("Invalid browserMode");
|
|
2888
|
+
}
|
|
2889
|
+
function optionalInspiredesignCaptureMode(value) {
|
|
2890
|
+
if (typeof value === "undefined") return void 0;
|
|
2891
|
+
if (value === "off" || value === "deep") {
|
|
2892
|
+
return value;
|
|
2893
|
+
}
|
|
2894
|
+
throw new Error("Invalid captureMode");
|
|
2895
|
+
}
|
|
2896
|
+
function requireWaitUntil(value) {
|
|
2897
|
+
if (value === "domcontentloaded" || value === "load" || value === "networkidle") {
|
|
2898
|
+
return value;
|
|
2899
|
+
}
|
|
2900
|
+
return "load";
|
|
2901
|
+
}
|
|
2902
|
+
function requireSnapshotMode(value) {
|
|
2903
|
+
if (value === "actionables") return "actionables";
|
|
2904
|
+
return "outline";
|
|
2905
|
+
}
|
|
2906
|
+
function requireScreenshotMode(value) {
|
|
2907
|
+
if (value === "visible" || value === "full" || value === "none") {
|
|
2908
|
+
return value;
|
|
2909
|
+
}
|
|
2910
|
+
return "visible";
|
|
2911
|
+
}
|
|
2912
|
+
function requireAnnotationTransport(value) {
|
|
2913
|
+
if (value === "auto" || value === "direct" || value === "relay") {
|
|
2914
|
+
return value;
|
|
2915
|
+
}
|
|
2916
|
+
if (typeof value === "undefined") {
|
|
2917
|
+
return "auto";
|
|
2918
|
+
}
|
|
2919
|
+
throw new Error("Invalid transport");
|
|
2920
|
+
}
|
|
2921
|
+
function requireAnnotationDispatchSource(value) {
|
|
2922
|
+
if (value === "annotate_item" || value === "annotate_all" || value === "popup_item" || value === "popup_all" || value === "canvas_item" || value === "canvas_all") {
|
|
2923
|
+
return value;
|
|
2924
|
+
}
|
|
2925
|
+
throw new Error("Invalid source");
|
|
2926
|
+
}
|
|
2927
|
+
function requireAnnotationPayload(value) {
|
|
2928
|
+
const payload = requireRecord(value, "payload");
|
|
2929
|
+
if (typeof payload.url !== "string" || typeof payload.timestamp !== "string" || !Array.isArray(payload.annotations)) {
|
|
2930
|
+
throw new Error("Invalid payload");
|
|
2931
|
+
}
|
|
2932
|
+
if (typeof payload.title !== "undefined" && typeof payload.title !== "string" || typeof payload.context !== "undefined" && typeof payload.context !== "string") {
|
|
2933
|
+
throw new Error("Invalid payload");
|
|
2934
|
+
}
|
|
2935
|
+
if (typeof payload.screenshotMode !== "undefined" && payload.screenshotMode !== "visible" && payload.screenshotMode !== "full" && payload.screenshotMode !== "none") {
|
|
2936
|
+
throw new Error("Invalid payload");
|
|
2937
|
+
}
|
|
2938
|
+
return payload;
|
|
2939
|
+
}
|
|
2940
|
+
function requireState(value) {
|
|
2941
|
+
if (value === "visible" || value === "hidden") return value;
|
|
2942
|
+
return "attached";
|
|
2943
|
+
}
|
|
2944
|
+
var MIN_WAIT_TIMEOUT_MS = 3e3;
|
|
2945
|
+
var WAIT_MIN_DELAY_MS = 250;
|
|
2946
|
+
var WAIT_MAX_DELAY_MS = 2e3;
|
|
2947
|
+
var RELAY_STATUS_TIMEOUT_MS = 1500;
|
|
2948
|
+
function clampWaitTimeout(timeoutMs) {
|
|
2949
|
+
if (!Number.isFinite(timeoutMs)) {
|
|
2950
|
+
return MIN_WAIT_TIMEOUT_MS;
|
|
2951
|
+
}
|
|
2952
|
+
return Math.max(timeoutMs, MIN_WAIT_TIMEOUT_MS);
|
|
2953
|
+
}
|
|
2954
|
+
async function waitForRelayHandshake(relay, observedPort, timeoutMs) {
|
|
2955
|
+
const start = Date.now();
|
|
2956
|
+
let delay = WAIT_MIN_DELAY_MS;
|
|
2957
|
+
while (Date.now() - start < timeoutMs) {
|
|
2958
|
+
const relayStatus = relay.status();
|
|
2959
|
+
if (relayStatus.extensionHandshakeComplete) {
|
|
2960
|
+
return true;
|
|
2961
|
+
}
|
|
2962
|
+
const observedStatus = getMatchingObservedRelayStatus(
|
|
2963
|
+
relayStatus,
|
|
2964
|
+
await fetchRelayObservedStatus(observedPort)
|
|
2965
|
+
);
|
|
2966
|
+
if (observedStatus?.extensionHandshakeComplete) {
|
|
2967
|
+
return true;
|
|
2968
|
+
}
|
|
2969
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
2970
|
+
delay = Math.min(delay * 2, WAIT_MAX_DELAY_MS);
|
|
2971
|
+
}
|
|
2972
|
+
return false;
|
|
2973
|
+
}
|
|
2974
|
+
function resolveObservedPort(relayStatus, configPort) {
|
|
2975
|
+
if (isValidPort(relayStatus.port)) return relayStatus.port;
|
|
2976
|
+
if (isValidPort(configPort)) return configPort;
|
|
2977
|
+
return null;
|
|
2978
|
+
}
|
|
2979
|
+
function isValidPort(port) {
|
|
2980
|
+
return typeof port === "number" && Number.isInteger(port) && port > 0 && port <= 65535;
|
|
2981
|
+
}
|
|
2982
|
+
async function fetchRelayObservedStatus(port) {
|
|
2983
|
+
if (!isValidPort(port)) {
|
|
2984
|
+
return null;
|
|
2985
|
+
}
|
|
2986
|
+
try {
|
|
2987
|
+
const response = await fetchWithTimeout(
|
|
2988
|
+
`http://127.0.0.1:${port}/status`,
|
|
2989
|
+
void 0,
|
|
2990
|
+
RELAY_STATUS_TIMEOUT_MS
|
|
2991
|
+
);
|
|
2992
|
+
if (!response.ok) {
|
|
2993
|
+
return null;
|
|
2994
|
+
}
|
|
2995
|
+
const data = await response.json();
|
|
2996
|
+
if (!data || typeof data !== "object") {
|
|
2997
|
+
return null;
|
|
2998
|
+
}
|
|
2999
|
+
const record = data;
|
|
3000
|
+
if (typeof record.instanceId !== "string") {
|
|
3001
|
+
return null;
|
|
3002
|
+
}
|
|
3003
|
+
return {
|
|
3004
|
+
instanceId: record.instanceId,
|
|
3005
|
+
running: Boolean(record.running),
|
|
3006
|
+
port: typeof record.port === "number" ? record.port : void 0,
|
|
3007
|
+
extensionConnected: Boolean(record.extensionConnected),
|
|
3008
|
+
extensionHandshakeComplete: Boolean(record.extensionHandshakeComplete),
|
|
3009
|
+
cdpConnected: Boolean(record.cdpConnected),
|
|
3010
|
+
opsConnected: Boolean(record.opsConnected),
|
|
3011
|
+
pairingRequired: Boolean(record.pairingRequired)
|
|
3012
|
+
};
|
|
3013
|
+
} catch {
|
|
3014
|
+
return null;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
function getMatchingObservedRelayStatus(relayStatus, observedStatus) {
|
|
3018
|
+
if (!observedStatus) {
|
|
3019
|
+
return null;
|
|
3020
|
+
}
|
|
3021
|
+
return observedStatus.instanceId === relayStatus.instanceId ? observedStatus : null;
|
|
3022
|
+
}
|
|
3023
|
+
async function loadMacroRuntime() {
|
|
3024
|
+
try {
|
|
3025
|
+
const module = await import("./macros-ND2M7LWU.js");
|
|
3026
|
+
return module;
|
|
3027
|
+
} catch {
|
|
3028
|
+
return null;
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
function parseFallbackMacro(expression, defaultProvider) {
|
|
3032
|
+
const raw = expression.trim();
|
|
3033
|
+
if (!raw.startsWith("@")) {
|
|
3034
|
+
throw new Error("Macro expressions must start with '@'");
|
|
3035
|
+
}
|
|
3036
|
+
const body = raw.slice(1).trim();
|
|
3037
|
+
if (!body) {
|
|
3038
|
+
throw new Error("Macro name is required");
|
|
3039
|
+
}
|
|
3040
|
+
const openParen = body.indexOf("(");
|
|
3041
|
+
const closeParen = body.endsWith(")") ? body.length - 1 : -1;
|
|
3042
|
+
const macroName = openParen >= 0 ? body.slice(0, openParen).trim() : body;
|
|
3043
|
+
const argsBody = openParen >= 0 && closeParen > openParen ? body.slice(openParen + 1, closeParen).trim() : "";
|
|
3044
|
+
const positional = argsBody ? argsBody.split(",").map((part) => part.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean) : [];
|
|
3045
|
+
const query = positional[0] ?? macroName;
|
|
3046
|
+
const provider = defaultProvider ?? "web/default";
|
|
3047
|
+
return {
|
|
3048
|
+
action: {
|
|
3049
|
+
source: "web",
|
|
3050
|
+
operation: "search",
|
|
3051
|
+
input: {
|
|
3052
|
+
query,
|
|
3053
|
+
limit: 10,
|
|
3054
|
+
providerId: provider
|
|
3055
|
+
}
|
|
3056
|
+
},
|
|
3057
|
+
provenance: {
|
|
3058
|
+
macro: macroName,
|
|
3059
|
+
provider,
|
|
3060
|
+
resolvedQuery: query,
|
|
3061
|
+
pack: "fallback",
|
|
3062
|
+
args: {
|
|
3063
|
+
positional,
|
|
3064
|
+
named: {}
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
async function resolveMacroExpression(options, config, manager, browserFallbackPort, existingRuntime) {
|
|
3070
|
+
const runtime = await loadMacroRuntime();
|
|
3071
|
+
const registry = runtime?.createDefaultMacroRegistry?.();
|
|
3072
|
+
let resolvedRuntime = "fallback";
|
|
3073
|
+
let resolution;
|
|
3074
|
+
let catalog;
|
|
3075
|
+
if (registry) {
|
|
3076
|
+
resolvedRuntime = "macros";
|
|
3077
|
+
resolution = await registry.resolve(options.expression, {
|
|
3078
|
+
defaultProvider: options.defaultProvider
|
|
3079
|
+
});
|
|
3080
|
+
catalog = options.includeCatalog ? registry.list().map((entry) => ({
|
|
3081
|
+
name: entry.name,
|
|
3082
|
+
pack: entry.pack,
|
|
3083
|
+
description: entry.description
|
|
3084
|
+
})) : void 0;
|
|
3085
|
+
} else {
|
|
3086
|
+
resolution = parseFallbackMacro(options.expression, options.defaultProvider);
|
|
3087
|
+
}
|
|
3088
|
+
if (!options.execute) {
|
|
3089
|
+
const handoff2 = buildMacroResolveSuccessHandoff({
|
|
3090
|
+
expression: options.expression,
|
|
3091
|
+
defaultProvider: options.defaultProvider,
|
|
3092
|
+
execute: false,
|
|
3093
|
+
blocked: false
|
|
3094
|
+
});
|
|
3095
|
+
return {
|
|
3096
|
+
runtime: resolvedRuntime,
|
|
3097
|
+
resolution,
|
|
3098
|
+
...catalog ? { catalog } : {},
|
|
3099
|
+
...handoff2
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
3102
|
+
const execution = await executeMacroWithRuntime({
|
|
3103
|
+
resolution,
|
|
3104
|
+
existingRuntime,
|
|
3105
|
+
config,
|
|
3106
|
+
manager,
|
|
3107
|
+
browserFallbackPort,
|
|
3108
|
+
timeoutMs: options.timeoutMs,
|
|
3109
|
+
browserMode: options.browserMode,
|
|
3110
|
+
useCookies: options.useCookies,
|
|
3111
|
+
challengeAutomationMode: options.challengeAutomationMode,
|
|
3112
|
+
cookiePolicyOverride: options.cookiePolicyOverride
|
|
3113
|
+
});
|
|
3114
|
+
const handoff = buildMacroResolveSuccessHandoff({
|
|
3115
|
+
expression: options.expression,
|
|
3116
|
+
defaultProvider: options.defaultProvider,
|
|
3117
|
+
execute: true,
|
|
3118
|
+
blocked: Boolean(execution.meta.blocker)
|
|
3119
|
+
});
|
|
3120
|
+
return {
|
|
3121
|
+
runtime: resolvedRuntime,
|
|
3122
|
+
resolution,
|
|
3123
|
+
...catalog ? { catalog } : {},
|
|
3124
|
+
execution,
|
|
3125
|
+
...handoff
|
|
3126
|
+
};
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
// src/cli/daemon.ts
|
|
3130
|
+
var DEFAULT_DAEMON_PORT = 8788;
|
|
3131
|
+
var DAEMON_STOP_DEBUG_ENV = "OPDEVBROWSER_DEBUG_DAEMON_STOP";
|
|
3132
|
+
var DAEMON_FINGERPRINT_FILE = "daemon-fingerprint.json";
|
|
3133
|
+
var DAEMON_STOP_REASON_HEADER = "x-opendevbrowser-stop-reason";
|
|
3134
|
+
var DAEMON_STOP_CLIENT_PID_HEADER = "x-opendevbrowser-stop-client-pid";
|
|
3135
|
+
var DAEMON_STOP_FINGERPRINT_HEADER = "x-opendevbrowser-stop-fingerprint";
|
|
3136
|
+
var RECOVERABLE_PLAYWRIGHT_TRANSPORT_ERRORS = [
|
|
3137
|
+
"Cannot find context with specified id",
|
|
3138
|
+
"Detached while handling command.",
|
|
3139
|
+
"No frame with given id found"
|
|
3140
|
+
];
|
|
3141
|
+
var DAEMON_FINGERPRINT_VERSION = "v1";
|
|
3142
|
+
function getCacheRoot() {
|
|
3143
|
+
const base = process.env.OPENCODE_CACHE_DIR ?? process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
|
|
3144
|
+
return join(base, "opendevbrowser");
|
|
3145
|
+
}
|
|
3146
|
+
function getDaemonMetadataPath() {
|
|
3147
|
+
return join(getCacheRoot(), "daemon.json");
|
|
3148
|
+
}
|
|
3149
|
+
function readDaemonMetadata() {
|
|
3150
|
+
const metadataPath = getDaemonMetadataPath();
|
|
3151
|
+
if (!existsSync(metadataPath)) {
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
3154
|
+
try {
|
|
3155
|
+
const content = readFileSync(metadataPath, "utf-8");
|
|
3156
|
+
return JSON.parse(content);
|
|
3157
|
+
} catch {
|
|
3158
|
+
return null;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
function writeDaemonMetadata(state) {
|
|
3162
|
+
const metadataPath = getDaemonMetadataPath();
|
|
3163
|
+
mkdirSync(join(getCacheRoot()), { recursive: true });
|
|
3164
|
+
writeFileSync(metadataPath, JSON.stringify(state, null, 2), { encoding: "utf-8", mode: 384 });
|
|
3165
|
+
}
|
|
3166
|
+
function clearDaemonMetadata() {
|
|
3167
|
+
const metadataPath = getDaemonMetadataPath();
|
|
3168
|
+
try {
|
|
3169
|
+
unlinkSync(metadataPath);
|
|
3170
|
+
} catch {
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
function resolveCurrentDaemonEntrypointPath(options = {}) {
|
|
3174
|
+
const rawEntry = options.argv1 ?? process.argv[1];
|
|
3175
|
+
if (typeof rawEntry === "string" && rawEntry.trim().length > 0) {
|
|
3176
|
+
return resolve(rawEntry);
|
|
3177
|
+
}
|
|
3178
|
+
const moduleUrl = options.moduleUrl ?? import.meta.url;
|
|
3179
|
+
const cliEntrypoint = resolve(fileURLToPath(new URL("./index.js", moduleUrl)));
|
|
3180
|
+
const entryExists = options.entryExists ?? existsSync;
|
|
3181
|
+
if (entryExists(cliEntrypoint)) {
|
|
3182
|
+
return cliEntrypoint;
|
|
3183
|
+
}
|
|
3184
|
+
return resolve(fileURLToPath(moduleUrl));
|
|
3185
|
+
}
|
|
3186
|
+
function hashFileContents(entryPath) {
|
|
3187
|
+
try {
|
|
3188
|
+
return createHash("sha256").update(readFileSync(entryPath)).digest("hex");
|
|
3189
|
+
} catch {
|
|
3190
|
+
return "missing";
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
function resolveDaemonFingerprintDistRoot(modulePath) {
|
|
3194
|
+
let currentDir = dirname(modulePath);
|
|
3195
|
+
while (true) {
|
|
3196
|
+
if (basename(currentDir) === "dist") {
|
|
3197
|
+
return currentDir;
|
|
3198
|
+
}
|
|
3199
|
+
const parentDir = dirname(currentDir);
|
|
3200
|
+
if (parentDir === currentDir) {
|
|
3201
|
+
break;
|
|
3202
|
+
}
|
|
3203
|
+
currentDir = parentDir;
|
|
3204
|
+
}
|
|
3205
|
+
return null;
|
|
3206
|
+
}
|
|
3207
|
+
function readDaemonFingerprintArtifact(modulePath) {
|
|
3208
|
+
const distRoot = resolveDaemonFingerprintDistRoot(modulePath);
|
|
3209
|
+
if (distRoot === null) {
|
|
3210
|
+
return null;
|
|
3211
|
+
}
|
|
3212
|
+
try {
|
|
3213
|
+
const content = readFileSync(join(distRoot, DAEMON_FINGERPRINT_FILE), "utf-8");
|
|
3214
|
+
const payload = JSON.parse(content);
|
|
3215
|
+
if (typeof payload.fingerprint === "string" && payload.fingerprint.trim().length > 0) {
|
|
3216
|
+
return payload.fingerprint.trim();
|
|
3217
|
+
}
|
|
3218
|
+
} catch {
|
|
3219
|
+
}
|
|
3220
|
+
return null;
|
|
3221
|
+
}
|
|
3222
|
+
function getCurrentDaemonFingerprint(options = {}) {
|
|
3223
|
+
const modulePath = resolve(fileURLToPath(options.moduleUrl ?? import.meta.url));
|
|
3224
|
+
const sharedFingerprint = readDaemonFingerprintArtifact(modulePath);
|
|
3225
|
+
const fingerprintParts = [
|
|
3226
|
+
DAEMON_FINGERPRINT_VERSION,
|
|
3227
|
+
sharedFingerprint ?? hashFileContents(modulePath)
|
|
3228
|
+
];
|
|
3229
|
+
return createHash("sha256").update(fingerprintParts.join("\n")).digest("hex");
|
|
3230
|
+
}
|
|
3231
|
+
function isCurrentDaemonFingerprint(fingerprint) {
|
|
3232
|
+
return typeof fingerprint === "string" && fingerprint === getCurrentDaemonFingerprint();
|
|
3233
|
+
}
|
|
3234
|
+
function createDaemonStopHeaders(token, reason) {
|
|
3235
|
+
const headers = {
|
|
3236
|
+
Authorization: `Bearer ${token}`,
|
|
3237
|
+
[DAEMON_STOP_FINGERPRINT_HEADER]: getCurrentDaemonFingerprint(),
|
|
3238
|
+
[DAEMON_STOP_REASON_HEADER]: reason
|
|
3239
|
+
};
|
|
3240
|
+
if (process.env[DAEMON_STOP_DEBUG_ENV] === "1") {
|
|
3241
|
+
headers[DAEMON_STOP_CLIENT_PID_HEADER] = String(process.pid);
|
|
3242
|
+
}
|
|
3243
|
+
return headers;
|
|
3244
|
+
}
|
|
3245
|
+
function resolveDaemonFingerprint(...candidates) {
|
|
3246
|
+
for (const candidate of candidates) {
|
|
3247
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
3248
|
+
return candidate;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
return "missing";
|
|
3252
|
+
}
|
|
3253
|
+
function isRecoverablePlaywrightTransportError(error) {
|
|
3254
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
3255
|
+
return RECOVERABLE_PLAYWRIGHT_TRANSPORT_ERRORS.some((pattern) => message.includes(pattern));
|
|
3256
|
+
}
|
|
3257
|
+
function isRecoverablePlaywrightTransportAssertion(error) {
|
|
3258
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
3259
|
+
const isKnownTransportAssertion = message.includes("Assertion error") || message.includes("No tab attached");
|
|
3260
|
+
if (!isKnownTransportAssertion) {
|
|
3261
|
+
return false;
|
|
3262
|
+
}
|
|
3263
|
+
const stack = error instanceof Error && typeof error.stack === "string" ? error.stack : "";
|
|
3264
|
+
return stack.includes("playwright-core/lib/server/chromium/crConnection.js") || stack.includes("playwright-core/lib/server/transport.js");
|
|
3265
|
+
}
|
|
3266
|
+
function isAuthorized(request, token) {
|
|
3267
|
+
const header = request.headers.authorization ?? "";
|
|
3268
|
+
if (!header.startsWith("Bearer ")) {
|
|
3269
|
+
return false;
|
|
3270
|
+
}
|
|
3271
|
+
const received = header.slice("Bearer ".length).trim();
|
|
3272
|
+
const expectedBuf = Buffer.from(token, "utf-8");
|
|
3273
|
+
const receivedBuf = Buffer.from(received, "utf-8");
|
|
3274
|
+
if (expectedBuf.length !== receivedBuf.length) {
|
|
3275
|
+
timingSafeEqual(expectedBuf, expectedBuf);
|
|
3276
|
+
return false;
|
|
3277
|
+
}
|
|
3278
|
+
return timingSafeEqual(expectedBuf, receivedBuf);
|
|
3279
|
+
}
|
|
3280
|
+
function sendJson(response, status, payload) {
|
|
3281
|
+
response.writeHead(status, {
|
|
3282
|
+
"Content-Type": "application/json",
|
|
3283
|
+
"Cache-Control": "no-store"
|
|
3284
|
+
});
|
|
3285
|
+
response.end(JSON.stringify(payload));
|
|
3286
|
+
}
|
|
3287
|
+
function logDaemonStopDebug(message, details) {
|
|
3288
|
+
if (process.env[DAEMON_STOP_DEBUG_ENV] !== "1") {
|
|
3289
|
+
return;
|
|
3290
|
+
}
|
|
3291
|
+
const suffix = details ? ` ${JSON.stringify(details)}` : "";
|
|
3292
|
+
console.error(`[daemon-stop-debug] ${message}${suffix}`);
|
|
3293
|
+
}
|
|
3294
|
+
function readSingleHeader(request, name) {
|
|
3295
|
+
const value = request.headers[name];
|
|
3296
|
+
if (typeof value === "string") {
|
|
3297
|
+
return value;
|
|
3298
|
+
}
|
|
3299
|
+
return null;
|
|
3300
|
+
}
|
|
3301
|
+
var isDaemonCommandRequest = (value) => {
|
|
3302
|
+
if (typeof value.name !== "string") {
|
|
3303
|
+
return false;
|
|
3304
|
+
}
|
|
3305
|
+
if (typeof value.params === "undefined") {
|
|
3306
|
+
return true;
|
|
3307
|
+
}
|
|
3308
|
+
return typeof value.params === "object" && value.params !== null && !Array.isArray(value.params);
|
|
3309
|
+
};
|
|
3310
|
+
async function startDaemon(options = {}) {
|
|
3311
|
+
const config = typeof options.config === "undefined" ? loadGlobalConfig() : resolveConfig(options.config);
|
|
3312
|
+
const port = options.port ?? config.daemonPort ?? DEFAULT_DAEMON_PORT;
|
|
3313
|
+
const token = options.token ?? config.daemonToken ?? generateSecureToken();
|
|
3314
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3315
|
+
const fingerprint = getCurrentDaemonFingerprint();
|
|
3316
|
+
const core = createOpenDevBrowserCore({
|
|
3317
|
+
directory: options.directory ?? process.cwd(),
|
|
3318
|
+
worktree: options.worktree ?? null,
|
|
3319
|
+
config
|
|
3320
|
+
});
|
|
3321
|
+
await core.ensureRelay(config.relayPort);
|
|
3322
|
+
const server = createServer(async (request, response) => {
|
|
3323
|
+
if (!isAuthorized(request, token)) {
|
|
3324
|
+
sendJson(response, 401, { error: "Unauthorized" });
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
3328
|
+
if (request.method === "GET" && url.pathname === "/status") {
|
|
3329
|
+
const relayStatus = core.relay.status();
|
|
3330
|
+
writeDaemonMetadata({
|
|
3331
|
+
port,
|
|
3332
|
+
token,
|
|
3333
|
+
pid: process.pid,
|
|
3334
|
+
relayPort: relayStatus.port ?? config.relayPort,
|
|
3335
|
+
startedAt,
|
|
3336
|
+
fingerprint,
|
|
3337
|
+
hubInstanceId: getHubInstanceId(),
|
|
3338
|
+
relayInstanceId: relayStatus.instanceId,
|
|
3339
|
+
relayEpoch: relayStatus.epoch
|
|
3340
|
+
});
|
|
3341
|
+
sendJson(response, 200, {
|
|
3342
|
+
ok: true,
|
|
3343
|
+
pid: process.pid,
|
|
3344
|
+
fingerprint,
|
|
3345
|
+
hub: { instanceId: getHubInstanceId() },
|
|
3346
|
+
relay: relayStatus,
|
|
3347
|
+
binding: getBindingDiagnostics()
|
|
3348
|
+
});
|
|
3349
|
+
return;
|
|
3350
|
+
}
|
|
3351
|
+
if (request.method === "POST" && url.pathname === "/stop") {
|
|
3352
|
+
const stopFingerprint = readSingleHeader(request, DAEMON_STOP_FINGERPRINT_HEADER);
|
|
3353
|
+
logDaemonStopDebug("http.stop", {
|
|
3354
|
+
remoteAddress: request.socket.remoteAddress ?? null,
|
|
3355
|
+
remotePort: request.socket.remotePort ?? null,
|
|
3356
|
+
reason: readSingleHeader(request, DAEMON_STOP_REASON_HEADER),
|
|
3357
|
+
clientPid: readSingleHeader(request, DAEMON_STOP_CLIENT_PID_HEADER),
|
|
3358
|
+
fingerprintMatches: stopFingerprint === fingerprint
|
|
3359
|
+
});
|
|
3360
|
+
if (stopFingerprint !== fingerprint) {
|
|
3361
|
+
sendJson(response, 409, { ok: false, error: "Stale daemon stop request." });
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
sendJson(response, 200, { ok: true });
|
|
3365
|
+
await stop("http.stop");
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
if (request.method === "POST" && url.pathname === "/command") {
|
|
3369
|
+
try {
|
|
3370
|
+
const body = await readJson(request);
|
|
3371
|
+
if (!isDaemonCommandRequest(body)) {
|
|
3372
|
+
throw new Error("Invalid daemon command request");
|
|
3373
|
+
}
|
|
3374
|
+
const data = await handleDaemonCommand(core, body);
|
|
3375
|
+
sendJson(response, 200, { ok: true, data });
|
|
3376
|
+
} catch (error) {
|
|
3377
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3378
|
+
sendJson(response, 400, { ok: false, error: message });
|
|
3379
|
+
}
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
sendJson(response, 404, { error: "Not found" });
|
|
3383
|
+
});
|
|
3384
|
+
await new Promise((resolve2, reject) => {
|
|
3385
|
+
server.once("error", reject);
|
|
3386
|
+
server.listen(port, "127.0.0.1", () => resolve2());
|
|
3387
|
+
});
|
|
3388
|
+
const state = {
|
|
3389
|
+
port,
|
|
3390
|
+
token,
|
|
3391
|
+
pid: process.pid,
|
|
3392
|
+
relayPort: config.relayPort,
|
|
3393
|
+
startedAt,
|
|
3394
|
+
fingerprint,
|
|
3395
|
+
hubInstanceId: getHubInstanceId(),
|
|
3396
|
+
relayInstanceId: core.relay.status().instanceId,
|
|
3397
|
+
relayEpoch: core.relay.status().epoch
|
|
3398
|
+
};
|
|
3399
|
+
writeDaemonMetadata(state);
|
|
3400
|
+
let stopping = false;
|
|
3401
|
+
const handleRecoverableDaemonError = (channel, error) => {
|
|
3402
|
+
if (!isRecoverablePlaywrightTransportError(error)) {
|
|
3403
|
+
if (isRecoverablePlaywrightTransportAssertion(error)) {
|
|
3404
|
+
const message2 = error instanceof Error ? error.message : String(error ?? "unknown");
|
|
3405
|
+
console.warn(`[daemon] ignored recoverable Playwright transport follow-on (${channel}): ${message2}`);
|
|
3406
|
+
return true;
|
|
3407
|
+
}
|
|
3408
|
+
return false;
|
|
3409
|
+
}
|
|
3410
|
+
const message = error instanceof Error ? error.message : String(error ?? "unknown");
|
|
3411
|
+
console.warn(`[daemon] ignored recoverable Playwright transport error (${channel}): ${message}`);
|
|
3412
|
+
return true;
|
|
3413
|
+
};
|
|
3414
|
+
const uncaughtExceptionHandler = (error) => {
|
|
3415
|
+
if (handleRecoverableDaemonError("uncaughtException", error)) {
|
|
3416
|
+
return;
|
|
3417
|
+
}
|
|
3418
|
+
console.error(error);
|
|
3419
|
+
void stop("uncaughtException").finally(() => {
|
|
3420
|
+
process.exitCode = 1;
|
|
3421
|
+
});
|
|
3422
|
+
};
|
|
3423
|
+
const unhandledRejectionHandler = (reason) => {
|
|
3424
|
+
if (handleRecoverableDaemonError("unhandledRejection", reason)) {
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3427
|
+
console.error(reason);
|
|
3428
|
+
void stop("unhandledRejection").finally(() => {
|
|
3429
|
+
process.exitCode = 1;
|
|
3430
|
+
});
|
|
3431
|
+
};
|
|
3432
|
+
const stop = async (reason = "unknown") => {
|
|
3433
|
+
if (stopping) {
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3436
|
+
stopping = true;
|
|
3437
|
+
logDaemonStopDebug("stop.begin", { reason });
|
|
3438
|
+
clearDaemonMetadata();
|
|
3439
|
+
clearBinding();
|
|
3440
|
+
process.off("SIGINT", sigintHandler);
|
|
3441
|
+
process.off("SIGTERM", sigtermHandler);
|
|
3442
|
+
process.off("uncaughtException", uncaughtExceptionHandler);
|
|
3443
|
+
process.off("unhandledRejection", unhandledRejectionHandler);
|
|
3444
|
+
core.cleanup();
|
|
3445
|
+
await new Promise((resolve2) => {
|
|
3446
|
+
server.close(() => resolve2());
|
|
3447
|
+
});
|
|
3448
|
+
logDaemonStopDebug("stop.complete", { reason });
|
|
3449
|
+
};
|
|
3450
|
+
const sigintHandler = () => {
|
|
3451
|
+
void stop("SIGINT").catch(() => {
|
|
3452
|
+
});
|
|
3453
|
+
};
|
|
3454
|
+
const sigtermHandler = () => {
|
|
3455
|
+
void stop("SIGTERM").catch(() => {
|
|
3456
|
+
});
|
|
3457
|
+
};
|
|
3458
|
+
process.on("SIGINT", sigintHandler);
|
|
3459
|
+
process.on("SIGTERM", sigtermHandler);
|
|
3460
|
+
process.on("uncaughtException", uncaughtExceptionHandler);
|
|
3461
|
+
process.on("unhandledRejection", unhandledRejectionHandler);
|
|
3462
|
+
return { state, stop };
|
|
3463
|
+
}
|
|
3464
|
+
function readJson(request) {
|
|
3465
|
+
return new Promise((resolve2, reject) => {
|
|
3466
|
+
let data = "";
|
|
3467
|
+
request.setEncoding("utf8");
|
|
3468
|
+
request.on("data", (chunk) => {
|
|
3469
|
+
data += chunk;
|
|
3470
|
+
});
|
|
3471
|
+
request.on("end", () => {
|
|
3472
|
+
try {
|
|
3473
|
+
const parsed = JSON.parse(data || "{}");
|
|
3474
|
+
if (!parsed || typeof parsed !== "object") {
|
|
3475
|
+
reject(new Error("Invalid JSON body"));
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
resolve2(parsed);
|
|
3479
|
+
} catch (error) {
|
|
3480
|
+
reject(error);
|
|
3481
|
+
}
|
|
3482
|
+
});
|
|
3483
|
+
request.on("error", reject);
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
// src/cli/daemon-status-policy.ts
|
|
3488
|
+
var DEFAULT_DAEMON_STATUS_FETCH_OPTIONS = {
|
|
3489
|
+
timeoutMs: 5e3,
|
|
3490
|
+
retryAttempts: 5,
|
|
3491
|
+
retryDelayMs: 250
|
|
3492
|
+
};
|
|
3493
|
+
|
|
3494
|
+
// src/cli/daemon-status.ts
|
|
3495
|
+
var DEFAULT_DAEMON_STATUS_TIMEOUT_MS = DEFAULT_DAEMON_STATUS_FETCH_OPTIONS.timeoutMs;
|
|
3496
|
+
var sleep = async (delayMs) => {
|
|
3497
|
+
if (!(Number.isFinite(delayMs) && delayMs > 0)) {
|
|
3498
|
+
return;
|
|
3499
|
+
}
|
|
3500
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
3501
|
+
};
|
|
3502
|
+
var resolveRetryAttempts = (retryAttempts) => {
|
|
3503
|
+
return typeof retryAttempts === "number" && Number.isFinite(retryAttempts) && retryAttempts > 1 ? Math.floor(retryAttempts) : 1;
|
|
3504
|
+
};
|
|
3505
|
+
var resolveRetryDelayMs = (retryDelayMs) => {
|
|
3506
|
+
return typeof retryDelayMs === "number" && Number.isFinite(retryDelayMs) && retryDelayMs > 0 ? retryDelayMs : 0;
|
|
3507
|
+
};
|
|
3508
|
+
var resolveStatusTimeoutMs = (timeoutMs) => {
|
|
3509
|
+
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_DAEMON_STATUS_TIMEOUT_MS;
|
|
3510
|
+
};
|
|
3511
|
+
var withFingerprintCurrent = (status) => ({
|
|
3512
|
+
...status,
|
|
3513
|
+
fingerprintCurrent: isCurrentDaemonFingerprint(status.fingerprint)
|
|
3514
|
+
});
|
|
3515
|
+
var readRemainingBudgetMs = (deadlineMs) => {
|
|
3516
|
+
return Math.max(0, deadlineMs - Date.now());
|
|
3517
|
+
};
|
|
3518
|
+
var readSeedTimeoutMs = (remainingBudgetMs, remainingSeedCount) => {
|
|
3519
|
+
if (remainingSeedCount <= 1) {
|
|
3520
|
+
return remainingBudgetMs;
|
|
3521
|
+
}
|
|
3522
|
+
return Math.max(1, Math.floor(remainingBudgetMs / remainingSeedCount));
|
|
3523
|
+
};
|
|
3524
|
+
var resolveDaemonStatusSeeds = (metadata, config) => {
|
|
3525
|
+
const seeds = [];
|
|
3526
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3527
|
+
const addSeed = (seed) => {
|
|
3528
|
+
if (!seed) {
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
const key = `${seed.port}:${seed.token}`;
|
|
3532
|
+
if (seen.has(key)) {
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
seen.add(key);
|
|
3536
|
+
seeds.push(seed);
|
|
3537
|
+
};
|
|
3538
|
+
addSeed(
|
|
3539
|
+
config.daemonPort > 0 && config.daemonToken ? {
|
|
3540
|
+
port: config.daemonPort,
|
|
3541
|
+
token: config.daemonToken,
|
|
3542
|
+
relayPort: config.relayPort
|
|
3543
|
+
} : null
|
|
3544
|
+
);
|
|
3545
|
+
addSeed(metadata);
|
|
3546
|
+
return seeds;
|
|
3547
|
+
};
|
|
3548
|
+
async function fetchDaemonStatus(port, token, options = {}) {
|
|
3549
|
+
const attempts = resolveRetryAttempts(options.retryAttempts);
|
|
3550
|
+
const retryDelayMs = resolveRetryDelayMs(options.retryDelayMs);
|
|
3551
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
3552
|
+
try {
|
|
3553
|
+
const timedResponse = await fetchWithTimeoutContext(`http://127.0.0.1:${port}/status`, {
|
|
3554
|
+
method: "GET",
|
|
3555
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
3556
|
+
}, options.timeoutMs);
|
|
3557
|
+
try {
|
|
3558
|
+
if (timedResponse.response.ok) {
|
|
3559
|
+
const status = await readResponseJsonWithTimeout(
|
|
3560
|
+
timedResponse.response,
|
|
3561
|
+
timedResponse.signal,
|
|
3562
|
+
timedResponse.timeoutMs
|
|
3563
|
+
);
|
|
3564
|
+
return withFingerprintCurrent(status);
|
|
3565
|
+
}
|
|
3566
|
+
} finally {
|
|
3567
|
+
timedResponse.dispose();
|
|
3568
|
+
}
|
|
3569
|
+
} catch {
|
|
3570
|
+
}
|
|
3571
|
+
if (attempt < attempts) {
|
|
3572
|
+
await sleep(retryDelayMs);
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
return null;
|
|
3576
|
+
}
|
|
3577
|
+
async function fetchDaemonStatusFromMetadata(config, options = {}) {
|
|
3578
|
+
const resolvedConfig = config ?? loadGlobalConfig();
|
|
3579
|
+
const attempts = resolveRetryAttempts(options.retryAttempts);
|
|
3580
|
+
const retryDelayMs = resolveRetryDelayMs(options.retryDelayMs);
|
|
3581
|
+
const deadlineMs = Date.now() + resolveStatusTimeoutMs(options.timeoutMs);
|
|
3582
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
3583
|
+
const metadata = readDaemonMetadata();
|
|
3584
|
+
const seeds = resolveDaemonStatusSeeds(metadata, resolvedConfig);
|
|
3585
|
+
for (let seedIndex = 0; seedIndex < seeds.length; seedIndex += 1) {
|
|
3586
|
+
const seed = seeds[seedIndex];
|
|
3587
|
+
if (!seed) {
|
|
3588
|
+
continue;
|
|
3589
|
+
}
|
|
3590
|
+
const remainingBudgetMs = readRemainingBudgetMs(deadlineMs);
|
|
3591
|
+
if (remainingBudgetMs <= 0) {
|
|
3592
|
+
return null;
|
|
3593
|
+
}
|
|
3594
|
+
const timeoutMs = readSeedTimeoutMs(remainingBudgetMs, seeds.length - seedIndex);
|
|
3595
|
+
const status = await fetchDaemonStatus(seed.port, seed.token, {
|
|
3596
|
+
timeoutMs
|
|
3597
|
+
});
|
|
3598
|
+
if (status?.ok) {
|
|
3599
|
+
persistDaemonStatusMetadata(seed, status, resolvedConfig);
|
|
3600
|
+
return status;
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
if (attempt < attempts) {
|
|
3604
|
+
const remainingBudgetMs = readRemainingBudgetMs(deadlineMs);
|
|
3605
|
+
if (remainingBudgetMs <= 0) {
|
|
3606
|
+
break;
|
|
3607
|
+
}
|
|
3608
|
+
await sleep(Math.min(retryDelayMs, remainingBudgetMs));
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
return null;
|
|
3612
|
+
}
|
|
3613
|
+
function persistDaemonStatusMetadata(base, status, config) {
|
|
3614
|
+
const resolvedConfig = config ?? loadGlobalConfig();
|
|
3615
|
+
writeDaemonMetadata({
|
|
3616
|
+
port: base.port,
|
|
3617
|
+
token: base.token,
|
|
3618
|
+
pid: status.pid,
|
|
3619
|
+
relayPort: status.relay.port ?? resolvedConfig.relayPort,
|
|
3620
|
+
startedAt: base.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3621
|
+
fingerprint: resolveDaemonFingerprint(status.fingerprint, base.fingerprint),
|
|
3622
|
+
hubInstanceId: status.hub.instanceId,
|
|
3623
|
+
relayInstanceId: status.relay.instanceId,
|
|
3624
|
+
relayEpoch: status.relay.epoch
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3628
|
+
export {
|
|
3629
|
+
inspectSession,
|
|
3630
|
+
buildCorrelatedAuditBundle,
|
|
3631
|
+
captureInspiredesignReferenceFromManager,
|
|
3632
|
+
executeMacroWithRuntime,
|
|
3633
|
+
fetchWithTimeout,
|
|
3634
|
+
fetchWithTimeoutContext,
|
|
3635
|
+
readResponseTextWithTimeout,
|
|
3636
|
+
readResponseJsonWithTimeout,
|
|
3637
|
+
DAEMON_STOP_DEBUG_ENV,
|
|
3638
|
+
getCacheRoot,
|
|
3639
|
+
readDaemonMetadata,
|
|
3640
|
+
resolveCurrentDaemonEntrypointPath,
|
|
3641
|
+
isCurrentDaemonFingerprint,
|
|
3642
|
+
createDaemonStopHeaders,
|
|
3643
|
+
startDaemon,
|
|
3644
|
+
DEFAULT_DAEMON_STATUS_FETCH_OPTIONS,
|
|
3645
|
+
fetchDaemonStatus,
|
|
3646
|
+
fetchDaemonStatusFromMetadata,
|
|
3647
|
+
persistDaemonStatusMetadata
|
|
3648
|
+
};
|
|
3649
|
+
//# sourceMappingURL=chunk-LBPELU7L.js.map
|