@reticlehq/server 2.11.0 → 2.13.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 +93 -111
- package/dist/bridge/auth-failure-reason.d.ts +6 -2
- package/dist/bridge/auth-failure-reason.js +15 -8
- package/dist/bridge/bridge.d.ts +18 -14
- package/dist/bridge/bridge.js +248 -165
- package/dist/bridge/bridge.test-harness.d.ts +2 -0
- package/dist/bridge/bridge.test-harness.js +8 -1
- package/dist/bridge/pairing-token.d.ts +5 -0
- package/dist/bridge/pairing-token.js +28 -0
- package/dist/bridge/protocol-skew.d.ts +37 -0
- package/dist/bridge/protocol-skew.js +45 -0
- package/dist/capsule/capsule-store.d.ts +8 -7
- package/dist/capsule/capsule-store.js +1 -1
- package/dist/capsule/causal-summary.d.ts +2 -2
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/chromium-launch-options.d.ts +28 -0
- package/dist/chromium-launch-options.js +29 -0
- package/dist/cli/cli-doctor.js +39 -1
- package/dist/cli/cli-flow-commands.d.ts +3 -1
- package/dist/cli/cli-flow-commands.js +34 -4
- package/dist/cli/cli-kill.d.ts +2 -1
- package/dist/cli/cli-launch.d.ts +11 -1
- package/dist/cli/cli-launch.js +59 -10
- package/dist/cli/cli-parse.d.ts +17 -1
- package/dist/cli/cli-parse.js +238 -12
- package/dist/cli/cli-update-commands.js +20 -1
- package/dist/cli/cli-verify.d.ts +2 -1
- package/dist/cli/cli-verify.js +5 -4
- package/dist/cli/cloud-cli.js +428 -253
- package/dist/cli/cloud-keystore.d.ts +37 -0
- package/dist/cli/cloud-keystore.js +97 -0
- package/dist/cli/cloud-kit.d.ts +37 -0
- package/dist/cli/cloud-kit.js +121 -0
- package/dist/cli/cloud-login.d.ts +17 -0
- package/dist/cli/cloud-login.js +183 -0
- package/dist/cli/cloud-session.d.ts +48 -0
- package/dist/cli/cloud-session.js +53 -0
- package/dist/cli/config-discovery.d.ts +2 -1
- package/dist/cli/config-discovery.js +1 -0
- package/dist/cli/daemon-start-options.d.ts +32 -0
- package/dist/cli/daemon-start-options.js +38 -0
- package/dist/cli/device-flow.d.ts +76 -0
- package/dist/cli/device-flow.js +56 -0
- package/dist/cli/doctor-rows.d.ts +12 -1
- package/dist/cli/doctor-rows.js +12 -1
- package/dist/cli/drive-attach.d.ts +13 -0
- package/dist/cli/drive-attach.js +18 -0
- package/dist/cli/drive-command.js +7 -3
- package/dist/cli/gate-exit.d.ts +21 -0
- package/dist/cli/gate-exit.js +21 -0
- package/dist/cli/gate-hook-message.d.ts +33 -0
- package/dist/cli/gate-hook-message.js +46 -0
- package/dist/cli/mcp-command.d.ts +25 -0
- package/dist/cli/mcp-command.js +174 -0
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/sibling-ports.d.ts +37 -0
- package/dist/cli/sibling-ports.js +56 -0
- package/dist/cli/status-next-action.d.ts +14 -0
- package/dist/cli/status-next-action.js +4 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +167 -186
- package/dist/cloud/cloud-config.d.ts +24 -2
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +16 -5
- package/dist/cloud/cloud-sync.js +64 -1
- package/dist/cloud/sync-cycle.d.ts +110 -0
- package/dist/cloud/sync-cycle.js +270 -0
- package/dist/cloud/sync-daemon.d.ts +69 -0
- package/dist/cloud/sync-daemon.js +260 -0
- package/dist/cloud/sync-disk.d.ts +9 -0
- package/dist/cloud/sync-disk.js +191 -0
- package/dist/cloud/sync-hash.d.ts +2 -0
- package/dist/cloud/sync-hash.js +35 -0
- package/dist/crawl/crawl-tools.js +5 -3
- package/dist/crawl/crawl.d.ts +12 -13
- package/dist/crawl/crawl.js +25 -3
- package/dist/daemon/daemon-resolve.d.ts +122 -0
- package/dist/daemon/daemon-resolve.js +187 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/free-port.d.ts +29 -0
- package/dist/daemon/free-port.js +55 -0
- package/dist/daemon/heartbeat.d.ts +0 -4
- package/dist/daemon/heartbeat.js +2 -2
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/dev/stale-issue-guard.d.ts +0 -2
- package/dist/dev/stale-issue-guard.js +1 -1
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +59 -0
- package/dist/events/contradictions.js +253 -27
- package/dist/events/declared.d.ts +17 -0
- package/dist/events/declared.js +43 -1
- package/dist/events/echo-mismatch.js +65 -1
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +17 -0
- package/dist/events/predicate-asks.d.ts +13 -0
- package/dist/events/predicate-asks.js +22 -0
- package/dist/events/predicate-eval.js +117 -27
- package/dist/events/predicate-parse.d.ts +0 -6
- package/dist/events/predicate-parse.js +43 -3
- package/dist/events/predicate-precheck.d.ts +8 -0
- package/dist/events/predicate-precheck.js +41 -0
- package/dist/events/predicate-route.d.ts +14 -0
- package/dist/events/predicate-route.js +36 -8
- package/dist/events/predicate-schema.d.ts +44 -0
- package/dist/events/predicate-schema.js +126 -23
- package/dist/events/predicate.d.ts +20 -0
- package/dist/events/predicate.js +77 -6
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/coverage.d.ts +0 -8
- package/dist/flows/coverage.js +1 -1
- package/dist/flows/decision.d.ts +5 -1
- package/dist/flows/decision.js +33 -7
- package/dist/flows/flake.d.ts +6 -8
- package/dist/flows/flake.js +1 -1
- package/dist/flows/flow-intent.d.ts +52 -0
- package/dist/flows/flow-intent.js +123 -0
- package/dist/flows/flow-memory-consult.d.ts +55 -0
- package/dist/flows/flow-memory-consult.js +38 -0
- package/dist/flows/flow-replay-run.d.ts +18 -7
- package/dist/flows/flow-replay-run.js +263 -23
- package/dist/flows/flow-replay.js +11 -2
- package/dist/flows/flow-step-runners.d.ts +8 -0
- package/dist/flows/flow-step-runners.js +8 -2
- package/dist/flows/flow-store-for-session.d.ts +31 -0
- package/dist/flows/flow-store-for-session.js +33 -0
- package/dist/flows/flow-success.js +15 -3
- package/dist/flows/flow-tools.js +87 -33
- package/dist/flows/flows.d.ts +14 -1
- package/dist/flows/flows.js +502 -24
- package/dist/flows/heal-run.js +5 -2
- package/dist/flows/predicate-to-expect.d.ts +17 -12
- package/dist/flows/predicate-to-expect.js +36 -15
- package/dist/flows/recordings.d.ts +13 -1
- package/dist/flows/recordings.js +17 -2
- package/dist/flows/replay.d.ts +22 -2
- package/dist/flows/replay.js +28 -2
- package/dist/flows/server-verify.js +16 -2
- package/dist/flows/verify-change-tools.js +9 -3
- package/dist/honesty/blind-spots.d.ts +51 -15
- package/dist/honesty/blind-spots.js +64 -4
- package/dist/honesty/feature-capture.d.ts +150 -0
- package/dist/honesty/feature-capture.js +233 -0
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +39 -6
- package/dist/honesty/instrumentation-gaps.js +88 -5
- package/dist/honesty/tool-hit-rate.d.ts +86 -0
- package/dist/honesty/tool-hit-rate.js +90 -0
- package/dist/honesty/undeclared-change.d.ts +51 -0
- package/dist/honesty/undeclared-change.js +56 -0
- package/dist/honesty/verified.d.ts +16 -0
- package/dist/honesty/verified.js +84 -5
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +13 -1
- package/dist/hunt/hunt-report.d.ts +0 -2
- package/dist/hunt/hunt-report.js +1 -1
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +13 -4
- package/dist/impact/impact-store.js +44 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +30 -6
- package/dist/init/agent-rules.js +54 -13
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -3
- package/dist/init/capabilities.js +23 -3
- package/dist/init/closing-hint.d.ts +36 -0
- package/dist/init/closing-hint.js +120 -0
- package/dist/init/confirm.d.ts +27 -28
- package/dist/init/confirm.js +51 -11
- package/dist/init/cra.d.ts +34 -5
- package/dist/init/cra.js +96 -25
- package/dist/init/csp-check.js +17 -2
- package/dist/init/csp-doctor.d.ts +8 -1
- package/dist/init/csp-doctor.js +8 -3
- package/dist/init/dev-script.d.ts +50 -0
- package/dist/init/dev-script.js +66 -0
- package/dist/init/format-generated.d.ts +18 -0
- package/dist/init/format-generated.js +39 -0
- package/dist/init/install-hint.d.ts +9 -0
- package/dist/init/install-hint.js +29 -0
- package/dist/init/mcp-clients.d.ts +7 -0
- package/dist/init/mcp-clients.js +7 -0
- package/dist/init/mcp.d.ts +17 -6
- package/dist/init/mcp.js +27 -11
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.d.ts +0 -5
- package/dist/init/plan-framework.js +147 -37
- package/dist/init/plan.d.ts +30 -37
- package/dist/init/plan.js +50 -60
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/refresh-rules.d.ts +39 -0
- package/dist/init/refresh-rules.js +51 -0
- package/dist/init/run.d.ts +32 -35
- package/dist/init/run.js +151 -202
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +90 -31
- package/dist/init/vite-config.d.ts +1 -1
- package/dist/init/vite-config.js +39 -25
- package/dist/init/workspace-apps.d.ts +17 -10
- package/dist/init/workspace-apps.js +110 -0
- package/dist/input/network-mock-tools.js +3 -6
- package/dist/input/real-input.d.ts +2 -0
- package/dist/input/real-input.js +28 -7
- package/dist/input/scroll-find.d.ts +7 -4
- package/dist/input/scroll-find.js +63 -25
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +37 -0
- package/dist/intent/inline-intent.js +193 -0
- package/dist/intent/intent-shard-store.d.ts +72 -0
- package/dist/intent/intent-shard-store.js +203 -0
- package/dist/intent/intent-shard.d.ts +411 -0
- package/dist/intent/intent-shard.js +176 -0
- package/dist/intent/intent-store.d.ts +49 -0
- package/dist/intent/intent-store.js +133 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.d.ts +2 -0
- package/dist/intent/intent-tools.js +143 -0
- package/dist/intent/open-intents.d.ts +42 -0
- package/dist/intent/open-intents.js +40 -0
- package/dist/journal/envelope.d.ts +26 -26
- package/dist/journal/journal-recorder.d.ts +23 -0
- package/dist/journal/journal-recorder.js +35 -0
- package/dist/license/license-env.d.ts +29 -0
- package/dist/license/license-env.js +148 -0
- package/dist/loopback-agent.d.ts +31 -0
- package/dist/loopback-agent.js +57 -0
- package/dist/mcp/attach-memory.d.ts +83 -0
- package/dist/mcp/attach-memory.js +185 -0
- package/dist/mcp/client-identity.d.ts +31 -0
- package/dist/mcp/client-identity.js +51 -0
- package/dist/mcp/mcp-post-transport.d.ts +26 -0
- package/dist/mcp/mcp-post-transport.js +109 -0
- package/dist/mcp/mcp-proxy.d.ts +2 -8
- package/dist/mcp/mcp-proxy.js +64 -62
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +52 -24
- package/dist/mcp/proxy-backoff.d.ts +11 -0
- package/dist/mcp/proxy-backoff.js +13 -0
- package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
- package/dist/mcp/proxy-daemon-probe.js +7 -1
- package/dist/mcp/proxy-handshake.d.ts +23 -0
- package/dist/mcp/proxy-handshake.js +31 -0
- package/dist/mcp/server-instructions.js +45 -3
- package/dist/mcp/tool-catalog-cache.d.ts +16 -0
- package/dist/mcp/tool-catalog-cache.js +22 -0
- package/dist/memory/memory-tools.d.ts +2 -0
- package/dist/memory/memory-tools.js +82 -0
- package/dist/memory/project-memory.d.ts +34 -0
- package/dist/memory/project-memory.js +96 -0
- package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
- package/dist/oracles/flow-instrument-gaps.js +4 -5
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/oracles/self-instrument.d.ts +9 -9
- package/dist/oracles/self-instrument.js +19 -13
- package/dist/pool/browser-pool.d.ts +75 -5
- package/dist/pool/browser-pool.js +144 -5
- package/dist/pool/playwright-launcher.js +12 -1
- package/dist/project/fs-port.d.ts +3 -0
- package/dist/project/fs-port.js +3 -2
- package/dist/project/memory-fs.js +6 -1
- package/dist/project/project-tools.js +6 -35
- package/dist/project/reticle-dir.d.ts +17 -2
- package/dist/project/reticle-dir.js +22 -4
- package/dist/runs/context-tools.d.ts +2 -0
- package/dist/runs/context-tools.js +89 -0
- package/dist/runs/run-context.d.ts +40 -0
- package/dist/runs/run-context.js +158 -0
- package/dist/runs/run-diff.d.ts +2 -1
- package/dist/runs/run-store.d.ts +10 -0
- package/dist/runs/run-store.js +10 -0
- package/dist/runs/verification-sync.d.ts +0 -11
- package/dist/runs/verification-sync.js +24 -3
- package/dist/runs/verify-server.d.ts +8 -1
- package/dist/runs/verify-server.js +14 -2
- package/dist/session/command-timeout.d.ts +5 -2
- package/dist/session/command-timeout.js +5 -4
- package/dist/session/dev-server-probe.d.ts +0 -8
- package/dist/session/dev-server-probe.js +1 -1
- package/dist/session/last-act.d.ts +7 -1
- package/dist/session/last-act.js +2 -2
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/live-control-tools.js +1 -42
- package/dist/session/no-session-diagnosis.d.ts +33 -0
- package/dist/session/no-session-diagnosis.js +82 -24
- package/dist/session/no-session-next-action.d.ts +12 -0
- package/dist/session/no-session-next-action.js +77 -6
- package/dist/session/no-session-watch.d.ts +18 -6
- package/dist/session/no-session-watch.js +125 -75
- package/dist/session/output-budget.js +4 -1
- package/dist/session/pending-commands.d.ts +11 -1
- package/dist/session/pending-commands.js +12 -2
- package/dist/session/resolve-within.d.ts +34 -0
- package/dist/session/resolve-within.js +53 -0
- package/dist/session/session-health.d.ts +9 -0
- package/dist/session/session-health.js +23 -0
- package/dist/session/session-info.d.ts +18 -0
- package/dist/session/session-info.js +11 -0
- package/dist/session/session-manager.d.ts +14 -0
- package/dist/session/session-manager.js +111 -4
- package/dist/session/session-mirror.d.ts +16 -0
- package/dist/session/session-mirror.js +22 -0
- package/dist/session/session-recommendation.d.ts +19 -5
- package/dist/session/session-recommendation.js +25 -9
- package/dist/session/session-successor.d.ts +37 -0
- package/dist/session/session-successor.js +113 -0
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +53 -3
- package/dist/session/session.js +162 -9
- package/dist/session/wall-clock.d.ts +12 -0
- package/dist/session/wall-clock.js +14 -0
- package/dist/setup/agent-configs.d.ts +96 -0
- package/dist/setup/agent-configs.js +333 -0
- package/dist/setup/agent-io.d.ts +9 -0
- package/dist/setup/agent-io.js +16 -0
- package/dist/setup/agent-writer.d.ts +51 -0
- package/dist/setup/agent-writer.js +115 -0
- package/dist/setup/approval-migration.d.ts +47 -0
- package/dist/setup/approval-migration.js +68 -0
- package/dist/setup/auto-approve.d.ts +85 -0
- package/dist/setup/auto-approve.js +150 -0
- package/dist/setup/bridge-port.d.ts +15 -0
- package/dist/setup/bridge-port.js +24 -0
- package/dist/setup/desktop-shape.d.ts +57 -0
- package/dist/setup/desktop-shape.js +63 -0
- package/dist/setup/dev-server-wait.d.ts +60 -0
- package/dist/setup/dev-server-wait.js +65 -0
- package/dist/setup/drive-agent.d.ts +71 -0
- package/dist/setup/drive-agent.js +171 -0
- package/dist/setup/drive-plan.d.ts +68 -0
- package/dist/setup/drive-plan.js +130 -0
- package/dist/setup/ensure-daemon.d.ts +29 -0
- package/dist/setup/ensure-daemon.js +58 -0
- package/dist/setup/init-runtime.d.ts +47 -0
- package/dist/setup/init-runtime.js +160 -0
- package/dist/setup/interrupt.d.ts +25 -0
- package/dist/setup/interrupt.js +40 -0
- package/dist/setup/license-key.d.ts +39 -0
- package/dist/setup/license-key.js +72 -0
- package/dist/setup/listeners.d.ts +37 -0
- package/dist/setup/listeners.js +85 -0
- package/dist/setup/node-effects.d.ts +60 -0
- package/dist/setup/node-effects.js +210 -0
- package/dist/setup/page-probe.d.ts +39 -0
- package/dist/setup/page-probe.js +49 -0
- package/dist/setup/relaunch.d.ts +36 -0
- package/dist/setup/relaunch.js +57 -0
- package/dist/setup/remaining-steps.d.ts +19 -0
- package/dist/setup/remaining-steps.js +31 -0
- package/dist/setup/run-setup.d.ts +99 -0
- package/dist/setup/run-setup.js +187 -0
- package/dist/setup/session-pick.d.ts +47 -0
- package/dist/setup/session-pick.js +51 -0
- package/dist/setup/setup-command.d.ts +45 -0
- package/dist/setup/setup-command.js +199 -0
- package/dist/setup/setup-options.d.ts +74 -0
- package/dist/setup/setup-options.js +67 -0
- package/dist/setup/transcripts.d.ts +16 -0
- package/dist/setup/transcripts.js +43 -0
- package/dist/status-payload.d.ts +14 -1
- package/dist/status-payload.js +23 -1
- package/dist/telemetry/automation-hint.d.ts +2 -1
- package/dist/telemetry/daemon-telemetry.d.ts +8 -1
- package/dist/telemetry/daemon-telemetry.js +52 -5
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +9 -1
- package/dist/telemetry/feedback-context.js +60 -29
- package/dist/telemetry/feedback-local.d.ts +15 -0
- package/dist/telemetry/feedback-local.js +56 -0
- package/dist/telemetry/feedback-outbox.d.ts +2 -1
- package/dist/telemetry/feedback.d.ts +11 -0
- package/dist/telemetry/feedback.js +19 -6
- package/dist/telemetry/init-telemetry.d.ts +2 -0
- package/dist/telemetry/init-telemetry.js +2 -0
- package/dist/telemetry/install-source.d.ts +14 -0
- package/dist/telemetry/install-source.js +30 -0
- package/dist/telemetry/license-activation.d.ts +7 -1
- package/dist/telemetry/license-activation.js +9 -5
- package/dist/telemetry/mcp-connection.js +7 -0
- package/dist/telemetry/project-profile.d.ts +12 -9
- package/dist/telemetry/project-profile.js +12 -7
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/run-telemetry.d.ts +0 -2
- package/dist/telemetry/run-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +12 -0
- package/dist/telemetry/session-metrics.js +35 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +3 -1
- package/dist/telemetry/tool-refused.js +34 -2
- package/dist/telemetry/verification-of.js +2 -2
- package/dist/tools/act-capsule.d.ts +10 -0
- package/dist/tools/act-capsule.js +8 -2
- package/dist/tools/act-danger.d.ts +2 -1
- package/dist/tools/act-danger.js +11 -3
- package/dist/tools/act-observation.d.ts +41 -0
- package/dist/tools/act-observation.js +36 -0
- package/dist/tools/act-preflight.d.ts +20 -0
- package/dist/tools/act-preflight.js +98 -0
- package/dist/tools/act-sequence-retry.d.ts +45 -0
- package/dist/tools/act-sequence-retry.js +58 -0
- package/dist/tools/act-sequence-tool.d.ts +13 -0
- package/dist/tools/act-sequence-tool.js +135 -0
- package/dist/tools/act-target.d.ts +14 -0
- package/dist/tools/act-target.js +47 -0
- package/dist/tools/act-tools.d.ts +21 -1
- package/dist/tools/act-tools.js +196 -204
- package/dist/tools/assert-source.d.ts +22 -0
- package/dist/tools/assert-source.js +53 -0
- package/dist/tools/assert-verdict.d.ts +20 -3
- package/dist/tools/assert-verdict.js +58 -20
- package/dist/tools/browser-tools.js +6 -22
- package/dist/tools/complete-snapshot.d.ts +48 -0
- package/dist/tools/complete-snapshot.js +75 -0
- package/dist/tools/coverage-tools.d.ts +10 -1
- package/dist/tools/coverage-tools.js +123 -69
- package/dist/tools/dynamic-tools.js +53 -13
- package/dist/tools/error-recovery.d.ts +25 -14
- package/dist/tools/error-recovery.js +78 -9
- package/dist/tools/feedback-tools.d.ts +1 -1
- package/dist/tools/feedback-tools.js +2 -2
- package/dist/tools/invoke-tool.js +121 -11
- package/dist/tools/lease-hint.d.ts +10 -0
- package/dist/tools/lease-hint.js +53 -4
- package/dist/tools/lease-tools.d.ts +35 -5
- package/dist/tools/lease-tools.js +191 -13
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/navigate-arrival.d.ts +0 -2
- package/dist/tools/navigate-arrival.js +1 -1
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +109 -65
- package/dist/tools/read-tools.js +31 -8
- package/dist/tools/real-input-attempt.d.ts +32 -0
- package/dist/tools/real-input-attempt.js +212 -2
- package/dist/tools/reconcile-tools.js +22 -8
- package/dist/tools/resolve-target.d.ts +0 -7
- package/dist/tools/sdk-marker-probe.d.ts +29 -0
- package/dist/tools/sdk-marker-probe.js +57 -0
- package/dist/tools/snapshot-delta.d.ts +1 -1
- package/dist/tools/tool-kit.d.ts +24 -0
- package/dist/tools/tool-kit.js +21 -0
- package/dist/tools/tool-names.d.ts +5 -0
- package/dist/tools/tool-names.js +5 -0
- package/dist/tools/tool-surface.d.ts +85 -6
- package/dist/tools/tool-surface.js +122 -2
- package/dist/tools/tools-helpers.d.ts +19 -0
- package/dist/tools/tools-helpers.js +46 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +78 -12
- package/dist/tools/unadvertised-help.js +4 -4
- package/dist/tools/verify-next-baton.d.ts +53 -0
- package/dist/tools/verify-next-baton.js +52 -0
- package/dist/update/reticle-deps.d.ts +9 -0
- package/dist/update/reticle-deps.js +27 -0
- package/dist/update/sdk-sync.d.ts +1 -2
- package/dist/update/sdk-sync.js +1 -21
- package/dist/update/update-nudge.d.ts +25 -10
- package/dist/update/update-nudge.js +32 -3
- package/dist/update/updater.d.ts +1 -1
- package/dist/version/peer-announce.js +4 -2
- package/dist/version/sdk-fix.d.ts +47 -0
- package/dist/version/sdk-fix.js +127 -0
- package/dist/version/version-nudge.d.ts +14 -0
- package/dist/version/version-nudge.js +21 -0
- package/dist/version/version-skew.d.ts +38 -3
- package/dist/version/version-skew.js +83 -7
- package/dist/visual/visual-store.d.ts +6 -6
- package/dist/visual/visual-store.js +18 -16
- package/dist/visual/visual-tools.js +57 -12
- package/docs/README.md +2 -1
- package/docs/cli/cloud.mdx +1 -1
- package/docs/cli/doctor.mdx +2 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli/verify.mdx +4 -5
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +2 -2
- package/docs/gates.md +1 -1
- package/docs/getting-started.md +19 -3
- package/docs/index.mdx +9 -6
- package/docs/install-agentic.mdx +38 -12
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/server.mdx +1 -1
- package/docs/packages.mdx +8 -6
- package/docs/platform-integration.md +17 -0
- package/docs/predicates.mdx +8 -0
- package/docs/reticle-manual.md +106 -0
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/tools-tools-and-run.mdx +2 -4
- package/docs/troubleshooting.mdx +9 -0
- package/docs/usage.md +4 -3
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
- package/dist/session/session-readiness.d.ts +0 -34
- package/dist/session/session-readiness.js +0 -36
|
@@ -10,14 +10,17 @@
|
|
|
10
10
|
* daemon that outlives the agent by hours has no business scanning ports it does not need.
|
|
11
11
|
*/
|
|
12
12
|
import { probeDevServers, probeDevServerStates } from './dev-server-probe.js';
|
|
13
|
-
import {
|
|
13
|
+
import { explainNoSession } from './no-session-diagnosis.js';
|
|
14
14
|
import { detectDevCommand } from './dev-command.js';
|
|
15
15
|
import { nextActionFor, renderNextAction } from './no-session-next-action.js';
|
|
16
16
|
import { readProjectFramework, readProjectId, readProjectPort } from '../cli/cli-port.js';
|
|
17
17
|
import { discoverProjectConfigs } from '../cli/config-discovery.js';
|
|
18
18
|
import { hasProjectConnectedBefore, rememberConnected } from './connection-memory.js';
|
|
19
|
-
import { reticleStateHome } from '../daemon/daemon.js';
|
|
19
|
+
import { isAlive, reticleStateHome } from '../daemon/daemon.js';
|
|
20
|
+
import { daemonsServingProjectElsewhere, splitBrainNote } from '../daemon/daemon-resolve.js';
|
|
20
21
|
import { stallUptime } from './stall-clock.js';
|
|
22
|
+
import { probeDaemon } from '../mcp/mcp-proxy.js';
|
|
23
|
+
import { findOccupiedSiblings } from '../cli/sibling-ports.js';
|
|
21
24
|
/** Slow enough to be free, fast enough that a dev server started 15s ago is already reflected. */
|
|
22
25
|
const REFRESH_MS = 15_000;
|
|
23
26
|
/** The URL an auto-attach opens. `localhost` for the same reason the probe uses it: both stacks. */
|
|
@@ -56,6 +59,7 @@ export function startNoSessionWatch(options) {
|
|
|
56
59
|
* `options.probe` returns only the serving set, so this stays empty for injected probes.
|
|
57
60
|
*/
|
|
58
61
|
let slowListeners = [];
|
|
62
|
+
let siblingListeners = [];
|
|
59
63
|
let running = false;
|
|
60
64
|
/** Ports auto-attach has already spent its one attempt on. Bounded: never a loop, never a retry. */
|
|
61
65
|
const attempted = new Set();
|
|
@@ -66,6 +70,27 @@ export function startNoSessionWatch(options) {
|
|
|
66
70
|
// is routinely written by `init` AFTER this daemon started, so re-read rather than cache. See the
|
|
67
71
|
// `initialized` comment below, which this shares.
|
|
68
72
|
const isWired = () => options.initialized || readProjectId(directory) !== undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the daemon's current project scope once for both halves of a sessions response.
|
|
75
|
+
*
|
|
76
|
+
* This remains a live read because `init` commonly writes the config after the daemon starts.
|
|
77
|
+
*/
|
|
78
|
+
const projectScopeFacts = () => {
|
|
79
|
+
const initialized = isWired();
|
|
80
|
+
if (initialized)
|
|
81
|
+
return { initialized };
|
|
82
|
+
const discovery = discoverProjectConfigs(directory);
|
|
83
|
+
const elsewhere = discovery.found.filter((config) => config.directory !== directory);
|
|
84
|
+
return 0 === elsewhere.length
|
|
85
|
+
? { initialized, searchedDirectories: discovery.searched }
|
|
86
|
+
: {
|
|
87
|
+
initialized,
|
|
88
|
+
configsElsewhere: elsewhere.map((config) => ({
|
|
89
|
+
directory: config.directory,
|
|
90
|
+
...(config.projectId === undefined ? {} : { projectId: config.projectId }),
|
|
91
|
+
})),
|
|
92
|
+
};
|
|
93
|
+
};
|
|
69
94
|
// Read at boot: the daemon's own identity does not change under it, and this is the key the
|
|
70
95
|
// durable bit is stored under.
|
|
71
96
|
const stateDir = options.stateDir ?? reticleStateHome();
|
|
@@ -78,6 +103,19 @@ export function startNoSessionWatch(options) {
|
|
|
78
103
|
* is the same over-confident claim this whole file exists to remove, pointing the other way.
|
|
79
104
|
*/
|
|
80
105
|
const connectedBefore = () => hasProjectConnectedBefore(stateDir, options.port, readProjectId(directory));
|
|
106
|
+
/**
|
|
107
|
+
* The split brain, asked fresh every time like everything else here.
|
|
108
|
+
*
|
|
109
|
+
* Cheap — a directory listing of `~/.reticle` and a few small JSON reads — and only ever reached
|
|
110
|
+
* on a daemon with no session, which is the state this whole file exists for. Reading it once at
|
|
111
|
+
* boot would miss the ordinary case entirely: the app connects to the other daemon SECONDS after
|
|
112
|
+
* this one starts, which is precisely the window the agent then spends being told to start a dev
|
|
113
|
+
* server that is already running.
|
|
114
|
+
*/
|
|
115
|
+
const splitBrain = () => {
|
|
116
|
+
const projectId = readProjectId(directory);
|
|
117
|
+
return splitBrainNote(options.port, daemonsServingProjectElsewhere(projectId, options.port, stateDir, isAlive, (port) => hasProjectConnectedBefore(stateDir, port, projectId)));
|
|
118
|
+
};
|
|
81
119
|
// Every path that registers a session goes through SessionManager.add, so this is the one hook
|
|
82
120
|
// that makes the bit durable. Recorded per port + projectId so a shared 4400 cannot make one
|
|
83
121
|
// project's success into evidence about another's.
|
|
@@ -129,6 +167,12 @@ export function startNoSessionWatch(options) {
|
|
|
129
167
|
: probe())
|
|
130
168
|
.then(async (ports) => {
|
|
131
169
|
listening = ports;
|
|
170
|
+
siblingListeners =
|
|
171
|
+
options.occupiedSiblings !== undefined
|
|
172
|
+
? await options.occupiedSiblings()
|
|
173
|
+
: options.probe === undefined
|
|
174
|
+
? await findOccupiedSiblings(options.port, probeDaemon)
|
|
175
|
+
: [];
|
|
132
176
|
await autoAttach(ports);
|
|
133
177
|
})
|
|
134
178
|
.catch(() => {
|
|
@@ -141,81 +185,87 @@ export function startNoSessionWatch(options) {
|
|
|
141
185
|
refresh();
|
|
142
186
|
const timer = setInterval(refresh, REFRESH_MS);
|
|
143
187
|
timer.unref();
|
|
144
|
-
const nextAction = () =>
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
188
|
+
const nextAction = (scope) => {
|
|
189
|
+
const split = splitBrain();
|
|
190
|
+
return nextActionFor({
|
|
191
|
+
everConnected: options.sessions.everConnected(),
|
|
192
|
+
initialized: scope.initialized,
|
|
193
|
+
...(scope.configsElsewhere === undefined ? {} : { configsElsewhere: scope.configsElsewhere }),
|
|
194
|
+
previouslyConnected: connectedBefore(),
|
|
195
|
+
...(split === undefined ? {} : { splitBrain: split }),
|
|
196
|
+
listening,
|
|
197
|
+
// Read when asked, like everything else here: a `package.json` can gain a dev script, and a
|
|
198
|
+
// daemon that cached "there is none" at boot would keep saying so for the rest of the day.
|
|
199
|
+
dev: detectDevCommand(directory),
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
options.sessions.setNoSessionNextAction(() => nextAction(projectScopeFacts()));
|
|
203
|
+
// ONE call for both registrations below. The prose and the branch code have to come from the
|
|
204
|
+
// same evaluation or they can describe different branches - the facts are read when asked, so two
|
|
205
|
+
// calls a moment apart can genuinely disagree (#615).
|
|
206
|
+
const explain = () => {
|
|
207
|
+
const scope = projectScopeFacts();
|
|
208
|
+
return explainNoSession({
|
|
209
|
+
everConnected: options.sessions.everConnected(),
|
|
210
|
+
// Read WHEN ASKED, for the same reason `projectPort` below is: `.reticle.json` is routinely
|
|
211
|
+
// written by `init` after this daemon started — that is the ordinary first-install order — and
|
|
212
|
+
// the boot-time answer is then permanently stale. Reported from the field as `reticle status`
|
|
213
|
+
// saying the project had never been through `init` about a project whose config named its
|
|
214
|
+
// framework and its projectId, and whose real problem was a dev server older than the plugin.
|
|
215
|
+
// The boot value still counts: it is the one the daemon scoped its sessions with.
|
|
216
|
+
...scope,
|
|
217
|
+
listening,
|
|
218
|
+
slowListeners,
|
|
219
|
+
port: options.port,
|
|
220
|
+
// The directory `initialized` was decided in. Named in the message because "there is no
|
|
221
|
+
// `.reticle.json`" is a claim about ONE directory, and a reader standing somewhere else
|
|
222
|
+
// cannot tell whether it is a claim about their app at all.
|
|
223
|
+
directory,
|
|
224
|
+
// The one fact that outranks every absence below it, and the reason a fresh daemon stopped
|
|
225
|
+
// claiming that an install which has demonstrably worked has never worked.
|
|
226
|
+
previouslyConnected: connectedBefore(),
|
|
227
|
+
// Ranks the causes. Read when asked, like the rest: `init` writes this file after the daemon
|
|
228
|
+
// starts on an ordinary first install.
|
|
229
|
+
...(() => {
|
|
230
|
+
const framework = readProjectFramework(directory);
|
|
231
|
+
return framework === undefined ? {} : { framework };
|
|
232
|
+
})(),
|
|
233
|
+
// Decided from the session that actually went away, not from a lifetime tally: the lease
|
|
234
|
+
// sentence is only right when the thing that vanished WAS a lease.
|
|
235
|
+
leaseExpired: (() => {
|
|
236
|
+
const departed = options.sessions.lastDeparted();
|
|
237
|
+
return departed === undefined ? false : (options.wasReapedLease?.(departed) ?? false);
|
|
238
|
+
})(),
|
|
239
|
+
// How long this daemon has been waiting with no app. The diagnosis uses it to surface
|
|
240
|
+
// "install never finished" — the same condition telemetry already knows about.
|
|
241
|
+
...(() => {
|
|
242
|
+
const upMs = stallUptime(Date.now());
|
|
243
|
+
return upMs === undefined ? {} : { daemonUpMs: upMs };
|
|
244
|
+
})(),
|
|
245
|
+
// Read here rather than at boot: `.reticle.json` can be written by `init` after this daemon
|
|
246
|
+
// started, which is the ordinary first-install order, and a port cached from before it existed
|
|
247
|
+
// would make the daemon confidently report no mismatch on the one run where there is one.
|
|
248
|
+
...(() => {
|
|
249
|
+
const configured = readProjectPort(directory);
|
|
250
|
+
return configured === undefined ? {} : { projectPort: configured };
|
|
251
|
+
})(),
|
|
252
|
+
...(0 === siblingListeners.length ? {} : { siblingListeners }),
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
options.sessions.setNoSessionHint(() => {
|
|
256
|
+
const scope = projectScopeFacts();
|
|
257
|
+
return (explain().message +
|
|
258
|
+
// Prose for the human, then the literal command for the agent. Both consume the same scope
|
|
259
|
+
// facts so a discovered workspace config cannot become an `init` recommendation below it.
|
|
260
|
+
` ${renderNextAction(nextAction(scope))}` +
|
|
261
|
+
(attachFailure ?? ''));
|
|
152
262
|
});
|
|
153
|
-
options.sessions.
|
|
154
|
-
options.sessions.setNoSessionHint(() => diagnoseNoSession({
|
|
155
|
-
everConnected: options.sessions.everConnected(),
|
|
156
|
-
// Read WHEN ASKED, for the same reason `projectPort` below is: `.reticle.json` is routinely
|
|
157
|
-
// written by `init` after this daemon started — that is the ordinary first-install order — and
|
|
158
|
-
// the boot-time answer is then permanently stale. Reported from the field as `reticle status`
|
|
159
|
-
// saying the project had never been through `init` about a project whose config named its
|
|
160
|
-
// framework and its projectId, and whose real problem was a dev server older than the plugin.
|
|
161
|
-
// The boot value still counts: it is the one the daemon scoped its sessions with.
|
|
162
|
-
initialized: isWired(),
|
|
163
|
-
listening,
|
|
164
|
-
slowListeners,
|
|
165
|
-
port: options.port,
|
|
166
|
-
// The directory `initialized` was decided in. Named in the message because "there is no
|
|
167
|
-
// `.reticle.json`" is a claim about ONE directory, and a reader standing somewhere else
|
|
168
|
-
// cannot tell whether it is a claim about their app at all.
|
|
169
|
-
directory,
|
|
170
|
-
// Where the config actually is, when it is not here — and where we looked, when it is
|
|
171
|
-
// nowhere. Computed only on the branch that needs it: a wired daemon has its answer already,
|
|
172
|
-
// and a workspace scan on every hint would be a directory walk per tool refusal.
|
|
173
|
-
...(isWired()
|
|
174
|
-
? {}
|
|
175
|
-
: (() => {
|
|
176
|
-
const discovery = discoverProjectConfigs(directory);
|
|
177
|
-
const elsewhere = discovery.found.filter((c) => c.directory !== directory);
|
|
178
|
-
return 0 === elsewhere.length
|
|
179
|
-
? { searchedDirectories: discovery.searched }
|
|
180
|
-
: {
|
|
181
|
-
configsElsewhere: elsewhere.map((c) => ({
|
|
182
|
-
directory: c.directory,
|
|
183
|
-
...(c.projectId === undefined ? {} : { projectId: c.projectId }),
|
|
184
|
-
})),
|
|
185
|
-
};
|
|
186
|
-
})()),
|
|
187
|
-
// The one fact that outranks every absence below it, and the reason a fresh daemon stopped
|
|
188
|
-
// claiming that an install which has demonstrably worked has never worked.
|
|
189
|
-
previouslyConnected: connectedBefore(),
|
|
190
|
-
// Ranks the causes. Read when asked, like the rest: `init` writes this file after the daemon
|
|
191
|
-
// starts on an ordinary first install.
|
|
192
|
-
...(() => {
|
|
193
|
-
const framework = readProjectFramework(directory);
|
|
194
|
-
return framework === undefined ? {} : { framework };
|
|
195
|
-
})(),
|
|
196
|
-
leaseExpired: (options.reapedLeases?.() ?? 0) > 0,
|
|
197
|
-
// How long this daemon has been waiting with no app. The diagnosis uses it to surface
|
|
198
|
-
// "install never finished" — the same condition telemetry already knows about.
|
|
199
|
-
...(() => {
|
|
200
|
-
const upMs = stallUptime(Date.now());
|
|
201
|
-
return upMs === undefined ? {} : { daemonUpMs: upMs };
|
|
202
|
-
})(),
|
|
203
|
-
// Read here rather than at boot: `.reticle.json` can be written by `init` after this daemon
|
|
204
|
-
// started, which is the ordinary first-install order, and a port cached from before it existed
|
|
205
|
-
// would make the daemon confidently report no mismatch on the one run where there is one.
|
|
206
|
-
...(() => {
|
|
207
|
-
const configured = readProjectPort(directory);
|
|
208
|
-
return configured === undefined ? {} : { projectPort: configured };
|
|
209
|
-
})(),
|
|
210
|
-
}) +
|
|
211
|
-
// Prose for the human, then the literal command for the agent. Appended rather than folded
|
|
212
|
-
// into the diagnosis so that file stays pure and its cases stay independently pinned.
|
|
213
|
-
` ${renderNextAction(nextAction())}` +
|
|
214
|
-
(attachFailure ?? ''));
|
|
263
|
+
options.sessions.setNoSessionReason(() => explain().reason);
|
|
215
264
|
return () => {
|
|
216
265
|
clearInterval(timer);
|
|
217
266
|
options.sessions.setConnectionRecorder(undefined);
|
|
218
267
|
options.sessions.setNoSessionHint(undefined);
|
|
268
|
+
options.sessions.setNoSessionReason(undefined);
|
|
219
269
|
options.sessions.setNoSessionNextAction(undefined);
|
|
220
270
|
};
|
|
221
271
|
}
|
|
@@ -226,8 +276,8 @@ export function startNoSessionWatch(options) {
|
|
|
226
276
|
* adjacent blocks in the bootstrap.
|
|
227
277
|
*/
|
|
228
278
|
export function wireSessionScope(sessions, activeProjectId, port,
|
|
229
|
-
/**
|
|
230
|
-
|
|
279
|
+
/** Asks the pool whether a session id was a lease it aged out; omitted when there is no pool. */
|
|
280
|
+
wasReapedLease,
|
|
231
281
|
/** Opens a URL in a Reticle-owned browser (the pool). Omitted ⇒ auto-attach is off. */
|
|
232
282
|
attach) {
|
|
233
283
|
if (activeProjectId !== undefined)
|
|
@@ -236,7 +286,7 @@ attach) {
|
|
|
236
286
|
sessions,
|
|
237
287
|
port,
|
|
238
288
|
initialized: activeProjectId !== undefined,
|
|
239
|
-
...(
|
|
289
|
+
...(wasReapedLease === undefined ? {} : { wasReapedLease }),
|
|
240
290
|
...(attach === undefined ? {} : { attach }),
|
|
241
291
|
});
|
|
242
292
|
}
|
|
@@ -50,7 +50,10 @@ export function estimateTokens(text) {
|
|
|
50
50
|
}
|
|
51
51
|
export function sizeCost(payload) {
|
|
52
52
|
const json = JSON.stringify(payload) ?? '';
|
|
53
|
-
|
|
53
|
+
// Actual UTF-8 wire bytes, not json.length (UTF-16 code units) — the field is labeled `bytes`
|
|
54
|
+
// and drives the large-snapshot recommendation, so a multibyte-heavy payload was under-counted.
|
|
55
|
+
const bytes = Buffer.byteLength(json, 'utf8');
|
|
56
|
+
return { bytes, tokens: estimateTokens(json) };
|
|
54
57
|
}
|
|
55
58
|
/**
|
|
56
59
|
* Attach a `cost` size preview to a read result. Pure: the cost is computed over the result BEFORE
|
|
@@ -11,6 +11,16 @@ import type { CommandResult } from '@reticlehq/core';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class CommandTimeoutError extends Error {
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* The transport under this command was displaced by a newer connection claiming the same id.
|
|
16
|
+
*
|
|
17
|
+
* Typed, not a string to match on, because the two failures need OPPOSITE handling and the only
|
|
18
|
+
* thing separating them used to be the wording of a message. A read can simply be re-asked on the
|
|
19
|
+
* successor; a WRITE cannot — nobody can prove it did not land — so the honest answer is that the
|
|
20
|
+
* outcome went unobserved, which is a verdict this engine already has a word for.
|
|
21
|
+
*/
|
|
22
|
+
export declare class SessionReplacedError extends Error {
|
|
23
|
+
}
|
|
14
24
|
export declare class PendingCommands {
|
|
15
25
|
#private;
|
|
16
26
|
/** Next correlation id. Monotonic per session, so a reply can never match an earlier command. */
|
|
@@ -20,5 +30,5 @@ export declare class PendingCommands {
|
|
|
20
30
|
/** Resolve the command this reply belongs to. Unknown ids are ignored, never thrown on. */
|
|
21
31
|
settle(result: CommandResult): void;
|
|
22
32
|
/** Reject everything still in flight — used on disconnect, so no caller waits on a dead socket. */
|
|
23
|
-
rejectAll(reason: string): void;
|
|
33
|
+
rejectAll(reason: string, replaced?: boolean): void;
|
|
24
34
|
}
|
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export class CommandTimeoutError extends Error {
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* The transport under this command was displaced by a newer connection claiming the same id.
|
|
15
|
+
*
|
|
16
|
+
* Typed, not a string to match on, because the two failures need OPPOSITE handling and the only
|
|
17
|
+
* thing separating them used to be the wording of a message. A read can simply be re-asked on the
|
|
18
|
+
* successor; a WRITE cannot — nobody can prove it did not land — so the honest answer is that the
|
|
19
|
+
* outcome went unobserved, which is a verdict this engine already has a word for.
|
|
20
|
+
*/
|
|
21
|
+
export class SessionReplacedError extends Error {
|
|
22
|
+
}
|
|
13
23
|
export class PendingCommands {
|
|
14
24
|
#pending = new Map();
|
|
15
25
|
#seq = 0;
|
|
@@ -39,10 +49,10 @@ export class PendingCommands {
|
|
|
39
49
|
pending.resolve(result);
|
|
40
50
|
}
|
|
41
51
|
/** Reject everything still in flight — used on disconnect, so no caller waits on a dead socket. */
|
|
42
|
-
rejectAll(reason) {
|
|
52
|
+
rejectAll(reason, replaced = false) {
|
|
43
53
|
for (const [id, pending] of this.#pending) {
|
|
44
54
|
clearTimeout(pending.timer);
|
|
45
|
-
pending.reject(new Error(reason));
|
|
55
|
+
pending.reject(replaced ? new SessionReplacedError(reason) : new Error(reason));
|
|
46
56
|
this.#pending.delete(id);
|
|
47
57
|
}
|
|
48
58
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a session, or spend the caller's stated budget waiting for one to arrive.
|
|
3
|
+
*
|
|
4
|
+
* Every tool that takes a `timeout_ms` used to resolve the session as its FIRST statement, so an
|
|
5
|
+
* empty session map threw before the budget was ever consulted. A caller that said "wait up to
|
|
6
|
+
* thirty seconds for the app" was refused in under a millisecond, and the only way to actually wait
|
|
7
|
+
* was to poll `reticle_sessions` blind.
|
|
8
|
+
*
|
|
9
|
+
* That is the wrong answer to the commonest situation in the product. The moment an agent has run
|
|
10
|
+
* `init` and restarted the dev server is exactly the moment there is no session yet and one is about
|
|
11
|
+
* to appear — and `no_session` is the refusal agents hit most. An agent that gets refused rarely
|
|
12
|
+
* retries; it concludes and moves on. Spending the budget it already asked for turns the most common
|
|
13
|
+
* dead end into a wait that ends in a verdict.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately narrow in two ways.
|
|
16
|
+
*
|
|
17
|
+
* Only the EMPTY case waits. A `sessionId` that names a session which is not connected while others
|
|
18
|
+
* are is a different error with a different fix, and waiting on it would hide a typo behind a
|
|
19
|
+
* timeout.
|
|
20
|
+
*
|
|
21
|
+
* The original error is what gets thrown on expiry, never a generic timeout message. That error
|
|
22
|
+
* carries the daemon's whole diagnosis — the port scan, the next action, the literal command — and
|
|
23
|
+
* it is the single most valuable string this product emits to an agent that has nothing connected.
|
|
24
|
+
* Replacing it with "timed out" would trade the answer for a stopwatch reading.
|
|
25
|
+
*/
|
|
26
|
+
/** Just the part of the session manager this needs, so a test does not have to build one. */
|
|
27
|
+
export interface ResolvableSessions<S> {
|
|
28
|
+
resolve: (sessionId?: string) => S;
|
|
29
|
+
count: () => number;
|
|
30
|
+
}
|
|
31
|
+
export declare function resolveSessionWithin<S>(sessions: ResolvableSessions<S>, sessionId: string | undefined, timeoutMs: number, deps: {
|
|
32
|
+
now: () => number;
|
|
33
|
+
sleep: (ms: number) => Promise<void>;
|
|
34
|
+
}): Promise<S>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a session, or spend the caller's stated budget waiting for one to arrive.
|
|
3
|
+
*
|
|
4
|
+
* Every tool that takes a `timeout_ms` used to resolve the session as its FIRST statement, so an
|
|
5
|
+
* empty session map threw before the budget was ever consulted. A caller that said "wait up to
|
|
6
|
+
* thirty seconds for the app" was refused in under a millisecond, and the only way to actually wait
|
|
7
|
+
* was to poll `reticle_sessions` blind.
|
|
8
|
+
*
|
|
9
|
+
* That is the wrong answer to the commonest situation in the product. The moment an agent has run
|
|
10
|
+
* `init` and restarted the dev server is exactly the moment there is no session yet and one is about
|
|
11
|
+
* to appear — and `no_session` is the refusal agents hit most. An agent that gets refused rarely
|
|
12
|
+
* retries; it concludes and moves on. Spending the budget it already asked for turns the most common
|
|
13
|
+
* dead end into a wait that ends in a verdict.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately narrow in two ways.
|
|
16
|
+
*
|
|
17
|
+
* Only the EMPTY case waits. A `sessionId` that names a session which is not connected while others
|
|
18
|
+
* are is a different error with a different fix, and waiting on it would hide a typo behind a
|
|
19
|
+
* timeout.
|
|
20
|
+
*
|
|
21
|
+
* The original error is what gets thrown on expiry, never a generic timeout message. That error
|
|
22
|
+
* carries the daemon's whole diagnosis — the port scan, the next action, the literal command — and
|
|
23
|
+
* it is the single most valuable string this product emits to an agent that has nothing connected.
|
|
24
|
+
* Replacing it with "timed out" would trade the answer for a stopwatch reading.
|
|
25
|
+
*/
|
|
26
|
+
/** How often to re-ask. A session appearing is an out-of-band event; this is a plain poll. */
|
|
27
|
+
const POLL_MS = 250;
|
|
28
|
+
export async function resolveSessionWithin(sessions, sessionId, timeoutMs, deps) {
|
|
29
|
+
try {
|
|
30
|
+
return sessions.resolve(sessionId);
|
|
31
|
+
}
|
|
32
|
+
catch (first) {
|
|
33
|
+
// Nothing to wait FOR unless the map is empty, and nothing to wait WITH unless a budget was
|
|
34
|
+
// given. Either way the caller gets the same error it would have got before.
|
|
35
|
+
if (timeoutMs <= 0 || sessions.count() > 0)
|
|
36
|
+
throw first;
|
|
37
|
+
const deadline = deps.now() + timeoutMs;
|
|
38
|
+
for (;;) {
|
|
39
|
+
const remaining = deadline - deps.now();
|
|
40
|
+
if (remaining <= 0)
|
|
41
|
+
throw first;
|
|
42
|
+
await deps.sleep(Math.min(POLL_MS, remaining));
|
|
43
|
+
try {
|
|
44
|
+
return sessions.resolve(sessionId);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Still nothing. Keep the FIRST error: it was raised when the daemon had most recently
|
|
48
|
+
// computed its diagnosis, and re-throwing the newest one would report the same fact with a
|
|
49
|
+
// staler explanation.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -45,6 +45,15 @@ export declare function healthEnvelope(session: Session): HealthEnvelope;
|
|
|
45
45
|
* warn-only so background testing never breaks.
|
|
46
46
|
*/
|
|
47
47
|
export declare function refuseIfThrottled(session: Session, refuse: unknown): void;
|
|
48
|
+
/**
|
|
49
|
+
* Suffix the starvation note onto a FAILED predicate verdict when the tab is throttled. A pass is
|
|
50
|
+
* returned untouched — starvation is context for a failure, not a reason to doubt a hold. Pure and
|
|
51
|
+
* generic over the verdict shape so assert/wait_for/act_and_wait share one rule.
|
|
52
|
+
*/
|
|
53
|
+
export declare function annotateStarvedFailure<V extends {
|
|
54
|
+
pass?: boolean;
|
|
55
|
+
failureReason?: string;
|
|
56
|
+
}>(session: Session, verdict: V): V;
|
|
48
57
|
/** The page's own visibility/runtime report, narrowed out of an untrusted PAGE_HEALTH payload. */
|
|
49
58
|
interface HealthReport {
|
|
50
59
|
hidden: boolean | undefined;
|
|
@@ -35,6 +35,29 @@ export function refuseIfThrottled(session, refuse) {
|
|
|
35
35
|
throw new Error(`refusing to act: ${THROTTLED_WARNING}`);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* The sentence appended to a FAILED predicate verdict waited out on a throttled tab.
|
|
40
|
+
*
|
|
41
|
+
* A starved tab and a missing element read identically as a bare near-miss: after a hard reload a
|
|
42
|
+
* backgrounded tab can sit on its loading state forever (hydration starved, nothing painted), and
|
|
43
|
+
* `wait_for { text }` timing out there looks exactly like "the code did not render" (#521). The
|
|
44
|
+
* health envelope already rode alongside saying `throttled:true`, but nothing connected it to the
|
|
45
|
+
* verdict an agent actually gates on. Appended, not replacing: the original diagnosis still leads,
|
|
46
|
+
* and on a healthy tab the failure says exactly what it always said.
|
|
47
|
+
*/
|
|
48
|
+
const STARVED_WAIT_NOTE = ' (the tab was throttled during this wait, so starvation can look exactly like absence: bring it to the front and re-check before concluding this is genuinely missing)';
|
|
49
|
+
/**
|
|
50
|
+
* Suffix the starvation note onto a FAILED predicate verdict when the tab is throttled. A pass is
|
|
51
|
+
* returned untouched — starvation is context for a failure, not a reason to doubt a hold. Pure and
|
|
52
|
+
* generic over the verdict shape so assert/wait_for/act_and_wait share one rule.
|
|
53
|
+
*/
|
|
54
|
+
export function annotateStarvedFailure(session, verdict) {
|
|
55
|
+
if (true === verdict.pass || verdict.failureReason === undefined)
|
|
56
|
+
return verdict;
|
|
57
|
+
if (true !== session.throttled())
|
|
58
|
+
return verdict;
|
|
59
|
+
return { ...verdict, failureReason: `${verdict.failureReason}${STARVED_WAIT_NOTE}` };
|
|
60
|
+
}
|
|
38
61
|
/**
|
|
39
62
|
* Narrow a PAGE_HEALTH payload. Every field is optional on the wire — an older SDK does not report
|
|
40
63
|
* `runtime` at all — so each is returned as undefined rather than defaulted here, letting the caller
|
|
@@ -8,6 +8,15 @@ export interface SessionInfo {
|
|
|
8
8
|
title?: string;
|
|
9
9
|
adapters: string[];
|
|
10
10
|
hasCapabilities: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Which shell answered: `web`, `electron` or `tauri`.
|
|
13
|
+
*
|
|
14
|
+
* Absent — never defaulted to `web` — on an SDK too old to report one, because the guess is wrong
|
|
15
|
+
* on exactly the machines this exists to tell apart. Two windows on the same url are otherwise
|
|
16
|
+
* indistinguishable, and a browser tab is not a desktop app: it has none of its IPC, and it does
|
|
17
|
+
* not render like it.
|
|
18
|
+
*/
|
|
19
|
+
runtime?: string;
|
|
11
20
|
/** Present only when the page's SDK version differs from the daemon's — see version-skew.ts. */
|
|
12
21
|
versionSkew?: string;
|
|
13
22
|
/** ms since the SDK last reported anything (silence ⇒ likely throttled). */
|
|
@@ -19,6 +28,13 @@ export interface SessionInfo {
|
|
|
19
28
|
recommendation?: string;
|
|
20
29
|
stale?: boolean;
|
|
21
30
|
cleanup_suggestion?: string;
|
|
31
|
+
/**
|
|
32
|
+
* present ONLY when this tab has stopped answering commands — attached, streaming events, and
|
|
33
|
+
* missing every command budget. Never `false`: absence means "answering, or not asked yet".
|
|
34
|
+
*/
|
|
35
|
+
unresponsive?: true;
|
|
36
|
+
/** present with `unresponsive` — the in-protocol way out of a wedged tab. */
|
|
37
|
+
unresponsive_suggestion?: string;
|
|
22
38
|
/** present only when the human has flagged bugs on this tab — count of pending review marks. */
|
|
23
39
|
pendingMarks?: number;
|
|
24
40
|
/** present with pendingMarks — nudges the agent to drain them with reticle_review. */
|
|
@@ -47,11 +63,13 @@ interface SessionView {
|
|
|
47
63
|
title: string;
|
|
48
64
|
adapters: string[];
|
|
49
65
|
hasCapabilities: boolean;
|
|
66
|
+
runtime: string | undefined;
|
|
50
67
|
versionSkew: string | undefined;
|
|
51
68
|
hidden: boolean;
|
|
52
69
|
health: () => SessionHealth;
|
|
53
70
|
staleMs: () => number;
|
|
54
71
|
pendingMarkCount: () => number;
|
|
72
|
+
unresponsive: () => boolean;
|
|
55
73
|
}
|
|
56
74
|
/**
|
|
57
75
|
* Project a live Session into the shape the tools return.
|
|
@@ -25,6 +25,8 @@ export function buildSessionInfo(session) {
|
|
|
25
25
|
...('' === session.title.trim() ? {} : { title: session.title }),
|
|
26
26
|
adapters: session.adapters,
|
|
27
27
|
hasCapabilities: session.hasCapabilities,
|
|
28
|
+
// Omitted when the page never said, so absence stays readable as "unknown" rather than "web".
|
|
29
|
+
...(session.runtime === undefined ? {} : { runtime: session.runtime }),
|
|
28
30
|
// On every listing, not buried in a log — skew explains failures that read as app bugs.
|
|
29
31
|
...(session.versionSkew === undefined ? {} : { versionSkew: session.versionSkew }),
|
|
30
32
|
hidden: session.hidden,
|
|
@@ -35,6 +37,15 @@ export function buildSessionInfo(session) {
|
|
|
35
37
|
base.cleanup_suggestion =
|
|
36
38
|
'Call reticle_session{action:"end"} to free this session before starting new work.';
|
|
37
39
|
}
|
|
40
|
+
// A tab that is attached and answering nothing. Said out loud because every health field beside it
|
|
41
|
+
// reads fine — that combination is exactly what made the wedge invisible.
|
|
42
|
+
if (session.unresponsive()) {
|
|
43
|
+
base.unresponsive = true;
|
|
44
|
+
base.unresponsive_suggestion =
|
|
45
|
+
'This tab is connected but has stopped answering commands, so every call against it will time ' +
|
|
46
|
+
'out. Ending the session does not revive the page. Reload the tab, or run `reticle open <url>` ' +
|
|
47
|
+
'to get a fresh one — it will no longer hand this tab back.';
|
|
48
|
+
}
|
|
38
49
|
// Surface human bug reports in reticle_sessions (only when > 0, so a clean session adds nothing).
|
|
39
50
|
const marks = session.pendingMarkCount();
|
|
40
51
|
if (marks > 0) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NoSessionReason } from '@reticlehq/core';
|
|
1
2
|
import { Session, type SessionInfo } from './session.js';
|
|
2
3
|
import { AttachmentHistory } from './attachment-history.js';
|
|
3
4
|
import type { NoSessionNextAction } from './no-session-next-action.js';
|
|
@@ -37,6 +38,8 @@ export declare class SessionManager {
|
|
|
37
38
|
* already computed for the error path; this exposes it to the read path too.
|
|
38
39
|
*/
|
|
39
40
|
noSessionHint(): string | undefined;
|
|
41
|
+
setNoSessionReason(reason: (() => NoSessionReason | undefined) | undefined): void;
|
|
42
|
+
noSessionReason(): NoSessionReason | undefined;
|
|
40
43
|
setNoSessionNextAction(next: (() => NoSessionNextAction | undefined) | undefined): void;
|
|
41
44
|
noSessionNextAction(): NoSessionNextAction | undefined;
|
|
42
45
|
everConnected(): boolean;
|
|
@@ -47,6 +50,17 @@ export declare class SessionManager {
|
|
|
47
50
|
at: number;
|
|
48
51
|
reason: string;
|
|
49
52
|
} | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* The id of the session that most recently went away, if any.
|
|
55
|
+
*
|
|
56
|
+
* Distinct from {@link lastClosure}, which only records closes the bridge itself initiated
|
|
57
|
+
* (handshake refusals, auth failures) — an ordinary disconnect never reaches it. This reads the
|
|
58
|
+
* tombstone ring `remove()` maintains, so it covers every departure.
|
|
59
|
+
*
|
|
60
|
+
* Read by the no-session diagnosis, which needs to say something about the session that actually
|
|
61
|
+
* vanished rather than about the daemon's lifetime history (#611).
|
|
62
|
+
*/
|
|
63
|
+
lastDeparted(): string | undefined;
|
|
50
64
|
resolve(sessionId?: string, scope?: ResolveScope): Session;
|
|
51
65
|
}
|
|
52
66
|
export {};
|