@reticlehq/server 2.4.0 → 2.5.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 +702 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
- package/dist/{bridge.js → bridge/bridge.js} +63 -13
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +1 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +9 -9
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +2 -11
- package/dist/{cli-parse.js → cli/cli-parse.js} +113 -60
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +174 -92
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +49 -0
- package/dist/daemon/daemon-resilience.js +191 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +80 -48
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +43 -20
- package/dist/events/predicate-eval.js +159 -55
- package/dist/events/predicate.d.ts +2 -2
- package/dist/events/predicate.js +112 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +1 -1
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +5 -7
- package/dist/flows/flow-replay.js +114 -47
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +157 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +53 -7
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +16 -3
- package/dist/honesty/blind-spots.js +24 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +22 -3
- package/dist/honesty/verified.js +48 -8
- package/dist/http-server.js +14 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +8 -6
- package/dist/index.js +32 -25
- package/dist/init/agent-rules.d.ts +2 -1
- package/dist/init/agent-rules.js +45 -7
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +37 -0
- package/dist/init/cra.js +90 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +3 -2
- package/dist/init/detect.js +16 -5
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +23 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +132 -8
- package/dist/init/plan.d.ts +73 -8
- package/dist/init/plan.js +133 -34
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +5 -0
- package/dist/init/run.js +210 -36
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +31 -0
- package/dist/mcp/mcp-outage.js +48 -0
- package/dist/mcp/mcp-proxy.d.ts +124 -0
- package/dist/mcp/mcp-proxy.js +697 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +110 -35
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +38 -0
- package/dist/session/no-session-diagnosis.js +70 -0
- package/dist/session/no-session-watch.d.ts +19 -0
- package/dist/session/no-session-watch.js +61 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +26 -7
- package/dist/session/session-info.js +42 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +3 -0
- package/dist/session/session-manager.js +52 -9
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +14 -2
- package/dist/session/session.js +69 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/daemon-telemetry.d.ts +19 -0
- package/dist/telemetry/daemon-telemetry.js +12 -2
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +1 -1
- package/dist/telemetry/feedback-cli.js +4 -4
- package/dist/telemetry/feedback-context.d.ts +22 -0
- package/dist/telemetry/feedback-context.js +41 -1
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +2 -0
- package/dist/telemetry/session-metrics.js +68 -6
- package/dist/telemetry/telemetry.d.ts +27 -2
- package/dist/telemetry/telemetry.js +119 -29
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +84 -115
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +3 -1
- package/dist/tools/assert-verdict.js +8 -5
- package/dist/tools/browser-tools.js +30 -11
- package/dist/tools/contract-tools.js +1 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +90 -18
- package/dist/tools/error-recovery.d.ts +20 -2
- package/dist/tools/error-recovery.js +179 -4
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -6
- package/dist/tools/invoke-tool.js +57 -32
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-result.d.ts +17 -0
- package/dist/tools/navigate-result.js +31 -0
- package/dist/tools/observe-tools.js +54 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +88 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +8 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +204 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures.md +28 -0
- package/docs/flows.md +166 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +184 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block until a reloaded page has re-announced itself under the same session id.
|
|
3
|
+
*
|
|
4
|
+
* A reload tears the SDK down. The id survives (see session-continuity), and the daemon replaces the
|
|
5
|
+
* old Session with a new one when the page says HELLO again — so "is it back?" is exactly "does a
|
|
6
|
+
* DIFFERENT object hold this id now?". Identity, not a flag: the returning page always constructs a
|
|
7
|
+
* new Session, and nothing else can claim an id it already owns.
|
|
8
|
+
*
|
|
9
|
+
* Polls a live getter rather than subscribing, because there is no reconnect event to subscribe to
|
|
10
|
+
* and inventing one for this would be a wire change for a two-second wait.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* How often to look for the reconnected session.
|
|
14
|
+
*
|
|
15
|
+
* 25ms, not 100. What is being polled is an in-memory map lookup — no I/O, no browser round-trip —
|
|
16
|
+
* while the thing being waited for is an EVENT (the reloaded page's HELLO). At 100ms the agent paid
|
|
17
|
+
* up to a full interval of dead time after the page was already back, on every navigate-with-reload.
|
|
18
|
+
* Measured across the fixture fleet, `reticle_navigate` clustered at 105 / 202-206 / 308 / 407ms —
|
|
19
|
+
* the poll grid, visible in the data. Four times the checks of something this cheap costs nothing
|
|
20
|
+
* anyone can measure.
|
|
21
|
+
*/
|
|
22
|
+
const DEFAULT_POLL_MS = 25;
|
|
23
|
+
/**
|
|
24
|
+
* How long a reload is worth waiting for before answering "not confirmed".
|
|
25
|
+
*
|
|
26
|
+
* A production build is back in well under a second; a cold dev-server route rebuild is the slow
|
|
27
|
+
* case this budget is sized for. It is a CEILING, not a delay — the common path returns on the first
|
|
28
|
+
* or second poll.
|
|
29
|
+
*/
|
|
30
|
+
export const RELOAD_RECONNECT_TIMEOUT_MS = 5000;
|
|
31
|
+
export async function waitForReconnect(opts) {
|
|
32
|
+
const poll = opts.pollMs ?? DEFAULT_POLL_MS;
|
|
33
|
+
const start = opts.now();
|
|
34
|
+
for (;;) {
|
|
35
|
+
const live = opts.current();
|
|
36
|
+
if (live !== undefined && live !== opts.previous)
|
|
37
|
+
return true;
|
|
38
|
+
if (opts.now() - start >= opts.timeoutMs)
|
|
39
|
+
return false;
|
|
40
|
+
await opts.sleep(poll);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -3,7 +3,7 @@ import { LastAct } from './last-act.js';
|
|
|
3
3
|
import { type PageRuntime } from './command-timeout.js';
|
|
4
4
|
import { type SessionHealth } from './session-health.js';
|
|
5
5
|
export type { SessionHealth };
|
|
6
|
-
import { PresenterTone, SessionState, type CommandResult, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
|
|
6
|
+
import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
|
|
7
7
|
import type { JournalReader, JournalRecorder } from '../journal/journal-recorder.js';
|
|
8
8
|
import { type EventQueryOptions } from '../journal/journal-query.js';
|
|
9
9
|
import { type AmbientCounts } from '../journal/ambient.js';
|
|
@@ -27,6 +27,8 @@ export declare class Session {
|
|
|
27
27
|
title: string;
|
|
28
28
|
adapters: string[];
|
|
29
29
|
hasCapabilities: boolean;
|
|
30
|
+
/** Set when the page's SDK version differs from the daemon's (see version-skew.ts). */
|
|
31
|
+
versionSkew?: string;
|
|
30
32
|
/**
|
|
31
33
|
* Extra key names this app declared sensitive via `connect({ redact: { keys } })`. Held so the
|
|
32
34
|
* DRIVEN path can redact them too — a request body the daemon captures from the network stack
|
|
@@ -46,10 +48,12 @@ export declare class Session {
|
|
|
46
48
|
* `runtime` is optional so an older SDK (which does not report it) still works — it simply loses
|
|
47
49
|
* the desktop-specific timeout diagnosis rather than breaking.
|
|
48
50
|
*/
|
|
49
|
-
applyHealth(hidden: boolean, focused: boolean, runtime?: string, engine?: string): void;
|
|
51
|
+
applyHealth(hidden: boolean, focused: boolean, runtime?: string, engine?: string, brand?: BrowserBrand): void;
|
|
50
52
|
/** The shell (web/electron/tauri) and rendering engine the SDK reported. Undefined on an older SDK. */
|
|
51
53
|
get runtime(): PageRuntime | undefined;
|
|
52
54
|
get engine(): string | undefined;
|
|
55
|
+
/** The browser brand the SDK reported. Absent (never guessed) when the page did not say. */
|
|
56
|
+
get brand(): BrowserBrand | undefined;
|
|
53
57
|
/** Throttled if the tab is hidden OR we have not heard from it recently. */
|
|
54
58
|
throttled(): boolean;
|
|
55
59
|
/** The attachable health block — single source of truth for the tools. */
|
|
@@ -60,6 +64,14 @@ export declare class Session {
|
|
|
60
64
|
/** Re-stamp an incoming event with server-relative time, buffer it, and fan out. */
|
|
61
65
|
pushEvent(event: ReticleEvent, byteSize?: number): void;
|
|
62
66
|
recordActedRef(ref: string): void;
|
|
67
|
+
actedLabels(): ReadonlySet<string>;
|
|
68
|
+
/**
|
|
69
|
+
* Derive the snapshot-shaped label (`button "Deploy"`) from an act reply and remember it.
|
|
70
|
+
*
|
|
71
|
+
* Same shape the interactive snapshot emits, so coverage can match a control the agent drove
|
|
72
|
+
* against the same control after a re-render gave it a different ref.
|
|
73
|
+
*/
|
|
74
|
+
private recordActedLabelFrom;
|
|
63
75
|
actedRefs(): ReadonlySet<string>;
|
|
64
76
|
noteRateLimited(dropped: number): void;
|
|
65
77
|
blindSpots(): Readonly<Record<string, number>>;
|
package/dist/session/session.js
CHANGED
|
@@ -2,7 +2,8 @@ import { LastAct } from './last-act.js';
|
|
|
2
2
|
import { commandTimeoutMessage } from './command-timeout.js';
|
|
3
3
|
import { readHealthEvent } from './session-health.js';
|
|
4
4
|
import { PendingCommands } from './pending-commands.js';
|
|
5
|
-
import {
|
|
5
|
+
import { span } from '../trace.js';
|
|
6
|
+
import { AppRuntime, EventType, HumanControlDataSchema, HumanControlKind, HumanMarkDataSchema, ReticleCommand, MessageKind, parseEventPayload, PresenterTone, SESSION_HEALTH, SESSION_LEASE, SESSION_LIFECYCLE, SessionState, } from '@reticlehq/core';
|
|
6
7
|
import { RingBuffer } from '../events/ring-buffer.js';
|
|
7
8
|
import { filterEvents, mergeEventsBySeq, } from '../journal/journal-query.js';
|
|
8
9
|
import {} from '../journal/ambient.js';
|
|
@@ -13,6 +14,7 @@ import { ReviewStore } from './review-store.js';
|
|
|
13
14
|
import { buildSessionRecommendation } from './session-recommendation.js';
|
|
14
15
|
import { buildPresenterArgs } from './presenter-args.js';
|
|
15
16
|
import { buildSessionLease } from './session-lease.js';
|
|
17
|
+
import { buildSessionInfo } from './session-info.js';
|
|
16
18
|
const DEFAULT_COMMAND_TIMEOUT_MS = 8000;
|
|
17
19
|
/** Prefix on correlated command ids (c1, c2, …) — distinguishes them from mark ids. */
|
|
18
20
|
const COMMAND_ID_PREFIX = 'c';
|
|
@@ -32,6 +34,8 @@ export class Session {
|
|
|
32
34
|
title;
|
|
33
35
|
adapters;
|
|
34
36
|
hasCapabilities;
|
|
37
|
+
/** Set when the page's SDK version differs from the daemon's (see version-skew.ts). */
|
|
38
|
+
versionSkew;
|
|
35
39
|
/**
|
|
36
40
|
* Extra key names this app declared sensitive via `connect({ redact: { keys } })`. Held so the
|
|
37
41
|
* DRIVEN path can redact them too — a request body the daemon captures from the network stack
|
|
@@ -50,6 +54,8 @@ export class Session {
|
|
|
50
54
|
/** Which shell the page reported (PAGE_HEALTH). Undefined until the first report lands. */
|
|
51
55
|
#runtime;
|
|
52
56
|
#engine;
|
|
57
|
+
/** Which browser the page said it is (chrome/edge/arc/…). Undefined on an SDK too old to report one. */
|
|
58
|
+
#brand;
|
|
53
59
|
#focused = true;
|
|
54
60
|
/** Liveness: wall-clock of the last AGENT command (distinct from browser chatter / lastSeen). */
|
|
55
61
|
#lastAgentActivityAt;
|
|
@@ -101,14 +107,18 @@ export class Session {
|
|
|
101
107
|
* `runtime` is optional so an older SDK (which does not report it) still works — it simply loses
|
|
102
108
|
* the desktop-specific timeout diagnosis rather than breaking.
|
|
103
109
|
*/
|
|
104
|
-
applyHealth(hidden, focused, runtime, engine) {
|
|
110
|
+
applyHealth(hidden, focused, runtime, engine, brand) {
|
|
105
111
|
this.#hidden = hidden;
|
|
106
112
|
this.#focused = focused;
|
|
107
|
-
if (
|
|
113
|
+
if (AppRuntime.ELECTRON === runtime ||
|
|
114
|
+
AppRuntime.TAURI === runtime ||
|
|
115
|
+
AppRuntime.WEB === runtime) {
|
|
108
116
|
this.#runtime = runtime;
|
|
109
117
|
}
|
|
110
118
|
if (engine !== undefined)
|
|
111
119
|
this.#engine = engine;
|
|
120
|
+
if (brand !== undefined)
|
|
121
|
+
this.#brand = brand;
|
|
112
122
|
}
|
|
113
123
|
/** The shell (web/electron/tauri) and rendering engine the SDK reported. Undefined on an older SDK. */
|
|
114
124
|
get runtime() {
|
|
@@ -117,6 +127,10 @@ export class Session {
|
|
|
117
127
|
get engine() {
|
|
118
128
|
return this.#engine;
|
|
119
129
|
}
|
|
130
|
+
/** The browser brand the SDK reported. Absent (never guessed) when the page did not say. */
|
|
131
|
+
get brand() {
|
|
132
|
+
return this.#brand;
|
|
133
|
+
}
|
|
120
134
|
/** Throttled if the tab is hidden OR we have not heard from it recently. */
|
|
121
135
|
throttled() {
|
|
122
136
|
return this.#hidden || this.lastSeenMs() > SESSION_HEALTH.STALE_THRESHOLD_MS;
|
|
@@ -137,29 +151,19 @@ export class Session {
|
|
|
137
151
|
return recommendation === undefined ? base : { ...base, recommendation };
|
|
138
152
|
}
|
|
139
153
|
info() {
|
|
140
|
-
|
|
141
|
-
|
|
154
|
+
return buildSessionInfo({
|
|
155
|
+
id: this.id,
|
|
142
156
|
url: this.url,
|
|
143
|
-
|
|
157
|
+
projectId: this.projectId,
|
|
144
158
|
title: this.title,
|
|
145
159
|
adapters: this.adapters,
|
|
146
160
|
hasCapabilities: this.hasCapabilities,
|
|
161
|
+
versionSkew: this.versionSkew,
|
|
147
162
|
hidden: this.#hidden,
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
base.cleanup_suggestion =
|
|
153
|
-
'Call reticle_session{action:"end"} to free this session before starting new work.';
|
|
154
|
-
}
|
|
155
|
-
// Surface human bug reports in reticle_sessions (only when > 0, so a clean session adds nothing).
|
|
156
|
-
const marks = this.#review.pendingCount();
|
|
157
|
-
if (marks > 0) {
|
|
158
|
-
base.pendingMarks = marks;
|
|
159
|
-
const s = marks === 1 ? '' : 's';
|
|
160
|
-
base.review_suggestion = `The human flagged ${String(marks)} issue${s} on this tab — call reticle_session{action:"review"} to see and fix ${marks === 1 ? 'it' : 'them'}.`;
|
|
161
|
-
}
|
|
162
|
-
return base;
|
|
163
|
+
health: () => this.health(),
|
|
164
|
+
staleMs: () => this.staleMs(),
|
|
165
|
+
pendingMarkCount: () => this.#review.pendingCount(),
|
|
166
|
+
});
|
|
163
167
|
}
|
|
164
168
|
/** Wall-clock age of the session in milliseconds. */
|
|
165
169
|
staleMs() {
|
|
@@ -169,7 +173,7 @@ export class Session {
|
|
|
169
173
|
pushEvent(event, byteSize) {
|
|
170
174
|
if (event.type === EventType.PAGE_HEALTH) {
|
|
171
175
|
const r = readHealthEvent(event.data);
|
|
172
|
-
this.applyHealth(r.hidden ?? this.#hidden, r.focused ?? this.#focused, r.runtime, r.engine);
|
|
176
|
+
this.applyHealth(r.hidden ?? this.#hidden, r.focused ?? this.#focused, r.runtime, r.engine, r.brand);
|
|
173
177
|
}
|
|
174
178
|
// The in-page half of Reticle reporting its own failure — recorded like a tool error
|
|
175
179
|
// (fingerprinted, variables stripped) so a broken observer is as visible as a broken tool.
|
|
@@ -195,7 +199,7 @@ export class Session {
|
|
|
195
199
|
// pushState/replaceState/popstate; without this the URL stays frozen at the hello value, and
|
|
196
200
|
// URL-based CDP correlation (real input) silently breaks after the first client-side nav.
|
|
197
201
|
const to = event.data['to'];
|
|
198
|
-
if (typeof to
|
|
202
|
+
if ('string' === typeof to && to.length > 0)
|
|
199
203
|
this.url = to;
|
|
200
204
|
}
|
|
201
205
|
const t = this.elapsed();
|
|
@@ -218,6 +222,35 @@ export class Session {
|
|
|
218
222
|
recordActedRef(ref) {
|
|
219
223
|
this.#observed.recordActedRef(ref);
|
|
220
224
|
}
|
|
225
|
+
actedLabels() {
|
|
226
|
+
return this.#observed.actedLabels();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Derive the snapshot-shaped label (`button "Deploy"`) from an act reply and remember it.
|
|
230
|
+
*
|
|
231
|
+
* Same shape the interactive snapshot emits, so coverage can match a control the agent drove
|
|
232
|
+
* against the same control after a re-render gave it a different ref.
|
|
233
|
+
*/
|
|
234
|
+
recordActedLabelFrom(result) {
|
|
235
|
+
const payload = result.result;
|
|
236
|
+
if (typeof payload !== 'object' || null === payload)
|
|
237
|
+
return;
|
|
238
|
+
const record = payload;
|
|
239
|
+
// The TESTID first: it is the strongest identity a control has and it survives any re-render.
|
|
240
|
+
// Coverage previously matched only on `role "name"`, so a control with a testid but no accessible
|
|
241
|
+
// name — or on a stack where the act reply carried neither — was unrecognisable after a
|
|
242
|
+
// re-render, and coverage read `exercised: 0` however much work had been done.
|
|
243
|
+
const testid = record['testid'];
|
|
244
|
+
if ('string' === typeof testid && testid.length > 0)
|
|
245
|
+
this.#observed.recordActedLabel(testid);
|
|
246
|
+
const role = record['role'];
|
|
247
|
+
const name = record['name'];
|
|
248
|
+
if (typeof role !== 'string' || typeof name !== 'string')
|
|
249
|
+
return;
|
|
250
|
+
if (0 === role.length || 0 === name.length)
|
|
251
|
+
return;
|
|
252
|
+
this.#observed.recordActedLabel(`${role} "${name}"`);
|
|
253
|
+
}
|
|
221
254
|
actedRefs() {
|
|
222
255
|
return this.#observed.actedRefs();
|
|
223
256
|
}
|
|
@@ -348,7 +381,7 @@ export class Session {
|
|
|
348
381
|
// still wrong, and it made the number depend on which tool the agent happened to use.
|
|
349
382
|
if (name === ReticleCommand.ACT) {
|
|
350
383
|
const ref = args['ref'];
|
|
351
|
-
if (typeof ref
|
|
384
|
+
if ('string' === typeof ref)
|
|
352
385
|
this.recordActedRef(ref);
|
|
353
386
|
}
|
|
354
387
|
const id = this.#pending.nextId(COMMAND_ID_PREFIX);
|
|
@@ -369,7 +402,17 @@ export class Session {
|
|
|
369
402
|
}));
|
|
370
403
|
this.#socket.send(payload);
|
|
371
404
|
const sentAt = Date.now(); // browser-leg timing; see recordBrowserLatency for why it is split out
|
|
372
|
-
|
|
405
|
+
// Traced because this is the boundary that decides whether a slow tool call is OUR overhead or
|
|
406
|
+
// the app taking its time — the split telemetry reports in aggregate, made visible per call.
|
|
407
|
+
return span('browser.command', { command: name, sessionId: this.id }, () => awaited)
|
|
408
|
+
.then((result) => {
|
|
409
|
+
// The label is only knowable from the REPLY — the request carries a ref, and a ref dies with
|
|
410
|
+
// the next re-render. Recorded here so coverage survives frameworks that replace nodes.
|
|
411
|
+
if (name === ReticleCommand.ACT)
|
|
412
|
+
this.recordActedLabelFrom(result);
|
|
413
|
+
return result;
|
|
414
|
+
})
|
|
415
|
+
.finally(() => recordBrowserLatency(Date.now() - sentAt));
|
|
373
416
|
}
|
|
374
417
|
handleResult(result) {
|
|
375
418
|
this.#pending.settle(result);
|
|
@@ -490,7 +533,7 @@ export class Session {
|
|
|
490
533
|
// still wrong, and it made the number depend on which tool the agent happened to use.
|
|
491
534
|
if (name === ReticleCommand.ACT) {
|
|
492
535
|
const ref = args['ref'];
|
|
493
|
-
if (typeof ref
|
|
536
|
+
if ('string' === typeof ref)
|
|
494
537
|
this.recordActedRef(ref);
|
|
495
538
|
}
|
|
496
539
|
const id = this.#pending.nextId(COMMAND_ID_PREFIX);
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* without paying the truncation tax); the server re-exports it for its back-compat fallback path
|
|
5
5
|
* (an older browser that returns the whole store) and for the predicate engine's `state` assertion.
|
|
6
6
|
*/
|
|
7
|
-
export { selectPath, capDepth, type PathSelection } from '@reticlehq/core';
|
|
7
|
+
export { selectPath, capDepth, projectComponentState, type PathSelection } from '@reticlehq/core';
|
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
* without paying the truncation tax); the server re-exports it for its back-compat fallback path
|
|
5
5
|
* (an older browser that returns the whole store) and for the predicate engine's `state` assertion.
|
|
6
6
|
*/
|
|
7
|
-
export { selectPath, capDepth } from '@reticlehq/core';
|
|
7
|
+
export { selectPath, capDepth, projectComponentState } from '@reticlehq/core';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `GET /status` answers — the daemon describing itself.
|
|
3
|
+
*
|
|
4
|
+
* Carries the daemon's OWN version, because a daemon outlives every agent attached to it: after an
|
|
5
|
+
* upgrade the new CLI attaches to the old daemon and serves its code, and until this field existed
|
|
6
|
+
* there was no surface anywhere — not /status, not `reticle status` — naming the version actually
|
|
7
|
+
* answering requests. See describeDaemonSkew.
|
|
8
|
+
*/
|
|
9
|
+
import type { SessionInfo } from './session/session-info.js';
|
|
10
|
+
interface StatusPayload {
|
|
11
|
+
running: true;
|
|
12
|
+
version: string;
|
|
13
|
+
/** The wire contract this daemon speaks — what another process compares against, not the version. */
|
|
14
|
+
contract: string;
|
|
15
|
+
sessionCount: number;
|
|
16
|
+
sessions: SessionInfo[];
|
|
17
|
+
}
|
|
18
|
+
export declare function statusPayload(sessionCount: number, sessions: SessionInfo[]): StatusPayload;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
|
|
2
|
+
import { SERVER_VERSION } from './version/server-version.js';
|
|
3
|
+
export function statusPayload(sessionCount, sessions) {
|
|
4
|
+
return {
|
|
5
|
+
running: true,
|
|
6
|
+
version: SERVER_VERSION,
|
|
7
|
+
contract: CONTRACT_FINGERPRINT,
|
|
8
|
+
sessionCount,
|
|
9
|
+
sessions,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -57,10 +57,19 @@ export function describeParam(name, value) {
|
|
|
57
57
|
return name;
|
|
58
58
|
return `${name}:${allowed.has(value) ? value : OTHER_VALUE}`;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Parameters that carry no information about how the tool is USED, and so are not counted.
|
|
62
|
+
*
|
|
63
|
+
* `sessionId` is accepted by nearly every tool and passed on nearly every call, so counting it
|
|
64
|
+
* produced a row per tool saying only "the agent addressed a session" — in one real day's export it
|
|
65
|
+
* was a third of the toolParams block. This histogram exists to answer "is anyone using the argument
|
|
66
|
+
* we shipped last release", and a parameter every call carries can never answer it.
|
|
67
|
+
*/
|
|
68
|
+
const UNINFORMATIVE_PARAMS = new Set(['sessionId']);
|
|
60
69
|
/** Every parameter an agent actually passed to one tool call, reduced to names (+ safe enums). */
|
|
61
70
|
export function describeToolParams(args) {
|
|
62
71
|
return Object.keys(args)
|
|
63
|
-
.filter((key) => args[key] !== undefined)
|
|
72
|
+
.filter((key) => args[key] !== undefined && !UNINFORMATIVE_PARAMS.has(key))
|
|
64
73
|
.map((key) => describeParam(key, args[key]))
|
|
65
74
|
.sort();
|
|
66
75
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Was this verification watched by a person, or run by a machine?
|
|
3
|
+
*
|
|
4
|
+
* A verification's cost and its meaning both depend on it. A headless run is CI or an unattended
|
|
5
|
+
* agent loop — it should be fast, and there is nobody to notice a wrong answer. A headed run has a
|
|
6
|
+
* human in front of it. And the most common case of all is neither: the SDK in somebody's own dev
|
|
7
|
+
* server, in their own browser, which Reticle never launched at all.
|
|
8
|
+
*
|
|
9
|
+
* Recorded once at daemon start, because that is when the decision is made and it does not change
|
|
10
|
+
* for the daemon's life. Defaults to ATTACHED, which is the honest answer when Reticle launched
|
|
11
|
+
* nothing: claiming "headless" for a browser we did not open would be a guess about somebody's
|
|
12
|
+
* machine.
|
|
13
|
+
*/
|
|
14
|
+
export declare const BrowserMode: {
|
|
15
|
+
/** Reticle launched the browser with no display — CI, or an unattended agent loop. */
|
|
16
|
+
readonly HEADLESS: "headless";
|
|
17
|
+
/** Reticle launched a visible browser — a human is watching. */
|
|
18
|
+
readonly HEADED: "headed";
|
|
19
|
+
/** Reticle launched nothing: the SDK connected from a browser somebody else opened. */
|
|
20
|
+
readonly ATTACHED: "attached";
|
|
21
|
+
};
|
|
22
|
+
export type BrowserMode = (typeof BrowserMode)[keyof typeof BrowserMode];
|
|
23
|
+
/** Record how this daemon drives a browser. Called once, at start. */
|
|
24
|
+
export declare function setBrowserMode(mode: BrowserMode): void;
|
|
25
|
+
export declare function getBrowserMode(): BrowserMode;
|
|
26
|
+
/** Tests only — restore the default so cases do not leak into each other. */
|
|
27
|
+
export declare function resetBrowserMode(): void;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Was this verification watched by a person, or run by a machine?
|
|
3
|
+
*
|
|
4
|
+
* A verification's cost and its meaning both depend on it. A headless run is CI or an unattended
|
|
5
|
+
* agent loop — it should be fast, and there is nobody to notice a wrong answer. A headed run has a
|
|
6
|
+
* human in front of it. And the most common case of all is neither: the SDK in somebody's own dev
|
|
7
|
+
* server, in their own browser, which Reticle never launched at all.
|
|
8
|
+
*
|
|
9
|
+
* Recorded once at daemon start, because that is when the decision is made and it does not change
|
|
10
|
+
* for the daemon's life. Defaults to ATTACHED, which is the honest answer when Reticle launched
|
|
11
|
+
* nothing: claiming "headless" for a browser we did not open would be a guess about somebody's
|
|
12
|
+
* machine.
|
|
13
|
+
*/
|
|
14
|
+
export const BrowserMode = {
|
|
15
|
+
/** Reticle launched the browser with no display — CI, or an unattended agent loop. */
|
|
16
|
+
HEADLESS: 'headless',
|
|
17
|
+
/** Reticle launched a visible browser — a human is watching. */
|
|
18
|
+
HEADED: 'headed',
|
|
19
|
+
/** Reticle launched nothing: the SDK connected from a browser somebody else opened. */
|
|
20
|
+
ATTACHED: 'attached',
|
|
21
|
+
};
|
|
22
|
+
let current = BrowserMode.ATTACHED;
|
|
23
|
+
/** Record how this daemon drives a browser. Called once, at start. */
|
|
24
|
+
export function setBrowserMode(mode) {
|
|
25
|
+
current = mode;
|
|
26
|
+
}
|
|
27
|
+
export function getBrowserMode() {
|
|
28
|
+
return current;
|
|
29
|
+
}
|
|
30
|
+
/** Tests only — restore the default so cases do not leak into each other. */
|
|
31
|
+
export function resetBrowserMode() {
|
|
32
|
+
current = BrowserMode.ATTACHED;
|
|
33
|
+
}
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Counting the defects Reticle actually finds.
|
|
3
|
-
*
|
|
4
|
-
* Every other metric in this product measures whether Reticle is USED. This one measures whether it
|
|
5
|
-
* WORKS — and it is the only number that can honestly be put in front of an investor or published,
|
|
6
|
-
* because it counts outcomes for users rather than activity by them.
|
|
7
|
-
*
|
|
8
|
-
* It is read off tool RESULTS at the single chokepoint every tool already crosses, rather than
|
|
9
|
-
* emitted from the four places that produce findings. That is deliberate: the detectors are pure
|
|
10
|
-
* functions and should stay that way, and reading results means a fifth source of findings is covered
|
|
11
|
-
* the day it starts returning one of these shapes, instead of the day someone remembers to instrument
|
|
12
|
-
* it.
|
|
13
|
-
*
|
|
14
|
-
* What is sent is the KIND from Reticle's own findings vocabulary — `signal-contradicted`,
|
|
15
|
-
* `console-error`, `duplicate-request`. Never a selector, a URL, an element, or a description of
|
|
16
|
-
* anyone's app: that a class of defect was found, never what it was found in.
|
|
17
|
-
*/
|
|
18
1
|
import { type BugFound } from '@reticlehq/core';
|
|
19
2
|
/**
|
|
20
3
|
* A defect as the CLASSIFIER sees it — everything except `repeat`.
|
|
@@ -24,10 +7,23 @@ import { type BugFound } from '@reticlehq/core';
|
|
|
24
7
|
*/
|
|
25
8
|
export type BugCandidate = Omit<BugFound, 'repeat'>;
|
|
26
9
|
/**
|
|
27
|
-
*
|
|
10
|
+
* `bug.attribution` was REMOVED, deliberately, and this note is here so it is not re-added casually.
|
|
11
|
+
*
|
|
12
|
+
* It shipped twice and was wrong both times. First it stamped `app` on every contradiction, including
|
|
13
|
+
* `request-never-settled` — which Reticle raises from the ABSENCE of a settle event, so a dev-overlay
|
|
14
|
+
* poll manufactured it with the app doing nothing wrong. That was fixed, and a real drive then found
|
|
15
|
+
* it still inverted: across two full runs, EVERY `attribution: 'app'` was a misattribution, while the
|
|
16
|
+
* one defect that genuinely was a bad agent predicate carried none. One session would have published
|
|
17
|
+
* "2 defects in the app" against a true count of 0.
|
|
18
|
+
*
|
|
19
|
+
* A metric that is confidently wrong about WHOSE fault a defect is, is worse than no metric: it is
|
|
20
|
+
* the number a founder steers on, and it points at the customer. Counting nothing is recoverable;
|
|
21
|
+
* publishing a false accusation about somebody's product is not.
|
|
28
22
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
23
|
+
* Bringing it back needs evidence that actually separates the cases. It does not exist in the payload
|
|
24
|
+
* today: `element.present` covers "the button is missing", "the API is down" and "the agent mistyped
|
|
25
|
+
* a testid" identically, and `signal.absent` covers both a signal the app never fired and a ref
|
|
26
|
+
* Reticle drove into nothing. Until a verdict carries the reason it came out that way, the honest
|
|
27
|
+
* output is no owner at all.
|
|
32
28
|
*/
|
|
33
29
|
export declare function bugsInResult(toolName: string, result: Record<string, unknown>): BugCandidate[];
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* `console-error`, `duplicate-request`. Never a selector, a URL, an element, or a description of
|
|
16
16
|
* anyone's app: that a class of defect was found, never what it was found in.
|
|
17
17
|
*/
|
|
18
|
+
import { ReticleTool } from '../tools/tool-names.js';
|
|
18
19
|
import { BugSource, ContradictionKind } from '@reticlehq/core';
|
|
19
20
|
/** Cap per tool call — a pathological crawl must not turn one call into a thousand events. */
|
|
20
21
|
const MAX_BUGS_PER_CALL = 25;
|
|
@@ -24,7 +25,7 @@ function kindsOf(value) {
|
|
|
24
25
|
return value
|
|
25
26
|
.map((entry) => {
|
|
26
27
|
const kind = entry?.kind;
|
|
27
|
-
return typeof kind
|
|
28
|
+
return 'string' === typeof kind ? kind : undefined;
|
|
28
29
|
})
|
|
29
30
|
.filter((kind) => kind !== undefined);
|
|
30
31
|
}
|
|
@@ -35,16 +36,111 @@ function kindsOf(value) {
|
|
|
35
36
|
* the case the product exists for — the screen looked right, the write failed, and every other tool
|
|
36
37
|
* on the market would have called it green. That pairing is what `falseGreen` marks.
|
|
37
38
|
*/
|
|
39
|
+
/**
|
|
40
|
+
* The verdict this result carries, whatever envelope it arrived in.
|
|
41
|
+
*
|
|
42
|
+
* `reticle_assert` reports a top-level boolean `pass`. `reticle_act_and_wait` — the tool agents
|
|
43
|
+
* actually reach for, and the one whose description calls it the act->observe->assert loop — reports
|
|
44
|
+
* `verdict.pass` with the summary at `verified`. Reading only `result.pass` meant a FAILED
|
|
45
|
+
* act_and_wait produced no bug at all, and a contradiction found next to a PASSING one was recorded
|
|
46
|
+
* with `falseGreen: false` — deflating the single number this product exists to publish, by the
|
|
47
|
+
* shape of its own envelope.
|
|
48
|
+
*/
|
|
49
|
+
function verdictOf(result) {
|
|
50
|
+
if ('boolean' === typeof result['pass'])
|
|
51
|
+
return result['pass'];
|
|
52
|
+
const verdict = result['verdict'];
|
|
53
|
+
if ('object' === typeof verdict && verdict !== null) {
|
|
54
|
+
const nested = verdict['pass'];
|
|
55
|
+
if ('boolean' === typeof nested)
|
|
56
|
+
return nested;
|
|
57
|
+
}
|
|
58
|
+
// `verified` is the field the agent is told to gate on; "unknown" is deliberately not a verdict.
|
|
59
|
+
const verified = result['verified'];
|
|
60
|
+
if ('yes' === verified)
|
|
61
|
+
return true;
|
|
62
|
+
if ('no' === verified)
|
|
63
|
+
return false;
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/** The assertion label, from either envelope — act_and_wait nests it under `verdict`. */
|
|
67
|
+
function assertionOf(result) {
|
|
68
|
+
if (result['assertion'] !== undefined)
|
|
69
|
+
return result['assertion'];
|
|
70
|
+
const verdict = result['verdict'];
|
|
71
|
+
return 'object' === typeof verdict && verdict !== null
|
|
72
|
+
? verdict['assertion']
|
|
73
|
+
: undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A verdict that could not be reached is not a defect found. `inconclusive` is set when the
|
|
77
|
+
* assertion was never evaluated — an under-specified call, or nothing instrumented to read — so
|
|
78
|
+
* counting it would put the agent's own mistakes into the number we publish.
|
|
79
|
+
*/
|
|
80
|
+
function isInconclusive(result) {
|
|
81
|
+
const verdict = result['verdict'];
|
|
82
|
+
return ('object' === typeof verdict &&
|
|
83
|
+
verdict !== null &&
|
|
84
|
+
'string' === typeof verdict['inconclusive']);
|
|
85
|
+
}
|
|
86
|
+
/** A suite row whose replay never STARTED — see SuiteFlowResult.couldNotRun. */
|
|
87
|
+
function couldNotRun(failure) {
|
|
88
|
+
return ('object' === typeof failure &&
|
|
89
|
+
failure !== null &&
|
|
90
|
+
true === failure.couldNotRun);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `bug.attribution` was REMOVED, deliberately, and this note is here so it is not re-added casually.
|
|
94
|
+
*
|
|
95
|
+
* It shipped twice and was wrong both times. First it stamped `app` on every contradiction, including
|
|
96
|
+
* `request-never-settled` — which Reticle raises from the ABSENCE of a settle event, so a dev-overlay
|
|
97
|
+
* poll manufactured it with the app doing nothing wrong. That was fixed, and a real drive then found
|
|
98
|
+
* it still inverted: across two full runs, EVERY `attribution: 'app'` was a misattribution, while the
|
|
99
|
+
* one defect that genuinely was a bad agent predicate carried none. One session would have published
|
|
100
|
+
* "2 defects in the app" against a true count of 0.
|
|
101
|
+
*
|
|
102
|
+
* A metric that is confidently wrong about WHOSE fault a defect is, is worse than no metric: it is
|
|
103
|
+
* the number a founder steers on, and it points at the customer. Counting nothing is recoverable;
|
|
104
|
+
* publishing a false accusation about somebody's product is not.
|
|
105
|
+
*
|
|
106
|
+
* Bringing it back needs evidence that actually separates the cases. It does not exist in the payload
|
|
107
|
+
* today: `element.present` covers "the button is missing", "the API is down" and "the agent mistyped
|
|
108
|
+
* a testid" identically, and `signal.absent` covers both a signal the app never fired and a ref
|
|
109
|
+
* Reticle drove into nothing. Until a verdict carries the reason it came out that way, the honest
|
|
110
|
+
* output is no owner at all.
|
|
111
|
+
*/
|
|
38
112
|
export function bugsInResult(toolName, result) {
|
|
113
|
+
// `reticle_run` is a WRAPPER: its handler calls runTool on the real tool, which already reported
|
|
114
|
+
// that result's defects under the real tool's name — then the outer chokepoint reports the very
|
|
115
|
+
// same object again as `reticle_run`. One sweep of 34 events was a perfect mirror image:
|
|
116
|
+
// flow-regression x8/reticle_flow_verify AND x8/reticle_run; assertion-failed x8/reticle_verify_change
|
|
117
|
+
// AND x8/reticle_run. Sixteen of the thirty-four were echoes, and a headline number that doubles
|
|
118
|
+
// because of HOW a tool was reached is not a measurement.
|
|
119
|
+
//
|
|
120
|
+
// Nothing is lost by staying silent here: when the inner tool refuses or throws, the wrapper
|
|
121
|
+
// returns { error, tool, params, hint }, which carries no verdict and no failures to classify.
|
|
122
|
+
if (toolName === ReticleTool.RUN)
|
|
123
|
+
return [];
|
|
39
124
|
const bugs = [];
|
|
40
|
-
const
|
|
125
|
+
const verdict = verdictOf(result);
|
|
126
|
+
const passed = true === verdict;
|
|
41
127
|
// 1. Contradictions — channels disagreeing. Invisible to a human watching the screen.
|
|
42
128
|
for (const kind of kindsOf(result['contradictions'])) {
|
|
43
|
-
|
|
129
|
+
// A contradiction is two of the APP's own channels disagreeing — never the agent's phrasing.
|
|
130
|
+
// Omitted for the one kind Reticle can produce on its own: see contradictionAttribution.
|
|
131
|
+
bugs.push({
|
|
132
|
+
source: BugSource.CONTRADICTION,
|
|
133
|
+
kind,
|
|
134
|
+
falseGreen: passed,
|
|
135
|
+
tool: toolName,
|
|
136
|
+
});
|
|
44
137
|
}
|
|
45
138
|
// 2. Crawl anomalies — found autonomously, with nobody writing a test. Crawl reports its own
|
|
46
139
|
// contradictions inside `anomalies` too, so classify by kind rather than by which array it sat in.
|
|
47
140
|
for (const kind of kindsOf(result['anomalies'])) {
|
|
141
|
+
// Crawl reports contradictions in this array too, so the same kind must get the same owner
|
|
142
|
+
// wherever it arrives — a `request-never-settled` found by the crawler is no better evidence
|
|
143
|
+
// about whose fault it was than one found by an assertion.
|
|
48
144
|
bugs.push({
|
|
49
145
|
source: BugSource.CRAWL,
|
|
50
146
|
kind,
|
|
@@ -52,28 +148,44 @@ export function bugsInResult(toolName, result) {
|
|
|
52
148
|
// — even though no assertion was made. See the definition on `falseGreen`.
|
|
53
149
|
falseGreen: CONTRADICTION_KINDS.has(kind),
|
|
54
150
|
tool: toolName,
|
|
151
|
+
// Nobody wrote a predicate here, so there is no bad request to blame: a crawl anomaly is a
|
|
152
|
+
// fault the app produced on its own.
|
|
55
153
|
});
|
|
56
154
|
}
|
|
57
155
|
// 3. A failed assertion — the agent declared a consequence and it did not hold.
|
|
58
156
|
// Only when nothing above already explained it, or one defect would be counted twice.
|
|
59
|
-
|
|
60
|
-
|
|
157
|
+
// A red the APP earned, not one Reticle produced. `verified: "unknown"` is the honesty layer
|
|
158
|
+
// saying nothing was proved either way, and a top-level `error` is a refusal — measured over one
|
|
159
|
+
// sweep, every bug_found emitted was one of these two: "no session connected" from reticle_run and
|
|
160
|
+
// an unverifiable suite from reticle_verify_change, counted as defects in the user's app.
|
|
161
|
+
const reticleFailure = 'unknown' === result['verified'] || 'string' === typeof result['error'];
|
|
162
|
+
if (false === verdict && 0 === bugs.length && !isInconclusive(result) && !reticleFailure) {
|
|
163
|
+
const reason = assertionOf(result);
|
|
164
|
+
const kind = 'string' === typeof reason && reason.length > 0 ? reason.slice(0, 64) : 'assertion-failed';
|
|
61
165
|
bugs.push({
|
|
62
166
|
source: BugSource.ASSERTION,
|
|
63
|
-
kind
|
|
167
|
+
kind,
|
|
64
168
|
falseGreen: false,
|
|
65
169
|
tool: toolName,
|
|
170
|
+
// Omitted when the label cannot say whose fault it was — see ASSERTION_ATTRIBUTION.
|
|
66
171
|
});
|
|
67
172
|
}
|
|
68
173
|
// 4. Replay failures — a saved flow that used to pass no longer does. That is a regression caught,
|
|
69
174
|
// which is a different and stronger claim than "a check failed".
|
|
70
|
-
if (
|
|
71
|
-
for (const
|
|
175
|
+
if ('fail' === result['status'] && Array.isArray(result['failures'])) {
|
|
176
|
+
for (const failure of result['failures'].slice(0, MAX_BUGS_PER_CALL)) {
|
|
177
|
+
// A suite that failed because nothing could RUN is not a regression in the user's app. This
|
|
178
|
+
// rule never looked past `status`, so a project whose flow files did not resolve reported one
|
|
179
|
+
// "defect found" per flow — Reticle's own failure, published as the customer's.
|
|
180
|
+
if (couldNotRun(failure))
|
|
181
|
+
continue;
|
|
72
182
|
bugs.push({
|
|
73
183
|
source: BugSource.REPLAY,
|
|
74
184
|
kind: 'flow-regression',
|
|
75
185
|
falseGreen: false,
|
|
76
186
|
tool: toolName,
|
|
187
|
+
// A saved flow that used to pass and no longer does is a regression in the app. Rows that
|
|
188
|
+
// never RAN are skipped above precisely so Reticle's own failures do not land here.
|
|
77
189
|
});
|
|
78
190
|
}
|
|
79
191
|
}
|