@reticlehq/server 2.13.0 → 2.14.0
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 +8 -18
- package/bin/reticle.js +3 -0
- package/dist/bridge/auth-failure-reason.js +12 -1
- package/dist/bridge/bridge-security.d.ts +8 -0
- package/dist/bridge/bridge-security.js +6 -1
- package/dist/bridge/bridge.d.ts +3 -0
- package/dist/bridge/bridge.js +23 -1
- package/dist/bridge/bridge.test-harness.d.ts +10 -1
- package/dist/bridge/bridge.test-harness.js +7 -1
- package/dist/bridge/pairing-token.d.ts +17 -0
- package/dist/bridge/pairing-token.js +26 -0
- package/dist/bridge/token-auth.d.ts +7 -0
- package/dist/bridge/token-auth.js +7 -1
- package/dist/capsule/capsule-store.d.ts +4 -4
- package/dist/capsule/capsule.d.ts +1 -1
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +21 -1
- package/dist/capsule/causal-summary.js +8 -6
- package/dist/capsule/divergence.d.ts +1 -1
- package/dist/capsule/divergence.js +26 -10
- package/dist/chromium-launch-options.d.ts +2 -1
- package/dist/cli/adhoc-verdict.d.ts +58 -0
- package/dist/cli/adhoc-verdict.js +115 -0
- package/dist/cli/chromium-hint.d.ts +18 -4
- package/dist/cli/chromium-hint.js +54 -6
- package/dist/cli/cli-doctor.js +1 -2
- package/dist/cli/cli-flow-commands.d.ts +16 -2
- package/dist/cli/cli-flow-commands.js +28 -9
- package/dist/cli/cli-parse-feedback.js +1 -1
- package/dist/cli/cli-parse.d.ts +2 -1
- package/dist/cli/cli-parse.js +38 -0
- package/dist/cli/cli-port.d.ts +5 -13
- package/dist/cli/cli-port.js +5 -38
- package/dist/cli/cli-update-commands.js +4 -4
- package/dist/cli/cli-verify.d.ts +24 -7
- package/dist/cli/cli-verify.js +87 -13
- package/dist/cli/cloud-keystore.d.ts +2 -1
- package/dist/cli/cloud-login.d.ts +2 -1
- package/dist/cli/cloud-session.d.ts +2 -1
- package/dist/cli/cloud-session.js +1 -1
- package/dist/cli/daemon-start-options.d.ts +2 -1
- package/dist/cli/device-flow.d.ts +2 -2
- package/dist/cli/doctor-daemon-line.d.ts +2 -1
- package/dist/cli/doctor-project-line.d.ts +2 -1
- package/dist/cli/doctor-rows.d.ts +2 -1
- package/dist/cli/drive-attach.d.ts +4 -3
- package/dist/cli/gate-hook-message.d.ts +2 -1
- package/dist/cli/status-next-action.js +10 -11
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +21 -9
- package/dist/cloud/account-state.d.ts +20 -0
- package/dist/cloud/account-state.js +65 -0
- package/dist/cloud/cloud-sync.d.ts +46 -3
- package/dist/cloud/cloud-sync.js +116 -0
- package/dist/cloud/sync-cycle.d.ts +5 -4
- package/dist/cloud/sync-cycle.js +1 -1
- package/dist/cloud/sync-daemon.d.ts +3 -19
- package/dist/cloud/sync-daemon.js +2 -2
- package/dist/crawl/crawl-tools.js +1 -1
- package/dist/crawl/crawl.d.ts +5 -5
- package/dist/crawl/crawl.js +138 -4
- package/dist/daemon/daemon-resilience.d.ts +3 -2
- package/dist/daemon/daemon-resilience.js +43 -4
- package/dist/daemon/daemon-resolve.d.ts +0 -9
- package/dist/daemon/daemon-resolve.js +2 -2
- package/dist/daemon/dev-servers.d.ts +1 -1
- package/dist/daemon/dev-servers.js +1 -1
- package/dist/daemon/loopback-alias.d.ts +2 -1
- package/dist/daemon/startup-failure.d.ts +11 -0
- package/dist/daemon/startup-failure.js +46 -0
- package/dist/events/contradictions.d.ts +10 -16
- package/dist/events/contradictions.js +149 -17
- package/dist/events/declared.d.ts +13 -1
- package/dist/events/declared.js +59 -3
- package/dist/events/echo-mismatch.js +75 -6
- package/dist/events/lineage.d.ts +52 -0
- package/dist/events/lineage.js +122 -0
- package/dist/events/net-evidence.d.ts +21 -0
- package/dist/events/net-evidence.js +81 -0
- package/dist/events/predicate-console.d.ts +13 -0
- package/dist/events/predicate-console.js +138 -0
- package/dist/events/predicate-element.d.ts +13 -0
- package/dist/events/predicate-element.js +214 -0
- package/dist/events/predicate-eval.d.ts +15 -3
- package/dist/events/predicate-eval.js +128 -110
- package/dist/events/predicate-parse.js +34 -1
- package/dist/events/predicate-request-body.d.ts +50 -0
- package/dist/events/predicate-request-body.js +120 -0
- package/dist/events/predicate-route.d.ts +21 -1
- package/dist/events/predicate-route.js +149 -21
- package/dist/events/predicate-schema.d.ts +8 -1
- package/dist/events/predicate-schema.js +34 -0
- package/dist/events/predicate.d.ts +2 -0
- package/dist/events/predicate.js +52 -159
- package/dist/events/split-text-miss.d.ts +6 -4
- package/dist/events/split-text-miss.js +12 -5
- package/dist/flows/affected-tools.js +22 -9
- package/dist/flows/annotate.d.ts +2 -1
- package/dist/flows/annotate.js +19 -1
- package/dist/flows/coverage.d.ts +2 -2
- package/dist/flows/flake.d.ts +3 -3
- package/dist/flows/flow-expect-grammar.d.ts +33 -0
- package/dist/flows/flow-expect-grammar.js +155 -0
- package/dist/flows/flow-replay-run.d.ts +1 -1
- package/dist/flows/flow-replay-run.js +28 -15
- package/dist/flows/flow-replay.d.ts +10 -0
- package/dist/flows/flow-replay.js +57 -22
- package/dist/flows/flow-secret-field.d.ts +14 -0
- package/dist/flows/flow-secret-field.js +22 -0
- package/dist/flows/flow-step-runners.d.ts +1 -0
- package/dist/flows/flow-step-runners.js +91 -34
- package/dist/flows/flow-success.js +22 -0
- package/dist/flows/flow-tools.js +27 -8
- package/dist/flows/flow-upload-resolve.d.ts +23 -0
- package/dist/flows/flow-upload-resolve.js +46 -0
- package/dist/flows/flows.d.ts +15 -0
- package/dist/flows/flows.js +17 -14
- package/dist/flows/git-changed.d.ts +23 -4
- package/dist/flows/git-changed.js +32 -6
- package/dist/flows/heal-run.js +2 -2
- package/dist/flows/predicate-to-expect.d.ts +3 -2
- package/dist/flows/predicate-to-expect.js +11 -5
- package/dist/flows/recording-backtrack.d.ts +24 -0
- package/dist/flows/recording-backtrack.js +73 -0
- package/dist/flows/recordings.d.ts +6 -0
- package/dist/flows/replay-halt.d.ts +26 -0
- package/dist/flows/replay-halt.js +30 -0
- package/dist/flows/replay.d.ts +0 -8
- package/dist/flows/replay.js +15 -3
- package/dist/flows/suite-flakes.d.ts +2 -1
- package/dist/flows/verify-change-tools.js +16 -4
- package/dist/honesty/already-true.d.ts +14 -0
- package/dist/honesty/already-true.js +47 -4
- package/dist/honesty/blind-spots.d.ts +3 -1
- package/dist/honesty/blind-spots.js +24 -1
- package/dist/honesty/body-capture-remedy.d.ts +42 -0
- package/dist/honesty/body-capture-remedy.js +95 -0
- package/dist/honesty/feature-capture.d.ts +3 -2
- package/dist/honesty/feature-capture.js +1 -1
- package/dist/honesty/gap-ledger.d.ts +1 -1
- package/dist/honesty/gap-ledger.js +1 -1
- package/dist/honesty/instrumentation-gaps.d.ts +8 -1
- package/dist/honesty/instrumentation-gaps.js +12 -2
- package/dist/honesty/observability.d.ts +4 -3
- package/dist/honesty/tool-hit-rate.d.ts +3 -2
- package/dist/honesty/uncaptured-bodies.d.ts +3 -1
- package/dist/honesty/uncaptured-bodies.js +6 -2
- package/dist/honesty/unsettled.d.ts +9 -0
- package/dist/honesty/unsettled.js +45 -0
- package/dist/honesty/verified.d.ts +32 -1
- package/dist/honesty/verified.js +37 -10
- package/dist/impact/impact-store.d.ts +5 -14
- package/dist/impact/impact-store.js +49 -6
- package/dist/index.js +1 -1
- package/dist/input/network-detail.d.ts +26 -1
- package/dist/input/network-detail.js +44 -12
- package/dist/input/network-mock-tools.js +24 -17
- package/dist/input/real-input.js +1 -1
- package/dist/input/viewport-tools.js +25 -13
- package/dist/intent/inline-intent.d.ts +0 -10
- package/dist/intent/inline-intent.js +1 -1
- package/dist/intent/intent-shard-store.d.ts +3 -2
- package/dist/intent/intent-shard-store.js +1 -1
- package/dist/intent/intent-shard.d.ts +14 -14
- package/dist/intent/intent-shard.js +4 -4
- package/dist/intent/intent-store.d.ts +1 -1
- package/dist/intent/intent-store.js +1 -1
- package/dist/intent/intent-subject.d.ts +2 -1
- package/dist/intent/open-intents.d.ts +3 -2
- package/dist/license/license.d.ts +4 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/attach-memory.d.ts +2 -1
- package/dist/mcp/client-identity.d.ts +2 -1
- package/dist/mcp/mcp-outage.d.ts +1 -1
- package/dist/mcp/mcp-outage.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +3 -1
- package/dist/mcp/mcp-proxy.js +32 -7
- package/dist/mcp/mcp.js +3 -1
- package/dist/mcp/proxy-handshake.d.ts +2 -1
- package/dist/mcp/proxy-idle-exit.d.ts +45 -0
- package/dist/mcp/proxy-idle-exit.js +94 -0
- package/dist/mcp/server-instructions.d.ts +2 -1
- package/dist/memory/project-memory.d.ts +2 -3
- package/dist/memory/project-memory.js +1 -1
- package/dist/oracles/propose-consequences.js +5 -5
- package/dist/oracles/self-instrument.d.ts +2 -2
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/platform.d.ts +3 -5
- package/dist/platform.js +3 -5
- package/dist/pool/browser-pool.d.ts +92 -0
- package/dist/pool/browser-pool.js +119 -5
- package/dist/pool/playwright-launcher.js +29 -2
- package/dist/pool/storage-seed.d.ts +23 -0
- package/dist/pool/storage-seed.js +198 -0
- package/dist/project/artifact-root.d.ts +2 -1
- package/dist/project/memory-fs.d.ts +2 -1
- package/dist/project/reticle-dir.js +1 -1
- package/dist/project/session-root.d.ts +8 -0
- package/dist/project/session-root.js +9 -2
- package/dist/runs/reticle-runner.d.ts +14 -1
- package/dist/runs/reticle-runner.js +39 -2
- package/dist/session/attachment-history.d.ts +2 -1
- package/dist/session/fake-session.d.ts +37 -0
- package/dist/session/fake-session.js +81 -0
- package/dist/session/lease-visibility.d.ts +2 -1
- package/dist/session/live-control-tools.js +6 -0
- package/dist/session/no-session-diagnosis.d.ts +17 -1
- package/dist/session/no-session-diagnosis.js +32 -6
- package/dist/session/no-session-next-action.d.ts +14 -0
- package/dist/session/no-session-next-action.js +18 -0
- package/dist/session/no-session-watch.js +46 -3
- package/dist/session/registered-projects.d.ts +54 -0
- package/dist/session/registered-projects.js +73 -0
- package/dist/session/review-store.d.ts +2 -1
- package/dist/session/session-health.d.ts +62 -1
- package/dist/session/session-health.js +82 -2
- package/dist/session/session-manager.d.ts +11 -1
- package/dist/session/session-manager.js +18 -3
- package/dist/session/session-replaced.d.ts +24 -0
- package/dist/session/session-replaced.js +29 -0
- package/dist/session/session-successor.js +20 -4
- package/dist/session/session.d.ts +13 -5
- package/dist/session/session.js +31 -23
- package/dist/setup/agent-configs.d.ts +3 -2
- package/dist/setup/agent-writer.d.ts +3 -2
- package/dist/setup/approval-migration.d.ts +2 -1
- package/dist/setup/approval-migration.js +1 -1
- package/dist/setup/auto-approve.d.ts +11 -19
- package/dist/setup/auto-approve.js +15 -12
- package/dist/{init → setup}/confirm.js +1 -1
- package/dist/setup/desktop-shape.d.ts +3 -2
- package/dist/setup/dev-server-wait.d.ts +8 -0
- package/dist/setup/dev-server-wait.js +3 -1
- package/dist/setup/drive-agent.d.ts +2 -3
- package/dist/setup/drive-agent.js +1 -1
- package/dist/setup/drive-plan.d.ts +3 -2
- package/dist/setup/ensure-daemon.d.ts +2 -1
- package/dist/setup/existing-app.d.ts +12 -0
- package/dist/setup/existing-app.js +13 -0
- package/dist/setup/init-host.d.ts +11 -0
- package/dist/setup/init-host.js +26 -0
- package/dist/setup/init-runtime.d.ts +4 -10
- package/dist/setup/init-runtime.js +23 -16
- package/dist/setup/license-key.d.ts +2 -1
- package/dist/setup/listeners.d.ts +2 -1
- package/dist/setup/loopback-probe-urls.d.ts +18 -0
- package/dist/setup/loopback-probe-urls.js +52 -0
- package/dist/setup/node-effects.d.ts +3 -0
- package/dist/setup/node-effects.js +26 -11
- package/dist/setup/page-probe.d.ts +5 -0
- package/dist/setup/relaunch.d.ts +2 -1
- package/dist/setup/run-setup.d.ts +5 -1
- package/dist/setup/run-setup.js +155 -48
- package/dist/setup/setup-command.d.ts +3 -2
- package/dist/setup/setup-command.js +8 -0
- package/dist/setup/setup-options.d.ts +5 -3
- package/dist/setup/setup-options.js +3 -2
- package/dist/setup/wait-progress.d.ts +28 -0
- package/dist/setup/wait-progress.js +43 -0
- package/dist/telemetry/app-instrumented.d.ts +2 -1
- package/dist/telemetry/app-instrumented.js +1 -1
- package/dist/telemetry/automation-hint.d.ts +1 -1
- package/dist/telemetry/automation-hint.js +1 -1
- package/dist/telemetry/cli-telemetry.js +1 -1
- package/dist/telemetry/connect-failure.d.ts +1 -1
- package/dist/telemetry/connect-failure.js +1 -1
- package/dist/telemetry/crash-cause.d.ts +3 -2
- package/dist/telemetry/crash-cause.js +1 -1
- package/dist/telemetry/daemon-telemetry.d.ts +3 -2
- package/dist/telemetry/daemon-telemetry.js +1 -1
- package/dist/telemetry/error-class.d.ts +3 -2
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-cli.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +3 -2
- package/dist/telemetry/feedback-context.js +5 -4
- package/dist/telemetry/feedback-local.d.ts +3 -2
- package/dist/telemetry/feedback.d.ts +4 -3
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/git-facts.d.ts +2 -2
- package/dist/telemetry/git-facts.js +1 -1
- package/dist/telemetry/identify.d.ts +1 -1
- package/dist/telemetry/identify.js +1 -1
- package/dist/telemetry/init-telemetry.d.ts +2 -14
- package/dist/telemetry/init-telemetry.js +4 -13
- package/dist/telemetry/install-source.d.ts +7 -13
- package/dist/telemetry/install-source.js +7 -29
- package/dist/telemetry/license-activation.d.ts +3 -2
- package/dist/telemetry/license-activation.js +1 -1
- package/dist/telemetry/machine-snapshot.d.ts +1 -1
- package/dist/telemetry/mcp-connection.js +1 -1
- package/dist/telemetry/project-profile.d.ts +0 -14
- package/dist/telemetry/project-profile.js +1 -1
- package/dist/telemetry/proxy-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +1 -1
- package/dist/telemetry/telemetry.d.ts +1 -17
- package/dist/telemetry/telemetry.js +4 -4
- package/dist/telemetry/tool-refused.d.ts +1 -1
- package/dist/telemetry/tool-refused.js +1 -1
- package/dist/tools/act-observation.d.ts +3 -2
- package/dist/tools/act-preflight.js +8 -3
- package/dist/tools/act-target.d.ts +4 -0
- package/dist/tools/act-target.js +14 -5
- package/dist/tools/act-tools.d.ts +0 -2
- package/dist/tools/act-tools.js +45 -15
- package/dist/tools/assert-source.d.ts +30 -0
- package/dist/tools/assert-source.js +21 -0
- package/dist/tools/assert-verdict.d.ts +1 -1
- package/dist/tools/assert-verdict.js +14 -4
- package/dist/tools/browser-tools.js +28 -8
- package/dist/tools/complete-snapshot.d.ts +3 -2
- package/dist/tools/dynamic-tools.js +8 -0
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +23 -0
- package/dist/tools/feedback-tools.js +1 -1
- package/dist/tools/invoke-tool.js +2 -0
- package/dist/tools/lease-tools.d.ts +48 -20
- package/dist/tools/lease-tools.js +311 -76
- package/dist/tools/lineage-tools.d.ts +14 -0
- package/dist/tools/lineage-tools.js +64 -0
- package/dist/tools/merge-tools.js +1 -3
- package/dist/tools/navigate-arrival.d.ts +45 -1
- package/dist/tools/navigate-arrival.js +39 -6
- package/dist/tools/navigate-result.d.ts +8 -1
- package/dist/tools/navigate-result.js +14 -4
- package/dist/tools/numeric-bounds.d.ts +0 -11
- package/dist/tools/numeric-bounds.js +4 -4
- package/dist/tools/observe-tools.js +22 -26
- package/dist/tools/observed-nothing.d.ts +9 -0
- package/dist/tools/observed-nothing.js +2 -1
- package/dist/tools/read-tools.js +12 -17
- package/dist/tools/real-input-attempt.d.ts +14 -6
- package/dist/tools/real-input-attempt.js +75 -18
- package/dist/tools/reconcile-tools.js +2 -1
- package/dist/tools/reload-result.d.ts +6 -1
- package/dist/tools/reload-result.js +7 -2
- package/dist/tools/resolve-target.d.ts +7 -1
- package/dist/tools/resolve-target.js +72 -9
- package/dist/tools/settle-in-flight.d.ts +1 -1
- package/dist/tools/settle-in-flight.js +9 -2
- package/dist/tools/snapshot-delta.d.ts +24 -1
- package/dist/tools/snapshot-delta.js +66 -7
- package/dist/tools/tool-names.d.ts +1 -0
- package/dist/tools/tool-names.js +1 -0
- package/dist/tools/tool-surface.js +20 -2
- package/dist/tools/tools.js +120 -11
- package/dist/tools/verify-next-baton.d.ts +2 -1
- package/dist/update/sdk-sync.d.ts +1 -1
- package/dist/update/sdk-sync.js +1 -1
- package/dist/update/updater.js +1 -1
- package/dist/version/sdk-fix.d.ts +2 -1
- package/dist/version/version-skew.d.ts +0 -8
- package/dist/version/version-skew.js +1 -1
- package/docs/README.md +8 -6
- package/docs/actions.mdx +1 -1
- package/docs/agent-cheatsheet.md +4 -2
- package/docs/architecture.md +1 -1
- package/docs/cli/mcp.mdx +3 -1
- package/docs/cli/watch.mdx +1 -1
- package/docs/cli.mdx +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +8 -3
- package/docs/faq.mdx +9 -0
- package/docs/first-drive.md +136 -0
- package/docs/frameworks.mdx +29 -7
- package/docs/gate-plan.md +2 -2
- package/docs/gates.md +19 -11
- package/docs/getting-started.md +7 -1
- package/docs/http-transport.md +164 -0
- package/docs/index.mdx +5 -3
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +15 -3
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +2 -0
- package/docs/system-map.md +2 -2
- package/docs/telemetry-contract.md +4 -4
- package/docs/telemetry-events.mdx +1 -1
- package/docs/telemetry.md +3 -3
- package/docs/tools-navigate.mdx +14 -6
- package/docs/usage.md +4 -4
- package/package.json +7 -4
- package/dist/init/agent-rules.d.ts +0 -116
- package/dist/init/agent-rules.js +0 -279
- package/dist/init/app-choice.d.ts +0 -20
- package/dist/init/app-choice.js +0 -25
- package/dist/init/astro-host.d.ts +0 -21
- package/dist/init/astro-host.js +0 -27
- package/dist/init/astro-patch.d.ts +0 -33
- package/dist/init/astro-patch.js +0 -300
- package/dist/init/capabilities.d.ts +0 -40
- package/dist/init/capabilities.js +0 -150
- package/dist/init/closing-hint.d.ts +0 -36
- package/dist/init/closing-hint.js +0 -120
- package/dist/init/connect-steps.d.ts +0 -2
- package/dist/init/connect-steps.js +0 -50
- package/dist/init/cra.d.ts +0 -79
- package/dist/init/cra.js +0 -178
- package/dist/init/csp-check.d.ts +0 -38
- package/dist/init/csp-check.js +0 -107
- package/dist/init/csp-doctor.d.ts +0 -32
- package/dist/init/csp-doctor.js +0 -54
- package/dist/init/desktop-doctor.d.ts +0 -41
- package/dist/init/desktop-doctor.js +0 -198
- package/dist/init/detect.d.ts +0 -96
- package/dist/init/detect.js +0 -209
- package/dist/init/dev-script.d.ts +0 -50
- package/dist/init/dev-script.js +0 -66
- package/dist/init/format-generated.d.ts +0 -18
- package/dist/init/format-generated.js +0 -39
- package/dist/init/install-hint.d.ts +0 -9
- package/dist/init/install-hint.js +0 -29
- package/dist/init/mcp-clients.d.ts +0 -89
- package/dist/init/mcp-clients.js +0 -299
- package/dist/init/mcp-registered.d.ts +0 -12
- package/dist/init/mcp-registered.js +0 -14
- package/dist/init/mcp.d.ts +0 -89
- package/dist/init/mcp.js +0 -129
- package/dist/init/next-patch.d.ts +0 -39
- package/dist/init/next-patch.js +0 -196
- package/dist/init/node-io.d.ts +0 -7
- package/dist/init/node-io.js +0 -117
- package/dist/init/non-js-project.d.ts +0 -27
- package/dist/init/non-js-project.js +0 -84
- package/dist/init/patch-kind.d.ts +0 -20
- package/dist/init/patch-kind.js +0 -10
- package/dist/init/plan-framework.d.ts +0 -64
- package/dist/init/plan-framework.js +0 -565
- package/dist/init/plan.d.ts +0 -242
- package/dist/init/plan.js +0 -703
- package/dist/init/preflight.d.ts +0 -28
- package/dist/init/preflight.js +0 -38
- package/dist/init/project-id.d.ts +0 -12
- package/dist/init/project-id.js +0 -25
- package/dist/init/refresh-rules.d.ts +0 -39
- package/dist/init/refresh-rules.js +0 -51
- package/dist/init/run.d.ts +0 -117
- package/dist/init/run.js +0 -806
- package/dist/init/slash-command.d.ts +0 -27
- package/dist/init/slash-command.js +0 -132
- package/dist/init/snippets.d.ts +0 -134
- package/dist/init/snippets.js +0 -572
- package/dist/init/vite-config.d.ts +0 -18
- package/dist/init/vite-config.js +0 -114
- package/dist/init/windows-quote.d.ts +0 -38
- package/dist/init/windows-quote.js +0 -70
- package/dist/init/workspace-apps.d.ts +0 -30
- package/dist/init/workspace-apps.js +0 -178
- package/dist/project/remember-project.d.ts +0 -24
- package/dist/project/remember-project.js +0 -40
- /package/dist/{init → setup}/confirm.d.ts +0 -0
|
@@ -16,7 +16,7 @@ import { readProjectFramework, readProjectId } from '../cli/cli-port.js';
|
|
|
16
16
|
import { hasConnectedBefore } from '../session/connection-memory.js';
|
|
17
17
|
import { AGENT_DRIVING_ELSEWHERE, AGENT_DRIVING_HERE_AGAIN, watchersToNotify, } from '../session/lease-visibility.js';
|
|
18
18
|
import { reticleStateHome } from '../daemon/daemon.js';
|
|
19
|
-
import { RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT } from '@reticlehq/core';
|
|
19
|
+
import { LeaseNotReadyReason, REDACTED_VALUE, RETICLE_URL_PARAM, RETICLE_DEFAULT_PORT, ReticleCommand, SeedStorageSchema, scrubKnownSecrets, } from '@reticlehq/core';
|
|
20
20
|
import { ReticleTool } from './tool-names.js';
|
|
21
21
|
import { asString } from './tools-helpers.js';
|
|
22
22
|
import { chromiumHint } from '../cli/chromium-hint.js';
|
|
@@ -84,13 +84,67 @@ export function appendReticleParams(url, session, projectId) {
|
|
|
84
84
|
return `${url}${sep}${RETICLE_URL_PARAM.SESSION}=${encodeURIComponent(session)}${proj}`;
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
+
function redactExactValue(text, value) {
|
|
88
|
+
if (0 === value.length)
|
|
89
|
+
return text;
|
|
90
|
+
const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
91
|
+
return text.replace(new RegExp(`(^|[^a-zA-Z0-9])${escaped}(?=$|[^a-zA-Z0-9])`, 'g'), `$1${REDACTED_VALUE}`);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Scrub known secret shapes and raw seedStorage values (cookies, localStorage, sessionStorage)
|
|
95
|
+
* from an error string before it reaches daemon logs, metrics, or MCP error responses.
|
|
96
|
+
*/
|
|
97
|
+
export function scrubSeedFromError(text, seed) {
|
|
98
|
+
let out = scrubKnownSecrets(text);
|
|
99
|
+
if (undefined !== seed && null !== seed && 'object' === typeof seed) {
|
|
100
|
+
const s = seed;
|
|
101
|
+
const cookies = s['cookies'];
|
|
102
|
+
if (undefined !== cookies && null !== cookies) {
|
|
103
|
+
if (Array.isArray(cookies)) {
|
|
104
|
+
for (const c of cookies) {
|
|
105
|
+
if (undefined !== c && null !== c && 'object' === typeof c) {
|
|
106
|
+
const val = c['value'];
|
|
107
|
+
if (undefined !== val && 'string' === typeof val) {
|
|
108
|
+
out = redactExactValue(out, val);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if ('object' === typeof cookies) {
|
|
114
|
+
for (const v of Object.values(cookies)) {
|
|
115
|
+
if ('string' === typeof v) {
|
|
116
|
+
out = redactExactValue(out, v);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const local = s['local'];
|
|
122
|
+
if (undefined !== local && null !== local && 'object' === typeof local) {
|
|
123
|
+
for (const v of Object.values(local)) {
|
|
124
|
+
if ('string' === typeof v) {
|
|
125
|
+
out = redactExactValue(out, v);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const session = s['session'];
|
|
130
|
+
if (undefined !== session && null !== session && 'object' === typeof session) {
|
|
131
|
+
for (const v of Object.values(session)) {
|
|
132
|
+
if ('string' === typeof v) {
|
|
133
|
+
out = redactExactValue(out, v);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
87
140
|
/**
|
|
88
141
|
* Turn a raw navigation failure (Playwright's `page.goto: net::ERR_… at <url>\nCall log:…`, often
|
|
89
142
|
* with ANSI codes) into a short, clean reason — so the agent/user sees "is the app running?" instead
|
|
90
143
|
* of an internals-leaking wall of text.
|
|
91
144
|
*/
|
|
92
|
-
export function cleanNavError(err) {
|
|
93
|
-
const
|
|
145
|
+
export function cleanNavError(err, seed) {
|
|
146
|
+
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
147
|
+
const msg = scrubSeedFromError(rawMsg, seed);
|
|
94
148
|
// Strip ANSI color codes (ESC[…m) Playwright emits — built via fromCharCode to keep the
|
|
95
149
|
// control character out of a regex literal (no-control-regex).
|
|
96
150
|
const ansi = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, 'g');
|
|
@@ -106,6 +160,44 @@ export function cleanNavError(err) {
|
|
|
106
160
|
.trim()
|
|
107
161
|
.slice(0, 100);
|
|
108
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Determine if a required seedStorage application precondition failed.
|
|
165
|
+
*
|
|
166
|
+
* Checks strong signals:
|
|
167
|
+
* 1. HTTP 401/403 status on the initial navigation.
|
|
168
|
+
* 2. Cross-origin redirect when localStorage or sessionStorage was requested (skipped by origin-scoping).
|
|
169
|
+
* 3. Clear redirect to an authentication/login endpoint when the requested URL was not a login page.
|
|
170
|
+
*
|
|
171
|
+
* Normal app redirects (e.g. / → /dashboard) are valid and return undefined.
|
|
172
|
+
*/
|
|
173
|
+
export function evaluateSeedPrecondition(requestedUrl, actualUrl, seed, navStatus) {
|
|
174
|
+
if (401 === navStatus || 403 === navStatus) {
|
|
175
|
+
return `HTTP ${navStatus} returned on initial seeded navigation to ${requestedUrl}`;
|
|
176
|
+
}
|
|
177
|
+
if (actualUrl === undefined || '' === actualUrl.trim())
|
|
178
|
+
return undefined;
|
|
179
|
+
let reqParsed;
|
|
180
|
+
let actParsed;
|
|
181
|
+
try {
|
|
182
|
+
reqParsed = new URL(requestedUrl);
|
|
183
|
+
actParsed = new URL(actualUrl);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
// Cross-origin redirect when local or session storage was requested
|
|
189
|
+
const hasStorage = (undefined !== seed.local && 0 < Object.keys(seed.local).length) ||
|
|
190
|
+
(undefined !== seed.session && 0 < Object.keys(seed.session).length);
|
|
191
|
+
if (hasStorage && reqParsed.origin !== actParsed.origin) {
|
|
192
|
+
return `seeded storage precondition not established: cross-origin redirect from ${reqParsed.origin} to ${actParsed.origin} skipped origin-scoped storage injection`;
|
|
193
|
+
}
|
|
194
|
+
// Clear redirect to an authentication/login endpoint when the requested URL was not a login page
|
|
195
|
+
const isAuthPath = (pathname) => /(?:^|\/)(?:login|signin|sign-in|auth\/login|auth\/signin|session\/new)(?:$|\/)/i.test(pathname);
|
|
196
|
+
if (!isAuthPath(reqParsed.pathname) && isAuthPath(actParsed.pathname)) {
|
|
197
|
+
return `seeded authentication precondition not established: redirected from ${reqParsed.pathname} to login page (${actParsed.pathname})`;
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
109
201
|
/** A fresh, collision-resistant lease id. Uses crypto at the I/O boundary (not pure logic). */
|
|
110
202
|
function newLeaseId() {
|
|
111
203
|
const uuid = 'function' === typeof globalThis.crypto?.randomUUID
|
|
@@ -176,6 +268,46 @@ function sessionParamOf(url) {
|
|
|
176
268
|
}
|
|
177
269
|
const LEASE_READY_ATTEMPTS = 100;
|
|
178
270
|
const LEASE_READY_POLL_MS = 100;
|
|
271
|
+
/**
|
|
272
|
+
* How long a liveness probe waits for the tab to say anything at all.
|
|
273
|
+
*
|
|
274
|
+
* Short on purpose. This is not "finish the work", it is "are you there" — a page executing
|
|
275
|
+
* JavaScript answers a no-argument command in single-digit milliseconds, and a wedged one is not
|
|
276
|
+
* going to answer in two seconds either.
|
|
277
|
+
*/
|
|
278
|
+
const LEASE_PROBE_TIMEOUT_MS = 1_500;
|
|
279
|
+
/**
|
|
280
|
+
* Does this tab still answer?
|
|
281
|
+
*
|
|
282
|
+
* `ready: true` used to mean "a row is in the sessions map", which is how a lease came back ready
|
|
283
|
+
* and was then rejected by `snapshot`, `state` and `console`. Presence is not liveness: the map
|
|
284
|
+
* still holds a tab that is attached, streaming events, and answering nothing (#692).
|
|
285
|
+
*
|
|
286
|
+
* ANY reply counts, including one that reports the command failed. The question is whether the SDK
|
|
287
|
+
* answers at all, not what it says — so an SDK too old to know the command replies
|
|
288
|
+
* `unknown command '…'`, and that is proof. Only the absence of a reply is evidence of absence:
|
|
289
|
+
* `PendingCommands.track` REJECTS on timeout and on a dropped socket, and resolves on every real
|
|
290
|
+
* answer, so the two cases are already separated for us.
|
|
291
|
+
*
|
|
292
|
+
* `CAPABILITIES` is the probe because it takes no arguments and returns a small fixed list. No new
|
|
293
|
+
* wire command is introduced: this rides an existing round trip.
|
|
294
|
+
*
|
|
295
|
+
* Fails OPEN. A registry entry with no `command` is a shape this code did not put there, and
|
|
296
|
+
* turning a lease that works into a refusal over a probe that could not run would be a worse
|
|
297
|
+
* failure than the one being fixed.
|
|
298
|
+
*/
|
|
299
|
+
export async function probeLeaseAlive(session, timeoutMs = LEASE_PROBE_TIMEOUT_MS) {
|
|
300
|
+
const send = session?.command;
|
|
301
|
+
if (send === undefined)
|
|
302
|
+
return true;
|
|
303
|
+
try {
|
|
304
|
+
await send.call(session, ReticleCommand.CAPABILITIES, {}, timeoutMs);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
179
311
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
180
312
|
/**
|
|
181
313
|
* Poll until the leased tab's SDK has actually registered on the bridge, so the sessionId we return
|
|
@@ -188,9 +320,18 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
188
320
|
* Returns the session id plus its release; the caller must release in a finally so a crashed flow never
|
|
189
321
|
* leaks a pool slot and starves the rest of the suite.
|
|
190
322
|
*/
|
|
191
|
-
export async function acquireLeasedSession(pool, sessions, url, projectId) {
|
|
323
|
+
export async function acquireLeasedSession(pool, sessions, url, projectId, seedStorage) {
|
|
192
324
|
const sessionId = newLeaseId();
|
|
193
|
-
|
|
325
|
+
let lease;
|
|
326
|
+
try {
|
|
327
|
+
lease = await pool.acquire(appendReticleParams(url, sessionId, projectId), {
|
|
328
|
+
sessionId,
|
|
329
|
+
...(seedStorage !== undefined ? { seedStorage } : {}),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
throw new Error(scrubSeedFromError(err instanceof Error ? err.message : String(err), seedStorage));
|
|
334
|
+
}
|
|
194
335
|
// Resolved, not assumed — see resolveLeasedSessionId. The parallel suite replays against whatever
|
|
195
336
|
// id comes back, so handing it the lease id for an app that named its own session sent every flow
|
|
196
337
|
// in the run at a session that does not exist.
|
|
@@ -201,7 +342,17 @@ export async function acquireLeasedSession(pool, sessions, url, projectId) {
|
|
|
201
342
|
});
|
|
202
343
|
if (registeredId !== undefined)
|
|
203
344
|
pool.alias?.(registeredId, lease.sessionId);
|
|
204
|
-
|
|
345
|
+
const effectiveId = registeredId ?? lease.sessionId;
|
|
346
|
+
if (seedStorage !== undefined) {
|
|
347
|
+
const session = sessions.get(effectiveId);
|
|
348
|
+
if (session?.setPreconditionFailure !== undefined) {
|
|
349
|
+
const failureReason = evaluateSeedPrecondition(url, session.url, seedStorage, lease.navStatus);
|
|
350
|
+
if (failureReason !== undefined) {
|
|
351
|
+
session.setPreconditionFailure(failureReason);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return { sessionId: effectiveId, release: () => lease.release() };
|
|
205
356
|
}
|
|
206
357
|
export async function waitForLeasedSession(isConnected, sleeper = sleep, attempts = LEASE_READY_ATTEMPTS, pollMs = LEASE_READY_POLL_MS) {
|
|
207
358
|
for (let i = 0; i < attempts; i++) {
|
|
@@ -220,6 +371,12 @@ export async function waitForLeasedSession(isConnected, sleeper = sleep, attempt
|
|
|
220
371
|
* handler keeps that literal — the drive route dispatches through `runTool`, so it is counted like
|
|
221
372
|
* any other call instead of being a second, invisible dispatch path.
|
|
222
373
|
*/
|
|
374
|
+
// Serializes lease acquisitions on the same origin so concurrent callers (e.g. seeded replacements)
|
|
375
|
+
// do not race and establish multiple overlapping contexts on that origin.
|
|
376
|
+
const originAcquireLocks = new Map();
|
|
377
|
+
export function hasOriginLock(origin) {
|
|
378
|
+
return originAcquireLocks.has(origin);
|
|
379
|
+
}
|
|
223
380
|
export const LEASE_ACQUIRE_TOOL = {
|
|
224
381
|
name: ReticleTool.LEASE_ACQUIRE,
|
|
225
382
|
description: 'Lease a fresh isolated headless browser context from the shared pool and navigate it to the app URL (the app must already be running and embed @reticlehq/core). If this origin is already leased and still connected, this returns THAT session rather than minting a second tab — a second acquire on the same origin poisons default session resolution. Returns the sessionId the leased tab registers — pass it to other tools. The pool keeps all leases in ONE browser and caps concurrency; if at capacity this waits for a free slot. Release with reticle_lease{action:"release"} when the flow is done. PREFER AN ALREADY-OPEN TAB: if reticle_sessions lists a non-leased session for this app, drive THAT instead — a lease is invisible to the person watching the app, whose HUD lives in their own tab, and a tab flagged hidden/throttled is often still driveable. Lease for isolation you actually need (a second identity, a clean context, parallel flows) or when driving the open tab has failed — this call answers with `preferExisting` when a live tab was available.',
|
|
@@ -231,13 +388,18 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
231
388
|
.string()
|
|
232
389
|
.optional()
|
|
233
390
|
.describe('Stable project id to stamp on the leased tab so the agent can scope to it.'),
|
|
391
|
+
seedStorage: SeedStorageSchema.optional().describe('Initial storage state (localStorage, sessionStorage, cookies) to seed before the first navigation (e.g. to start already authenticated).'),
|
|
234
392
|
},
|
|
235
393
|
outputSchema: {
|
|
236
394
|
sessionId: z.string(),
|
|
237
395
|
url: z.string(),
|
|
238
396
|
ready: z
|
|
239
397
|
.boolean()
|
|
240
|
-
.describe('Whether the leased tab connected —
|
|
398
|
+
.describe('Whether the leased tab is connected AND answering. On a reused lease this is probed, not assumed — a row in the session map is presence, not liveness. False ⇒ read notReadyReason.'),
|
|
399
|
+
notReadyReason: z
|
|
400
|
+
.enum([LeaseNotReadyReason.SDK_NEVER_DIALLED, LeaseNotReadyReason.SDK_STOPPED_ANSWERING])
|
|
401
|
+
.optional()
|
|
402
|
+
.describe('Present only when ready is false. sdk_never_dialled ⇒ nothing connected within the wait, so check the install (the app may not embed @reticlehq/core). sdk_stopped_answering ⇒ an SDK did connect and has stopped replying, so the tab is wedged and needs recovering, not reinstalling.'),
|
|
241
403
|
expiresInMs: z
|
|
242
404
|
.number()
|
|
243
405
|
.describe('Milliseconds until this lease expires if untouched. Each tool call that targets this session resets the clock. Plan your work to finish or re-acquire before this runs out.'),
|
|
@@ -278,83 +440,156 @@ export const LEASE_ACQUIRE_TOOL = {
|
|
|
278
440
|
}
|
|
279
441
|
}
|
|
280
442
|
const projectId = asString(args['projectId']);
|
|
443
|
+
const seedStorageArg = args['seedStorage'];
|
|
444
|
+
let validatedSeed;
|
|
445
|
+
if (seedStorageArg !== undefined) {
|
|
446
|
+
const parsed = SeedStorageSchema.safeParse(seedStorageArg);
|
|
447
|
+
if (!parsed.success) {
|
|
448
|
+
const issuesMsg = scrubSeedFromError(parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', '), seedStorageArg);
|
|
449
|
+
throw new Error(`reticle_lease{action:"acquire"} seedStorage is invalid: ${issuesMsg}`);
|
|
450
|
+
}
|
|
451
|
+
validatedSeed = parsed.data;
|
|
452
|
+
}
|
|
281
453
|
// Sampled BEFORE acquiring: afterwards this lease is itself a session, and the point is to name
|
|
282
454
|
// a tab that already existed. A human's open tab is the one they can watch, so if one is here
|
|
283
455
|
// the agent should be told at the moment it is choosing — not after it has gone dark on them.
|
|
284
456
|
const alreadyOpen = liveTabFor(deps, projectId);
|
|
285
457
|
const origin = originOf(url);
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
queued: pool.queuedCount(),
|
|
298
|
-
hint: alreadyHeldHint(existing, origin),
|
|
299
|
-
...(alreadyOpen === undefined
|
|
300
|
-
? {}
|
|
301
|
-
: { preferExisting: { sessionId: alreadyOpen, note: PREFER_EXISTING_NOTE } }),
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
// The lease is still held but the tab has gone — a reload dropped the session. Free the
|
|
305
|
-
// slot and mint, rather than handing back a dead id or leaving both contexts occupied.
|
|
306
|
-
await pool.release(existing);
|
|
458
|
+
const priorLock = origin !== undefined ? originAcquireLocks.get(origin) : undefined;
|
|
459
|
+
let releaseLock;
|
|
460
|
+
let lockPromise;
|
|
461
|
+
if (origin !== undefined) {
|
|
462
|
+
lockPromise = new Promise((resolve) => {
|
|
463
|
+
releaseLock = resolve;
|
|
464
|
+
});
|
|
465
|
+
originAcquireLocks.set(origin, lockPromise);
|
|
466
|
+
}
|
|
467
|
+
if (priorLock !== undefined) {
|
|
468
|
+
await priorLock.catch(() => undefined);
|
|
307
469
|
}
|
|
308
|
-
const sessionId = newLeaseId();
|
|
309
|
-
const navUrl = appendReticleParams(url, sessionId, projectId);
|
|
310
|
-
let lease;
|
|
311
470
|
try {
|
|
312
|
-
|
|
471
|
+
const existing = origin === undefined ? undefined : pool.leaseIdOnOrigin?.(origin);
|
|
472
|
+
if (validatedSeed !== undefined && existing !== undefined) {
|
|
473
|
+
// Caller explicitly requested storage seeding: do not reuse an existing context on this origin,
|
|
474
|
+
// release it so the new lease starts clean with the caller-provided state.
|
|
475
|
+
await pool.release(existing);
|
|
476
|
+
}
|
|
477
|
+
else if (existing !== undefined && origin !== undefined) {
|
|
478
|
+
// Resolved, not looked up — the same resolver the mint path below uses, for the same reason
|
|
479
|
+
// it states: an app that names its own session registers under that name, so the id the pool
|
|
480
|
+
// holds is not always an id the other tools accept. A direct `sessions.get` missed exactly
|
|
481
|
+
// those apps and fell through to the release, which frees a LIVE lease and mints a second
|
|
482
|
+
// context — the tab-poisoning this branch exists to prevent, reached through the branch
|
|
483
|
+
// itself. It bites when the first acquire returned `ready: false`, since the mint path only
|
|
484
|
+
// aliases once its wait has resolved, so nothing recorded the app's own name (#692).
|
|
485
|
+
const resolved = resolveLeasedSessionId(deps.sessions, existing);
|
|
486
|
+
if (resolved !== undefined) {
|
|
487
|
+
// The other name this lease answers to, told to the pool exactly as the mint path tells it.
|
|
488
|
+
// A no-op when the id resolved to itself; load-bearing when it did not, because every later
|
|
489
|
+
// touch and release arrives under the id being handed back here.
|
|
490
|
+
pool.alias(resolved, existing);
|
|
491
|
+
pool.touch(existing);
|
|
492
|
+
// Probed only HERE, never on the mint path below. There, the readiness wait resolved
|
|
493
|
+
// moments ago and IS the liveness evidence; on reuse the last evidence may be minutes
|
|
494
|
+
// old, or there may be none at all — `unresponsive` is set by past commands failing, and
|
|
495
|
+
// its own contract says absence means "answering, OR NOT ASKED YET". So the happy path of
|
|
496
|
+
// a first acquire pays nothing for this.
|
|
497
|
+
const alive = await probeLeaseAlive(deps.sessions.get(resolved));
|
|
498
|
+
return {
|
|
499
|
+
sessionId: resolved,
|
|
500
|
+
url,
|
|
501
|
+
ready: alive,
|
|
502
|
+
...(alive ? {} : { notReadyReason: LeaseNotReadyReason.SDK_STOPPED_ANSWERING }),
|
|
503
|
+
reused: true,
|
|
504
|
+
expiresInMs: pool.leaseTtlMs(),
|
|
505
|
+
leased: pool.activeCount(),
|
|
506
|
+
queued: pool.queuedCount(),
|
|
507
|
+
hint: alreadyHeldHint(resolved, origin),
|
|
508
|
+
...(alreadyOpen === undefined
|
|
509
|
+
? {}
|
|
510
|
+
: { preferExisting: { sessionId: alreadyOpen, note: PREFER_EXISTING_NOTE } }),
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
// The lease is still held but the tab has gone — a reload dropped the session, and no session
|
|
514
|
+
// anywhere is driving it. Free the slot and mint, rather than handing back a dead id or
|
|
515
|
+
// leaving both contexts occupied.
|
|
516
|
+
await pool.release(existing);
|
|
517
|
+
}
|
|
518
|
+
const sessionId = newLeaseId();
|
|
519
|
+
const navUrl = appendReticleParams(url, sessionId, projectId);
|
|
520
|
+
let lease;
|
|
521
|
+
try {
|
|
522
|
+
lease = await pool.acquire(navUrl, {
|
|
523
|
+
sessionId,
|
|
524
|
+
...(validatedSeed !== undefined ? { seedStorage: validatedSeed } : {}),
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
if (err instanceof Error && err.message.startsWith('Storage seeding failed')) {
|
|
529
|
+
throw new Error(scrubSeedFromError(err.message, validatedSeed));
|
|
530
|
+
}
|
|
531
|
+
// A raw page.goto failure is noisy and leaks the internal URL params — surface a clean,
|
|
532
|
+
// actionable message instead.
|
|
533
|
+
throw new Error(`could not open ${url} — is the app running there? (${cleanNavError(err, validatedSeed)})`);
|
|
534
|
+
}
|
|
535
|
+
// Wait for the leased tab's SDK to connect so the returned sessionId is usable right away.
|
|
536
|
+
// Resolved rather than assumed: an app that names its own session registers under that name,
|
|
537
|
+
// and the id we hand back has to be the one the agent can actually drive.
|
|
538
|
+
let registeredId;
|
|
539
|
+
const ready = await waitForLeasedSession(() => {
|
|
540
|
+
registeredId = resolveLeasedSessionId(deps.sessions, lease.sessionId);
|
|
541
|
+
return registeredId !== undefined;
|
|
542
|
+
});
|
|
543
|
+
// Tell the pool the other name this lease answers to. Every later touch and release arrives
|
|
544
|
+
// under the id we are about to hand back, and the pool is keyed by the id it navigated with;
|
|
545
|
+
// without this the touches miss, the lease ages out despite continuous activity, and the
|
|
546
|
+
// reaper closes the context mid-flow. See BrowserPool.alias and #157.
|
|
547
|
+
if (registeredId !== undefined)
|
|
548
|
+
pool.alias(registeredId, lease.sessionId);
|
|
549
|
+
const effectiveSessionId = registeredId ?? lease.sessionId;
|
|
550
|
+
const session = deps.sessions.get(effectiveSessionId);
|
|
551
|
+
if (validatedSeed !== undefined && session !== undefined) {
|
|
552
|
+
const failureReason = evaluateSeedPrecondition(url, session.url, validatedSeed, lease.navStatus);
|
|
553
|
+
if (failureReason !== undefined) {
|
|
554
|
+
session.setPreconditionFailure?.(failureReason);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// The lease now exists, so any HUD a human is watching has just gone dark. Say so.
|
|
558
|
+
tellWatchers(deps, projectId, AGENT_DRIVING_ELSEWHERE);
|
|
559
|
+
// ready means the SDK dialled in — not that contracts match. Carry the skew warning on acquire
|
|
560
|
+
// so the agent does not learn it only after a CDP tool invents a closed page (#688).
|
|
561
|
+
const versionSkew = registeredId === undefined ? undefined : deps.sessions.get(registeredId)?.versionSkew;
|
|
562
|
+
return {
|
|
563
|
+
sessionId: registeredId ?? lease.sessionId,
|
|
564
|
+
url,
|
|
565
|
+
ready,
|
|
566
|
+
// The other half of the pair. `ready: false` carried two opposite situations under one
|
|
567
|
+
// word: no SDK ever dialled in (look at the install) versus one dialled in and stopped
|
|
568
|
+
// answering (recover the tab). They want different next actions, so they get names.
|
|
569
|
+
...(ready ? {} : { notReadyReason: LeaseNotReadyReason.SDK_NEVER_DIALLED }),
|
|
570
|
+
expiresInMs: pool.leaseTtlMs(),
|
|
571
|
+
leased: pool.activeCount(),
|
|
572
|
+
queued: pool.queuedCount(),
|
|
573
|
+
...(alreadyOpen === undefined
|
|
574
|
+
? {}
|
|
575
|
+
: {
|
|
576
|
+
preferExisting: {
|
|
577
|
+
sessionId: alreadyOpen,
|
|
578
|
+
note: PREFER_EXISTING_NOTE,
|
|
579
|
+
},
|
|
580
|
+
}),
|
|
581
|
+
...(versionSkew === undefined ? {} : { versionSkew }),
|
|
582
|
+
...(ready
|
|
583
|
+
? {}
|
|
584
|
+
: { hint: await notConnectedHint(deps, url, pool.dialFailureUrl?.(lease.sessionId)) }),
|
|
585
|
+
};
|
|
313
586
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
587
|
+
finally {
|
|
588
|
+
releaseLock?.();
|
|
589
|
+
if (origin !== undefined && originAcquireLocks.get(origin) === lockPromise) {
|
|
590
|
+
originAcquireLocks.delete(origin);
|
|
591
|
+
}
|
|
318
592
|
}
|
|
319
|
-
// Wait for the leased tab's SDK to connect so the returned sessionId is usable right away.
|
|
320
|
-
// Resolved rather than assumed: an app that names its own session registers under that name,
|
|
321
|
-
// and the id we hand back has to be the one the agent can actually drive.
|
|
322
|
-
let registeredId;
|
|
323
|
-
const ready = await waitForLeasedSession(() => {
|
|
324
|
-
registeredId = resolveLeasedSessionId(deps.sessions, lease.sessionId);
|
|
325
|
-
return registeredId !== undefined;
|
|
326
|
-
});
|
|
327
|
-
// Tell the pool the other name this lease answers to. Every later touch and release arrives
|
|
328
|
-
// under the id we are about to hand back, and the pool is keyed by the id it navigated with;
|
|
329
|
-
// without this the touches miss, the lease ages out despite continuous activity, and the
|
|
330
|
-
// reaper closes the context mid-flow. See BrowserPool.alias and #157.
|
|
331
|
-
if (registeredId !== undefined)
|
|
332
|
-
pool.alias(registeredId, lease.sessionId);
|
|
333
|
-
// The lease now exists, so any HUD a human is watching has just gone dark. Say so.
|
|
334
|
-
tellWatchers(deps, projectId, AGENT_DRIVING_ELSEWHERE);
|
|
335
|
-
// ready means the SDK dialled in — not that contracts match. Carry the skew warning on acquire
|
|
336
|
-
// so the agent does not learn it only after a CDP tool invents a closed page (#688).
|
|
337
|
-
const versionSkew = registeredId === undefined ? undefined : deps.sessions.get(registeredId)?.versionSkew;
|
|
338
|
-
return {
|
|
339
|
-
sessionId: registeredId ?? lease.sessionId,
|
|
340
|
-
url,
|
|
341
|
-
ready,
|
|
342
|
-
expiresInMs: pool.leaseTtlMs(),
|
|
343
|
-
leased: pool.activeCount(),
|
|
344
|
-
queued: pool.queuedCount(),
|
|
345
|
-
...(alreadyOpen === undefined
|
|
346
|
-
? {}
|
|
347
|
-
: {
|
|
348
|
-
preferExisting: {
|
|
349
|
-
sessionId: alreadyOpen,
|
|
350
|
-
note: PREFER_EXISTING_NOTE,
|
|
351
|
-
},
|
|
352
|
-
}),
|
|
353
|
-
...(versionSkew === undefined ? {} : { versionSkew }),
|
|
354
|
-
...(ready
|
|
355
|
-
? {}
|
|
356
|
-
: { hint: await notConnectedHint(deps, url, pool.dialFailureUrl?.(lease.sessionId)) }),
|
|
357
|
-
};
|
|
358
593
|
},
|
|
359
594
|
};
|
|
360
595
|
/** The whole not-connected diagnosis, gathered and worded. Split out so the acquire path stays flat. */
|
|
@@ -405,7 +640,7 @@ const LEASE_RELEASE_TOOL = {
|
|
|
405
640
|
* when both exist the visible one is the better default and the agent should hear that here, where
|
|
406
641
|
* it is choosing, rather than discover it when somebody asks why nothing is happening.
|
|
407
642
|
*/
|
|
408
|
-
|
|
643
|
+
const PREFER_EXISTING_NOTE = 'a non-leased tab for this app was already connected — that is the one a human can see, and this lease is not. Unless you need an isolated context (a second identity, a clean profile, parallel flows), release this lease and drive that sessionId instead.';
|
|
409
644
|
/**
|
|
410
645
|
* The first live non-leased session for this project, if any.
|
|
411
646
|
*
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle_lineage` — where a runtime value came from.
|
|
3
|
+
*
|
|
4
|
+
* When a verdict is `no`, or a contradiction fires, the next question is always the same. Today an
|
|
5
|
+
* agent answers it by hand: `reticle_state` for the value, `reticle_network` for the requests,
|
|
6
|
+
* `reticle_observe` for the window, then correlates the three itself — spending tokens and judgement
|
|
7
|
+
* on a join the daemon can do once from data it already holds.
|
|
8
|
+
*
|
|
9
|
+
* READ-ONLY BY CONSTRUCTION. It produces no verdict, so it is registered in `TOOLS` and deliberately
|
|
10
|
+
* NOT in the verification set, and nothing on an assert path reaches it. The decision logic lives in
|
|
11
|
+
* `events/lineage.ts` and is where the honesty rules are argued; this file is the surface.
|
|
12
|
+
*/
|
|
13
|
+
import type { ToolDef } from './tools.js';
|
|
14
|
+
export declare const LINEAGE_TOOLS: ToolDef[];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle_lineage` — where a runtime value came from.
|
|
3
|
+
*
|
|
4
|
+
* When a verdict is `no`, or a contradiction fires, the next question is always the same. Today an
|
|
5
|
+
* agent answers it by hand: `reticle_state` for the value, `reticle_network` for the requests,
|
|
6
|
+
* `reticle_observe` for the window, then correlates the three itself — spending tokens and judgement
|
|
7
|
+
* on a join the daemon can do once from data it already holds.
|
|
8
|
+
*
|
|
9
|
+
* READ-ONLY BY CONSTRUCTION. It produces no verdict, so it is registered in `TOOLS` and deliberately
|
|
10
|
+
* NOT in the verification set, and nothing on an assert path reaches it. The decision logic lives in
|
|
11
|
+
* `events/lineage.ts` and is where the honesty rules are argued; this file is the surface.
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { ReticleTool } from './tool-names.js';
|
|
15
|
+
import { traceLineage } from '../events/lineage.js';
|
|
16
|
+
import { asString } from './tools-helpers.js';
|
|
17
|
+
import { cursorSchema } from './numeric-bounds.js';
|
|
18
|
+
export const LINEAGE_TOOLS = [
|
|
19
|
+
{
|
|
20
|
+
name: ReticleTool.LINEAGE,
|
|
21
|
+
example: { path: 'user.profile.name' },
|
|
22
|
+
description: 'Trace BACKWARDS from a runtime value: which state change set it, which signal preceded that, and which request preceded the signal. Read-only — it draws no verdict and changes nothing. The state change is OBSERVED; every link above it is an INFERENCE from timing and says so in its own text. Where several candidates could explain a link it names ALL of them rather than choosing, because timing alone cannot justify the choice — so a chain here is a place to look, never a proven cause. Use it after a `no` verdict or a contradiction, instead of correlating reticle_state, reticle_network and reticle_observe by hand.',
|
|
23
|
+
inputSchema: {
|
|
24
|
+
path: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe('The state path to trace, e.g. "user.profile.name". Matched as a substring.'),
|
|
27
|
+
value: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Narrow to the change that set THIS value, when the path changed more than once.'),
|
|
31
|
+
since: cursorSchema
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Only consider events after this cursor (from an act or observe result).'),
|
|
34
|
+
...{},
|
|
35
|
+
},
|
|
36
|
+
outputSchema: {
|
|
37
|
+
found: z
|
|
38
|
+
.boolean()
|
|
39
|
+
.describe('False when the path was never seen to change — not the same as an empty chain.'),
|
|
40
|
+
chain: z
|
|
41
|
+
.array(z.object({
|
|
42
|
+
kind: z.string(),
|
|
43
|
+
text: z.string(),
|
|
44
|
+
observed: z.boolean(),
|
|
45
|
+
candidates: z.array(z.string()).optional(),
|
|
46
|
+
}))
|
|
47
|
+
.describe('Most recent change first. `observed` is true only for events we hold; every join is false.'),
|
|
48
|
+
note: z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe('Why the chain stops where it does, when it stops early.'),
|
|
52
|
+
},
|
|
53
|
+
handler: (deps, args) => {
|
|
54
|
+
const session = deps.sessions.resolve(asString(args['sessionId']));
|
|
55
|
+
const since = 'number' === typeof args['since'] ? args['since'] : 0;
|
|
56
|
+
const path = asString(args['path']) ?? '';
|
|
57
|
+
const value = asString(args['value']);
|
|
58
|
+
return Promise.resolve(traceLineage(session.eventsSince(since), {
|
|
59
|
+
path,
|
|
60
|
+
...(value === undefined ? {} : { value }),
|
|
61
|
+
}));
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
];
|
|
@@ -51,9 +51,7 @@ export function mergeTools(spec) {
|
|
|
51
51
|
description: spec.description,
|
|
52
52
|
...(spec.example === undefined ? {} : { example: spec.example }),
|
|
53
53
|
inputSchema: {
|
|
54
|
-
action: z
|
|
55
|
-
.enum(actionNames)
|
|
56
|
-
.describe(`Which operation to run: ${actionNames.join(' | ')}.`),
|
|
54
|
+
action: z.enum(actionNames).describe('Which operation to run.'),
|
|
57
55
|
...unionShape(spec.actions),
|
|
58
56
|
},
|
|
59
57
|
handler: (deps, args) => {
|
|
@@ -10,9 +10,53 @@
|
|
|
10
10
|
* Bounded and best-effort by construction: a navigation to a page that is not instrumented, or not
|
|
11
11
|
* there at all, must still return promptly with `confirmed:false` rather than hanging. The clock and
|
|
12
12
|
* the sleep are injected so this is testable without waiting on a real one.
|
|
13
|
+
*
|
|
14
|
+
* The bound is the CALLER'S to set. It was a fixed 5s that `reticle_navigate` did not expose, and a
|
|
15
|
+
* Nuxt SPA reattaching under HMR was measured coming back in 30–60s — so every navigation to it
|
|
16
|
+
* answered `confirmed:false` while the app was still on its way, and nothing the agent could pass
|
|
17
|
+
* would make the daemon wait. `assert` / `wait_for` / `act_and_wait` all spend the caller's
|
|
18
|
+
* `timeout_ms` (resolve-within.ts); this is the same budget applied to the same wait.
|
|
13
19
|
*/
|
|
14
20
|
import type { SessionManager } from '../session/session-manager.js';
|
|
15
21
|
import type { NavigateArrival } from './navigate-result.js';
|
|
22
|
+
/**
|
|
23
|
+
* The default when the caller gives no `timeout_ms`: long enough for a dev server to serve a page
|
|
24
|
+
* and the SDK to dial back; short enough to not hang. A caller who knows the app is slower says so.
|
|
25
|
+
*/
|
|
26
|
+
export declare const ARRIVAL_TIMEOUT_MS = 5000;
|
|
27
|
+
/**
|
|
28
|
+
* Which session an arrival may be attributed to.
|
|
29
|
+
*
|
|
30
|
+
* Arrival means a session is at the target BECAUSE OF THIS NAVIGATION. The scan had no notion of
|
|
31
|
+
* that: it returned the first session whose page matched, whoever it belonged to. A stale row parked
|
|
32
|
+
* on the origin — in the field report, one belonging to a different app that had previously held the
|
|
33
|
+
* same port — therefore answered for a navigation it had nothing to do with, and `reticle_navigate`
|
|
34
|
+
* reported ITS id as the session the caller had arrived at. Targeting was never wrong; the result
|
|
35
|
+
* was (#785).
|
|
36
|
+
*
|
|
37
|
+
* `sessions.all()` is Map insertion order, so the older stale row is scanned FIRST. That made the
|
|
38
|
+
* wrong id the likely answer rather than a coin flip.
|
|
39
|
+
*/
|
|
40
|
+
export interface ArrivalScope {
|
|
41
|
+
/**
|
|
42
|
+
* The session the navigation was dispatched to.
|
|
43
|
+
*
|
|
44
|
+
* Exempt from the exclusion below, because it is legitimately at the target both before and after
|
|
45
|
+
* a navigation that keeps the document — a reload, or a same-page route change. Excluding it would
|
|
46
|
+
* report `confirmed: false` for the navigations most likely to have worked.
|
|
47
|
+
*/
|
|
48
|
+
navigatedId: string;
|
|
49
|
+
/** Sessions already on the target BEFORE dispatch. They cannot be evidence of this arrival. */
|
|
50
|
+
priorIds: ReadonlySet<string>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The sessions already on `target` — the set `ArrivalScope.priorIds` is built from, sampled before
|
|
54
|
+
* the navigation is dispatched.
|
|
55
|
+
*
|
|
56
|
+
* Shares `samePage` with the arrival scan on purpose: "was it already there" and "is it there now"
|
|
57
|
+
* are the same question asked at two moments, and two spellings of it would eventually disagree.
|
|
58
|
+
*/
|
|
59
|
+
export declare function idsAtTarget(sessions: SessionManager, target: string): ReadonlySet<string>;
|
|
16
60
|
export interface ArrivalClock {
|
|
17
61
|
now: () => number;
|
|
18
62
|
sleep: (ms: number) => Promise<void>;
|
|
@@ -21,4 +65,4 @@ export interface ArrivalClock {
|
|
|
21
65
|
* Poll until a session is present at `target`, or the budget runs out. Returns `null` on timeout —
|
|
22
66
|
* never throws, because a failure to confirm is a legitimate answer, not an error.
|
|
23
67
|
*/
|
|
24
|
-
export declare function awaitArrival(sessions: SessionManager, target: string, timeoutMs?: number, clock?: ArrivalClock): Promise<NavigateArrival | null>;
|
|
68
|
+
export declare function awaitArrival(sessions: SessionManager, target: string, scope: ArrivalScope, timeoutMs?: number, clock?: ArrivalClock): Promise<NavigateArrival | null>;
|