@reticlehq/server 3.7.0 → 3.8.1
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/SKILL.md +59 -68
- package/bin/reticle.js +9 -2
- package/dist/command/cli/adhoc-suite.js +1 -1
- package/dist/command/cli/adhoc-verdict.d.ts +12 -0
- package/dist/command/cli/adhoc-verdict.js +62 -4
- package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
- package/dist/command/cli/answers/gate-hook-message.js +5 -1
- package/dist/command/cli/auth/cloud-session.d.ts +11 -19
- package/dist/command/cli/auth/cloud-session.js +23 -0
- package/dist/command/cli/auth/device-flow.d.ts +3 -37
- package/dist/command/cli/cli-doctor.js +66 -21
- package/dist/command/cli/cli-flow-commands.js +53 -11
- package/dist/command/cli/cli-kill.d.ts +8 -0
- package/dist/command/cli/cli-kill.js +43 -3
- package/dist/command/cli/cli-parse-feedback.d.ts +4 -0
- package/dist/command/cli/cli-parse-feedback.js +13 -1
- package/dist/command/cli/cli-parse-grammar.d.ts +1 -2
- package/dist/command/cli/cli-parse-grammar.js +1 -6
- package/dist/command/cli/cli-parse-serve.d.ts +24 -0
- package/dist/command/cli/cli-parse-serve.js +124 -0
- package/dist/command/cli/cli-parse.d.ts +12 -11
- package/dist/command/cli/cli-parse.js +100 -123
- package/dist/command/cli/cli-usage.d.ts +0 -11
- package/dist/command/cli/cli-usage.js +43 -16
- package/dist/command/cli/cli-verify.d.ts +1 -1
- package/dist/command/cli/cli-verify.js +29 -14
- package/dist/command/cli/cloud-cli.js +27 -9
- package/dist/command/cli/cloud-kit.js +3 -3
- package/dist/command/cli/cloud-link.d.ts +1 -20
- package/dist/command/cli/cloud-link.js +2 -0
- package/dist/command/cli/cloud-login.d.ts +6 -2
- package/dist/command/cli/cloud-login.js +13 -41
- package/dist/command/cli/config/checkout.d.ts +10 -0
- package/dist/command/cli/config/checkout.js +78 -0
- package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
- package/dist/command/cli/doctor/doctor-coverage-line.js +1 -7
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/drive/drive-attach.d.ts +5 -0
- package/dist/command/cli/drive/drive-attach.js +21 -1
- package/dist/command/cli/hook/codex-transcript.d.ts +20 -0
- package/dist/command/cli/hook/codex-transcript.js +224 -0
- package/dist/command/cli/hook/episode-structure.d.ts +24 -0
- package/dist/command/cli/hook/episode-structure.js +138 -0
- package/dist/command/cli/hook/hook-command.d.ts +9 -0
- package/dist/command/cli/hook/hook-command.js +211 -0
- package/dist/command/cli/hook/hook-fast.d.ts +31 -0
- package/dist/command/cli/hook/hook-fast.js +136 -0
- package/dist/command/cli/hook/prompt-hook.d.ts +30 -0
- package/dist/command/cli/hook/prompt-hook.js +199 -0
- package/dist/command/cli/hook/tool-hook.d.ts +21 -0
- package/dist/command/cli/hook/tool-hook.js +259 -0
- package/dist/command/cli/launch/cli-launch.d.ts +2 -0
- package/dist/command/cli/launch/cli-launch.js +22 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +28 -2
- package/dist/command/cli/mcp-command.d.ts +9 -0
- package/dist/command/cli/mcp-command.js +127 -41
- package/dist/command/cli/open-navigate.d.ts +43 -0
- package/dist/command/cli/open-navigate.js +85 -0
- package/dist/command/cli/ports/port-holder.d.ts +16 -4
- package/dist/command/cli/ports/port-holder.js +58 -12
- package/dist/command/cli/ports/resolve/cli-port.js +18 -2
- package/dist/command/cli/project-name.d.ts +1 -7
- package/dist/command/cli/project-name.js +1 -0
- package/dist/command/cli/report-command.d.ts +8 -2
- package/dist/command/cli/report-command.js +95 -18
- package/dist/command/cli/status-fields.d.ts +9 -2
- package/dist/command/cli/status-fields.js +148 -4
- package/dist/command/cli/try-command.d.ts +3 -4
- package/dist/command/cli/try-command.js +6 -11
- package/dist/command/cli/verify-results.d.ts +4 -1
- package/dist/command/cli/verify-results.js +3 -1
- package/dist/command/cli.d.ts +1 -7
- package/dist/command/cli.js +79 -160
- package/dist/command/daemon/daemon-resilience.d.ts +6 -1
- package/dist/command/daemon/daemon-resilience.js +31 -18
- package/dist/command/daemon/daemon.d.ts +11 -0
- package/dist/command/daemon/daemon.js +70 -2
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +2 -0
- package/dist/command/daemon/lifetime/daemon-usefulness.js +13 -2
- package/dist/command/daemon/wake-decision.js +4 -2
- package/dist/command/daemon/win32-escape.d.ts +32 -0
- package/dist/command/daemon/win32-escape.js +119 -0
- package/dist/command/setup/agent-configs.d.ts +9 -0
- package/dist/command/setup/agent-configs.js +9 -0
- package/dist/command/setup/auto-approve.d.ts +10 -3
- package/dist/command/setup/auto-approve.js +51 -3
- package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
- package/dist/command/setup/bringup/dev-server-wait.js +5 -1
- package/dist/command/setup/bringup/ensure-daemon.js +4 -7
- package/dist/command/setup/init/init-host.js +6 -0
- package/dist/command/setup/init/init-port.js +5 -4
- package/dist/command/setup/init/init-runtime.js +7 -1
- package/dist/command/setup/node-effects.d.ts +8 -0
- package/dist/command/setup/node-effects.js +36 -4
- package/dist/command/setup/probe/dev-port.d.ts +26 -0
- package/dist/command/setup/probe/dev-port.js +117 -0
- package/dist/command/setup/probe/page-probe.d.ts +1 -1
- package/dist/command/setup/probe/page-probe.js +11 -3
- package/dist/command/setup/remaining-steps.d.ts +0 -7
- package/dist/command/setup/remaining-steps.js +3 -2
- package/dist/command/setup/run-setup.d.ts +11 -0
- package/dist/command/setup/run-setup.js +33 -5
- package/dist/command/setup/setup-command.d.ts +2 -0
- package/dist/command/setup/setup-command.js +22 -4
- package/dist/command/update/updater.d.ts +1 -6
- package/dist/command/update/updater.js +4 -5
- package/dist/command/update/version-seen.d.ts +24 -0
- package/dist/command/update/version-seen.js +65 -0
- package/dist/command/version/peer-announce.d.ts +9 -1
- package/dist/command/version/peer-announce.js +25 -2
- package/dist/command/version/sdk-fix.d.ts +5 -6
- package/dist/command/version/sdk-fix.js +17 -32
- package/dist/command/version/version-skew.d.ts +16 -1
- package/dist/command/version/version-skew.js +84 -10
- package/dist/features/crawl/crawl-tools.js +2 -2
- package/dist/features/crawl/crawl.js +2 -0
- package/dist/features/exhaust/ledger.d.ts +15 -96
- package/dist/features/exhaust/ledger.js +3 -3
- package/dist/features/harness/drive-runs.d.ts +28 -4
- package/dist/features/harness/drive-runs.js +99 -7
- package/dist/features/harness/drivers.d.ts +1 -1
- package/dist/features/harness/drivers.js +1 -1
- package/dist/features/harness/harness.d.ts +5 -0
- package/dist/features/harness/harness.js +24 -12
- package/dist/features/harness/platform/platform-drives.d.ts +7 -1
- package/dist/features/harness/platform/platform-drives.js +12 -7
- package/dist/features/harness/platform/refusal.d.ts +1 -1
- package/dist/features/harness/platform/refusal.js +4 -4
- package/dist/features/harness/platform/remote-drive.js +70 -28
- package/dist/features/harness/platform/server-driver.js +22 -1
- package/dist/features/harness/platform-config.d.ts +1 -10
- package/dist/features/harness/platform-config.js +0 -20
- package/dist/features/license/license.d.ts +2 -14
- package/dist/features/visual/step-shots.d.ts +33 -1
- package/dist/features/visual/step-shots.js +155 -1
- package/dist/features/visual/visual-tools.js +12 -4
- package/dist/hooks/coding-agents.d.ts +6 -0
- package/dist/hooks/coding-agents.js +24 -3
- package/dist/hooks/episode-stamp.d.ts +32 -0
- package/dist/hooks/episode-stamp.js +61 -0
- package/dist/hooks/hook-commands.js +19 -9
- package/dist/index.d.ts +5 -1
- package/dist/index.js +66 -24
- package/dist/judgement/app-graph/journal-steps.d.ts +8 -0
- package/dist/judgement/app-graph/journal-steps.js +92 -0
- package/dist/judgement/capsule/capsule-store.d.ts +2 -39
- package/dist/judgement/domain/domain-model.js +1 -10
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/runs/artifact/run-store.d.ts +28 -8
- package/dist/judgement/runs/artifact/run-store.js +153 -28
- package/dist/judgement/runs/default-run-id.d.ts +3 -1
- package/dist/judgement/runs/default-run-id.js +15 -3
- package/dist/judgement/runs/drive-run.d.ts +3 -3
- package/dist/judgement/runs/drive-run.js +11 -7
- package/dist/judgement/runs/run-ledger.d.ts +21 -0
- package/dist/judgement/runs/run-ledger.js +143 -0
- package/dist/judgement/runs/runner-port.d.ts +1 -1
- package/dist/judgement/runs/runner-port.js +8 -2
- package/dist/judgement/runs/verification-sync.d.ts +4 -3
- package/dist/judgement/runs/verification-sync.js +27 -5
- package/dist/language/flows/affected-tools.js +2 -1
- package/dist/language/flows/annotate-notes/annotate.js +8 -3
- package/dist/language/flows/annotate-tools.js +1 -1
- package/dist/language/flows/change/affected.d.ts +8 -0
- package/dist/language/flows/change/affected.js +20 -7
- package/dist/language/flows/decision.js +19 -4
- package/dist/language/flows/fields/flow-secret-field.d.ts +9 -0
- package/dist/language/flows/fields/flow-secret-field.js +67 -5
- package/dist/language/flows/flow-anchor.d.ts +5 -0
- package/dist/language/flows/flow-anchor.js +4 -0
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +9 -0
- package/dist/language/flows/flow-expect-grammar.js +15 -10
- package/dist/language/flows/flow-intent.js +6 -1
- package/dist/language/flows/flow-journey.d.ts +8 -0
- package/dist/language/flows/flow-journey.js +13 -0
- package/dist/language/flows/flow-lease.d.ts +27 -0
- package/dist/language/flows/flow-lease.js +59 -0
- package/dist/language/flows/flow-replay-run.d.ts +1 -87
- package/dist/language/flows/flow-replay-run.js +111 -310
- package/dist/language/flows/flow-replay.js +21 -19
- package/dist/language/flows/flow-replayable.d.ts +38 -0
- package/dist/language/flows/flow-replayable.js +179 -0
- package/dist/language/flows/flow-start-path.d.ts +108 -0
- package/dist/language/flows/flow-start-path.js +313 -0
- package/dist/language/flows/flow-step-runners.d.ts +23 -2
- package/dist/language/flows/flow-step-runners.js +191 -32
- package/dist/language/flows/flow-store-for-session.d.ts +8 -1
- package/dist/language/flows/flow-store-for-session.js +6 -0
- package/dist/language/flows/flow-tools.d.ts +2 -23
- package/dist/language/flows/flow-tools.js +63 -113
- package/dist/language/flows/flows.d.ts +4 -6
- package/dist/language/flows/flows.js +90 -15
- package/dist/language/flows/heal-run.d.ts +10 -4
- package/dist/language/flows/heal-run.js +26 -9
- package/dist/language/flows/recording/flake.d.ts +3 -27
- package/dist/language/flows/recording/flake.js +1 -1
- package/dist/language/flows/recording/recording-backtrack.js +1 -4
- package/dist/language/flows/recording/tape/recordings.d.ts +11 -1
- package/dist/language/flows/recording/tape/recordings.js +9 -3
- package/dist/language/flows/replay.d.ts +25 -5
- package/dist/language/flows/replay.js +83 -16
- package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
- package/dist/language/flows/suite/coverage.d.ts +9 -0
- package/dist/language/flows/suite/coverage.js +12 -1
- package/dist/language/flows/suite-selection.d.ts +25 -0
- package/dist/language/flows/suite-selection.js +41 -9
- package/dist/machine/git-tracks.d.ts +6 -0
- package/dist/machine/git-tracks.js +36 -0
- package/dist/memory/cloud/background-sync.d.ts +25 -0
- package/dist/memory/cloud/background-sync.js +79 -0
- package/dist/memory/cloud/cloud-config.d.ts +12 -1
- package/dist/memory/cloud/cloud-config.js +39 -13
- package/dist/memory/cloud/cloud-sync.d.ts +3 -24
- package/dist/memory/cloud/cloud-sync.js +5 -2
- package/dist/memory/cloud/harness-config.d.ts +0 -7
- package/dist/memory/cloud/memory-scope.d.ts +19 -0
- package/dist/memory/cloud/memory-scope.js +48 -0
- package/dist/memory/cloud/sync-answer.d.ts +43 -0
- package/dist/memory/cloud/sync-answer.js +42 -0
- package/dist/memory/cloud/sync-cycle.d.ts +40 -10
- package/dist/memory/cloud/sync-cycle.js +97 -144
- package/dist/memory/cloud/sync-daemon.d.ts +50 -3
- package/dist/memory/cloud/sync-daemon.js +473 -83
- package/dist/memory/cloud/sync-describe.d.ts +28 -0
- package/dist/memory/cloud/sync-describe.js +92 -0
- package/dist/memory/cloud/sync-disk.d.ts +9 -1
- package/dist/memory/cloud/sync-disk.js +137 -33
- package/dist/memory/cloud/sync-flow-versions.d.ts +26 -0
- package/dist/memory/cloud/sync-flow-versions.js +42 -0
- package/dist/memory/impact/impact-store.js +32 -15
- package/dist/memory/intent/intent-shard.d.ts +41 -266
- package/dist/memory/intent/intent-tools.js +20 -61
- package/dist/memory/intent/record-request.d.ts +27 -0
- package/dist/memory/intent/record-request.js +88 -0
- package/dist/memory/intent/settled-words.js +9 -1
- package/dist/memory/journal/drive-flow.d.ts +3 -0
- package/dist/memory/journal/drive-flow.js +11 -41
- package/dist/memory/journal/envelope-store.js +1 -1
- package/dist/memory/journal/envelope.d.ts +6 -154
- package/dist/memory/journal/episode-log.d.ts +107 -0
- package/dist/memory/journal/episode-log.js +302 -0
- package/dist/memory/journal/journal-recorder.d.ts +7 -0
- package/dist/memory/journal/journal-recorder.js +22 -9
- package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
- package/dist/memory/journal/on-disk/ambient-file.js +1 -1
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +24 -3
- package/dist/memory/journal/on-disk/workspace-gitignore.js +38 -7
- package/dist/memory/journal/on-disk/workspace-tiers.js +7 -0
- package/dist/memory/journal/self-observation.js +3 -1
- package/dist/memory/journal/session-end.js +23 -9
- package/dist/memory/journal/session-journal.js +39 -20
- package/dist/memory/project/artifact-root-resolver.d.ts +14 -1
- package/dist/memory/project/artifact-root-resolver.js +68 -23
- package/dist/memory/project/artifact-root.d.ts +17 -3
- package/dist/memory/project/artifact-root.js +47 -9
- package/dist/memory/project/fs/fs-port.d.ts +5 -0
- package/dist/memory/project/fs/fs-port.js +13 -0
- package/dist/memory/project/learned-routes.d.ts +3 -4
- package/dist/memory/project/learned-routes.js +3 -11
- package/dist/memory/project/link-directory.js +5 -2
- package/dist/memory/project/root-claims.d.ts +3 -1
- package/dist/memory/project/root-claims.js +41 -7
- package/dist/memory/project/session-root.d.ts +1 -1
- package/dist/memory/project/session-root.js +1 -15
- package/dist/memory/project/sync-status.js +5 -6
- package/dist/memory/recall/memory-tools.js +2 -2
- package/dist/memory/recall/platform-memory.d.ts +3 -1
- package/dist/memory/recall/platform-memory.js +9 -1
- package/dist/memory/recall/project-memory.js +15 -19
- package/dist/portal/bridge/auth-failure-reason.d.ts +13 -0
- package/dist/portal/bridge/auth-failure-reason.js +19 -0
- package/dist/portal/bridge/bridge.d.ts +19 -4
- package/dist/portal/bridge/bridge.js +150 -177
- package/dist/portal/bridge/harness-request.d.ts +15 -0
- package/dist/portal/bridge/harness-request.js +19 -0
- package/dist/portal/bridge/origin/origin-follow.d.ts +46 -0
- package/dist/portal/bridge/origin/origin-follow.js +110 -0
- package/dist/portal/bridge/pairing-token.d.ts +6 -0
- package/dist/portal/bridge/pairing-token.js +47 -16
- package/dist/portal/bridge/protocol-skew.d.ts +27 -2
- package/dist/portal/bridge/protocol-skew.js +66 -2
- package/dist/portal/bridge/session-nonces.d.ts +46 -0
- package/dist/portal/bridge/session-nonces.js +72 -0
- package/dist/portal/input/viewport-tools.d.ts +31 -0
- package/dist/portal/input/viewport-tools.js +106 -15
- package/dist/portal/pool/bounded-close.d.ts +23 -0
- package/dist/portal/pool/bounded-close.js +38 -0
- package/dist/portal/pool/browser-pool.d.ts +44 -39
- package/dist/portal/pool/browser-pool.js +247 -95
- package/dist/portal/pool/context-permissions.d.ts +9 -1
- package/dist/portal/pool/context-permissions.js +26 -0
- package/dist/portal/pool/lease-emulation.d.ts +37 -0
- package/dist/portal/pool/lease-emulation.js +29 -0
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +74 -7
- package/dist/portal/pool/pool-contract.d.ts +23 -1
- package/dist/portal/pool/pool-lease.d.ts +128 -0
- package/dist/portal/pool/pool-lease.js +49 -0
- package/dist/portal/pool/zero-install.d.ts +8 -1
- package/dist/portal/pool/zero-install.js +24 -2
- package/dist/portal/production/production-activation.d.ts +136 -0
- package/dist/portal/production/production-activation.js +173 -0
- package/dist/portal/production/production-drive.d.ts +52 -0
- package/dist/portal/production/production-drive.js +301 -0
- package/dist/portal/production/production-hello.d.ts +23 -0
- package/dist/portal/production/production-hello.js +23 -0
- package/dist/portal/realm/web-realm.js +42 -15
- package/dist/portal/session/control-envelope.d.ts +1 -1
- package/dist/portal/session/dev-server/served-document.d.ts +0 -5
- package/dist/portal/session/dev-server/served-document.js +6 -8
- package/dist/portal/session/facts/handshake-facts.d.ts +8 -1
- package/dist/portal/session/first-run-wiring.d.ts +3 -36
- package/dist/portal/session/first-run-wiring.js +10 -103
- package/dist/portal/session/live-control-tools.js +61 -19
- package/dist/portal/session/no-session-next-action.d.ts +11 -0
- package/dist/portal/session/no-session-next-action.js +40 -15
- package/dist/portal/session/no-session-watch.d.ts +7 -0
- package/dist/portal/session/no-session-watch.js +67 -23
- package/dist/portal/session/output-budget.d.ts +10 -0
- package/dist/portal/session/output-budget.js +16 -0
- package/dist/portal/session/presence/session-lease.d.ts +2 -0
- package/dist/portal/session/presence/session-lease.js +8 -0
- package/dist/portal/session/recorded-gaps.d.ts +15 -11
- package/dist/portal/session/recorded-gaps.js +39 -18
- package/dist/portal/session/serving-directory.d.ts +24 -2
- package/dist/portal/session/serving-directory.js +93 -5
- package/dist/portal/session/session-info.d.ts +26 -0
- package/dist/portal/session/session-info.js +20 -0
- package/dist/portal/session/session-manager.d.ts +35 -3
- package/dist/portal/session/session-manager.js +69 -14
- package/dist/portal/session/session-successor.js +1 -9
- package/dist/portal/session/session-tools.js +22 -4
- package/dist/portal/session/session.d.ts +10 -10
- package/dist/portal/session/session.js +37 -29
- package/dist/portal/session/zero-install-first-call.d.ts +7 -2
- package/dist/portal/session/zero-install-first-call.js +43 -20
- package/dist/start-wiring.d.ts +2 -2
- package/dist/start-wiring.js +17 -3
- package/dist/status-payload.d.ts +11 -1
- package/dist/status-payload.js +7 -1
- package/dist/surface/agent-presence-grace.d.ts +11 -0
- package/dist/surface/agent-presence-grace.js +34 -0
- package/dist/surface/http-server.d.ts +12 -1
- package/dist/surface/http-server.js +64 -15
- package/dist/surface/mcp/faults/mcp-outage.d.ts +12 -2
- package/dist/surface/mcp/faults/mcp-outage.js +19 -1
- package/dist/surface/mcp/mcp-proxy.d.ts +17 -74
- package/dist/surface/mcp/mcp-proxy.js +194 -267
- package/dist/surface/mcp/mcp.d.ts +20 -3
- package/dist/surface/mcp/mcp.js +88 -25
- package/dist/surface/mcp/proxy/drive-calls.d.ts +44 -0
- package/dist/surface/mcp/proxy/drive-calls.js +114 -5
- package/dist/surface/mcp/proxy/proxy-boot.d.ts +11 -0
- package/dist/surface/mcp/proxy/proxy-boot.js +1 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +6 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +14 -1
- package/dist/surface/mcp/proxy/proxy-handshake.d.ts +34 -2
- package/dist/surface/mcp/proxy/proxy-handshake.js +76 -8
- package/dist/surface/mcp/server-instructions.js +35 -16
- package/dist/surface/mcp/shared-params.d.ts +8 -0
- package/dist/surface/mcp/shared-params.js +15 -1
- package/dist/surface/mcp/sse-frame-parser.d.ts +11 -0
- package/dist/surface/mcp/sse-frame-parser.js +18 -0
- package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
- package/dist/surface/mcp/surface-vocabulary.js +13 -0
- package/dist/surface/mcp/tool-catalog-cache.d.ts +2 -0
- package/dist/surface/mcp/tool-catalog-cache.js +14 -4
- package/dist/surface/tools/account-tool.d.ts +4 -3
- package/dist/surface/tools/account-tool.js +20 -31
- package/dist/surface/tools/act/act-danger.d.ts +2 -0
- package/dist/surface/tools/act/act-danger.js +41 -14
- package/dist/surface/tools/act/act-observation.d.ts +11 -0
- package/dist/surface/tools/act/act-observation.js +33 -0
- package/dist/surface/tools/act/act-preflight.js +10 -3
- package/dist/surface/tools/act/act-sequence-retry.js +2 -1
- package/dist/surface/tools/act/act-target.js +19 -5
- package/dist/surface/tools/act/durable.js +1 -1
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +9 -7
- package/dist/surface/tools/act/navigation/navigate-arrival.js +11 -14
- package/dist/surface/tools/act/unsettled-find.d.ts +36 -0
- package/dist/surface/tools/act/unsettled-find.js +71 -0
- package/dist/surface/tools/act-sequence-tool.js +53 -24
- package/dist/surface/tools/act-tools.d.ts +2 -3
- package/dist/surface/tools/act-tools.js +49 -47
- package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
- package/dist/surface/tools/agent/for-the-agent.js +41 -3
- package/dist/surface/tools/assert/assert-grade.js +8 -3
- package/dist/surface/tools/assert/assert-verdict.js +4 -2
- package/dist/surface/tools/browser-tools.js +7 -4
- package/dist/surface/tools/chat-drives.js +50 -25
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.d.ts +8 -0
- package/dist/surface/tools/dynamic-tools.js +89 -34
- package/dist/surface/tools/empty-session-list.d.ts +18 -25
- package/dist/surface/tools/empty-session-list.js +67 -38
- package/dist/surface/tools/error-recovery.d.ts +17 -7
- package/dist/surface/tools/error-recovery.js +53 -20
- package/dist/surface/tools/explore-tools.d.ts +10 -0
- package/dist/surface/tools/explore-tools.js +72 -19
- package/dist/surface/tools/feedback-tools.js +23 -2
- package/dist/surface/tools/gap-novelty.d.ts +6 -0
- package/dist/surface/tools/gap-novelty.js +9 -0
- package/dist/surface/tools/gaps/capability-absences.js +4 -3
- package/dist/surface/tools/harness-explore.d.ts +1 -1
- package/dist/surface/tools/harness-explore.js +6 -4
- package/dist/surface/tools/harness-gate.d.ts +43 -21
- package/dist/surface/tools/harness-gate.js +55 -81
- package/dist/surface/tools/harness-script.d.ts +2 -0
- package/dist/surface/tools/harness-script.js +11 -6
- package/dist/surface/tools/harness-toolset.js +3 -4
- package/dist/surface/tools/invoke-tool.d.ts +0 -8
- package/dist/surface/tools/invoke-tool.js +164 -178
- package/dist/surface/tools/lease-hint.d.ts +9 -0
- package/dist/surface/tools/lease-hint.js +22 -3
- package/dist/surface/tools/lease-permissions.d.ts +1 -1
- package/dist/surface/tools/lease-readiness.d.ts +55 -0
- package/dist/surface/tools/lease-readiness.js +150 -2
- package/dist/surface/tools/lease-tools.d.ts +3 -75
- package/dist/surface/tools/lease-tools.js +124 -188
- package/dist/surface/tools/map-tools.d.ts +20 -0
- package/dist/surface/tools/map-tools.js +145 -0
- package/dist/surface/tools/merge-tools.js +11 -7
- package/dist/surface/tools/next-step.js +6 -3
- package/dist/surface/tools/observe-tools.js +46 -35
- package/dist/surface/tools/platform-moment.d.ts +3 -12
- package/dist/surface/tools/platform-moment.js +12 -24
- package/dist/surface/tools/read/complete-snapshot.d.ts +7 -1
- package/dist/surface/tools/read/complete-snapshot.js +13 -2
- package/dist/surface/tools/read/query-paginate.js +6 -0
- package/dist/surface/tools/read/query-shape.d.ts +9 -0
- package/dist/surface/tools/read/query-shape.js +15 -0
- package/dist/surface/tools/read/snapshot-unmounted-note.d.ts +1 -0
- package/dist/surface/tools/read/snapshot-unmounted-note.js +73 -0
- package/dist/surface/tools/read-tools.js +5 -5
- package/dist/surface/tools/real-input-attempt.d.ts +11 -0
- package/dist/surface/tools/real-input-attempt.js +206 -38
- package/dist/surface/tools/reconcile-tools.js +21 -8
- package/dist/surface/tools/reticle-result.d.ts +3 -0
- package/dist/surface/tools/reticle-result.js +8 -1
- package/dist/surface/tools/run-report.d.ts +22 -0
- package/dist/surface/tools/run-report.js +141 -0
- package/dist/surface/tools/session-ports.d.ts +57 -0
- package/dist/surface/tools/session-ports.js +102 -0
- package/dist/surface/tools/tool-kit.d.ts +58 -4
- package/dist/surface/tools/tool-kit.js +140 -6
- package/dist/surface/tools/tool-surface.d.ts +2 -0
- package/dist/surface/tools/tool-surface.js +45 -0
- package/dist/surface/tools/tools.js +49 -87
- package/dist/telemetry/automation-hint.d.ts +5 -0
- package/dist/telemetry/automation-hint.js +7 -0
- package/dist/telemetry/cli-telemetry.d.ts +22 -0
- package/dist/telemetry/cli-telemetry.js +80 -11
- package/dist/telemetry/daemon-telemetry.d.ts +3 -1
- package/dist/telemetry/daemon-telemetry.js +9 -2
- package/dist/telemetry/feedback-cli.d.ts +1 -1
- package/dist/telemetry/feedback-cli.js +8 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/feedback.js +51 -4
- package/dist/telemetry/harness-run.d.ts +3 -3
- package/dist/telemetry/harness-run.js +18 -7
- package/dist/telemetry/hud-events.d.ts +1 -1
- package/dist/telemetry/hud-metrics.d.ts +6 -3
- package/dist/telemetry/hud-metrics.js +20 -8
- package/dist/telemetry/install-source.js +1 -1
- package/dist/telemetry/mcp-connection.d.ts +1 -1
- package/dist/telemetry/mcp-connection.js +21 -2
- package/dist/telemetry/onboarding-firsts.d.ts +6 -1
- package/dist/telemetry/onboarding-firsts.js +41 -1
- package/dist/telemetry/proxy-telemetry.d.ts +42 -0
- package/dist/telemetry/proxy-telemetry.js +70 -1
- package/dist/telemetry/session-metrics.d.ts +1 -3
- package/dist/telemetry/session-metrics.js +16 -12
- package/dist/telemetry/telemetry.d.ts +9 -1
- package/dist/telemetry/telemetry.js +16 -1
- package/dist/telemetry/verification-of.d.ts +3 -1
- package/dist/telemetry/verification-of.js +7 -1
- package/dist/wire-journal.js +12 -2
- package/docs/README.md +4 -2
- package/docs/actions.mdx +7 -6
- package/docs/adapters.md +2 -1
- package/docs/agent-cheatsheet.md +19 -5
- package/docs/architecture.md +13 -10
- package/docs/autodrive.md +6 -5
- package/docs/benchmarks.md +2 -2
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/affected.mdx +1 -1
- package/docs/cli/cloud.mdx +6 -6
- package/docs/cli/doctor.mdx +3 -1
- package/docs/cli/feedback.mdx +11 -7
- package/docs/cli/gate.mdx +1 -1
- package/docs/cli/help.mdx +1 -1
- package/docs/cli/init.mdx +20 -16
- package/docs/cli/mcp.mdx +5 -2
- package/docs/cli/open.mdx +5 -2
- package/docs/cli/report.mdx +1 -1
- package/docs/cli/restart.mdx +1 -1
- package/docs/cli/rollback.mdx +4 -4
- package/docs/cli/serve.mdx +7 -5
- package/docs/cli/setup.mdx +1 -1
- package/docs/cli/status.mdx +6 -2
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/telemetry.mdx +5 -1
- package/docs/cli/try.mdx +8 -8
- package/docs/cli/update.mdx +8 -5
- package/docs/cli/verify.mdx +12 -5
- package/docs/cli/version.mdx +1 -1
- package/docs/cli.mdx +10 -6
- package/docs/debugging.md +1 -1
- package/docs/deploy-checks.md +8 -6
- package/docs/desktop.mdx +14 -18
- package/docs/enterprise.md +6 -6
- package/docs/environment.mdx +5 -3
- package/docs/faq.mdx +9 -6
- package/docs/first-drive.md +19 -9
- package/docs/flows.md +3 -4
- package/docs/for-agents.md +1 -1
- package/docs/frameworks.mdx +34 -11
- package/docs/getting-started.md +46 -29
- package/docs/http-transport.md +2 -2
- package/docs/human-control.md +8 -8
- package/docs/index.mdx +2 -2
- package/docs/install-agentic.mdx +18 -19
- package/docs/install-manual.mdx +8 -4
- package/docs/integration-patterns.md +5 -5
- package/docs/multi-agent-testing.md +7 -5
- package/docs/packages/browser.mdx +34 -2
- package/docs/packages/core.mdx +2 -1
- package/docs/packages/electron.mdx +15 -3
- package/docs/packages/eslint-plugin.mdx +1 -1
- package/docs/packages/next.mdx +17 -5
- package/docs/packages/react.mdx +8 -1
- package/docs/packages/server.mdx +3 -3
- package/docs/packages/tauri.mdx +3 -3
- package/docs/packages/vite-plugin.mdx +1 -1
- package/docs/packages.mdx +19 -6
- package/docs/platform-integration.md +12 -4
- package/docs/predicates.mdx +18 -7
- package/docs/production-mode.md +108 -0
- package/docs/prompt-capture.md +99 -0
- package/docs/quickstart.mdx +14 -12
- package/docs/recipes.mdx +9 -8
- package/docs/required-checks.md +3 -2
- package/docs/skill-file.mdx +4 -3
- package/docs/skill-setup.md +2 -2
- package/docs/skill.md +62 -70
- package/docs/state-files.md +1 -0
- package/docs/state-management.mdx +11 -9
- package/docs/system-map.md +14 -13
- package/docs/telemetry.md +32 -24
- package/docs/testing.md +9 -5
- package/docs/tools/act-and-wait.mdx +17 -13
- package/docs/tools/act-sequence.mdx +40 -66
- package/docs/tools/act.mdx +7 -5
- package/docs/tools/assert.mdx +1 -1
- package/docs/tools/look.mdx +1 -1
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/network.mdx +13 -11
- package/docs/tools/overview.mdx +3 -3
- package/docs/tools/query.mdx +3 -3
- package/docs/tools/session-and-feedback.mdx +9 -6
- package/docs/tools/sessions.mdx +7 -0
- package/docs/tools/snapshot.mdx +9 -7
- package/docs/tools/tools-and-run.mdx +11 -10
- package/docs/tools/verify.mdx +1 -1
- package/docs/troubleshooting.mdx +21 -9
- package/docs/usage.md +58 -43
- package/docs/use-cases.mdx +2 -2
- package/docs/vs-chrome-devtools-mcp.mdx +5 -5
- package/docs/vs-playwright-mcp.mdx +5 -4
- package/docs/what-is-recorded.md +12 -7
- package/docs/what-is-reticle.mdx +2 -2
- package/docs/why-reticle.mdx +1 -1
- package/package.json +9 -12
- package/dist/command/dev/stale-issue-guard.d.ts +0 -34
- package/dist/command/dev/stale-issue-guard.js +0 -69
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { bridgeWsUrl, HelloScope, LOOPBACK_HOST, PRODUCTION_ACTIVATE_PARAM, PRODUCTION_TICKET_PATH, ProductionTicketResponseSchema, } from '@reticlehq/core';
|
|
3
|
+
/**
|
|
4
|
+
* Turn `reticle_lease { production }` into a page that will connect on a live site, in one step.
|
|
5
|
+
*
|
|
6
|
+
* 1. Mint a single-use session nonce for the site's origin, bound to a production drive.
|
|
7
|
+
* 2. Ask the service for an activation ticket with the linked API key:
|
|
8
|
+
* `POST /v1/production-mode/tickets { workspaceId, nonce, bridgePort, scope, origin, bridge }`.
|
|
9
|
+
* 3. Hand back `<url>?reticle_activate=<ticket>` for the lease to open.
|
|
10
|
+
*
|
|
11
|
+
* The service decides who may activate (an owner or admin of a workspace with production mode on)
|
|
12
|
+
* and signs the ticket. No static secret leaves the machine: the page only ever sees the nonce.
|
|
13
|
+
*/
|
|
14
|
+
/** Said when the service has no ticket route: an older or self-hosted service. */
|
|
15
|
+
export const PRODUCTION_MODE_NOT_OFFERED = `the server does not offer production mode (POST ${PRODUCTION_TICKET_PATH} answered 404). ` +
|
|
16
|
+
'Production mode needs a Reticle service that issues activation tickets.';
|
|
17
|
+
/** Said when this project has no linked cloud credentials to ask with. */
|
|
18
|
+
export const PRODUCTION_MODE_NOT_LINKED = 'production mode needs a linked Reticle cloud account: run `reticle connect`, or set RETICLE_API_KEY';
|
|
19
|
+
/** Said when the daemon cannot mint a nonce here (no bridge in this process). */
|
|
20
|
+
const PRODUCTION_MODE_NO_BRIDGE = 'production mode needs the daemon that owns the bridge: start `reticle mcp` or the daemon, then lease again';
|
|
21
|
+
const TICKET_TIMEOUT_MS = 10_000;
|
|
22
|
+
/** The `production` argument of `reticle_lease { action: "acquire" }`. */
|
|
23
|
+
export const ProductionLeaseArgSchema = z.object({
|
|
24
|
+
workspaceId: z.string().min(1).max(128).describe('The workspace the service activates for.'),
|
|
25
|
+
scope: z
|
|
26
|
+
.nativeEnum(HelloScope)
|
|
27
|
+
.describe('observe: read only. act: also click and type, under the production policy.'),
|
|
28
|
+
intent: z
|
|
29
|
+
.string()
|
|
30
|
+
.max(2000)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('What this drive is meant to do, in words. It is the only thing that lifts a refused action: "invite a teammate" allows sending an invite.'),
|
|
33
|
+
});
|
|
34
|
+
export async function activateProduction(input) {
|
|
35
|
+
const parsed = ProductionLeaseArgSchema.safeParse(input.production);
|
|
36
|
+
if (!parsed.success) {
|
|
37
|
+
throw new Error('reticle_lease{action:"acquire"} production needs { workspaceId, scope: "observe" | "act" }');
|
|
38
|
+
}
|
|
39
|
+
const ask = parsed.data;
|
|
40
|
+
if (input.nonces === undefined || input.bridgePort === undefined) {
|
|
41
|
+
throw new Error(PRODUCTION_MODE_NO_BRIDGE);
|
|
42
|
+
}
|
|
43
|
+
const cloud = await input.cloud();
|
|
44
|
+
if (null === cloud)
|
|
45
|
+
throw new Error(PRODUCTION_MODE_NOT_LINKED);
|
|
46
|
+
const origin = new URL(input.url).origin;
|
|
47
|
+
const minted = input.nonces.mint({
|
|
48
|
+
origin,
|
|
49
|
+
production: {
|
|
50
|
+
scope: ask.scope,
|
|
51
|
+
origin,
|
|
52
|
+
...(ask.intent === undefined ? {} : { intent: ask.intent }),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const drive = minted.production;
|
|
56
|
+
if (drive === undefined)
|
|
57
|
+
throw new Error(PRODUCTION_MODE_NO_BRIDGE);
|
|
58
|
+
const ticket = await requestTicket(cloud, {
|
|
59
|
+
workspaceId: ask.workspaceId,
|
|
60
|
+
nonce: minted.nonce,
|
|
61
|
+
bridgePort: input.bridgePort,
|
|
62
|
+
scope: ask.scope,
|
|
63
|
+
// Told, never guessed: the ticket is bound to this origin and names this bridge. The bridge
|
|
64
|
+
// listens on loopback, which the loader accepts over ws://.
|
|
65
|
+
origin,
|
|
66
|
+
bridge: bridgeWsUrl(input.bridgePort, LOOPBACK_HOST),
|
|
67
|
+
}, input.fetch);
|
|
68
|
+
if (ticket.policy !== undefined)
|
|
69
|
+
drive.setPolicy(ticket.policy);
|
|
70
|
+
const target = new URL(input.url);
|
|
71
|
+
target.searchParams.set(PRODUCTION_ACTIVATE_PARAM, ticket.ticket);
|
|
72
|
+
return { url: target.toString(), drive, scope: ask.scope, expiresAt: minted.expiresAt };
|
|
73
|
+
}
|
|
74
|
+
async function requestTicket(cloud, request, doFetch = (url, init) => fetch(url, init)) {
|
|
75
|
+
const controller = new AbortController();
|
|
76
|
+
const timer = setTimeout(() => {
|
|
77
|
+
controller.abort();
|
|
78
|
+
}, TICKET_TIMEOUT_MS);
|
|
79
|
+
try {
|
|
80
|
+
const res = await doFetch(`${cloud.url.replace(/\/+$/, '')}${PRODUCTION_TICKET_PATH}`, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: { authorization: `Bearer ${cloud.apiKey}`, 'content-type': 'application/json' },
|
|
83
|
+
body: JSON.stringify(request),
|
|
84
|
+
signal: controller.signal,
|
|
85
|
+
});
|
|
86
|
+
if (404 === res.status)
|
|
87
|
+
throw new Error(PRODUCTION_MODE_NOT_OFFERED);
|
|
88
|
+
const body = await res.json().catch(() => undefined);
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
const reason = z.object({ error: z.string() }).safeParse(body);
|
|
91
|
+
throw new Error(`the server refused a production-mode ticket (HTTP ${String(res.status)})` +
|
|
92
|
+
(reason.success ? `: ${reason.data.error}` : ''));
|
|
93
|
+
}
|
|
94
|
+
const parsed = ProductionTicketResponseSchema.safeParse(body);
|
|
95
|
+
if (!parsed.success) {
|
|
96
|
+
throw new Error('the server answered the ticket request with something that is not a ticket');
|
|
97
|
+
}
|
|
98
|
+
return parsed.data;
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The lease as the readiness wait sees it, minus the zero-install injector. An injected SDK would
|
|
106
|
+
* connect with the machine's pairing token as a development session, outside the policy; a
|
|
107
|
+
* production page connects only through its own activated loader.
|
|
108
|
+
*/
|
|
109
|
+
export function leaseWithoutInjection(lease) {
|
|
110
|
+
return lease.isLoading === undefined ? {} : { isLoading: lease.isLoading };
|
|
111
|
+
}
|
|
112
|
+
/** `reticle_lease`'s `production` argument, described for the agent. */
|
|
113
|
+
export const PRODUCTION_LEASE_ARG = ProductionLeaseArgSchema.optional().describe('Drive a live site in production mode: mints a single-use session nonce, asks the linked Reticle service for an activation ticket, and opens the site with it. Every act then passes the production policy (docs/production-mode.md).');
|
|
114
|
+
/** What a production lease reports back. */
|
|
115
|
+
export const PRODUCTION_LEASE_OUTPUT = z
|
|
116
|
+
.object({ scope: z.string(), expiresAt: z.number() })
|
|
117
|
+
.optional()
|
|
118
|
+
.describe('Present on a production lease: its scope and when its nonce expires (epoch ms).');
|
|
119
|
+
/** Activate when the lease asked for production, else nothing. */
|
|
120
|
+
export function activationFor(ports, asked, url, root) {
|
|
121
|
+
if (asked === undefined)
|
|
122
|
+
return Promise.resolve(undefined);
|
|
123
|
+
return activateProduction({
|
|
124
|
+
production: asked,
|
|
125
|
+
url,
|
|
126
|
+
nonces: ports.nonces,
|
|
127
|
+
bridgePort: ports.bridgePort,
|
|
128
|
+
cloud: () => ports.linkedCloud?.(root) ?? Promise.resolve(null),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Guard a production lease's requests with its drive, and hand the readiness wait a lease it
|
|
133
|
+
* cannot inject into. A development lease passes through untouched.
|
|
134
|
+
*/
|
|
135
|
+
export async function guardedForProduction(pool, lease, activated) {
|
|
136
|
+
if (activated === undefined)
|
|
137
|
+
return lease;
|
|
138
|
+
const { drive } = activated;
|
|
139
|
+
await pool.guardLease(lease.sessionId, (method, url) => drive.refuseRequest(method, url));
|
|
140
|
+
return leaseWithoutInjection(lease);
|
|
141
|
+
}
|
|
142
|
+
/** The `production` field of the lease result. */
|
|
143
|
+
export function summary(activated) {
|
|
144
|
+
return activated === undefined
|
|
145
|
+
? {}
|
|
146
|
+
: { production: { scope: activated.scope, expiresAt: activated.expiresAt } };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Why an activated page never connected. The service redeems a ticket only for a browser signed in
|
|
150
|
+
* as the person who asked for it, and a leased browser starts signed out. The generic diagnosis
|
|
151
|
+
* blamed the page's CSP and said to allow the bridge for development only, which is wrong here.
|
|
152
|
+
*/
|
|
153
|
+
export function notActivatedHint(url) {
|
|
154
|
+
return (`${url} did not start production mode: no page connected with the session nonce. The ` +
|
|
155
|
+
'service redeems the activation ticket only for a browser signed in as you, in the workspace ' +
|
|
156
|
+
'the lease names, and a leased browser starts signed out. Release this lease, then acquire ' +
|
|
157
|
+
'again with your session cookies for this site in seedStorage { cookies }. When the browser is ' +
|
|
158
|
+
'signed in, the site did not load the SDK on the activated page (docs/production-mode.md).');
|
|
159
|
+
}
|
|
160
|
+
/** Playwright's name for Chrome's Local Network Access grant (local + loopback network). */
|
|
161
|
+
const LOCAL_NETWORK_ACCESS = 'local-network-access';
|
|
162
|
+
/**
|
|
163
|
+
* The permissions a lease is opened with. A production page on a public origin dials the bridge on
|
|
164
|
+
* loopback, which Chromium blocks unless a person grants Local Network Access, and a leased browser
|
|
165
|
+
* has no person. Granted on the lease's own origin only. Not a launch flag: a second
|
|
166
|
+
* `--disable-features` replaces the list Playwright launches with instead of adding to it.
|
|
167
|
+
*/
|
|
168
|
+
export function leasePermissions(asked, production) {
|
|
169
|
+
if (production === undefined)
|
|
170
|
+
return asked;
|
|
171
|
+
const granted = asked ?? [];
|
|
172
|
+
return granted.includes(LOCAL_NETWORK_ACCESS) ? granted : [...granted, LOCAL_NETWORK_ACCESS];
|
|
173
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { HelloScope, type CommandResult, type LiftableProductionRule, type ProductionPolicyEntry, type ReticleEvent } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* The refused actions an intent asks for in so many words. "Invite a teammate" asks for one;
|
|
4
|
+
* "she does not invite anybody" asks for none.
|
|
5
|
+
*/
|
|
6
|
+
export declare function rulesAsked(intent: string): LiftableProductionRule[];
|
|
7
|
+
/** The refused action a control's own words name, if any. */
|
|
8
|
+
export declare function controlRule(words: string): LiftableProductionRule | undefined;
|
|
9
|
+
/** The policy entry a request matches, if any. */
|
|
10
|
+
export declare function requestRule(policy: readonly ProductionPolicyEntry[], method: string, url: string, base?: string): LiftableProductionRule | undefined;
|
|
11
|
+
/** What a production nonce grants. Supplied by the local caller that mints it. */
|
|
12
|
+
export interface ProductionGrant {
|
|
13
|
+
scope: HelloScope;
|
|
14
|
+
/** The drive's declared intent, in words. It is what lifts a refused rule. */
|
|
15
|
+
intent?: string;
|
|
16
|
+
/** The service's refused requests. Absent: the default list. */
|
|
17
|
+
policy?: readonly ProductionPolicyEntry[];
|
|
18
|
+
/** The origin the drive was activated on. */
|
|
19
|
+
origin?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare class ProductionDrive {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(grant: ProductionGrant & {
|
|
24
|
+
expiresAt: number;
|
|
25
|
+
}, clock: () => number);
|
|
26
|
+
get scope(): HelloScope;
|
|
27
|
+
/** The service's refused requests, from the ticket. Set before the page opens. */
|
|
28
|
+
setPolicy(policy: readonly ProductionPolicyEntry[]): void;
|
|
29
|
+
/** A page may narrow its drive to observing. Nothing widens it again. */
|
|
30
|
+
narrowToObserve(): void;
|
|
31
|
+
/** Learn ids from what the drive created: a 2xx POST/PUT whose JSON body names an `id`. */
|
|
32
|
+
noteEvent(event: ReticleEvent): void;
|
|
33
|
+
/** The refusal for a request the page is about to send, or undefined to let it go. */
|
|
34
|
+
refuseRequest(method: string, url: string): string | undefined;
|
|
35
|
+
/** A refusal made outside a command, once. */
|
|
36
|
+
takePending(): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* The refusal for a command, or undefined to send it. `inspect` reads a ref's control from the
|
|
39
|
+
* page: what the control says about itself is evidence; what it does is decided here.
|
|
40
|
+
*/
|
|
41
|
+
refuseCommand(name: string, args: Record<string, unknown>, inspect: (ref: string) => Promise<unknown>): Promise<string | undefined>;
|
|
42
|
+
/**
|
|
43
|
+
* Send a command through the policy. A refusal comes back as a failed result naming the rule,
|
|
44
|
+
* never as a silent no-op; a request the act sent and the guard refused fails the act that sent it.
|
|
45
|
+
*/
|
|
46
|
+
gate(name: string, args: Record<string, unknown>, send: (name: string, args: Record<string, unknown>) => Promise<CommandResult>): Promise<CommandResult>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Judge a native (CDP) gesture, which reaches the page without `Session.command`. Throws the
|
|
50
|
+
* refusal so the gesture is never performed.
|
|
51
|
+
*/
|
|
52
|
+
export declare function assertNativeAllowed(drive: ProductionDrive | undefined, action: string, inspected: unknown): Promise<void>;
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { ActionType, DEFAULT_PRODUCTION_POLICY, EventType, HelloScope, MessageKind, ProductionRule, ReticleCommand, asRecord, asString, productionRefusal, } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* One production drive: what it was granted, what it created, and the rule that refuses an act.
|
|
4
|
+
*
|
|
5
|
+
* Enforced where the daemon dispatches a command (`Session.command`) and, on a leased browser, where
|
|
6
|
+
* the page's requests leave it. The page is never asked. Two things are recognised:
|
|
7
|
+
*
|
|
8
|
+
* - the control an act would press: its name, text, value, test id, link and form target;
|
|
9
|
+
* - the request it would send: method and path against the policy list.
|
|
10
|
+
*
|
|
11
|
+
* An action passes when the drive's declared intent names it, or when it touches only something
|
|
12
|
+
* this drive created (an id it saw come back from a 2xx create).
|
|
13
|
+
*/
|
|
14
|
+
/** Commands that read or move around. Everything else a page is told to do is an act. */
|
|
15
|
+
const READ_COMMANDS = new Set([
|
|
16
|
+
ReticleCommand.SNAPSHOT,
|
|
17
|
+
ReticleCommand.QUERY,
|
|
18
|
+
ReticleCommand.MATCH,
|
|
19
|
+
ReticleCommand.INSPECT,
|
|
20
|
+
ReticleCommand.ANIMATIONS,
|
|
21
|
+
ReticleCommand.NARRATE,
|
|
22
|
+
ReticleCommand.CAPABILITIES,
|
|
23
|
+
ReticleCommand.STATE_READ,
|
|
24
|
+
ReticleCommand.STORAGE_READ,
|
|
25
|
+
ReticleCommand.SCROLL,
|
|
26
|
+
ReticleCommand.SESSION_CONFIG,
|
|
27
|
+
ReticleCommand.PRESENTER,
|
|
28
|
+
ReticleCommand.IMPACT,
|
|
29
|
+
ReticleCommand.CAPTURE,
|
|
30
|
+
ReticleCommand.RENDER,
|
|
31
|
+
ReticleCommand.NAVIGATE,
|
|
32
|
+
ReticleCommand.REFRESH,
|
|
33
|
+
ReticleCommand.FLOWS,
|
|
34
|
+
ReticleCommand.FLOW_PROGRESS,
|
|
35
|
+
ReticleCommand.PLAN,
|
|
36
|
+
ReticleCommand.MARKERS,
|
|
37
|
+
]);
|
|
38
|
+
/** Acts that only move the pointer or the view. They press nothing. */
|
|
39
|
+
const LOOKING_ACTIONS = new Set([
|
|
40
|
+
ActionType.HOVER,
|
|
41
|
+
ActionType.FOCUS,
|
|
42
|
+
ActionType.BLUR,
|
|
43
|
+
ActionType.SCROLL,
|
|
44
|
+
ActionType.SCROLL_INTO_VIEW,
|
|
45
|
+
]);
|
|
46
|
+
/** Each refused action as words on a control, and as words in an intent. */
|
|
47
|
+
const RULE_WORDS = [
|
|
48
|
+
[
|
|
49
|
+
ProductionRule.CANCEL_SUBSCRIPTION,
|
|
50
|
+
/\bcancel\b.{0,40}\b(?:subscription|plan|trial|membership)\b|\bdowngrade\b|\/subscriptions?\b.*\bcancel/i,
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
ProductionRule.DELETE_WORKSPACE,
|
|
54
|
+
/\b(?:delete|remove|destroy|erase)\b.{0,40}\b(?:workspace|project|organi[sz]ation|org|account)s?\b/i,
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
ProductionRule.REVOKE_KEY,
|
|
58
|
+
/\b(?:revoke|rotate|regenerate|roll)\b.{0,40}\b(?:key|token|secret)s?\b|\brevoke\b/i,
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
ProductionRule.SEND_INVITE,
|
|
62
|
+
/\binvit(?:e|es|ed|ing|ation|ations)\b|\/invit|\bsend (?:an? )?(?:email|invite|invitation)s?\b|\bresend\b|\bemail (?:it|them|the)\b/i,
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
ProductionRule.CHANGE_ROLE,
|
|
66
|
+
/\bchange (?:the |their |a |his |her )?(?:member'?s? )?role\b|\bmake (?:an? |them )?(?:admin|owner)\b|\bpromote\b|\bdemote\b|\bset role\b|\btransfer ownership\b/i,
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
ProductionRule.PAYMENT,
|
|
70
|
+
/\b(?:pay|pays|paying|payment|purchase|buy|checkout|check out|subscribe|upgrade|place order|confirm order|add (?:a )?card)\b|\/(?:billing|checkout)\b/i,
|
|
71
|
+
],
|
|
72
|
+
];
|
|
73
|
+
/** A word that turns the action after it into one the intent forbids: "does not invite". */
|
|
74
|
+
const NEGATION = /\b(?:not|no|never|without|nor|avoid|don't|doesn't|won't)\b/i;
|
|
75
|
+
const NEGATION_REACH = 4;
|
|
76
|
+
/**
|
|
77
|
+
* The refused actions an intent asks for in so many words. "Invite a teammate" asks for one;
|
|
78
|
+
* "she does not invite anybody" asks for none.
|
|
79
|
+
*/
|
|
80
|
+
// ponytail: the same negation rule as `outwardActionsAsked` in core; one helper once both settle.
|
|
81
|
+
export function rulesAsked(intent) {
|
|
82
|
+
const asked = [];
|
|
83
|
+
for (const [rule, pattern] of RULE_WORDS) {
|
|
84
|
+
for (const match of intent.matchAll(new RegExp(pattern.source, 'gi'))) {
|
|
85
|
+
const before = intent.slice(0, match.index).split(/\s+/).slice(-NEGATION_REACH).join(' ');
|
|
86
|
+
if (!NEGATION.test(before)) {
|
|
87
|
+
asked.push(rule);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return asked;
|
|
93
|
+
}
|
|
94
|
+
/** The refused action a control's own words name, if any. */
|
|
95
|
+
export function controlRule(words) {
|
|
96
|
+
const text = words.replace(/[_-]+/g, ' ');
|
|
97
|
+
return RULE_WORDS.find(([, pattern]) => pattern.test(text) || pattern.test(words))?.[0];
|
|
98
|
+
}
|
|
99
|
+
/** The policy entry a request matches, if any. */
|
|
100
|
+
export function requestRule(policy, method, url, base) {
|
|
101
|
+
let pathname;
|
|
102
|
+
try {
|
|
103
|
+
pathname = new URL(url, base ?? 'http://localhost').pathname;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
const verb = method.toUpperCase();
|
|
109
|
+
return policy.find((entry) => entry.methods.some((m) => m.toUpperCase() === verb) &&
|
|
110
|
+
new RegExp(entry.path, 'i').test(pathname))?.rule;
|
|
111
|
+
}
|
|
112
|
+
/** The text a control shows about itself, from an INSPECT reply or an act's own target. */
|
|
113
|
+
function controlWords(descriptor) {
|
|
114
|
+
const d = asRecord(descriptor);
|
|
115
|
+
return ['name', 'text', 'label', 'value', 'testid', 'href', 'formAction']
|
|
116
|
+
.map((key) => asString(d[key]) ?? '')
|
|
117
|
+
.join(' ');
|
|
118
|
+
}
|
|
119
|
+
/** The shortest created id that counts. Shorter strings match too much by accident. */
|
|
120
|
+
const MIN_CREATED_ID_LENGTH = 4;
|
|
121
|
+
export class ProductionDrive {
|
|
122
|
+
#scope;
|
|
123
|
+
#intentAllows;
|
|
124
|
+
#policy;
|
|
125
|
+
#origin;
|
|
126
|
+
#expiresAt;
|
|
127
|
+
#clock;
|
|
128
|
+
/** Ids this drive saw come back from its own creates. Deleting those is allowed. */
|
|
129
|
+
#created = new Set();
|
|
130
|
+
/** Refusals made outside a command (a blocked request), told to the next act. */
|
|
131
|
+
#pending;
|
|
132
|
+
constructor(grant, clock) {
|
|
133
|
+
this.#scope = grant.scope;
|
|
134
|
+
this.#intentAllows = new Set(rulesAsked(grant.intent ?? ''));
|
|
135
|
+
this.#policy = grant.policy ?? DEFAULT_PRODUCTION_POLICY;
|
|
136
|
+
this.#origin = grant.origin;
|
|
137
|
+
this.#expiresAt = grant.expiresAt;
|
|
138
|
+
this.#clock = clock;
|
|
139
|
+
}
|
|
140
|
+
get scope() {
|
|
141
|
+
return this.#scope;
|
|
142
|
+
}
|
|
143
|
+
/** The service's refused requests, from the ticket. Set before the page opens. */
|
|
144
|
+
setPolicy(policy) {
|
|
145
|
+
this.#policy = policy;
|
|
146
|
+
}
|
|
147
|
+
/** A page may narrow its drive to observing. Nothing widens it again. */
|
|
148
|
+
narrowToObserve() {
|
|
149
|
+
this.#scope = HelloScope.OBSERVE;
|
|
150
|
+
}
|
|
151
|
+
/** Learn ids from what the drive created: a 2xx POST/PUT whose JSON body names an `id`. */
|
|
152
|
+
noteEvent(event) {
|
|
153
|
+
if (event.type !== EventType.NET_REQUEST)
|
|
154
|
+
return;
|
|
155
|
+
const data = event.data;
|
|
156
|
+
const method = (asString(data['method']) ?? '').toUpperCase();
|
|
157
|
+
const status = data['status'];
|
|
158
|
+
if ('number' !== typeof status || status < 200 || status > 299)
|
|
159
|
+
return;
|
|
160
|
+
// A refused request that SUCCEEDED got past every guard (no leased browser to stop it). The
|
|
161
|
+
// drive cannot take it back; it stops acting and says why on the next act.
|
|
162
|
+
const url = asString(data['url']) ?? '';
|
|
163
|
+
const rule = requestRule(this.#policy, method, url, this.#origin);
|
|
164
|
+
if (rule !== undefined && !this.#lifted(rule, url))
|
|
165
|
+
this.#pending ??= productionRefusal(rule);
|
|
166
|
+
if (method !== 'POST' && method !== 'PUT')
|
|
167
|
+
return;
|
|
168
|
+
const body = asString(data['responseBody']);
|
|
169
|
+
if (body === undefined)
|
|
170
|
+
return;
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = JSON.parse(body);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const top = asRecord(parsed);
|
|
179
|
+
const candidates = [top['id'], ...Object.values(top).map((v) => asRecord(v)['id'])];
|
|
180
|
+
for (const id of candidates) {
|
|
181
|
+
const text = 'number' === typeof id ? String(id) : asString(id);
|
|
182
|
+
if (text !== undefined && MIN_CREATED_ID_LENGTH <= text.length)
|
|
183
|
+
this.#created.add(text);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
#createdHere(text) {
|
|
187
|
+
for (const id of this.#created)
|
|
188
|
+
if (text.includes(id))
|
|
189
|
+
return true;
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
#lifted(rule, touched) {
|
|
193
|
+
return this.#intentAllows.has(rule) || this.#createdHere(touched);
|
|
194
|
+
}
|
|
195
|
+
/** The refusal for a request the page is about to send, or undefined to let it go. */
|
|
196
|
+
refuseRequest(method, url) {
|
|
197
|
+
const rule = requestRule(this.#policy, method, url, this.#origin);
|
|
198
|
+
if (rule === undefined || this.#lifted(rule, url))
|
|
199
|
+
return undefined;
|
|
200
|
+
const refusal = productionRefusal(rule);
|
|
201
|
+
this.#pending = refusal;
|
|
202
|
+
return refusal;
|
|
203
|
+
}
|
|
204
|
+
/** A refusal made outside a command, once. */
|
|
205
|
+
takePending() {
|
|
206
|
+
const pending = this.#pending;
|
|
207
|
+
this.#pending = undefined;
|
|
208
|
+
return pending;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The refusal for a command, or undefined to send it. `inspect` reads a ref's control from the
|
|
212
|
+
* page: what the control says about itself is evidence; what it does is decided here.
|
|
213
|
+
*/
|
|
214
|
+
async refuseCommand(name, args, inspect) {
|
|
215
|
+
if (this.#expiresAt <= this.#clock())
|
|
216
|
+
return productionRefusal(ProductionRule.EXPIRED);
|
|
217
|
+
if (name === ReticleCommand.NAVIGATE)
|
|
218
|
+
return this.#refuseNavigation(asString(args['url']));
|
|
219
|
+
if (READ_COMMANDS.has(name))
|
|
220
|
+
return undefined;
|
|
221
|
+
const pending = this.takePending();
|
|
222
|
+
if (pending !== undefined)
|
|
223
|
+
return pending;
|
|
224
|
+
if (this.#scope === HelloScope.OBSERVE)
|
|
225
|
+
return productionRefusal(ProductionRule.OBSERVE_ONLY);
|
|
226
|
+
const steps = name === ReticleCommand.ACT_SEQUENCE
|
|
227
|
+
? (Array.isArray(args['steps']) ? args['steps'] : []).map(asRecord)
|
|
228
|
+
: name === ReticleCommand.ACT
|
|
229
|
+
? [args]
|
|
230
|
+
: [];
|
|
231
|
+
for (const step of steps) {
|
|
232
|
+
const refusal = await this.#refuseStep(step, inspect);
|
|
233
|
+
if (refusal !== undefined)
|
|
234
|
+
return refusal;
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Send a command through the policy. A refusal comes back as a failed result naming the rule,
|
|
240
|
+
* never as a silent no-op; a request the act sent and the guard refused fails the act that sent it.
|
|
241
|
+
*/
|
|
242
|
+
async gate(name, args, send) {
|
|
243
|
+
const inspect = async (ref) => (await send(ReticleCommand.INSPECT, { ref })).result;
|
|
244
|
+
const refusal = await this.refuseCommand(name, args, inspect);
|
|
245
|
+
if (refusal !== undefined)
|
|
246
|
+
return refusedCommand(refusal);
|
|
247
|
+
const result = await send(name, args);
|
|
248
|
+
if (READ_COMMANDS.has(name))
|
|
249
|
+
return result;
|
|
250
|
+
const late = this.takePending();
|
|
251
|
+
return late === undefined ? result : refusedCommand(late);
|
|
252
|
+
}
|
|
253
|
+
#refuseNavigation(url) {
|
|
254
|
+
if (url === undefined || this.#origin === undefined)
|
|
255
|
+
return undefined;
|
|
256
|
+
try {
|
|
257
|
+
if (new URL(url, this.#origin).origin === this.#origin)
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
return productionRefusal(ProductionRule.OFF_ORIGIN);
|
|
264
|
+
}
|
|
265
|
+
async #refuseStep(step, inspect) {
|
|
266
|
+
const action = asString(step['action']) ?? ActionType.CLICK;
|
|
267
|
+
if (LOOKING_ACTIONS.has(action))
|
|
268
|
+
return undefined;
|
|
269
|
+
const ref = asString(step['ref']);
|
|
270
|
+
const control = ref === undefined ? asRecord(step['target']) : await inspect(ref);
|
|
271
|
+
const words = controlWords(control);
|
|
272
|
+
// A role picker says "Admin" in its value and "Role" in its name: selecting one changes a role.
|
|
273
|
+
const picksRole = action === ActionType.SELECT && /\brole\b/i.test(words);
|
|
274
|
+
const formAction = asString(asRecord(control)['formAction']);
|
|
275
|
+
const rule = (picksRole ? ProductionRule.CHANGE_ROLE : controlRule(words)) ??
|
|
276
|
+
(formAction === undefined
|
|
277
|
+
? undefined
|
|
278
|
+
: requestRule(this.#policy, 'POST', formAction, this.#origin));
|
|
279
|
+
if (rule === undefined || this.#lifted(rule, words))
|
|
280
|
+
return undefined;
|
|
281
|
+
return productionRefusal(rule);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/** A refusal in the shape every command result has: `ok: false`, the rule in `error`. */
|
|
285
|
+
function refusedCommand(error) {
|
|
286
|
+
return { kind: MessageKind.COMMAND_RESULT, id: REFUSED_ID, ok: false, error };
|
|
287
|
+
}
|
|
288
|
+
const REFUSED_ID = 'production-policy';
|
|
289
|
+
/** The ref a native gesture is judged under: the control was already inspected, so none is read. */
|
|
290
|
+
const NATIVE_REF = 'native';
|
|
291
|
+
/**
|
|
292
|
+
* Judge a native (CDP) gesture, which reaches the page without `Session.command`. Throws the
|
|
293
|
+
* refusal so the gesture is never performed.
|
|
294
|
+
*/
|
|
295
|
+
export async function assertNativeAllowed(drive, action, inspected) {
|
|
296
|
+
if (drive === undefined)
|
|
297
|
+
return;
|
|
298
|
+
const refusal = await drive.refuseCommand(ReticleCommand.ACT, { ref: NATIVE_REF, action }, () => Promise.resolve(inspected));
|
|
299
|
+
if (refusal !== undefined)
|
|
300
|
+
throw new Error(refusal);
|
|
301
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HelloEnv, HelloScope } from '@reticlehq/core';
|
|
2
|
+
import type { ProductionDrive } from './production-drive.js';
|
|
3
|
+
/**
|
|
4
|
+
* Which HELLO may open a production session, and with what scope.
|
|
5
|
+
*
|
|
6
|
+
* The nonce decides, never the page. A page that says `production` without a production nonce is
|
|
7
|
+
* refused, because the pairing token belongs to a developer's machine and must not open a drive on a
|
|
8
|
+
* live site. A production nonce makes the session production even when the page says nothing: a page
|
|
9
|
+
* cannot talk its way out of the policy. The page may narrow `act` to `observe`, and never widen.
|
|
10
|
+
*/
|
|
11
|
+
/** The close reason for a refused production HELLO. Under the 123-byte close-frame limit. */
|
|
12
|
+
export declare const PRODUCTION_HELLO_REFUSED = "production mode refused \u2014 it needs a live production session nonce and a scope";
|
|
13
|
+
export type HelloAdmission = {
|
|
14
|
+
ok: true;
|
|
15
|
+
production?: ProductionDrive;
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
reason: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function admitHello(hello: {
|
|
21
|
+
env?: HelloEnv | undefined;
|
|
22
|
+
scope?: HelloScope | undefined;
|
|
23
|
+
}, granted: ProductionDrive | undefined): HelloAdmission;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HelloEnv, HelloScope } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* Which HELLO may open a production session, and with what scope.
|
|
4
|
+
*
|
|
5
|
+
* The nonce decides, never the page. A page that says `production` without a production nonce is
|
|
6
|
+
* refused, because the pairing token belongs to a developer's machine and must not open a drive on a
|
|
7
|
+
* live site. A production nonce makes the session production even when the page says nothing: a page
|
|
8
|
+
* cannot talk its way out of the policy. The page may narrow `act` to `observe`, and never widen.
|
|
9
|
+
*/
|
|
10
|
+
/** The close reason for a refused production HELLO. Under the 123-byte close-frame limit. */
|
|
11
|
+
export const PRODUCTION_HELLO_REFUSED = 'production mode refused — it needs a live production session nonce and a scope';
|
|
12
|
+
export function admitHello(hello, granted) {
|
|
13
|
+
const claimsProduction = hello.env === HelloEnv.PRODUCTION;
|
|
14
|
+
if (granted === undefined) {
|
|
15
|
+
return claimsProduction ? { ok: false, reason: PRODUCTION_HELLO_REFUSED } : { ok: true };
|
|
16
|
+
}
|
|
17
|
+
if (claimsProduction && hello.scope === undefined) {
|
|
18
|
+
return { ok: false, reason: PRODUCTION_HELLO_REFUSED };
|
|
19
|
+
}
|
|
20
|
+
if (hello.scope === HelloScope.OBSERVE)
|
|
21
|
+
granted.narrowToObserve();
|
|
22
|
+
return { ok: true, production: granted };
|
|
23
|
+
}
|