@reticlehq/server 2.7.0 → 2.9.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/README.md +14 -5
- package/SKILL.md +130 -778
- package/dist/bridge/bridge.d.ts +23 -0
- package/dist/bridge/bridge.js +80 -2
- package/dist/bridge/bridge.test-harness.d.ts +5 -0
- package/dist/bridge/bridge.test-harness.js +6 -4
- package/dist/capsule/causal-summary.d.ts +36 -1
- package/dist/capsule/causal-summary.js +33 -7
- package/dist/capsule/divergence.js +7 -1
- package/dist/cli/chromium-hint.d.ts +33 -5
- package/dist/cli/chromium-hint.js +82 -12
- package/dist/cli/cli-doctor.js +65 -34
- package/dist/cli/cli-flow-commands.js +8 -8
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-parse.d.ts +16 -1
- package/dist/cli/cli-parse.js +32 -9
- package/dist/cli/cli-port.d.ts +32 -0
- package/dist/cli/cli-port.js +82 -35
- package/dist/cli/cli-verify.d.ts +21 -0
- package/dist/cli/cli-verify.js +47 -3
- package/dist/cli/config-discovery.d.ts +35 -0
- package/dist/cli/config-discovery.js +163 -0
- package/dist/cli/doctor-daemon-line.js +4 -1
- package/dist/cli/doctor-project-line.d.ts +37 -0
- package/dist/cli/doctor-project-line.js +35 -0
- package/dist/cli/doctor-rows.d.ts +51 -0
- package/dist/cli/doctor-rows.js +54 -0
- package/dist/cli/doctor-sessions-line.d.ts +12 -0
- package/dist/cli/doctor-sessions-line.js +69 -0
- package/dist/cli/drive-attach.d.ts +99 -0
- package/dist/cli/drive-attach.js +203 -0
- package/dist/cli/drive-command.d.ts +13 -0
- package/dist/cli/drive-command.js +103 -0
- package/dist/cli/open-note.d.ts +20 -0
- package/dist/cli/open-note.js +37 -0
- package/dist/cli/port-holder.d.ts +10 -0
- package/dist/cli/port-holder.js +26 -2
- package/dist/cli/status-next-action.d.ts +37 -0
- package/dist/cli/status-next-action.js +61 -0
- package/dist/cli.js +119 -39
- package/dist/crawl/crawl-tools.js +12 -2
- package/dist/crawl/crawl.d.ts +13 -0
- package/dist/crawl/crawl.js +50 -2
- package/dist/events/contradictions.d.ts +46 -0
- package/dist/events/contradictions.js +35 -22
- package/dist/events/declared.d.ts +45 -0
- package/dist/events/declared.js +73 -0
- package/dist/events/echo-mismatch.d.ts +8 -2
- package/dist/events/echo-mismatch.js +33 -5
- package/dist/events/event-filters.d.ts +1 -1
- package/dist/events/event-filters.js +7 -4
- package/dist/events/predicate-eval.d.ts +17 -72
- package/dist/events/predicate-eval.js +117 -236
- package/dist/events/predicate-parse.js +31 -3
- package/dist/events/predicate-route.d.ts +32 -0
- package/dist/events/predicate-route.js +102 -0
- package/dist/events/predicate-schema.d.ts +99 -0
- package/dist/events/predicate-schema.js +309 -0
- package/dist/events/predicate.d.ts +8 -0
- package/dist/events/predicate.js +296 -22
- package/dist/flows/annotate-tools.js +3 -0
- package/dist/flows/annotate.js +18 -4
- package/dist/flows/coverage.d.ts +38 -5
- package/dist/flows/coverage.js +30 -4
- package/dist/flows/decision.d.ts +15 -0
- package/dist/flows/decision.js +1 -1
- package/dist/flows/flow-classify.js +1 -1
- package/dist/flows/flow-replay-run.js +8 -1
- package/dist/flows/flow-tools.js +21 -5
- package/dist/flows/heal-run.js +1 -1
- package/dist/flows/replay.js +3 -0
- package/dist/honesty/accepted-write.d.ts +3 -0
- package/dist/honesty/accepted-write.js +8 -3
- package/dist/honesty/already-true.js +15 -0
- package/dist/honesty/blind-spots.js +10 -2
- package/dist/honesty/honesty.d.ts +11 -0
- package/dist/honesty/honesty.js +1 -0
- package/dist/honesty/uncaptured-bodies.d.ts +4 -2
- package/dist/honesty/uncaptured-bodies.js +9 -2
- package/dist/honesty/unread-outcome.d.ts +8 -0
- package/dist/honesty/unread-outcome.js +36 -9
- package/dist/honesty/unsettled.d.ts +49 -0
- package/dist/honesty/unsettled.js +82 -0
- package/dist/honesty/verified.d.ts +36 -3
- package/dist/honesty/verified.js +54 -11
- package/dist/http-server.d.ts +19 -0
- package/dist/http-server.js +202 -4
- package/dist/impact/impact-recorder.d.ts +23 -0
- package/dist/impact/impact-recorder.js +76 -0
- package/dist/impact/impact-store.d.ts +47 -0
- package/dist/impact/impact-store.js +171 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +47 -10
- package/dist/init/agent-rules.d.ts +36 -0
- package/dist/init/agent-rules.js +132 -22
- package/dist/init/astro-patch.d.ts +2 -1
- package/dist/init/astro-patch.js +38 -6
- package/dist/init/capabilities.d.ts +22 -0
- package/dist/init/capabilities.js +76 -0
- package/dist/init/confirm.d.ts +114 -0
- package/dist/init/confirm.js +165 -0
- package/dist/init/connect-steps.d.ts +2 -0
- package/dist/init/connect-steps.js +50 -0
- package/dist/init/cra.js +3 -2
- package/dist/init/csp-check.d.ts +38 -0
- package/dist/init/csp-check.js +92 -0
- package/dist/init/csp-doctor.d.ts +25 -0
- package/dist/init/csp-doctor.js +49 -0
- package/dist/init/cursor.js +6 -9
- package/dist/init/detect.d.ts +8 -0
- package/dist/init/detect.js +33 -3
- package/dist/init/mcp-clients.d.ts +1 -0
- package/dist/init/mcp-clients.js +51 -7
- package/dist/init/mcp.d.ts +15 -0
- package/dist/init/mcp.js +19 -0
- package/dist/init/non-js-project.js +29 -5
- package/dist/init/plan-framework.d.ts +10 -0
- package/dist/init/plan-framework.js +78 -16
- package/dist/init/plan.d.ts +47 -6
- package/dist/init/plan.js +228 -69
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +157 -45
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +54 -6
- package/dist/init/snippets.d.ts +45 -5
- package/dist/init/snippets.js +194 -42
- package/dist/init/vite-config.js +53 -5
- package/dist/input/network-mock-tools.d.ts +19 -0
- package/dist/input/network-mock-tools.js +37 -5
- package/dist/input/scroll-find.d.ts +6 -0
- package/dist/input/scroll-find.js +17 -2
- package/dist/input/scroll-tools.js +4 -0
- package/dist/journal/attach-journal.d.ts +9 -1
- package/dist/journal/attach-journal.js +13 -2
- package/dist/journal/journal-query.d.ts +13 -0
- package/dist/journal/journal-query.js +28 -3
- package/dist/journal/workspace-gitignore.d.ts +10 -0
- package/dist/journal/workspace-gitignore.js +59 -0
- package/dist/license/license.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +35 -30
- package/dist/mcp/mcp-proxy.js +306 -104
- package/dist/mcp/mcp.d.ts +10 -1
- package/dist/mcp/mcp.js +70 -34
- package/dist/mcp/proxy-handshake.d.ts +82 -1
- package/dist/mcp/proxy-handshake.js +109 -2
- package/dist/mcp/proxy-lifecycle.d.ts +11 -2
- package/dist/mcp/proxy-lifecycle.js +12 -3
- package/dist/mcp/proxy-log.d.ts +63 -0
- package/dist/mcp/proxy-log.js +132 -0
- package/dist/mcp/server-instructions.d.ts +17 -0
- package/dist/mcp/server-instructions.js +56 -0
- package/dist/mcp/shared-params.d.ts +27 -0
- package/dist/mcp/shared-params.js +36 -0
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/project/learned-routes.d.ts +24 -0
- package/dist/project/learned-routes.js +63 -0
- package/dist/project/project-store.d.ts +6 -0
- package/dist/project/project-store.js +25 -1
- package/dist/project/reticle-dir.d.ts +2 -0
- package/dist/project/reticle-dir.js +1 -0
- package/dist/session/connection-memory.d.ts +59 -0
- package/dist/session/connection-memory.js +108 -0
- package/dist/session/dev-command.d.ts +26 -0
- package/dist/session/dev-command.js +89 -0
- package/dist/session/dev-server-probe.d.ts +68 -0
- package/dist/session/dev-server-probe.js +102 -7
- package/dist/session/last-act.d.ts +26 -0
- package/dist/session/last-act.js +35 -1
- package/dist/session/live-control-tools.js +42 -5
- package/dist/session/no-session-diagnosis.d.ts +67 -2
- package/dist/session/no-session-diagnosis.js +236 -27
- package/dist/session/no-session-next-action.d.ts +46 -0
- package/dist/session/no-session-next-action.js +85 -0
- package/dist/session/no-session-watch.d.ts +40 -1
- package/dist/session/no-session-watch.js +174 -8
- package/dist/session/pending-commands.d.ts +12 -0
- package/dist/session/pending-commands.js +13 -1
- package/dist/session/ref-provenance.d.ts +54 -0
- package/dist/session/ref-provenance.js +61 -0
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +4 -0
- package/dist/session/session-manager.js +35 -5
- package/dist/session/session.d.ts +10 -1
- package/dist/session/session.js +146 -5
- package/dist/telemetry/app-instrumented.d.ts +8 -0
- package/dist/telemetry/app-instrumented.js +10 -0
- package/dist/telemetry/automation-hint.d.ts +17 -0
- package/dist/telemetry/automation-hint.js +71 -0
- package/dist/telemetry/bug-found.d.ts +15 -17
- package/dist/telemetry/bug-found.js +52 -21
- package/dist/telemetry/cli-telemetry.js +7 -1
- package/dist/telemetry/daemon-telemetry.js +24 -0
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/init-telemetry.js +9 -1
- package/dist/telemetry/install-source.d.ts +56 -0
- package/dist/telemetry/install-source.js +74 -0
- package/dist/telemetry/instrumentation-stall.d.ts +50 -0
- package/dist/telemetry/instrumentation-stall.js +90 -0
- package/dist/telemetry/run-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.d.ts +8 -0
- package/dist/telemetry/session-metrics.js +15 -0
- package/dist/telemetry/telemetry.d.ts +7 -1
- package/dist/telemetry/telemetry.js +26 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +46 -7
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +209 -28
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +49 -7
- package/dist/tools/browser-tools.js +10 -0
- package/dist/tools/contract-tools.js +4 -0
- package/dist/tools/dynamic-tools.js +16 -7
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +73 -9
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +159 -26
- package/dist/tools/lease-hint.d.ts +30 -8
- package/dist/tools/lease-hint.js +65 -14
- package/dist/tools/lease-tools.d.ts +40 -1
- package/dist/tools/lease-tools.js +181 -83
- package/dist/tools/navigate-result.js +1 -1
- package/dist/tools/observe-tools.js +10 -4
- package/dist/tools/observed-nothing.js +1 -1
- package/dist/tools/react-grace.d.ts +52 -0
- package/dist/tools/react-grace.js +90 -0
- package/dist/tools/read-tools.js +67 -5
- package/dist/tools/resolve-target.d.ts +33 -0
- package/dist/tools/resolve-target.js +53 -0
- package/dist/tools/settle-in-flight.d.ts +94 -0
- package/dist/tools/settle-in-flight.js +146 -0
- package/dist/tools/snapshot-delta.d.ts +10 -0
- package/dist/tools/snapshot-delta.js +194 -9
- package/dist/tools/tool-kit.d.ts +8 -0
- package/dist/tools/tool-surface.d.ts +60 -0
- package/dist/tools/tool-surface.js +86 -1
- package/dist/tools/tools.js +61 -8
- package/dist/tools/unadvertised-help.js +9 -6
- package/docs/README.md +55 -11
- package/docs/actions.mdx +120 -0
- package/docs/agent-cheatsheet.md +95 -29
- package/docs/architecture.md +52 -35
- package/docs/benchmarks.md +127 -75
- package/docs/best-practices.mdx +216 -0
- package/docs/capabilities.mdx +534 -0
- package/docs/cli/affected.mdx +53 -0
- package/docs/cli/capsules.mdx +70 -0
- package/docs/cli/cloud.mdx +153 -0
- package/docs/cli/doctor.mdx +109 -0
- package/docs/cli/drive.mdx +74 -0
- package/docs/cli/feedback.mdx +67 -0
- package/docs/cli/gate.mdx +64 -0
- package/docs/cli/help.mdx +94 -0
- package/docs/cli/hunt.mdx +73 -0
- package/docs/cli/identify.mdx +76 -0
- package/docs/cli/init.mdx +121 -0
- package/docs/cli/kill.mdx +91 -0
- package/docs/cli/license.mdx +48 -0
- package/docs/cli/mcp.mdx +61 -0
- package/docs/cli/open.mdx +74 -0
- package/docs/cli/restart.mdx +60 -0
- package/docs/cli/rollback.mdx +35 -0
- package/docs/cli/serve.mdx +69 -0
- package/docs/cli/status.mdx +88 -0
- package/docs/cli/stop.mdx +64 -0
- package/docs/cli/telemetry.mdx +57 -0
- package/docs/cli/update.mdx +52 -0
- package/docs/cli/verify.mdx +76 -0
- package/docs/cli/version.mdx +39 -0
- package/docs/cli/watch.mdx +62 -0
- package/docs/cli.mdx +241 -0
- package/docs/debugging.md +16 -10
- package/docs/deploy-checks.md +45 -12
- package/docs/desktop.mdx +384 -0
- package/docs/docs.json +364 -0
- package/docs/enterprise.md +22 -16
- package/docs/faq.mdx +102 -0
- package/docs/fixtures.md +13 -7
- package/docs/flows.md +29 -23
- package/docs/for-agents.md +71 -0
- package/docs/frameworks.mdx +238 -0
- package/docs/gate-plan.md +48 -42
- package/docs/gates.md +28 -22
- package/docs/getting-started.md +129 -77
- package/docs/human-control.md +24 -18
- package/docs/index.mdx +143 -0
- package/docs/install-agentic.mdx +224 -0
- package/docs/install-manual.mdx +196 -0
- package/docs/instrumentation.mdx +238 -0
- package/docs/integration-patterns.md +41 -35
- package/docs/local-registry.md +19 -13
- package/docs/multi-agent-testing.md +19 -13
- package/docs/packages/babel-plugin.mdx +79 -0
- package/docs/packages/browser.mdx +111 -0
- package/docs/packages/core.mdx +116 -0
- package/docs/packages/electron.mdx +85 -0
- package/docs/packages/eslint-plugin.mdx +86 -0
- package/docs/packages/next.mdx +89 -0
- package/docs/packages/react.mdx +91 -0
- package/docs/packages/server.mdx +98 -0
- package/docs/packages/tauri.mdx +63 -0
- package/docs/packages/test.mdx +119 -0
- package/docs/packages/vite-plugin.mdx +94 -0
- package/docs/packages.mdx +218 -0
- package/docs/platform-integration.md +35 -26
- package/docs/predicates.mdx +248 -0
- package/docs/quickstart.mdx +257 -0
- package/docs/recipes.mdx +282 -0
- package/docs/skill-file.mdx +107 -0
- package/docs/state-management.mdx +199 -0
- package/docs/style.css +14 -0
- package/docs/system-map.md +50 -33
- package/docs/telemetry-contract.md +106 -52
- package/docs/telemetry-events.mdx +475 -0
- package/docs/telemetry.md +63 -55
- package/docs/testing.md +44 -6
- package/docs/token-efficiency.md +37 -27
- package/docs/tools-act-and-wait.mdx +173 -0
- package/docs/tools-act-sequence.mdx +137 -0
- package/docs/tools-act.mdx +95 -0
- package/docs/tools-assert.mdx +129 -0
- package/docs/tools-console.mdx +63 -0
- package/docs/tools-inspect.mdx +88 -0
- package/docs/tools-navigate.mdx +99 -0
- package/docs/tools-network.mdx +119 -0
- package/docs/tools-observe.mdx +145 -0
- package/docs/tools-overview.mdx +160 -0
- package/docs/tools-query.mdx +126 -0
- package/docs/tools-session-and-feedback.mdx +112 -0
- package/docs/tools-sessions.mdx +94 -0
- package/docs/tools-snapshot.mdx +129 -0
- package/docs/tools-state.mdx +104 -0
- package/docs/tools-tools-and-run.mdx +156 -0
- package/docs/tools-wait-for.mdx +85 -0
- package/docs/troubleshooting.mdx +263 -0
- package/docs/usage.md +209 -165
- package/docs/vs-chrome-devtools-mcp.mdx +123 -0
- package/docs/vs-playwright-mcp.mdx +125 -0
- package/docs/vs-screenshots.mdx +137 -0
- package/docs/what-is-reticle.mdx +63 -0
- package/docs/why-reticle.mdx +109 -0
- package/package.json +5 -5
- package/docs/desktop-apps.md +0 -256
- package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
- package/docs/matrix/2.5.0/codex-machine.json +0 -20
- package/docs/matrix/2.5.0/cursor-machine.json +0 -22
- package/docs/matrix/2.5.0/gemini-machine.json +0 -22
- package/docs/matrix/2.5.0/opencode-machine.json +0 -22
- package/docs/matrix/2.5.0/vscode-machine.json +0 -22
- package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
- package/docs/matrix/MATRIX.md +0 -26
- package/docs/matrix/README.md +0 -59
- package/docs/mint.json +0 -61
|
@@ -4,6 +4,7 @@ import { findBodyFailures } from './body-failures.js';
|
|
|
4
4
|
import { findEchoMismatches } from './echo-mismatch.js';
|
|
5
5
|
import { findUnitMismatches } from './unit-mismatch.js';
|
|
6
6
|
import { asNumber, asString } from '../tools/tools-helpers.js';
|
|
7
|
+
import { matchesDeclaredFailure } from './declared.js';
|
|
7
8
|
function netCall(e) {
|
|
8
9
|
const status = asNumber(e.data['status']);
|
|
9
10
|
return {
|
|
@@ -171,7 +172,7 @@ export function findContradictions(allEvents, options = {}) {
|
|
|
171
172
|
// Needs body capture; silent without it, which is why the assert path also declares when bodies
|
|
172
173
|
// were never recorded rather than letting an unread payload read as an empty one.
|
|
173
174
|
found.push(...findBodyFailures(events));
|
|
174
|
-
found.push(...findEchoMismatches(events));
|
|
175
|
+
found.push(...findEchoMismatches(events, options.actionSince));
|
|
175
176
|
// ── A money value written back at the wrong SCALE ───────────────────────────────────────────
|
|
176
177
|
found.push(...findUnitMismatches(events, options.prior ?? []));
|
|
177
178
|
// ── The action landed on something that does not react ──────────────────────────────────────
|
|
@@ -192,6 +193,9 @@ export function findContradictions(allEvents, options = {}) {
|
|
|
192
193
|
];
|
|
193
194
|
}
|
|
194
195
|
const failed = settled.filter((c) => false === c.ok);
|
|
196
|
+
// The failures NOBODY declared — see ContradictionOptions.expectedFailures. Only the heuristic
|
|
197
|
+
// "the UI moved while a request failed" rule reads this; the sharp rules still read `failed`.
|
|
198
|
+
const unexpected = failed.filter((c) => !matchesDeclaredFailure(c, options.expectedFailures ?? []));
|
|
195
199
|
const advanced = uiAdvanced(events);
|
|
196
200
|
const signals = events
|
|
197
201
|
.filter((e) => e.type === EventType.SIGNAL)
|
|
@@ -215,7 +219,7 @@ export function findContradictions(allEvents, options = {}) {
|
|
|
215
219
|
e.type === EventType.DOM_REMOVED ||
|
|
216
220
|
e.type === EventType.DOM_TEXT);
|
|
217
221
|
const fetched = events.some((e) => e.type === EventType.NET_REQUEST || e.type === EventType.NET_PENDING);
|
|
218
|
-
if (routed && !rendered && !fetched) {
|
|
222
|
+
if (routed && !rendered && !fetched && true !== options.renderProved) {
|
|
219
223
|
found.push({
|
|
220
224
|
kind: ContradictionKind.ROUTE_RENDERED_NOTHING,
|
|
221
225
|
claim: 'the app navigated to a new route',
|
|
@@ -259,12 +263,12 @@ export function findContradictions(allEvents, options = {}) {
|
|
|
259
263
|
detail: failed.map(describe).join('; '),
|
|
260
264
|
});
|
|
261
265
|
}
|
|
262
|
-
else if (
|
|
266
|
+
else if (unexpected.length > 0 && advanced && !misattributed && !failureAcknowledged(events)) {
|
|
263
267
|
found.push({
|
|
264
268
|
kind: ContradictionKind.UI_ADVANCED_REQUEST_FAILED,
|
|
265
269
|
claim: 'the UI moved forward (DOM/store/route changed)',
|
|
266
|
-
counter: `${String(
|
|
267
|
-
detail:
|
|
270
|
+
counter: `${String(unexpected.length)} request(s) in the same window failed`,
|
|
271
|
+
detail: unexpected.map(describe).join('; '),
|
|
268
272
|
});
|
|
269
273
|
}
|
|
270
274
|
// ── A write succeeded and nothing on the client moved ───────────────────────────────────────
|
|
@@ -281,23 +285,32 @@ export function findContradictions(allEvents, options = {}) {
|
|
|
281
285
|
});
|
|
282
286
|
}
|
|
283
287
|
}
|
|
284
|
-
// ── The same write fired more than once
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
288
|
+
// ── The same write fired more than once, inside ONE action's window ─────────────────────────
|
|
289
|
+
// Attribution is the whole rule here, not a refinement of it: counting `method + url` over whatever
|
|
290
|
+
// window the caller handed in turns two legitimate separate saves into a double submit. See
|
|
291
|
+
// ContradictionOptions.actionSince.
|
|
292
|
+
const actionSince = options.actionSince;
|
|
293
|
+
if (actionSince !== undefined) {
|
|
294
|
+
const writeCounts = new Map();
|
|
295
|
+
for (const event of events) {
|
|
296
|
+
if (event.type !== EventType.NET_REQUEST || event.t < actionSince)
|
|
297
|
+
continue;
|
|
298
|
+
const call = netCall(event);
|
|
299
|
+
if (!isMutating(call))
|
|
300
|
+
continue;
|
|
301
|
+
const key = `${call.method} ${call.url}`;
|
|
302
|
+
writeCounts.set(key, (writeCounts.get(key) ?? 0) + 1);
|
|
303
|
+
}
|
|
304
|
+
for (const [key, count] of writeCounts) {
|
|
305
|
+
if (count < 2)
|
|
306
|
+
continue;
|
|
307
|
+
found.push({
|
|
308
|
+
kind: ContradictionKind.DUPLICATE_REQUEST,
|
|
309
|
+
claim: 'one user action was performed',
|
|
310
|
+
counter: `the same write fired ${String(count)} times`,
|
|
311
|
+
detail: `${key} ×${String(count)}`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
301
314
|
}
|
|
302
315
|
// ── The UI advanced over a request that never came back ─────────────────────────────────────
|
|
303
316
|
// Gated on the UI having moved: an in-flight request while the app is still visibly waiting is
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the caller DECLARED before acting — the oracle, read back.
|
|
3
|
+
*
|
|
4
|
+
* `act_and_wait` asks the agent to name the expected consequence BEFORE the action, and that
|
|
5
|
+
* declaration was then thrown away: everything downstream judged the window as if nobody had said
|
|
6
|
+
* what they were expecting. Two measured consequences, one bug:
|
|
7
|
+
*
|
|
8
|
+
* - An error path can be declared exactly — `{ net, POST, /api/login, status: 500 }` — and the
|
|
9
|
+
* contradiction hunter still reported `ui-advanced-request-failed`, because a failed request plus
|
|
10
|
+
* a moved DOM is all it can see. The failure was EXPECTED and said so in the predicate, so a
|
|
11
|
+
* verdict of "channels disagree" is a statement about a disagreement nobody had.
|
|
12
|
+
* - A destination whose content was asserted and FOUND was still reported as
|
|
13
|
+
* `route-rendered-nothing`, a clause the element evidence beside it disproves.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately conservative about what counts as declared. Only what the caller REQUIRED is read:
|
|
16
|
+
* the top level and `allOf` chains. An `anyOf` branch may never have held, and a `not` declares the
|
|
17
|
+
* opposite of a consequence — honouring either would suppress a real finding on the strength of
|
|
18
|
+
* something that never happened. Pure: a predicate in, a description out.
|
|
19
|
+
*/
|
|
20
|
+
import type { Predicate } from './predicate-eval.js';
|
|
21
|
+
/** A failing call the caller named in advance — matched against the window's real calls. */
|
|
22
|
+
export interface DeclaredNetFailure {
|
|
23
|
+
method?: string;
|
|
24
|
+
urlContains?: string;
|
|
25
|
+
status?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface DeclaredExpectations {
|
|
28
|
+
/** Requests the caller declared would FAIL, so failing is the expected outcome, not a disagreement. */
|
|
29
|
+
netFailures: readonly DeclaredNetFailure[];
|
|
30
|
+
/** The caller required something to be ON SCREEN — an element or text, present rather than absent. */
|
|
31
|
+
rendersContent: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function declaredExpectations(predicate: Predicate | undefined): DeclaredExpectations;
|
|
34
|
+
/**
|
|
35
|
+
* Does a call the window recorded match a failure the caller declared?
|
|
36
|
+
*
|
|
37
|
+
* Every field the caller named must agree — a declaration about `POST /api/login → 500` says nothing
|
|
38
|
+
* about `POST /api/orders`, and treating it as a blanket amnesty for failed requests would remove
|
|
39
|
+
* the check rather than inform it. Fields the caller left out are not constraints.
|
|
40
|
+
*/
|
|
41
|
+
export declare function matchesDeclaredFailure(call: {
|
|
42
|
+
method: string;
|
|
43
|
+
url: string;
|
|
44
|
+
status: number | undefined;
|
|
45
|
+
}, declared: readonly DeclaredNetFailure[]): boolean;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the caller DECLARED before acting — the oracle, read back.
|
|
3
|
+
*
|
|
4
|
+
* `act_and_wait` asks the agent to name the expected consequence BEFORE the action, and that
|
|
5
|
+
* declaration was then thrown away: everything downstream judged the window as if nobody had said
|
|
6
|
+
* what they were expecting. Two measured consequences, one bug:
|
|
7
|
+
*
|
|
8
|
+
* - An error path can be declared exactly — `{ net, POST, /api/login, status: 500 }` — and the
|
|
9
|
+
* contradiction hunter still reported `ui-advanced-request-failed`, because a failed request plus
|
|
10
|
+
* a moved DOM is all it can see. The failure was EXPECTED and said so in the predicate, so a
|
|
11
|
+
* verdict of "channels disagree" is a statement about a disagreement nobody had.
|
|
12
|
+
* - A destination whose content was asserted and FOUND was still reported as
|
|
13
|
+
* `route-rendered-nothing`, a clause the element evidence beside it disproves.
|
|
14
|
+
*
|
|
15
|
+
* Deliberately conservative about what counts as declared. Only what the caller REQUIRED is read:
|
|
16
|
+
* the top level and `allOf` chains. An `anyOf` branch may never have held, and a `not` declares the
|
|
17
|
+
* opposite of a consequence — honouring either would suppress a real finding on the strength of
|
|
18
|
+
* something that never happened. Pure: a predicate in, a description out.
|
|
19
|
+
*/
|
|
20
|
+
import { PredicateKind } from '@reticlehq/core';
|
|
21
|
+
/** Below this, a status is a success or a redirect: not a declared failure. */
|
|
22
|
+
const FAILURE_STATUS_MIN = 400;
|
|
23
|
+
export function declaredExpectations(predicate) {
|
|
24
|
+
const netFailures = [];
|
|
25
|
+
let rendersContent = false;
|
|
26
|
+
const walk = (p) => {
|
|
27
|
+
switch (p.kind) {
|
|
28
|
+
case PredicateKind.ALL_OF:
|
|
29
|
+
for (const child of p.predicates)
|
|
30
|
+
walk(child);
|
|
31
|
+
return;
|
|
32
|
+
case PredicateKind.NET: {
|
|
33
|
+
const declaredFailure = false === p.ok || (p.status !== undefined && p.status >= FAILURE_STATUS_MIN);
|
|
34
|
+
if (!declaredFailure)
|
|
35
|
+
return;
|
|
36
|
+
netFailures.push({
|
|
37
|
+
...(p.method === undefined ? {} : { method: p.method }),
|
|
38
|
+
...(p.urlContains === undefined ? {} : { urlContains: p.urlContains }),
|
|
39
|
+
...(p.status === undefined ? {} : { status: p.status }),
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
case PredicateKind.ELEMENT:
|
|
44
|
+
case PredicateKind.TEXT:
|
|
45
|
+
if (true !== p.absent)
|
|
46
|
+
rendersContent = true;
|
|
47
|
+
return;
|
|
48
|
+
default:
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
if (predicate !== undefined)
|
|
53
|
+
walk(predicate);
|
|
54
|
+
return { netFailures, rendersContent };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Does a call the window recorded match a failure the caller declared?
|
|
58
|
+
*
|
|
59
|
+
* Every field the caller named must agree — a declaration about `POST /api/login → 500` says nothing
|
|
60
|
+
* about `POST /api/orders`, and treating it as a blanket amnesty for failed requests would remove
|
|
61
|
+
* the check rather than inform it. Fields the caller left out are not constraints.
|
|
62
|
+
*/
|
|
63
|
+
export function matchesDeclaredFailure(call, declared) {
|
|
64
|
+
return declared.some((d) => {
|
|
65
|
+
if (d.method !== undefined && d.method.toUpperCase() !== call.method.toUpperCase())
|
|
66
|
+
return false;
|
|
67
|
+
if (d.urlContains !== undefined && !call.url.includes(d.urlContains))
|
|
68
|
+
return false;
|
|
69
|
+
if (d.status !== undefined && d.status !== call.status)
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { type ReticleEvent } from '@reticlehq/core';
|
|
2
2
|
import type { Contradiction } from './contradictions.js';
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Contradictions for writes whose response echoes a different value than the request asked for.
|
|
5
|
+
*
|
|
6
|
+
* `actionSince` is the attribution floor `findContradictions` already keeps — the event time the
|
|
7
|
+
* action that opened this window was dispatched. A finding about a request that had already settled
|
|
8
|
+
* before the agent acted otherwise reads as a verdict on the change it just made.
|
|
9
|
+
*/
|
|
10
|
+
export declare function findEchoMismatches(events: readonly ReticleEvent[], actionSince?: number): Contradiction[];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContradictionKind, EventType } from '@reticlehq/core';
|
|
1
|
+
import { ContradictionKind, EventType, MUTATING_METHODS } from '@reticlehq/core';
|
|
2
2
|
/**
|
|
3
3
|
* A write that returned OK and quietly did not apply part of what it was asked to.
|
|
4
4
|
*
|
|
@@ -18,6 +18,11 @@ import { ContradictionKind, EventType } from '@reticlehq/core';
|
|
|
18
18
|
* would fire on healthy APIs constantly, and a contradiction kind that cries wolf gets filtered out
|
|
19
19
|
* by the agents reading it, taking the true positives with it. So:
|
|
20
20
|
*
|
|
21
|
+
* - only requests that are WRITES are considered. Reported from the field: a lookup that sends its
|
|
22
|
+
* key in the body (`POST /get-branding` with `{workspace_id}`) and gets a record back was graded
|
|
23
|
+
* as a half-applied write. The gate was on the RESPONSE being a success and never on the REQUEST
|
|
24
|
+
* being a write, so every successful call carrying a body read as a save — and the same key name
|
|
25
|
+
* legitimately means different things on the two sides of a request/response pair;
|
|
21
26
|
* - only keys the request actually SENT are considered;
|
|
22
27
|
* - only scalars, since deep structural diffing is where the false positives live;
|
|
23
28
|
* - values are compared NORMALISED (trimmed, case-folded, numbers as numbers), so `FR` vs `fr` and
|
|
@@ -89,12 +94,33 @@ function scalarsByKey(body, into = new Map(), depth = 0) {
|
|
|
89
94
|
}
|
|
90
95
|
const OK_MIN = 200;
|
|
91
96
|
const OK_MAX = 300;
|
|
92
|
-
/**
|
|
93
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Contradictions for writes whose response echoes a different value than the request asked for.
|
|
99
|
+
*
|
|
100
|
+
* `actionSince` is the attribution floor `findContradictions` already keeps — the event time the
|
|
101
|
+
* action that opened this window was dispatched. A finding about a request that had already settled
|
|
102
|
+
* before the agent acted otherwise reads as a verdict on the change it just made.
|
|
103
|
+
*/
|
|
104
|
+
export function findEchoMismatches(events, actionSince) {
|
|
94
105
|
const found = [];
|
|
95
106
|
for (const event of events) {
|
|
96
107
|
if (event.type !== EventType.NET_REQUEST)
|
|
97
108
|
continue;
|
|
109
|
+
// `MUTATING_METHODS` is the one definition of "is this a write" in the codebase — the same list
|
|
110
|
+
// `contradictions.ts`'s `isMutating` reads. A read has no fields to half-apply.
|
|
111
|
+
//
|
|
112
|
+
// KNOWN LIMIT, stated because it is easy to read this fix as bigger than it is: POST is in that
|
|
113
|
+
// list, so a lookup that POSTs its key and gets a record back — the shape actually reported —
|
|
114
|
+
// still reaches the comparison below. Method is the only signal available here, and a read-shaped
|
|
115
|
+
// POST is genuinely indistinguishable from a write by method alone.
|
|
116
|
+
//
|
|
117
|
+
// The tempting next step is to skip a pair whose JSON types disagree (a string key sent, a number
|
|
118
|
+
// echoed). Deliberately not taken: that also silences `{"qty":"5"}` answered with `3`, which is a
|
|
119
|
+
// real dropped write. A false negative here is a missed bug in someone's app, and that is not a
|
|
120
|
+
// trade this file makes to quieten a false positive.
|
|
121
|
+
const method = 'string' === typeof event.data['method'] ? event.data['method'] : '';
|
|
122
|
+
if (!MUTATING_METHODS.includes(method.toUpperCase()))
|
|
123
|
+
continue;
|
|
98
124
|
// IPC reports `ok` rather than a status; HTTP reports a status. Either must say success — a write
|
|
99
125
|
// that already failed is a different (and louder) finding than one that half-applied.
|
|
100
126
|
const status = event.data['status'];
|
|
@@ -126,12 +152,14 @@ export function findEchoMismatches(events) {
|
|
|
126
152
|
if (0 === dropped.length)
|
|
127
153
|
continue;
|
|
128
154
|
const url = 'string' === typeof event.data['url'] ? event.data['url'] : '';
|
|
129
|
-
const
|
|
155
|
+
const attribution = actionSince !== undefined && event.t < actionSince
|
|
156
|
+
? ' — this request completed before the action being verified, so the finding is not about that change'
|
|
157
|
+
: '';
|
|
130
158
|
found.push({
|
|
131
159
|
kind: ContradictionKind.WRITE_FIELD_IGNORED,
|
|
132
160
|
claim: 'the write returned success and the page treated it as saved',
|
|
133
161
|
counter: `its own echo shows ${String(dropped.length)} field(s) NOT applied — ${dropped.join('; ')}`,
|
|
134
|
-
detail: `${method} ${url} — the server answered OK and returned a different value than it was asked to set, so the write half-applied; the UI has no way to know and will show the value the user typed`,
|
|
162
|
+
detail: `${method} ${url} — the server answered OK and returned a different value than it was asked to set, so the write half-applied; the UI has no way to know and will show the value the user typed${attribution}`,
|
|
135
163
|
});
|
|
136
164
|
}
|
|
137
165
|
return found;
|
|
@@ -57,7 +57,7 @@ interface NetCallView {
|
|
|
57
57
|
*/
|
|
58
58
|
oneWay?: boolean;
|
|
59
59
|
}
|
|
60
|
-
export declare function projectNetCall(e: ReticleEvent): NetCallView;
|
|
60
|
+
export declare function projectNetCall(e: ReticleEvent, includeBodies?: boolean): NetCallView;
|
|
61
61
|
/**
|
|
62
62
|
* Compact console-log summary for reticle_console output.
|
|
63
63
|
*
|
|
@@ -86,7 +86,7 @@ export function reconcileNet(events) {
|
|
|
86
86
|
.map((e) => ({ ...e, data: { ...e.data, status: 'pending', pending: true } }));
|
|
87
87
|
return [...completed, ...unresolved].sort((a, b) => a.t - b.t);
|
|
88
88
|
}
|
|
89
|
-
export function projectNetCall(e) {
|
|
89
|
+
export function projectNetCall(e, includeBodies = true) {
|
|
90
90
|
const status = e.data['status'];
|
|
91
91
|
const ms = asNumber(e.data['durationMs']);
|
|
92
92
|
const statusText = asString(e.data['statusText']);
|
|
@@ -106,11 +106,14 @@ export function projectNetCall(e) {
|
|
|
106
106
|
view.contentType = contentType;
|
|
107
107
|
if (responseSize !== undefined)
|
|
108
108
|
view.responseSize = responseSize;
|
|
109
|
-
|
|
109
|
+
// Bodies dominate the payload of the most-called read tool, and are only occasionally the part
|
|
110
|
+
// anyone wanted — `bodies: false` on reticle_network drops them for a body-free listing (#401).
|
|
111
|
+
if (includeBodies && requestBody !== undefined)
|
|
110
112
|
view.requestBody = requestBody;
|
|
111
|
-
if (responseBody !== undefined)
|
|
113
|
+
if (includeBodies && responseBody !== undefined)
|
|
112
114
|
view.responseBody = responseBody;
|
|
113
|
-
if (
|
|
115
|
+
if (includeBodies &&
|
|
116
|
+
(true === e.data['requestBodyTruncated'] || true === e.data['responseBodyTruncated'])) {
|
|
114
117
|
view.bodyTruncated = true;
|
|
115
118
|
}
|
|
116
119
|
if (ms !== undefined)
|
|
@@ -1,72 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export
|
|
4
|
-
kind: typeof PredicateKind.ELEMENT;
|
|
5
|
-
query: ElementQuery;
|
|
6
|
-
state?: ElementState;
|
|
7
|
-
absent?: boolean;
|
|
8
|
-
} | {
|
|
9
|
-
kind: typeof PredicateKind.TEXT;
|
|
10
|
-
contains: string;
|
|
11
|
-
visible?: boolean;
|
|
12
|
-
absent?: boolean;
|
|
13
|
-
} | {
|
|
14
|
-
kind: typeof PredicateKind.NET;
|
|
15
|
-
method?: string;
|
|
16
|
-
urlContains?: string;
|
|
17
|
-
status?: number;
|
|
18
|
-
/** Did the call succeed? The honest field for IPC, which has no status code. */
|
|
19
|
-
ok?: boolean;
|
|
20
|
-
since?: number;
|
|
21
|
-
count?: number;
|
|
22
|
-
} | {
|
|
23
|
-
kind: typeof PredicateKind.ROUTE;
|
|
24
|
-
pathname?: string;
|
|
25
|
-
contains?: string;
|
|
26
|
-
since?: number;
|
|
27
|
-
} | {
|
|
28
|
-
kind: typeof PredicateKind.CONSOLE;
|
|
29
|
-
level?: string;
|
|
30
|
-
absent?: boolean;
|
|
31
|
-
since?: number;
|
|
32
|
-
} | {
|
|
33
|
-
kind: typeof PredicateKind.ANIMATION;
|
|
34
|
-
name?: string;
|
|
35
|
-
target?: string;
|
|
36
|
-
completed?: boolean;
|
|
37
|
-
since?: number;
|
|
38
|
-
} | {
|
|
39
|
-
kind: typeof PredicateKind.SIGNAL;
|
|
40
|
-
name?: string;
|
|
41
|
-
dataMatches?: Record<string, unknown>;
|
|
42
|
-
since?: number;
|
|
43
|
-
} | {
|
|
44
|
-
kind: typeof PredicateKind.STATE;
|
|
45
|
-
store?: string;
|
|
46
|
-
path: string;
|
|
47
|
-
equals?: unknown;
|
|
48
|
-
} | {
|
|
49
|
-
kind: typeof PredicateKind.SETTLED;
|
|
50
|
-
quietMs?: number;
|
|
51
|
-
} | {
|
|
52
|
-
kind: typeof PredicateKind.ALL_OF;
|
|
53
|
-
predicates: Predicate[];
|
|
54
|
-
} | {
|
|
55
|
-
kind: typeof PredicateKind.ANY_OF;
|
|
56
|
-
predicates: Predicate[];
|
|
57
|
-
} | {
|
|
58
|
-
kind: typeof PredicateKind.NOT;
|
|
59
|
-
predicate: Predicate;
|
|
60
|
-
};
|
|
61
|
-
export declare const PredicateSchema: z.ZodType<Predicate>;
|
|
62
|
-
/**
|
|
63
|
-
* The fields a given predicate kind accepts, read off the schema itself.
|
|
64
|
-
*
|
|
65
|
-
* Derived rather than listed so the two can never disagree: a rejection message that names a stale
|
|
66
|
-
* field set is worse than one that names none, because the agent trusts it and retries into the same
|
|
67
|
-
* wall. Empty for a kind that is not in the union.
|
|
68
|
-
*/
|
|
69
|
-
export declare function predicateFieldsFor(kind: string): readonly string[];
|
|
1
|
+
import { PredicateKind, type ReticleEvent } from '@reticlehq/core';
|
|
2
|
+
import type { Predicate } from './predicate-schema.js';
|
|
3
|
+
export * from './predicate-schema.js';
|
|
70
4
|
export interface EvalResult {
|
|
71
5
|
pass: boolean;
|
|
72
6
|
/**
|
|
@@ -85,6 +19,19 @@ export interface EvalResult {
|
|
|
85
19
|
* not a defect in the user's app, and reporting it as one puts agent mistakes into the bug count.
|
|
86
20
|
*/
|
|
87
21
|
inconclusive?: string;
|
|
22
|
+
/**
|
|
23
|
+
* No later event in this window can change this answer, so waiting out the rest of the budget
|
|
24
|
+
* buys nothing.
|
|
25
|
+
*
|
|
26
|
+
* Set ONLY where that is provable, which is rarer than it looks. "The toast never appeared" is not
|
|
27
|
+
* decided — it is only knowable when the budget ends, and ending early there would manufacture the
|
|
28
|
+
* false negative the budget exists to prevent. Exact cardinality IS decided once exceeded, because
|
|
29
|
+
* a window only accumulates matches and a count cannot come back down.
|
|
30
|
+
*
|
|
31
|
+
* A scheduling fact, never a verdict: `pass` already says what the answer is, and this only says
|
|
32
|
+
* that it is final.
|
|
33
|
+
*/
|
|
34
|
+
decided?: boolean;
|
|
88
35
|
evidence?: unknown;
|
|
89
36
|
failureReason?: string;
|
|
90
37
|
/**
|
|
@@ -113,6 +60,7 @@ export interface EvalResult {
|
|
|
113
60
|
expected?: string;
|
|
114
61
|
assertion?: string;
|
|
115
62
|
}
|
|
63
|
+
export declare function str(value: unknown): string | undefined;
|
|
116
64
|
/**
|
|
117
65
|
* Match one value against a pattern. Supports `*` (present), strict equality, and operators:
|
|
118
66
|
* `{$gte,$lte,$gt,$lt}` (numbers), `{$contains}` (array membership or substring), `{$length}`.
|
|
@@ -121,9 +69,6 @@ export declare function matchValue(got: unknown, want: unknown): boolean;
|
|
|
121
69
|
export declare function evalNet(events: ReticleEvent[], p: Extract<Predicate, {
|
|
122
70
|
kind: typeof PredicateKind.NET;
|
|
123
71
|
}>): EvalResult;
|
|
124
|
-
export declare function evalRoute(events: ReticleEvent[], p: Extract<Predicate, {
|
|
125
|
-
kind: typeof PredicateKind.ROUTE;
|
|
126
|
-
}>): EvalResult;
|
|
127
72
|
export declare function evalConsole(events: ReticleEvent[], p: Extract<Predicate, {
|
|
128
73
|
kind: typeof PredicateKind.CONSOLE;
|
|
129
74
|
}>): EvalResult;
|