@reticlehq/server 2.12.0 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +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 +68 -0
- package/dist/setup/dev-server-wait.js +67 -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 +203 -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
package/dist/init/vite-config.js
CHANGED
|
@@ -8,30 +8,38 @@ import { PatchKind } from './patch-kind.js';
|
|
|
8
8
|
export const VITE_IMPORT = "import { reticle } from '@reticlehq/vite-plugin';";
|
|
9
9
|
const RETICLE_MARKER = '@reticlehq/vite-plugin';
|
|
10
10
|
/**
|
|
11
|
-
* The `reticle(...)` call — the bridge port so the injected connect targets it
|
|
11
|
+
* The `reticle(...)` call — the bridge port so the injected connect targets it.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* describes the transport and not the result
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* the user had typed rather than the amount that came back, and every DOM-level check passed. An agent
|
|
19
|
-
* asked to verify that flow had to edit the app's own vite config mid-task to see the payload, and
|
|
20
|
-
* then tell its human to undo the edit.
|
|
13
|
+
* **`captureNetworkBodies` is OPT-IN.** It was written in by default, and the argument for that was
|
|
14
|
+
* a good one: without a body, a write that answers 2xx grades `unknown / outcome_unread`, because a
|
|
15
|
+
* 200 describes the transport and not the result. Measured on a real payments UI — a refund posted
|
|
16
|
+
* rupees into a paise field, the server answered 200 having refunded a hundredth of it, the page
|
|
17
|
+
* rendered the amount the user had typed, and every DOM-level check passed.
|
|
21
18
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
19
|
+
* That argument justifies the CAPABILITY. It does not justify the default, and the distinction is
|
|
20
|
+
* what #705 is about. A healthcare workspace proxying authenticated API traffic through Vite ran
|
|
21
|
+
* `init`, and on the first drive their login tokens and patient payloads were in the daemon's
|
|
22
|
+
* buffer. The old comment here conceded the exact gap — "the credential classes are redacted ... but
|
|
23
|
+
* an address or an email is not, and nothing here should decide that for someone silently" — and
|
|
24
|
+
* then decided it for them, because writing the line into their config IS deciding. `init` is run
|
|
25
|
+
* unattended by an agent; the person who knows the data is sensitive is not in the room.
|
|
26
|
+
*
|
|
27
|
+
* So the default is off and the ways in are all deliberate:
|
|
28
|
+
* - `reticle init --capture-bodies` writes the line, for someone who has decided
|
|
29
|
+
* - `VITE_RETICLE_CAPTURE_BODIES=1` turns it on for ONE dev-server run, no config edit
|
|
30
|
+
* - adding the option by hand, which is what the tools tell you to do
|
|
31
|
+
*
|
|
32
|
+
* Nothing goes quiet in exchange. Every tool that needs a body already says so when it is missing
|
|
33
|
+
* and names the option — see honesty/uncaptured-bodies.ts, honesty/verified.ts, predicate-eval.ts
|
|
34
|
+
* and reconcile-tools.ts — so the capability is discovered at the moment it is wanted, by the person
|
|
35
|
+
* who wanted it, instead of being switched on before anyone has asked.
|
|
28
36
|
*/
|
|
29
|
-
function reticlePluginCall(port) {
|
|
37
|
+
function reticlePluginCall(port, captureBodies) {
|
|
30
38
|
const options = [
|
|
31
39
|
...(port === undefined ? [] : [`port: ${String(port)}`]),
|
|
32
|
-
'captureNetworkBodies: true',
|
|
40
|
+
...(captureBodies ? ['captureNetworkBodies: true'] : []),
|
|
33
41
|
];
|
|
34
|
-
return `reticle({ ${options.join(', ')} })`;
|
|
42
|
+
return 0 === options.length ? 'reticle()' : `reticle({ ${options.join(', ')} })`;
|
|
35
43
|
}
|
|
36
44
|
/** Matches the start of a `plugins: [` array literal. */
|
|
37
45
|
const PLUGINS_ARRAY = /plugins\s*:\s*\[/;
|
|
@@ -66,35 +74,41 @@ function insertImport(source) {
|
|
|
66
74
|
* what a formatter rewrites, turning a one-line install into a diff against the user's own style. A
|
|
67
75
|
* single-line array needs the space, or the result reads `[reticle(),react()]`.
|
|
68
76
|
*/
|
|
69
|
-
function insertPlugin(source, port) {
|
|
77
|
+
function insertPlugin(source, port, captureBodies) {
|
|
70
78
|
return source.replace(PLUGINS_ARRAY, (match, _g, offset) => {
|
|
71
79
|
const next = source[offset + match.length] ?? '';
|
|
72
80
|
const separator = '' === next || /\s/.test(next) ? '' : ' ';
|
|
73
|
-
return `${match}${reticlePluginCall(port)},${separator}`;
|
|
81
|
+
return `${match}${reticlePluginCall(port, captureBodies)},${separator}`;
|
|
74
82
|
});
|
|
75
83
|
}
|
|
76
84
|
/**
|
|
77
85
|
* Add a whole `plugins: [reticle()]` key to a config object that has none, matching the layout of
|
|
78
86
|
* the object it lands in: a multi-line object gets its own indented line, a one-liner stays inline.
|
|
79
87
|
*/
|
|
80
|
-
function insertPluginsKey(source, port) {
|
|
88
|
+
function insertPluginsKey(source, port, captureBodies) {
|
|
81
89
|
return source.replace(CONFIG_OBJECT, (_match, prefix, offset) => {
|
|
82
90
|
const rest = source.slice(offset + _match.length);
|
|
83
91
|
const multiline = /^\s*\n/.test(rest);
|
|
84
92
|
const indent = /^\s*\n(\s*)\S/.exec(rest)?.[1] ?? ' ';
|
|
85
|
-
const key = `plugins: [${reticlePluginCall(port)}],`;
|
|
93
|
+
const key = `plugins: [${reticlePluginCall(port, captureBodies)}],`;
|
|
86
94
|
return multiline ? `${prefix}{\n${indent}${key}` : `${prefix}{ ${key}`;
|
|
87
95
|
});
|
|
88
96
|
}
|
|
89
|
-
export function patchViteConfig(source, port) {
|
|
97
|
+
export function patchViteConfig(source, port, captureBodies = false) {
|
|
90
98
|
if (source.includes(RETICLE_MARKER)) {
|
|
91
99
|
return { kind: VitePatchKind.ALREADY };
|
|
92
100
|
}
|
|
93
101
|
if (PLUGINS_ARRAY.test(source)) {
|
|
94
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
kind: VitePatchKind.APPLY,
|
|
104
|
+
code: insertImport(insertPlugin(source, port, captureBodies)),
|
|
105
|
+
};
|
|
95
106
|
}
|
|
96
107
|
if (CONFIG_OBJECT.test(source)) {
|
|
97
|
-
return {
|
|
108
|
+
return {
|
|
109
|
+
kind: VitePatchKind.APPLY,
|
|
110
|
+
code: insertImport(insertPluginsKey(source, port, captureBodies)),
|
|
111
|
+
};
|
|
98
112
|
}
|
|
99
113
|
return { kind: VitePatchKind.MANUAL, reason: NO_PLUGINS_REASON };
|
|
100
114
|
}
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
+
import type { InitIo } from './run.js';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* The file names that say what a directory IS, shared by everything that has to ask.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* against the root, and it reported ✓ for writing `app/reticle-dev.tsx` into a directory Next never
|
|
7
|
-
* compiles. A ⚠ tells a human to act; a ✓ tells them it is handled.
|
|
8
|
-
*
|
|
9
|
-
* A workspace DECLARES its packages, so that declaration is used first and directory names are not
|
|
10
|
-
* guessed at all. Where nothing is declared, every top-level directory is a better candidate than two
|
|
11
|
-
* hardcoded ones — filtered by the usual non-source suspects, and still subject to the `looksLikeApp`
|
|
12
|
-
* check that follows, so a wrong guess here costs a `package.json` read.
|
|
5
|
+
* They lived in run.ts and are read from here now because `findWorkspaceApps` moved and needs them —
|
|
6
|
+
* one definition, imported in both directions of the question ("is this an app?" and "which app?").
|
|
13
7
|
*/
|
|
8
|
+
export declare const PACKAGE_JSON = "package.json";
|
|
9
|
+
export declare const PNPM_WORKSPACE = "pnpm-workspace.yaml";
|
|
10
|
+
export declare const VITE_CONFIG_CANDIDATES: string[];
|
|
11
|
+
export declare const NEXT_CONFIG_CANDIDATES: string[];
|
|
14
12
|
interface WorkspaceSources {
|
|
15
13
|
/** Raw contents of pnpm-workspace.yaml, when present. */
|
|
16
14
|
pnpmWorkspace?: string;
|
|
@@ -20,4 +18,13 @@ interface WorkspaceSources {
|
|
|
20
18
|
topLevelDirs?: readonly string[];
|
|
21
19
|
}
|
|
22
20
|
export declare function workspaceParents(sources: WorkspaceSources): string[];
|
|
21
|
+
/**
|
|
22
|
+
* App directories under a workspace root.
|
|
23
|
+
*
|
|
24
|
+
* Running `reticle init` at the repo root is what people actually do, and in a monorepo the app is a
|
|
25
|
+
* directory down — so init detected "no framework", printed a wall of manual HTML instructions, and
|
|
26
|
+
* would have installed the SDK into the ROOT package.json. It already walks UP for the lockfile, so
|
|
27
|
+
* it knows it is in a workspace; this is the matching walk DOWN.
|
|
28
|
+
*/
|
|
29
|
+
export declare function findWorkspaceApps(io: Pick<InitIo, 'exists' | 'readFile' | 'listDirs'>): string[];
|
|
23
30
|
export {};
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
import { DEV_SCRIPT_NAMES } from './dev-script.js';
|
|
2
|
+
/**
|
|
3
|
+
* The file names that say what a directory IS, shared by everything that has to ask.
|
|
4
|
+
*
|
|
5
|
+
* They lived in run.ts and are read from here now because `findWorkspaceApps` moved and needs them —
|
|
6
|
+
* one definition, imported in both directions of the question ("is this an app?" and "which app?").
|
|
7
|
+
*/
|
|
8
|
+
export const PACKAGE_JSON = 'package.json';
|
|
9
|
+
export const PNPM_WORKSPACE = 'pnpm-workspace.yaml';
|
|
10
|
+
export const VITE_CONFIG_CANDIDATES = [
|
|
11
|
+
'vite.config.ts',
|
|
12
|
+
'vite.config.js',
|
|
13
|
+
'vite.config.mjs',
|
|
14
|
+
'vite.config.mts',
|
|
15
|
+
];
|
|
16
|
+
export const NEXT_CONFIG_CANDIDATES = [
|
|
17
|
+
'next.config.mjs',
|
|
18
|
+
'next.config.js',
|
|
19
|
+
'next.config.ts',
|
|
20
|
+
'next.config.cjs',
|
|
21
|
+
];
|
|
1
22
|
/**
|
|
2
23
|
* Which directories to look in for the app, when `reticle init` runs at a monorepo root.
|
|
3
24
|
*
|
|
@@ -66,3 +87,92 @@ export function workspaceParents(sources) {
|
|
|
66
87
|
: (sources.topLevelDirs ?? []).filter((d) => !d.startsWith('.') && !NEVER_A_PACKAGE.has(d));
|
|
67
88
|
return [...new Set(candidates)];
|
|
68
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Which directories under this workspace are runnable apps.
|
|
92
|
+
*
|
|
93
|
+
* Moved here from run.ts, which was over the 1000-line cap and had carried a note saying it was at
|
|
94
|
+
* its cohesion limit. `library-path-boundary.test.ts` had already written down that this function
|
|
95
|
+
* "squats in init/ for historical reasons" — it is a general-purpose question about a repository's
|
|
96
|
+
* shape, next to `workspaceParents`, which answers the other half of it.
|
|
97
|
+
*/
|
|
98
|
+
/** Deps that mark a directory as a runnable web app even when it has no bundler config file. */
|
|
99
|
+
const APP_DEPS = ['next', 'vite'];
|
|
100
|
+
function hasDevScript(pkgRaw) {
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(pkgRaw);
|
|
103
|
+
const scripts = 'object' === typeof parsed && parsed !== null
|
|
104
|
+
? parsed.scripts
|
|
105
|
+
: undefined;
|
|
106
|
+
if ('object' !== typeof scripts || null === scripts)
|
|
107
|
+
return false;
|
|
108
|
+
const named = scripts;
|
|
109
|
+
return DEV_SCRIPT_NAMES.some((n) => 'string' === typeof named[n] && '' !== named[n]);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// A manifest that will not parse says nothing either way, and this is a filter, not a verdict.
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function looksLikeApp(dir, io) {
|
|
117
|
+
const pkgRaw = io.readFile(`${dir}/${PACKAGE_JSON}`);
|
|
118
|
+
if (null === pkgRaw)
|
|
119
|
+
return false;
|
|
120
|
+
const configs = [...VITE_CONFIG_CANDIDATES, ...NEXT_CONFIG_CANDIDATES];
|
|
121
|
+
if (configs.some((c) => io.exists(`${dir}/${c}`)))
|
|
122
|
+
return true;
|
|
123
|
+
// `next.config` is optional in Next, so the dependency list is the other half of the signal.
|
|
124
|
+
if (APP_DEPS.some((d) => pkgRaw.includes(`"${d}"`)))
|
|
125
|
+
return true;
|
|
126
|
+
// And the honest third: an app somebody can SERVE. Asking only for a bundler missed every app
|
|
127
|
+
// built on anything else — Remix, Astro, a plain node server — and in a monorepo missing it means
|
|
128
|
+
// init never redirects, wires the ROOT, and reports ✓ for files nothing compiles.
|
|
129
|
+
//
|
|
130
|
+
// A monorepo root has no dev script by design, and a package that can only be BUILT is not the app
|
|
131
|
+
// somebody is working in, so this stays a filter rather than matching every directory.
|
|
132
|
+
return hasDevScript(pkgRaw);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* App directories under a workspace root.
|
|
136
|
+
*
|
|
137
|
+
* Running `reticle init` at the repo root is what people actually do, and in a monorepo the app is a
|
|
138
|
+
* directory down — so init detected "no framework", printed a wall of manual HTML instructions, and
|
|
139
|
+
* would have installed the SDK into the ROOT package.json. It already walks UP for the lockfile, so
|
|
140
|
+
* it knows it is in a workspace; this is the matching walk DOWN.
|
|
141
|
+
*/
|
|
142
|
+
export function findWorkspaceApps(io) {
|
|
143
|
+
const found = [];
|
|
144
|
+
// A workspace DECLARES its packages; `['apps','packages']` was a guess that missed a real repo
|
|
145
|
+
// with three Next apps at web/, admin/ and space/ — and missing them meant init ran against the
|
|
146
|
+
// root and reported ✓ for a file Next never compiles. See workspace-apps.
|
|
147
|
+
const pkgRaw = io.readFile(PACKAGE_JSON);
|
|
148
|
+
let pkgWorkspaces;
|
|
149
|
+
try {
|
|
150
|
+
const parsed = null === pkgRaw ? undefined : JSON.parse(pkgRaw);
|
|
151
|
+
pkgWorkspaces =
|
|
152
|
+
'object' === typeof parsed && parsed !== null
|
|
153
|
+
? parsed.workspaces
|
|
154
|
+
: undefined;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
pkgWorkspaces = undefined;
|
|
158
|
+
}
|
|
159
|
+
const parents = workspaceParents({
|
|
160
|
+
...(null === io.readFile(PNPM_WORKSPACE)
|
|
161
|
+
? {}
|
|
162
|
+
: { pnpmWorkspace: io.readFile(PNPM_WORKSPACE) ?? '' }),
|
|
163
|
+
...(pkgWorkspaces === undefined ? {} : { pkgWorkspaces }),
|
|
164
|
+
topLevelDirs: io.listDirs('.'),
|
|
165
|
+
});
|
|
166
|
+
// A declared parent can itself BE the app (`workspaces: ["web"]`), so check both the directory and
|
|
167
|
+
// its children rather than assuming one level of nesting.
|
|
168
|
+
for (const parent of parents) {
|
|
169
|
+
if (looksLikeApp(parent, io))
|
|
170
|
+
found.push(parent);
|
|
171
|
+
for (const name of io.listDirs(parent)) {
|
|
172
|
+
const dir = `${parent}/${name}`;
|
|
173
|
+
if (looksLikeApp(dir, io))
|
|
174
|
+
found.push(dir);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return [...new Set(found)];
|
|
178
|
+
}
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { CDP_NO_PROVIDER_REASON, CDP_NO_PROVIDER_RECOMMENDATION } from '@reticlehq/core';
|
|
3
3
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
4
4
|
import { sessionIdShape } from '../tools/tool-kit.js';
|
|
5
|
+
import { delayMsSchema, httpStatusSchema } from '../tools/numeric-bounds.js';
|
|
5
6
|
import { asString } from '../tools/tools-helpers.js';
|
|
6
7
|
function mockProvider(deps) {
|
|
7
8
|
const p = deps.realInput;
|
|
@@ -13,14 +14,10 @@ const ruleShape = z.object({
|
|
|
13
14
|
.min(1)
|
|
14
15
|
.describe('Substring the request URL must contain, e.g. "/api/pay".'),
|
|
15
16
|
method: z.string().optional().describe('Optional method filter (GET/POST/…), case-insensitive.'),
|
|
16
|
-
status:
|
|
17
|
+
status: httpStatusSchema.optional().describe('Fulfill with this HTTP status (default 200).'),
|
|
17
18
|
body: z.string().optional().describe('Response body to fulfill with.'),
|
|
18
19
|
contentType: z.string().optional().describe('Response content type (default application/json).'),
|
|
19
|
-
delayMs:
|
|
20
|
-
.number()
|
|
21
|
-
.int()
|
|
22
|
-
.optional()
|
|
23
|
-
.describe('Delay (ms) before fulfilling — simulate a slow API.'),
|
|
20
|
+
delayMs: delayMsSchema.optional().describe('Delay (ms) before fulfilling — simulate a slow API.'),
|
|
24
21
|
abort: z
|
|
25
22
|
.boolean()
|
|
26
23
|
.optional()
|
package/dist/input/real-input.js
CHANGED
|
@@ -302,8 +302,28 @@ export class LaunchedRealInputProvider {
|
|
|
302
302
|
// No SDK on the page (or it connected already) — the no-session guard in verify reports it.
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
|
-
|
|
305
|
+
/**
|
|
306
|
+
* The page we own, or undefined once it is gone.
|
|
307
|
+
*
|
|
308
|
+
* The handle is cached for the life of the provider and Playwright keeps answering `url()` after
|
|
309
|
+
* the page has closed, so a closed window read as AVAILABLE and every method below threw
|
|
310
|
+
* "Target page, context or browser has been closed" — the raw Playwright message, surfaced to the
|
|
311
|
+
* agent as a tool error, on every call for the rest of the run. A dead page is the same fact as no
|
|
312
|
+
* page, and every caller already handles that. Only an EXPLICIT `true` drops it, matching
|
|
313
|
+
* `CdpRealInputProvider`'s `isConnected` check: a test fake without the method is assumed live.
|
|
314
|
+
*/
|
|
315
|
+
#livePage() {
|
|
306
316
|
const page = this.#page;
|
|
317
|
+
if (page === undefined)
|
|
318
|
+
return undefined;
|
|
319
|
+
if (true === page.isClosed?.()) {
|
|
320
|
+
this.#page = undefined; // never ask a corpse twice
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
return page;
|
|
324
|
+
}
|
|
325
|
+
isAvailableFor(sessionUrl) {
|
|
326
|
+
const page = this.#livePage();
|
|
307
327
|
if (page === undefined)
|
|
308
328
|
return Promise.resolve(false);
|
|
309
329
|
if (page.url() === sessionUrl)
|
|
@@ -311,14 +331,14 @@ export class LaunchedRealInputProvider {
|
|
|
311
331
|
return Promise.resolve(stripVolatile(page.url()) === stripVolatile(sessionUrl));
|
|
312
332
|
}
|
|
313
333
|
perform(_sessionUrl, action, box, args) {
|
|
314
|
-
const page = this.#
|
|
334
|
+
const page = this.#livePage();
|
|
315
335
|
if (page === undefined)
|
|
316
336
|
return Promise.resolve({ performed: false, center: boxCenter(box) });
|
|
317
337
|
return performGesture(page, action, box, args, this.#sleep);
|
|
318
338
|
}
|
|
319
339
|
/** PNG of the owned page, or undefined before navigate / after dispose. */
|
|
320
340
|
screenshot(_sessionUrl, opts) {
|
|
321
|
-
const page = this.#
|
|
341
|
+
const page = this.#livePage();
|
|
322
342
|
if (page === undefined)
|
|
323
343
|
return Promise.resolve(undefined);
|
|
324
344
|
return capturePage(page, opts);
|
|
@@ -332,7 +352,7 @@ export class LaunchedRealInputProvider {
|
|
|
332
352
|
* screenshots the whole time; only these two methods were missing.
|
|
333
353
|
*/
|
|
334
354
|
async setViewport(_sessionUrl, size) {
|
|
335
|
-
const page = this.#
|
|
355
|
+
const page = this.#livePage();
|
|
336
356
|
if (page === undefined)
|
|
337
357
|
return false;
|
|
338
358
|
await page.setViewportSize({ width: size.width, height: size.height });
|
|
@@ -340,7 +360,7 @@ export class LaunchedRealInputProvider {
|
|
|
340
360
|
}
|
|
341
361
|
/** Apply network-mock rules to the owned page; false before navigate / after dispose. */
|
|
342
362
|
async setMocks(_sessionUrl, rules) {
|
|
343
|
-
const page = this.#
|
|
363
|
+
const page = this.#livePage();
|
|
344
364
|
if (page === undefined)
|
|
345
365
|
return false;
|
|
346
366
|
await installNetworkMocks(page, rules);
|
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { QueryBy } from '@reticlehq/core';
|
|
3
3
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
4
4
|
import { asNumber, asString } from '../tools/tools-helpers.js';
|
|
5
|
+
import { listIndexSchema, scrollCountSchema } from '../tools/numeric-bounds.js';
|
|
5
6
|
import { scrollToFind } from './scroll-find.js';
|
|
6
7
|
/**
|
|
7
8
|
* reticle_scroll_to — the windowed/virtualized-list escape hatch. A plain reticle_query
|
|
@@ -30,16 +31,13 @@ export const SCROLL_TOOLS = [
|
|
|
30
31
|
.string()
|
|
31
32
|
.optional()
|
|
32
33
|
.describe('Element ref for the scrollable container. Omit to scroll the document.'),
|
|
33
|
-
maxScrolls:
|
|
34
|
-
.number()
|
|
34
|
+
maxScrolls: scrollCountSchema
|
|
35
35
|
.optional()
|
|
36
36
|
.describe('Maximum number of scroll steps before giving up. Default: 20.'),
|
|
37
|
-
targetIndex:
|
|
38
|
-
.number()
|
|
37
|
+
targetIndex: listIndexSchema
|
|
39
38
|
.optional()
|
|
40
39
|
.describe('Known row index of the target in the list. Combine with totalCount for bisection — jumps directly to the estimated offset.'),
|
|
41
|
-
totalCount:
|
|
42
|
-
.number()
|
|
40
|
+
totalCount: listIndexSchema
|
|
43
41
|
.optional()
|
|
44
42
|
.describe('Total item count in the virtualized list. Required for bisection with targetIndex.'),
|
|
45
43
|
sessionId: z
|
|
@@ -2,10 +2,11 @@ import { z } from 'zod';
|
|
|
2
2
|
import { CDP_NO_PROVIDER_REASON, CDP_NO_PROVIDER_RECOMMENDATION } from '@reticlehq/core';
|
|
3
3
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
4
4
|
import { sessionIdShape } from '../tools/tool-kit.js';
|
|
5
|
+
import { MAX_VIEWPORT_PX, MIN_VIEWPORT_PX, viewportPxSchema } from '../tools/numeric-bounds.js';
|
|
5
6
|
import { asString } from '../tools/tools-helpers.js';
|
|
6
7
|
/** Bounds so a viewport request stays sane (and a typo can't ask for a 1px or 100k-px window). */
|
|
7
|
-
const MIN_DIM =
|
|
8
|
-
const MAX_DIM =
|
|
8
|
+
const MIN_DIM = MIN_VIEWPORT_PX;
|
|
9
|
+
const MAX_DIM = MAX_VIEWPORT_PX;
|
|
9
10
|
function viewportProvider(deps) {
|
|
10
11
|
const p = deps.realInput;
|
|
11
12
|
return p !== undefined && 'function' === typeof p.setViewport
|
|
@@ -20,8 +21,8 @@ export const VIEWPORT_TOOLS = [
|
|
|
20
21
|
'reticle_visual_diff `masks` and a frozen clock (reticle_clock). Set it once before reticle_screenshot / ' +
|
|
21
22
|
'reticle_visual_diff. Returns { applied, width, height } or the no-provider recommendation.',
|
|
22
23
|
inputSchema: {
|
|
23
|
-
width:
|
|
24
|
-
height:
|
|
24
|
+
width: viewportPxSchema.describe('Viewport width in CSS px (e.g. 1280).'),
|
|
25
|
+
height: viewportPxSchema.describe('Viewport height in CSS px (e.g. 800).'),
|
|
25
26
|
...sessionIdShape,
|
|
26
27
|
},
|
|
27
28
|
outputSchema: {
|
|
@@ -11,24 +11,13 @@ import type { ToolDeps } from '../tools/tool-kit.js';
|
|
|
11
11
|
export declare function inlineIntentId(statement: string): string;
|
|
12
12
|
/** What `provenBy` records: which tool drew the verdict, and when. */
|
|
13
13
|
export declare function inlineVerdictId(tool: string, at: number): string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* that happens to equal an existing id is the same intent said twice, so pointing at the row is the
|
|
22
|
-
* right answer there too, not a collision.
|
|
23
|
-
*
|
|
24
|
-
* A referenced intent is never re-declared, which would overwrite the agent's own words with an id,
|
|
25
|
-
* and its binding is left alone when it already has one — a predicate bound deliberately through
|
|
26
|
-
* `reticle_intent { action: "bind" }` is a stronger statement than whatever this one call asserts,
|
|
27
|
-
* and quietly replacing it is exactly the narrowing the ledger exists to make visible.
|
|
28
|
-
*
|
|
29
|
-
* Best-effort by construction: a ledger that cannot be written is a small problem, and a verdict
|
|
30
|
-
* that fails to return because of one is a large problem.
|
|
31
|
-
*/
|
|
14
|
+
export declare function surfaceForInlineIntent(url: string | undefined, flow: string | undefined,
|
|
15
|
+
/** `file` or `file:line` for the element the verdict acted on, when it had one. */
|
|
16
|
+
sourceLabel?: string): {
|
|
17
|
+
route?: string;
|
|
18
|
+
flow?: string;
|
|
19
|
+
files?: string[];
|
|
20
|
+
} | undefined;
|
|
32
21
|
export declare function linkInlineIntent(deps: ToolDeps, sessionId: string | undefined, intent: string | undefined,
|
|
33
22
|
/** The predicate this verdict will evaluate, or undefined when it proves nothing (a bare settle). */
|
|
34
23
|
binding: unknown): Promise<string | undefined>;
|
|
@@ -43,4 +32,6 @@ export declare function dischargeInlineIntent(deps: ToolDeps, sessionId: string
|
|
|
43
32
|
verdictId: string;
|
|
44
33
|
grade: string;
|
|
45
34
|
at: number;
|
|
46
|
-
}
|
|
35
|
+
},
|
|
36
|
+
/** `file:line` of the element this verdict acted on, when there was one. */
|
|
37
|
+
sourceLabel?: string): Promise<void>;
|
|
@@ -64,6 +64,81 @@ export function inlineVerdictId(tool, at) {
|
|
|
64
64
|
* Best-effort by construction: a ledger that cannot be written is a small problem, and a verdict
|
|
65
65
|
* that fails to return because of one is a large problem.
|
|
66
66
|
*/
|
|
67
|
+
/**
|
|
68
|
+
* The surface an inline intent was captured on, so the store can FILE it.
|
|
69
|
+
*
|
|
70
|
+
* Measured on a real corpus: 167 of 173 things a project knew landed in `unsorted`, because
|
|
71
|
+
* `act_and_wait({ intent })` declared a statement and nothing else. The subject ladder had no flow,
|
|
72
|
+
* no route and no explicit subject to work from, so every record fell to the bucket of last resort
|
|
73
|
+
* — and a coverage map that is one pile with six labels tells a manager the team knows nothing,
|
|
74
|
+
* when the truth is that it knows a great deal and none of it is filed.
|
|
75
|
+
*
|
|
76
|
+
* The ROUTE is what makes this work at all, because it is always available: an agent asserting
|
|
77
|
+
* something is always somewhere. A flow name is better when there is one, and `subjectFor` already
|
|
78
|
+
* prefers it — this only has to supply both and let that ladder decide.
|
|
79
|
+
*
|
|
80
|
+
* Query and hash are dropped. `/issues?category=severe` and `/issues` are the same subject seen
|
|
81
|
+
* twice; keeping the query would shard one area of the product across every filter anybody used.
|
|
82
|
+
*
|
|
83
|
+
* Returns undefined rather than an empty object when there is nothing to record. A surface that
|
|
84
|
+
* says nothing reads as a capture that looked for a location and found none, rather than one that
|
|
85
|
+
* never had one — and the second is the truth.
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* Strip a trailing `:line` from a `file:line` label.
|
|
89
|
+
*
|
|
90
|
+
* Anchored to the END and to digits, because a Windows path carries its own colon: splitting on the
|
|
91
|
+
* first one turns `C:\\app\\src\\cart.tsx:12` into `C`, which is a path to nothing and would be
|
|
92
|
+
* filed as though it were real.
|
|
93
|
+
*/
|
|
94
|
+
const SOURCE_LINE_SUFFIX = /:\d+$/;
|
|
95
|
+
export function surfaceForInlineIntent(url, flow,
|
|
96
|
+
/** `file` or `file:line` for the element the verdict acted on, when it had one. */
|
|
97
|
+
sourceLabel) {
|
|
98
|
+
let route;
|
|
99
|
+
if (url !== undefined) {
|
|
100
|
+
try {
|
|
101
|
+
const path = new URL(url).pathname;
|
|
102
|
+
// A bare origin has no path worth filing under: `/` would put every record in one bucket,
|
|
103
|
+
// which is the problem this exists to fix rather than a solution to it.
|
|
104
|
+
if (path.length > 1)
|
|
105
|
+
route = path;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
route = undefined;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/*
|
|
112
|
+
* The file, without its line.
|
|
113
|
+
*
|
|
114
|
+
* A record is about a FILE; the line is where one verdict happened to touch it, and keeping it
|
|
115
|
+
* would make the same rule look like a different one every time the file was edited above it.
|
|
116
|
+
*/
|
|
117
|
+
const file = sourceLabel === undefined || 0 === sourceLabel.length
|
|
118
|
+
? undefined
|
|
119
|
+
: sourceLabel.replace(SOURCE_LINE_SUFFIX, '');
|
|
120
|
+
if (route === undefined && flow === undefined && file === undefined)
|
|
121
|
+
return undefined;
|
|
122
|
+
return {
|
|
123
|
+
...(route === undefined ? {} : { route }),
|
|
124
|
+
...(flow === undefined ? {} : { flow }),
|
|
125
|
+
...(file === undefined ? {} : { files: [file] }),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The session's live URL, or undefined when no session can be named.
|
|
130
|
+
*
|
|
131
|
+
* `sessions.resolve` throws for every "cannot tell which tab" case, and none of them is a reason to
|
|
132
|
+
* fail the caller's verdict — a missing surface costs a filing, not a result.
|
|
133
|
+
*/
|
|
134
|
+
function sessionUrl(deps, sessionId) {
|
|
135
|
+
try {
|
|
136
|
+
return deps.sessions.resolve(sessionId).url;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
67
142
|
export async function linkInlineIntent(deps, sessionId, intent,
|
|
68
143
|
/** The predicate this verdict will evaluate, or undefined when it proves nothing (a bare settle). */
|
|
69
144
|
binding) {
|
|
@@ -73,6 +148,8 @@ binding) {
|
|
|
73
148
|
const store = new IntentStore(deps.fs, sessionRoot(deps, sessionId), { now: deps.now });
|
|
74
149
|
const existing = (await store.read()).find((row) => row.id === intent);
|
|
75
150
|
const id = existing?.id ?? inlineIntentId(intent);
|
|
151
|
+
// Declared WITHOUT a surface on purpose: see dischargeInlineIntent for why the route that
|
|
152
|
+
// describes this record only exists after the action it is about.
|
|
76
153
|
if (existing === undefined)
|
|
77
154
|
await store.declare([{ id, statement: intent }]);
|
|
78
155
|
if (binding !== undefined && existing?.binding === undefined)
|
|
@@ -90,11 +167,24 @@ binding) {
|
|
|
90
167
|
* intent declared inline on a wait that asserted no consequence stays open and honest instead of
|
|
91
168
|
* collecting a proof nothing earned.
|
|
92
169
|
*/
|
|
93
|
-
export async function dischargeInlineIntent(deps, sessionId, id, proof
|
|
170
|
+
export async function dischargeInlineIntent(deps, sessionId, id, proof,
|
|
171
|
+
/** `file:line` of the element this verdict acted on, when there was one. */
|
|
172
|
+
sourceLabel) {
|
|
94
173
|
if (id === undefined)
|
|
95
174
|
return;
|
|
96
175
|
try {
|
|
97
176
|
const store = new IntentStore(deps.fs, sessionRoot(deps, sessionId), { now: deps.now });
|
|
177
|
+
/*
|
|
178
|
+
* File it HERE, not at declaration.
|
|
179
|
+
*
|
|
180
|
+
* The intent is declared before the action so a verdict can see it open, and at that instant the
|
|
181
|
+
* agent is still on the page it is leaving — clicking through to /issues from / would have filed
|
|
182
|
+
* the record under the page it left, or under nothing at all. The route that describes what the
|
|
183
|
+
* intent is about is the one that exists once the consequence has landed.
|
|
184
|
+
*/
|
|
185
|
+
const surface = surfaceForInlineIntent(sessionUrl(deps, sessionId), undefined, sourceLabel);
|
|
186
|
+
if (surface !== undefined)
|
|
187
|
+
await store.place(id, surface);
|
|
98
188
|
await store.discharge(id, proof);
|
|
99
189
|
}
|
|
100
190
|
catch {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading and writing the sharded intent store, and migrating the flat one into it.
|
|
3
|
+
*
|
|
4
|
+
* The operations an agent actually performs, and what each is allowed to cost:
|
|
5
|
+
*
|
|
6
|
+
* index() every id + one line, always affordable — the session-start read
|
|
7
|
+
* subject(name) the full records for one subject, when it is about to work on that
|
|
8
|
+
* get(id) one record, via the index rather than by scanning shards
|
|
9
|
+
* record()/update() a write that touches ONE shard file plus the index
|
|
10
|
+
*
|
|
11
|
+
* The last one is the reason for the whole design. Under the flat file, changing one intent rewrote
|
|
12
|
+
* all 141, so two sessions touching unrelated subjects raced each other and the loser's write
|
|
13
|
+
* vanished. Sharded, they only collide when they genuinely edit the same subject.
|
|
14
|
+
*
|
|
15
|
+
* ## Migration is automatic, and it does not delete
|
|
16
|
+
*
|
|
17
|
+
* `.reticle/intent.json` is read on every load and folded in when the sharded copy has nothing for
|
|
18
|
+
* an id. That makes the move invisible: an agent on the new store sees old intents immediately, and
|
|
19
|
+
* an older build still reading the flat file keeps working, because nothing removes it. Deleting it
|
|
20
|
+
* is a separate decision somebody can take once they believe the migration.
|
|
21
|
+
*/
|
|
22
|
+
import { type Intent } from '@reticlehq/core';
|
|
23
|
+
import type { FileSystemPort } from '../project/fs-port.js';
|
|
24
|
+
import { IntentStatus, type IntentIndex, type IntentRecord } from './intent-shard.js';
|
|
25
|
+
export interface Clock {
|
|
26
|
+
now: () => number;
|
|
27
|
+
}
|
|
28
|
+
/** What a caller supplies to write an intent. Everything optional is genuinely optional. */
|
|
29
|
+
export interface IntentInput {
|
|
30
|
+
id: string;
|
|
31
|
+
statement: string;
|
|
32
|
+
subject?: string | undefined;
|
|
33
|
+
why?: string | undefined;
|
|
34
|
+
source?: string | undefined;
|
|
35
|
+
status?: IntentStatus | undefined;
|
|
36
|
+
surface?: Intent['surface'];
|
|
37
|
+
binding?: unknown;
|
|
38
|
+
}
|
|
39
|
+
export declare class IntentShardStore {
|
|
40
|
+
#private;
|
|
41
|
+
constructor(fs: FileSystemPort, root: string, clock: Clock);
|
|
42
|
+
/**
|
|
43
|
+
* The cheap read: one line per intent, and the call an agent makes at the start of a session.
|
|
44
|
+
*
|
|
45
|
+
* Includes anything still only in the legacy flat file, so the very first call on an unmigrated
|
|
46
|
+
* project already answers completely — which is what makes the migration invisible rather than a
|
|
47
|
+
* step somebody has to remember to run.
|
|
48
|
+
*/
|
|
49
|
+
index(): Promise<IntentIndex>;
|
|
50
|
+
/** Every record for one subject — the working read, once an agent knows what it is touching. */
|
|
51
|
+
subject(name: string): Promise<IntentRecord[]>;
|
|
52
|
+
/** One record by id, or null. */
|
|
53
|
+
get(id: string): Promise<IntentRecord | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Write one intent, creating or updating it.
|
|
56
|
+
*
|
|
57
|
+
* Merges onto whatever is already stored, so a caller adding a `why` to a migrated record does not
|
|
58
|
+
* have to restate its binding — and cannot silently drop it by omission, which is the failure mode
|
|
59
|
+
* that makes agents afraid to touch a store.
|
|
60
|
+
*/
|
|
61
|
+
record(input: IntentInput): Promise<IntentRecord>;
|
|
62
|
+
/**
|
|
63
|
+
* Fold the flat file into shards on disk, so the move is visible in a diff rather than implicit.
|
|
64
|
+
*
|
|
65
|
+
* Idempotent, and it does not delete `.reticle/intent.json`: an older build still reads it, and a
|
|
66
|
+
* migration that removes its own source cannot be checked afterwards.
|
|
67
|
+
*/
|
|
68
|
+
migrate(): Promise<{
|
|
69
|
+
migrated: number;
|
|
70
|
+
subjects: string[];
|
|
71
|
+
}>;
|
|
72
|
+
}
|