@reticlehq/server 2.12.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 +67 -109
- 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 -1
- package/dist/bridge/bridge.js +247 -164
- 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 +6 -6
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/cli/cli-doctor.js +31 -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 +15 -1
- package/dist/cli/cli-parse.js +212 -8
- package/dist/cli/cli-verify.d.ts +1 -0
- package/dist/cli/cli-verify.js +3 -2
- package/dist/cli/cloud-cli.js +428 -259
- 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.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 +9 -0
- package/dist/cli/doctor-rows.js +9 -0
- 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/mcp-command.js +51 -20
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -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 +134 -80
- package/dist/cloud/cloud-config.d.ts +22 -1
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +5 -5
- package/dist/cloud/cloud-sync.js +33 -2
- 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 +3 -0
- package/dist/crawl/crawl.js +5 -0
- package/dist/daemon/daemon-resolve.d.ts +43 -0
- package/dist/daemon/daemon-resolve.js +69 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +18 -0
- package/dist/events/contradictions.js +134 -21
- package/dist/events/declared.d.ts +7 -4
- package/dist/events/declared.js +15 -6
- package/dist/events/echo-mismatch.js +64 -14
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/predicate-eval.js +53 -5
- 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.d.ts +5 -0
- package/dist/events/predicate.js +37 -5
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/flake.d.ts +6 -6
- 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 +239 -22
- 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-tools.js +58 -32
- package/dist/flows/flows.d.ts +8 -0
- package/dist/flows/flows.js +468 -2
- 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 +30 -12
- 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/verify-change-tools.js +5 -2
- package/dist/honesty/blind-spots.d.ts +55 -2
- package/dist/honesty/blind-spots.js +63 -3
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +9 -0
- package/dist/honesty/instrumentation-gaps.js +29 -2
- package/dist/honesty/verified.d.ts +2 -1
- package/dist/honesty/verified.js +16 -1
- package/dist/http-server.js +12 -0
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +15 -1
- package/dist/impact/impact-store.js +43 -2
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +13 -6
- package/dist/init/agent-rules.js +23 -12
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -1
- package/dist/init/capabilities.js +22 -2
- package/dist/init/confirm.d.ts +31 -7
- package/dist/init/confirm.js +48 -8
- package/dist/init/cra.d.ts +20 -4
- package/dist/init/cra.js +57 -17
- 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 +3 -1
- package/dist/init/dev-script.js +8 -5
- 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 +1 -1
- package/dist/init/mcp.js +1 -1
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.js +81 -45
- package/dist/init/plan.d.ts +20 -7
- package/dist/init/plan.js +20 -45
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/run.d.ts +32 -9
- package/dist/init/run.js +141 -119
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +41 -25
- 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.js +25 -5
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +10 -19
- package/dist/intent/inline-intent.js +91 -1
- 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 +12 -0
- package/dist/intent/intent-store.js +21 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.js +65 -2
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +15 -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 +18 -2
- package/dist/mcp/attach-memory.js +50 -7
- 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 -1
- package/dist/mcp/mcp-proxy.js +52 -62
- package/dist/mcp/mcp.js +7 -1
- 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/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +12 -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/propose-consequences.js +12 -3
- package/dist/pool/browser-pool.d.ts +63 -4
- package/dist/pool/browser-pool.js +113 -4
- package/dist/pool/playwright-launcher.js +9 -0
- package/dist/project/project-tools.js +3 -3
- package/dist/project/reticle-dir.d.ts +15 -2
- package/dist/project/reticle-dir.js +21 -4
- 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 +22 -1
- 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/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/no-session-diagnosis.d.ts +23 -0
- package/dist/session/no-session-diagnosis.js +58 -17
- package/dist/session/no-session-next-action.d.ts +7 -0
- package/dist/session/no-session-next-action.js +42 -3
- package/dist/session/no-session-watch.d.ts +10 -6
- package/dist/session/no-session-watch.js +51 -19
- 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-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 +61 -3
- 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.js +36 -1
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +22 -2
- package/dist/session/session.js +87 -7
- 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/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.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 -1
- package/dist/telemetry/project-profile.js +11 -6
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- 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/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 +100 -213
- package/dist/tools/assert-verdict.js +22 -16
- package/dist/tools/browser-tools.js +6 -2
- package/dist/tools/dynamic-tools.js +24 -5
- package/dist/tools/error-recovery.d.ts +1 -0
- package/dist/tools/error-recovery.js +8 -0
- package/dist/tools/invoke-tool.js +59 -9
- package/dist/tools/lease-tools.d.ts +30 -4
- package/dist/tools/lease-tools.js +169 -5
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +47 -40
- package/dist/tools/read-tools.js +28 -7
- package/dist/tools/real-input-attempt.d.ts +10 -0
- package/dist/tools/real-input-attempt.js +59 -1
- package/dist/tools/reconcile-tools.js +4 -4
- package/dist/tools/tool-kit.d.ts +12 -0
- package/dist/tools/tool-kit.js +6 -1
- package/dist/tools/tool-names.d.ts +2 -0
- package/dist/tools/tool-names.js +2 -0
- 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 +56 -8
- 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 +18 -0
- package/dist/update/update-nudge.js +20 -0
- 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/cli/doctor.mdx +1 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +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.mdx +1 -1
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/usage.md +1 -1
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
|
@@ -13,12 +13,15 @@ export interface RecordedStep {
|
|
|
13
13
|
interface ActiveRecording {
|
|
14
14
|
cursor: number;
|
|
15
15
|
steps: RecordedStep[];
|
|
16
|
+
/** The route the journey began on. See CompiledProgram.startPath. */
|
|
17
|
+
startPath?: string;
|
|
16
18
|
}
|
|
17
19
|
/** A finished, replayable program compiled from a recording. */
|
|
18
20
|
export interface CompiledProgram {
|
|
19
21
|
name: string;
|
|
20
22
|
version: number;
|
|
21
23
|
steps: RecordedStep[];
|
|
24
|
+
startPath?: string;
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* Tracks in-flight recordings (name -> { buffer cursor at record_start, captured steps })
|
|
@@ -26,7 +29,7 @@ export interface CompiledProgram {
|
|
|
26
29
|
*/
|
|
27
30
|
export declare class RecordingStore {
|
|
28
31
|
#private;
|
|
29
|
-
start(name: string, cursor: number): void;
|
|
32
|
+
start(name: string, cursor: number, startPath?: string): void;
|
|
30
33
|
isRecording(name: string): boolean;
|
|
31
34
|
/**
|
|
32
35
|
* Number of steps captured so far in the named ACTIVE recording (0 if it
|
|
@@ -41,5 +44,14 @@ export declare class RecordingStore {
|
|
|
41
44
|
saveCompiled(program: CompiledProgram): void;
|
|
42
45
|
getCompiled(name: string): CompiledProgram | undefined;
|
|
43
46
|
active(): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Names of recordings that have been STOPPED and compiled.
|
|
49
|
+
*
|
|
50
|
+
* The mirror of `active()`, and needed for the same defaulting: after `reticle_record stop` a
|
|
51
|
+
* recording is no longer active, so resolving "the obvious one" from `active()` finds nothing.
|
|
52
|
+
* Saving it then demanded the exact name back from the caller, which is a thing to remember for
|
|
53
|
+
* no reason when exactly one recording exists.
|
|
54
|
+
*/
|
|
55
|
+
compiled(): string[];
|
|
44
56
|
}
|
|
45
57
|
export {};
|
package/dist/flows/recordings.js
CHANGED
|
@@ -5,8 +5,12 @@
|
|
|
5
5
|
export class RecordingStore {
|
|
6
6
|
#active = new Map();
|
|
7
7
|
#compiled = new Map();
|
|
8
|
-
start(name, cursor) {
|
|
9
|
-
this.#active.set(name, {
|
|
8
|
+
start(name, cursor, startPath) {
|
|
9
|
+
this.#active.set(name, {
|
|
10
|
+
cursor,
|
|
11
|
+
steps: [],
|
|
12
|
+
...(startPath === undefined ? {} : { startPath }),
|
|
13
|
+
});
|
|
10
14
|
}
|
|
11
15
|
isRecording(name) {
|
|
12
16
|
return this.#active.has(name);
|
|
@@ -39,4 +43,15 @@ export class RecordingStore {
|
|
|
39
43
|
active() {
|
|
40
44
|
return [...this.#active.keys()];
|
|
41
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Names of recordings that have been STOPPED and compiled.
|
|
48
|
+
*
|
|
49
|
+
* The mirror of `active()`, and needed for the same defaulting: after `reticle_record stop` a
|
|
50
|
+
* recording is no longer active, so resolving "the obvious one" from `active()` finds nothing.
|
|
51
|
+
* Saving it then demanded the exact name back from the caller, which is a thing to remember for
|
|
52
|
+
* no reason when exactly one recording exists.
|
|
53
|
+
*/
|
|
54
|
+
compiled() {
|
|
55
|
+
return [...this.#compiled.keys()];
|
|
56
|
+
}
|
|
42
57
|
}
|
package/dist/flows/replay.d.ts
CHANGED
|
@@ -13,8 +13,28 @@ export declare function ambiguousTestidNote(value: string): string;
|
|
|
13
13
|
* resolution — can't drift between them.
|
|
14
14
|
*/
|
|
15
15
|
export declare function queryRefs(result: CommandResult): string[];
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The environment variable that supplies one redacted field.
|
|
18
|
+
*
|
|
19
|
+
* Named after the field so it is guessable from the flow alone: `auth-password` is read from
|
|
20
|
+
* `RETICLE_SECRET_AUTH_PASSWORD`. A scheme requiring a lookup table would mean the flow says a
|
|
21
|
+
* value is missing and cannot say what to set.
|
|
22
|
+
*/
|
|
23
|
+
export declare const secretEnvKey: (field: string) => string;
|
|
24
|
+
/**
|
|
25
|
+
* A destructive-action confirmation is one-shot and must never persist into a recording.
|
|
26
|
+
*
|
|
27
|
+
* This is also where a REDACTED fill is supplied back. Stripping credentials out of a git-checked
|
|
28
|
+
* flow is only half a fix: sign-in still has to replay, and a flow that drifts at step two forever
|
|
29
|
+
* because its own password was removed is a flow nobody keeps — along with everything behind the
|
|
30
|
+
* login it guards. The value comes from the environment, the one place a secret can live that is
|
|
31
|
+
* neither the customer's repository nor our database.
|
|
32
|
+
*
|
|
33
|
+
* Left as the PLACEHOLDER when nothing supplies it, rather than blanked. The replay then fails at
|
|
34
|
+
* the login form with `<redacted: supply at replay>` visible in the field, which names its own fix;
|
|
35
|
+
* an empty box fails identically and tells the reader nothing.
|
|
36
|
+
*/
|
|
37
|
+
export declare function replayActionArgs(value: unknown, confirmDangerous?: boolean, field?: string): Record<string, unknown>;
|
|
18
38
|
/**
|
|
19
39
|
* Capture an act into every in-flight recording, or do nothing when none is running.
|
|
20
40
|
*
|
package/dist/flows/replay.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { REDACTED_FILL } from './flows.js';
|
|
1
2
|
import { DANGEROUS_ACTION_CONFIRM_ARG, ReticleCommand, QueryBy, } from '@reticlehq/core';
|
|
2
3
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
3
4
|
import { predicateToExpect, enforcedOnReplay } from './predicate-to-expect.js';
|
|
@@ -23,12 +24,37 @@ export function queryRefs(result) {
|
|
|
23
24
|
const elements = Array.isArray(payload['elements']) ? payload['elements'] : [];
|
|
24
25
|
return elements.map((e) => asString(asRecord(e)['ref']) ?? '').filter((r) => r.length > 0);
|
|
25
26
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* The environment variable that supplies one redacted field.
|
|
29
|
+
*
|
|
30
|
+
* Named after the field so it is guessable from the flow alone: `auth-password` is read from
|
|
31
|
+
* `RETICLE_SECRET_AUTH_PASSWORD`. A scheme requiring a lookup table would mean the flow says a
|
|
32
|
+
* value is missing and cannot say what to set.
|
|
33
|
+
*/
|
|
34
|
+
export const secretEnvKey = (field) => `RETICLE_SECRET_${field.replace(/[^a-zA-Z0-9]+/g, '_').toUpperCase()}`;
|
|
35
|
+
/**
|
|
36
|
+
* A destructive-action confirmation is one-shot and must never persist into a recording.
|
|
37
|
+
*
|
|
38
|
+
* This is also where a REDACTED fill is supplied back. Stripping credentials out of a git-checked
|
|
39
|
+
* flow is only half a fix: sign-in still has to replay, and a flow that drifts at step two forever
|
|
40
|
+
* because its own password was removed is a flow nobody keeps — along with everything behind the
|
|
41
|
+
* login it guards. The value comes from the environment, the one place a secret can live that is
|
|
42
|
+
* neither the customer's repository nor our database.
|
|
43
|
+
*
|
|
44
|
+
* Left as the PLACEHOLDER when nothing supplies it, rather than blanked. The replay then fails at
|
|
45
|
+
* the login form with `<redacted: supply at replay>` visible in the field, which names its own fix;
|
|
46
|
+
* an empty box fails identically and tells the reader nothing.
|
|
47
|
+
*/
|
|
48
|
+
export function replayActionArgs(value, confirmDangerous = false, field) {
|
|
28
49
|
const args = { ...asRecord(value) };
|
|
29
50
|
delete args[DANGEROUS_ACTION_CONFIRM_ARG];
|
|
30
51
|
if (confirmDangerous)
|
|
31
52
|
args[DANGEROUS_ACTION_CONFIRM_ARG] = true;
|
|
53
|
+
if (REDACTED_FILL === args['value'] && field !== undefined) {
|
|
54
|
+
const supplied = process.env[secretEnvKey(field)];
|
|
55
|
+
if (supplied !== undefined && supplied.length > 0)
|
|
56
|
+
args['value'] = supplied;
|
|
57
|
+
}
|
|
32
58
|
return args;
|
|
33
59
|
}
|
|
34
60
|
/** The element's source location from an action result, when the framework stamped one. */
|
|
@@ -5,6 +5,7 @@ import { attributedFailures } from './attributed-failure.js';
|
|
|
5
5
|
import { Verified } from '@reticlehq/core';
|
|
6
6
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
7
7
|
import { asNumber, asRecord, asString } from '../tools/tools-helpers.js';
|
|
8
|
+
import { workerCountSchema } from '../tools/numeric-bounds.js';
|
|
8
9
|
import { loadNamedFlows, resolveChangedFiles } from '../cli/cli-flow-commands.js';
|
|
9
10
|
import { affectedSavedFlows } from './flow-sources.js';
|
|
10
11
|
import { FLOW_TOOLS } from './flow-tools.js';
|
|
@@ -25,8 +26,7 @@ export const VERIFY_CHANGE_TOOLS = [
|
|
|
25
26
|
.string()
|
|
26
27
|
.optional()
|
|
27
28
|
.describe('Git ref to diff against (e.g. "HEAD~1", "main"). Unioned with `files`.'),
|
|
28
|
-
parallel:
|
|
29
|
-
.number()
|
|
29
|
+
parallel: workerCountSchema
|
|
30
30
|
.optional()
|
|
31
31
|
.describe('Replay this many affected flows at once (needs the browser pool).'),
|
|
32
32
|
sessionId: z
|
|
@@ -200,6 +200,9 @@ async function inspectAfterReplay(deps, args, cursor) {
|
|
|
200
200
|
const contradictions = findContradictions(session.eventsSince(cursor), {
|
|
201
201
|
currentDocumentId: session.currentDocumentId,
|
|
202
202
|
currentEditEpoch: session.currentEditEpoch,
|
|
203
|
+
appOrigin: session.url,
|
|
204
|
+
// The replay is the action, and the cursor is where it started.
|
|
205
|
+
actionSince: cursor,
|
|
203
206
|
});
|
|
204
207
|
const snapshot = await session.command(ReticleCommand.SNAPSHOT, {
|
|
205
208
|
mode: SnapshotMode.INTERACTIVE,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { BlindSpotKind } from '@reticlehq/core';
|
|
8
8
|
import { BlindSpotKind, type ReticleEvent } from '@reticlehq/core';
|
|
9
|
-
interface BlindSpot {
|
|
9
|
+
export interface BlindSpot {
|
|
10
10
|
kind: BlindSpotKind;
|
|
11
11
|
count: number;
|
|
12
12
|
}
|
|
@@ -55,6 +55,15 @@ export declare function buildCoverageStatement(spots: readonly BlindSpot[]): Cov
|
|
|
55
55
|
* from what the SDK observed during the action, with no extra round-trip.
|
|
56
56
|
*/
|
|
57
57
|
export declare function blindSpotsFromEvents(events: readonly ReticleEvent[]): BlindSpot[];
|
|
58
|
+
/**
|
|
59
|
+
* Drop Electron-only coverage rows unless this session is actually an Electron renderer.
|
|
60
|
+
*
|
|
61
|
+
* The kinds live in the same vocabulary as "no store registered", so listing whatever is present
|
|
62
|
+
* reported a Vite + React tab as an un-instrumented Electron renderer. The session already knows
|
|
63
|
+
* the runtime (PAGE_HEALTH). Unknown runtime (older SDK) keeps the rows — dropping them would hide
|
|
64
|
+
* a real missing-preload warning.
|
|
65
|
+
*/
|
|
66
|
+
export declare function spotsForRuntime(spots: readonly BlindSpot[], runtime: string | undefined): BlindSpot[];
|
|
58
67
|
/**
|
|
59
68
|
* Blind spots from the session's remembered LEVEL state rather than from a window of events.
|
|
60
69
|
*
|
|
@@ -63,8 +72,35 @@ export declare function blindSpotsFromEvents(events: readonly ReticleEvent[]): B
|
|
|
63
72
|
* reports "full" for a page a third of which is unobservable — and the act tool's own description
|
|
64
73
|
* tells harnesses to gate on that block. Ask the session what is true now instead of inferring it
|
|
65
74
|
* from what happened to be said recently.
|
|
75
|
+
*
|
|
76
|
+
* `runtime` gates Electron-only kinds (see `spotsForRuntime`) so a web session cannot inherit them
|
|
77
|
+
* from the coverage vocabulary.
|
|
66
78
|
*/
|
|
67
|
-
export declare function blindSpotsFromState(state: Readonly<Record<string, number
|
|
79
|
+
export declare function blindSpotsFromState(state: Readonly<Record<string, number>>, runtime?: string): BlindSpot[];
|
|
80
|
+
/**
|
|
81
|
+
* The narrow slice of a predicate the absence-blind-spot check needs.
|
|
82
|
+
*
|
|
83
|
+
* Kept minimal to avoid pulling the full Predicate type (and its transitive deps) into the
|
|
84
|
+
* honesty module. Anything that quacks like this works.
|
|
85
|
+
*/
|
|
86
|
+
interface AbsencePredicate {
|
|
87
|
+
kind: string;
|
|
88
|
+
absent?: boolean;
|
|
89
|
+
query?: {
|
|
90
|
+
scope?: unknown;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* When an absence assertion targets a page with regions Reticle cannot observe, "the element is
|
|
95
|
+
* absent" means only "it is absent in what I CAN see". Returns a note when that distinction
|
|
96
|
+
* matters, undefined otherwise.
|
|
97
|
+
*
|
|
98
|
+
* Fires on three blind-spot kinds:
|
|
99
|
+
* - CROSS_ORIGIN_IFRAME (only when the predicate is scoped to a frame region)
|
|
100
|
+
* - VIRTUALIZED_UNMOUNTED — a row that was never rendered could hold the element
|
|
101
|
+
* - CLOSED_SHADOW_ROOT — a subtree Reticle cannot see into
|
|
102
|
+
*/
|
|
103
|
+
export declare function absenceBlindSpotNote(predicate: AbsencePredicate, spots: readonly BlindSpot[]): string | undefined;
|
|
68
104
|
/** The impeaching note for a transport gap, or undefined when the window was intact. */
|
|
69
105
|
export declare function transportGapNote(events: readonly ReticleEvent[]): string | undefined;
|
|
70
106
|
/**
|
|
@@ -76,3 +112,20 @@ export declare function transportGapNote(events: readonly ReticleEvent[]): strin
|
|
|
76
112
|
* predicate would eventually disagree with the first about what "unwatched" means.
|
|
77
113
|
*/
|
|
78
114
|
export declare function isStateUnwatched(spots: readonly BlindSpot[]): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Does a passing verdict on this predicate REST on the window being complete?
|
|
117
|
+
*
|
|
118
|
+
* Only an absence claim does. A positive assertion that passed found its evidence, and events aged
|
|
119
|
+
* out elsewhere in the buffer do not unmake it. An absence claim concluded "nothing is there" — and
|
|
120
|
+
* the thing the buffer dropped is precisely the disproof, so "absent" degrades to "absent in what I
|
|
121
|
+
* still hold".
|
|
122
|
+
*
|
|
123
|
+
* The distinction is not optional. Scarce-loss is recorded for AGE eviction too, so `lostSince(0)`
|
|
124
|
+
* is true on any session older than the 60s cutoff; impeaching every verdict over a caller-chosen
|
|
125
|
+
* window would make `unknown` the answer to everything, which this repo has already paid for once.
|
|
126
|
+
*/
|
|
127
|
+
export declare function restsOnCompleteWindow(predicate: {
|
|
128
|
+
kind: string;
|
|
129
|
+
absent?: boolean;
|
|
130
|
+
predicate?: unknown;
|
|
131
|
+
}): boolean;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// The kind enum lives in core (it crosses the wire in a BLIND_SPOT event); re-exported here so existing
|
|
8
8
|
// honesty-side imports keep working.
|
|
9
9
|
export { BlindSpotKind } from '@reticlehq/core';
|
|
10
|
-
import { BlindSpotKind, EventType, ReticleEnv, TRANSPORT_LIMITS, } from '@reticlehq/core';
|
|
10
|
+
import { AppRuntime, BlindSpotKind, EventType, PredicateKind, ReticleEnv, TRANSPORT_LIMITS, isDesktopBlindSpot, } from '@reticlehq/core';
|
|
11
11
|
/**
|
|
12
12
|
* Whether the observation window saw everything it was asked about.
|
|
13
13
|
*
|
|
@@ -107,6 +107,19 @@ export function blindSpotsFromEvents(events) {
|
|
|
107
107
|
}
|
|
108
108
|
return [...latest].map(([kind, count]) => ({ kind, count }));
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Drop Electron-only coverage rows unless this session is actually an Electron renderer.
|
|
112
|
+
*
|
|
113
|
+
* The kinds live in the same vocabulary as "no store registered", so listing whatever is present
|
|
114
|
+
* reported a Vite + React tab as an un-instrumented Electron renderer. The session already knows
|
|
115
|
+
* the runtime (PAGE_HEALTH). Unknown runtime (older SDK) keeps the rows — dropping them would hide
|
|
116
|
+
* a real missing-preload warning.
|
|
117
|
+
*/
|
|
118
|
+
export function spotsForRuntime(spots, runtime) {
|
|
119
|
+
if (runtime === undefined || AppRuntime.ELECTRON === runtime)
|
|
120
|
+
return [...spots];
|
|
121
|
+
return spots.filter((spot) => !isDesktopBlindSpot(spot.kind));
|
|
122
|
+
}
|
|
110
123
|
/**
|
|
111
124
|
* Blind spots from the session's remembered LEVEL state rather than from a window of events.
|
|
112
125
|
*
|
|
@@ -115,9 +128,16 @@ export function blindSpotsFromEvents(events) {
|
|
|
115
128
|
* reports "full" for a page a third of which is unobservable — and the act tool's own description
|
|
116
129
|
* tells harnesses to gate on that block. Ask the session what is true now instead of inferring it
|
|
117
130
|
* from what happened to be said recently.
|
|
131
|
+
*
|
|
132
|
+
* `runtime` gates Electron-only kinds (see `spotsForRuntime`) so a web session cannot inherit them
|
|
133
|
+
* from the coverage vocabulary.
|
|
118
134
|
*/
|
|
119
|
-
export function blindSpotsFromState(state) {
|
|
120
|
-
|
|
135
|
+
export function blindSpotsFromState(state, runtime) {
|
|
136
|
+
const spots = Object.entries(state).map(([kind, count]) => ({
|
|
137
|
+
kind: kind,
|
|
138
|
+
count,
|
|
139
|
+
}));
|
|
140
|
+
return spotsForRuntime(spots, runtime);
|
|
121
141
|
}
|
|
122
142
|
/**
|
|
123
143
|
* Events the BROWSER's own transport queue threw away inside this window, or 0.
|
|
@@ -145,6 +165,29 @@ function droppedByTransport(events) {
|
|
|
145
165
|
}
|
|
146
166
|
return dropped;
|
|
147
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* When an absence assertion targets a page with regions Reticle cannot observe, "the element is
|
|
170
|
+
* absent" means only "it is absent in what I CAN see". Returns a note when that distinction
|
|
171
|
+
* matters, undefined otherwise.
|
|
172
|
+
*
|
|
173
|
+
* Fires on three blind-spot kinds:
|
|
174
|
+
* - CROSS_ORIGIN_IFRAME (only when the predicate is scoped to a frame region)
|
|
175
|
+
* - VIRTUALIZED_UNMOUNTED — a row that was never rendered could hold the element
|
|
176
|
+
* - CLOSED_SHADOW_ROOT — a subtree Reticle cannot see into
|
|
177
|
+
*/
|
|
178
|
+
export function absenceBlindSpotNote(predicate, spots) {
|
|
179
|
+
if ('element' !== predicate.kind || true !== predicate.absent)
|
|
180
|
+
return undefined;
|
|
181
|
+
const relevant = spots.filter((spot) => spot.count > 0 &&
|
|
182
|
+
(spot.kind === BlindSpotKind.CROSS_ORIGIN_IFRAME
|
|
183
|
+
? undefined !== predicate.query?.scope
|
|
184
|
+
: spot.kind === BlindSpotKind.VIRTUALIZED_UNMOUNTED ||
|
|
185
|
+
spot.kind === BlindSpotKind.CLOSED_SHADOW_ROOT));
|
|
186
|
+
if (0 === relevant.length)
|
|
187
|
+
return undefined;
|
|
188
|
+
const statement = buildCoverageStatement(relevant);
|
|
189
|
+
return `the absence assertion targeted a region Reticle could not observe (${statement.note ?? 'partial coverage'}), so a passing DOM check cannot prove absence`;
|
|
190
|
+
}
|
|
148
191
|
/** The impeaching note for a transport gap, or undefined when the window was intact. */
|
|
149
192
|
export function transportGapNote(events) {
|
|
150
193
|
const dropped = droppedByTransport(events);
|
|
@@ -163,3 +206,20 @@ export function transportGapNote(events) {
|
|
|
163
206
|
export function isStateUnwatched(spots) {
|
|
164
207
|
return spots.some((spot) => spot.kind === BlindSpotKind.UNWATCHED_STATE && spot.count > 0);
|
|
165
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Does a passing verdict on this predicate REST on the window being complete?
|
|
211
|
+
*
|
|
212
|
+
* Only an absence claim does. A positive assertion that passed found its evidence, and events aged
|
|
213
|
+
* out elsewhere in the buffer do not unmake it. An absence claim concluded "nothing is there" — and
|
|
214
|
+
* the thing the buffer dropped is precisely the disproof, so "absent" degrades to "absent in what I
|
|
215
|
+
* still hold".
|
|
216
|
+
*
|
|
217
|
+
* The distinction is not optional. Scarce-loss is recorded for AGE eviction too, so `lostSince(0)`
|
|
218
|
+
* is true on any session older than the 60s cutoff; impeaching every verdict over a caller-chosen
|
|
219
|
+
* window would make `unknown` the answer to everything, which this repo has already paid for once.
|
|
220
|
+
*/
|
|
221
|
+
export function restsOnCompleteWindow(predicate) {
|
|
222
|
+
if (PredicateKind.NOT === predicate.kind)
|
|
223
|
+
return true;
|
|
224
|
+
return true === predicate.absent;
|
|
225
|
+
}
|
|
@@ -22,6 +22,15 @@ interface HonestyInputs {
|
|
|
22
22
|
envelopeSamples?: number;
|
|
23
23
|
coveragePct?: number;
|
|
24
24
|
coveragePartial?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The sentence naming WHAT went unobserved, from `buildCoverageStatement`.
|
|
27
|
+
*
|
|
28
|
+
* Separate from `blindSpots`, which carries only the IMPEACHING notes — the ones that downgrade a
|
|
29
|
+
* verdict. A bounding spot does not impeach anything, so it never entered that list, and the flag
|
|
30
|
+
* that survived (`partial: true`) is unanswerable on its own: the verdict prose tells the reader
|
|
31
|
+
* to see `coverage` for what went unobserved, and there was nothing there to see.
|
|
32
|
+
*/
|
|
33
|
+
coverageNote?: string;
|
|
25
34
|
truncated?: boolean;
|
|
26
35
|
blindSpots?: readonly string[];
|
|
27
36
|
/**
|
|
@@ -55,6 +64,7 @@ export interface HonestyBlock {
|
|
|
55
64
|
coverage: {
|
|
56
65
|
pct?: number;
|
|
57
66
|
partial: boolean;
|
|
67
|
+
note?: string;
|
|
58
68
|
};
|
|
59
69
|
integrity: {
|
|
60
70
|
clean: boolean;
|
|
@@ -76,4 +86,18 @@ export declare function meetsHonestyBar(block: HonestyBlock, bar?: HonestyBar):
|
|
|
76
86
|
ok: boolean;
|
|
77
87
|
reasons: string[];
|
|
78
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* The coverage sentence, kept only on a verdict that points at it.
|
|
91
|
+
*
|
|
92
|
+
* Exactly one branch of `decideVerified` promises it — the YES branch says "coverage was PARTIAL —
|
|
93
|
+
* see `coverage` for what went unobserved". UNKNOWN keeps it too, because "I could not tell" is a
|
|
94
|
+
* statement about what went unseen and the note is the answer. A NO has already named a concrete
|
|
95
|
+
* counter-example, nothing directs the reader to `coverage`, and `partial` plus the spot kinds still
|
|
96
|
+
* travel — so the prose there is cost with no question behind it.
|
|
97
|
+
*
|
|
98
|
+
* Measured: emitting it on every verdict regressed the benchmark's verification efficiency by 4.1%,
|
|
99
|
+
* one note per observation. Accuracy outranks tokens, but this buys no accuracy — the promise is
|
|
100
|
+
* kept wherever it is made.
|
|
101
|
+
*/
|
|
102
|
+
export declare function honestyForVerdict(verified: string, honesty: HonestyBlock): HonestyBlock;
|
|
79
103
|
export {};
|
package/dist/honesty/honesty.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* block. Harnesses gate on it (e.g. "grade ≥ net AND integrity clean"). This is pure composition +
|
|
6
6
|
* assessment — all five components already exist individually; wiring it onto results is a later step.
|
|
7
7
|
*/
|
|
8
|
+
import { Verified } from '@reticlehq/core';
|
|
8
9
|
/** Assertion grade, strongest first — the tier the verdict actually proved. */
|
|
9
10
|
export const HonestyGrade = {
|
|
10
11
|
SIGNAL: 'signal',
|
|
@@ -45,7 +46,11 @@ export function buildHonestyBlock(inputs) {
|
|
|
45
46
|
...(samples === undefined
|
|
46
47
|
? {}
|
|
47
48
|
: { envelope: { samples, sufficient: samples >= MIN_ENVELOPE_SAMPLES } }),
|
|
48
|
-
coverage: {
|
|
49
|
+
coverage: {
|
|
50
|
+
...(pct === undefined ? {} : { pct }),
|
|
51
|
+
partial,
|
|
52
|
+
...(partial && inputs.coverageNote !== undefined ? { note: inputs.coverageNote } : {}),
|
|
53
|
+
},
|
|
49
54
|
...(inputs.settled === undefined ? {} : { settled: inputs.settled }),
|
|
50
55
|
integrity: {
|
|
51
56
|
clean: 0 === issues.length,
|
|
@@ -69,3 +74,22 @@ export function meetsHonestyBar(block, bar = {}) {
|
|
|
69
74
|
}
|
|
70
75
|
return { ok: 0 === reasons.length, reasons };
|
|
71
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* The coverage sentence, kept only on a verdict that points at it.
|
|
79
|
+
*
|
|
80
|
+
* Exactly one branch of `decideVerified` promises it — the YES branch says "coverage was PARTIAL —
|
|
81
|
+
* see `coverage` for what went unobserved". UNKNOWN keeps it too, because "I could not tell" is a
|
|
82
|
+
* statement about what went unseen and the note is the answer. A NO has already named a concrete
|
|
83
|
+
* counter-example, nothing directs the reader to `coverage`, and `partial` plus the spot kinds still
|
|
84
|
+
* travel — so the prose there is cost with no question behind it.
|
|
85
|
+
*
|
|
86
|
+
* Measured: emitting it on every verdict regressed the benchmark's verification efficiency by 4.1%,
|
|
87
|
+
* one note per observation. Accuracy outranks tokens, but this buys no accuracy — the promise is
|
|
88
|
+
* kept wherever it is made.
|
|
89
|
+
*/
|
|
90
|
+
export function honestyForVerdict(verified, honesty) {
|
|
91
|
+
if (Verified.NO !== verified || honesty.coverage.note === undefined)
|
|
92
|
+
return honesty;
|
|
93
|
+
const { note: _dropped, ...coverage } = honesty.coverage;
|
|
94
|
+
return { ...honesty, coverage };
|
|
95
|
+
}
|
|
@@ -75,5 +75,14 @@ export interface ActionInstrumentationFacts {
|
|
|
75
75
|
* finished, and a bare flag makes them read the same.
|
|
76
76
|
*/
|
|
77
77
|
openIntentCount?: number;
|
|
78
|
+
/**
|
|
79
|
+
* How long the OLDEST open intent has been owed, in ms.
|
|
80
|
+
*
|
|
81
|
+
* A count alone cannot tell "you declared this a minute ago and have not proved it yet" from
|
|
82
|
+
* "this project has owed eighteen things since last week". Only the first is actionable on the
|
|
83
|
+
* result being read, and a number that is always large is a number people learn to skip — which
|
|
84
|
+
* is exactly how an honest gap gets filtered out along with the noise.
|
|
85
|
+
*/
|
|
86
|
+
oldestOpenIntentAgeMs?: number;
|
|
78
87
|
}
|
|
79
88
|
export declare function gapsForAction(facts: ActionInstrumentationFacts): InstrumentationGap[];
|
|
@@ -20,7 +20,19 @@ export function gapsForAction(facts) {
|
|
|
20
20
|
//
|
|
21
21
|
// Reported as the same kind, because it is the same missing thing seen from the other side, and
|
|
22
22
|
// guarded so the two conditions cannot report it twice.
|
|
23
|
-
|
|
23
|
+
//
|
|
24
|
+
// BOTH arms require `stateUnwatched`, which is the store registry. Keying the first on
|
|
25
|
+
// `hasCapabilities` alone made this fire on apps whose store IS registered and readable: an app
|
|
26
|
+
// that calls `registerStore()` without `registerCapabilities()` has no declaration and a perfectly
|
|
27
|
+
// good store, and got a gap saying "no state can be read from it" in the same response that
|
|
28
|
+
// carried `reticle_state { found: true }`, a populated `stateDiffs`, and `statePathsChanged`
|
|
29
|
+
// naming the store (#700). A gap contradicted by its own response tells the agent to redo work it
|
|
30
|
+
// has already done, and teaches it to distrust the whole block.
|
|
31
|
+
//
|
|
32
|
+
// The kind is NO_STORE_REGISTERED and every sentence it carries is about stores, so the store
|
|
33
|
+
// registry is what it must be keyed on. An undeclared-capabilities app with a working store is a
|
|
34
|
+
// different observation, and if it is worth reporting it needs its own kind and its own words.
|
|
35
|
+
if (facts.stateUnwatched && (false === facts.hasCapabilities || facts.stateAsked)) {
|
|
24
36
|
// Same missing thing, two ways of meeting it — and the sentence has to say which. Describing an
|
|
25
37
|
// assertion about state to an agent that made no such assertion is a false explanation, and a
|
|
26
38
|
// gap nobody can act on is worse than no gap: it costs the trip and teaches the wrong lesson.
|
|
@@ -55,6 +67,21 @@ export function gapsForAction(facts) {
|
|
|
55
67
|
if (true === facts.changeUndeclared) {
|
|
56
68
|
gaps.push(instrumentationGap(InstrumentationGapKind.UNDECLARED_CHANGE, 'code changed since the last verdict and no intent says what the change was for', 'this verdict can only check the app against itself, so a green here means "nothing visibly broke" rather than "the change did what it was meant to"'));
|
|
57
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Say how old the debt is, when it is old enough to change what the reader should do.
|
|
72
|
+
*
|
|
73
|
+
* The ledger is a PROJECT's, deliberately — "what does this still owe" outlives one session. But an
|
|
74
|
+
* intent declared minutes ago and one abandoned last week produce the same sentence, and the second
|
|
75
|
+
* one is not about the work in front of you. Naming the age separates them without weakening the
|
|
76
|
+
* gap, and without pretending a stale backlog is this verdict's fault.
|
|
77
|
+
*/
|
|
78
|
+
const DAY_MS = 86_400_000;
|
|
79
|
+
function describeIntentAge(ageMs) {
|
|
80
|
+
if (ageMs === undefined || ageMs < DAY_MS)
|
|
81
|
+
return '';
|
|
82
|
+
const days = Math.floor(ageMs / DAY_MS);
|
|
83
|
+
return ` (the oldest for ${String(days)} day${1 === days ? '' : 's'} — a backlog this old is probably not what this run is about; retire them or prove them)`;
|
|
84
|
+
}
|
|
58
85
|
// A green that does not settle what the run OWES.
|
|
59
86
|
//
|
|
60
87
|
// The mirror of `changeUndeclared` above, and the more expensive half: that fires when nothing was
|
|
@@ -71,7 +98,7 @@ export function gapsForAction(facts) {
|
|
|
71
98
|
// to a result whose own `because` says it is not verification.
|
|
72
99
|
const owed = facts.openIntentCount ?? 0;
|
|
73
100
|
if (facts.pass && true === facts.proved && 0 < owed) {
|
|
74
|
-
gaps.push(instrumentationGap(InstrumentationGapKind.INTENT_UNDISCHARGED, `this verdict passed, and ${String(owed)} declared intent(s) are still unproved`, 'a green settles what it asserted, not what the run set out to do — reporting done here rests on the change looking right rather than on anything having checked it'));
|
|
101
|
+
gaps.push(instrumentationGap(InstrumentationGapKind.INTENT_UNDISCHARGED, `this verdict passed, and ${String(owed)} declared intent(s) are still unproved${describeIntentAge(facts.oldestOpenIntentAgeMs)}`, 'a green settles what it asserted, not what the run set out to do — reporting done here rests on the change looking right rather than on anything having checked it'));
|
|
75
102
|
}
|
|
76
103
|
return dedupeGaps(gaps);
|
|
77
104
|
}
|
|
@@ -78,7 +78,8 @@ interface VerifiedInputs {
|
|
|
78
78
|
outcomeUnread?: readonly string[];
|
|
79
79
|
/**
|
|
80
80
|
* The declared consequence that held does not depend on the response body — an exact string
|
|
81
|
-
* rendered, a store path, a signal. See
|
|
81
|
+
* rendered, a store path, a signal, a route, or an element located by role / name / testid. See
|
|
82
|
+
* `declaresBodyIndependentChannel`.
|
|
82
83
|
*
|
|
83
84
|
* The unread-body clause exists for the case where the body is the ONLY channel that could have
|
|
84
85
|
* contradicted the screen (a 200 with batch/GraphQL errors inside). When a body-independent
|
package/dist/honesty/verified.js
CHANGED
|
@@ -152,9 +152,24 @@ export function decideVerified(inputs) {
|
|
|
152
152
|
// with routeChanges 0, because the text was the nav link already on screen — the real navigation
|
|
153
153
|
// landed 1.8s later. UNKNOWN rather than NO on purpose: the app may well be fine, and reporting a
|
|
154
154
|
// failure we did not observe would be its own false claim.
|
|
155
|
+
//
|
|
156
|
+
// `no-fault` rather than `unknown`, once the window settled — the same distinction
|
|
157
|
+
// `nothing_declared` already draws, gated the same way.
|
|
158
|
+
//
|
|
159
|
+
// The two words ask for opposite things. `unknown` means LOOK AGAIN WITH BETTER COVERAGE, so an
|
|
160
|
+
// agent reading it reasonably concludes the engine could not see, and spends its remaining turns
|
|
161
|
+
// enabling capture, widening timeouts and re-driving — none of which can help, because the engine
|
|
162
|
+
// saw the whole window and there was nothing wrong with the evidence. The fault is in the
|
|
163
|
+
// ASSERTION: it named something that was true before the action, so it cannot tell success from a
|
|
164
|
+
// no-op. `no-fault` says exactly that, and `because` already carried the remedy that `unknown`
|
|
165
|
+
// was burying.
|
|
166
|
+
//
|
|
167
|
+
// Gated on `true === settled` for the same reason `nothing_declared` is: no-fault claims the whole
|
|
168
|
+
// window was seen, and a call that returned before the app stopped moving has not earned that.
|
|
169
|
+
// Unsettled stays `unknown`, which is then the honest word for it.
|
|
155
170
|
if (true === inputs.alreadyTrue) {
|
|
156
171
|
return {
|
|
157
|
-
verified: Verified.UNKNOWN,
|
|
172
|
+
verified: true === settled ? Verified.NO_FAULT : Verified.UNKNOWN,
|
|
158
173
|
verifiedReason: VerifiedReason.ALREADY_TRUE,
|
|
159
174
|
because: 'the declared consequence was already true before this action, so it proves nothing about it — assert something the action CHANGES (a signal, a request, a route, or store state)',
|
|
160
175
|
};
|
package/dist/http-server.js
CHANGED
|
@@ -6,6 +6,7 @@ import { noteAgentPeer, PEER_VERSION_PARAM, PEER_CONTRACT_PARAM } from './versio
|
|
|
6
6
|
import { log } from './log.js';
|
|
7
7
|
import { reportMcpConnected } from './telemetry/mcp-connection.js';
|
|
8
8
|
import { isLoopbackHost, isLocalWebOrigin, isLoopbackPeer, requestToken, tokensMatch, } from './bridge/token-auth.js';
|
|
9
|
+
import { LOOPBACK_IDLE_MS } from './loopback-agent.js';
|
|
9
10
|
/**
|
|
10
11
|
* Creates a shared HTTP server that handles both the WebSocket bridge (browser SDK) and the
|
|
11
12
|
* SSE MCP transport (Claude/agent). Does NOT call listen — caller controls that.
|
|
@@ -258,6 +259,17 @@ export function createSharedServer(options = {}) {
|
|
|
258
259
|
});
|
|
259
260
|
});
|
|
260
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* The daemon's half of the keep-alive pact with `loopbackAgent`.
|
|
264
|
+
*
|
|
265
|
+
* Node's default is 5 seconds, and the proxy's POST leg is one request per JSON-RPC message. An
|
|
266
|
+
* agent thinks for longer than 5s between tool calls essentially always, so with the default every
|
|
267
|
+
* tool call had to open a fresh socket however the client was configured — which on Windows ends
|
|
268
|
+
* in `ENOBUFS`. This has to stay ABOVE `LOOPBACK_IDLE_MS` so the CLIENT is always the side that
|
|
269
|
+
* retires an idle socket: if the server closed first, the proxy would keep picking up sockets that
|
|
270
|
+
* were already gone. It also has to stay below Node's 60s default `headersTimeout`.
|
|
271
|
+
*/
|
|
272
|
+
httpServer.keepAliveTimeout = LOOPBACK_IDLE_MS + 10_000;
|
|
261
273
|
return {
|
|
262
274
|
httpServer,
|
|
263
275
|
attachMcp,
|
|
@@ -1,18 +1,30 @@
|
|
|
1
|
-
import { type ImpactCounts, type ImpactSnapshot } from '@reticlehq/core';
|
|
1
|
+
import { type ImpactCounts, type ImpactDefect, type ImpactSnapshot } from '@reticlehq/core';
|
|
2
2
|
import { ImpactStore, type ImpactFoldMeta } from './impact-store.js';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Wire the DEFAULT root — the daemon's own. Idempotent; the first one wins.
|
|
5
|
+
*
|
|
6
|
+
* Still idempotent, and still first-wins, because the daemon's own root genuinely does not change
|
|
7
|
+
* while it runs. What changed is that this is no longer the ONLY root: a call that knows its
|
|
8
|
+
* project passes one, and this is the fallback for calls that do not.
|
|
9
|
+
*/
|
|
4
10
|
export declare function initImpact(opts: {
|
|
5
11
|
reticleRoot: string | undefined;
|
|
6
12
|
projectName?: string;
|
|
7
13
|
now?: () => number;
|
|
8
14
|
}): ImpactStore | undefined;
|
|
9
|
-
/** The live store, if one has been initialised. */
|
|
10
|
-
export declare function impactStore(): ImpactStore | undefined;
|
|
11
|
-
/** Test seam: drop the
|
|
15
|
+
/** The live store for a root (default when omitted), if one has been initialised. */
|
|
16
|
+
export declare function impactStore(root?: string): ImpactStore | undefined;
|
|
17
|
+
/** Test seam: drop the stores so a suite can start from a clean record. */
|
|
12
18
|
export declare function resetImpactForTest(): void;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Record one delta against a project's ledger. Never throws: stats must not break a tool call.
|
|
21
|
+
*
|
|
22
|
+
* `root` is the `.reticle` directory this call's evidence belongs to. Omitted means "could not tell
|
|
23
|
+
* which project", which falls back to the daemon's own root — the old behaviour, kept for the paths
|
|
24
|
+
* that genuinely have no session to ask.
|
|
25
|
+
*/
|
|
26
|
+
export declare function recordImpact(delta: Partial<ImpactCounts>, meta?: ImpactFoldMeta, root?: string): void;
|
|
27
|
+
export declare function impactSnapshot(root?: string): ImpactSnapshot | undefined;
|
|
16
28
|
/**
|
|
17
29
|
* What one tool call did, as counters.
|
|
18
30
|
*
|
|
@@ -21,3 +33,14 @@ export declare function impactSnapshot(): ImpactSnapshot | undefined;
|
|
|
21
33
|
* the same rule the product states to agents - everything else moves or reads the app.
|
|
22
34
|
*/
|
|
23
35
|
export declare function deltaForToolResult(raw: unknown, durationMs: number, refused: boolean): Partial<ImpactCounts>;
|
|
36
|
+
/**
|
|
37
|
+
* What one FAILED verdict was, in a line.
|
|
38
|
+
*
|
|
39
|
+
* Only a verified:"no" produces one. That is the same rule the counters follow and the same rule
|
|
40
|
+
* the product states to agents: an "unknown" is not a defect, it is Reticle admitting it could not
|
|
41
|
+
* tell — recording those here would fill the user's short list with the tool's own blind spots.
|
|
42
|
+
*
|
|
43
|
+
* Everything is read from the result the tool already returned, so this costs nothing extra and
|
|
44
|
+
* cannot disagree with the verdict it describes.
|
|
45
|
+
*/
|
|
46
|
+
export declare function defectForToolResult(raw: unknown, at: number): ImpactDefect | undefined;
|