@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
|
@@ -12,7 +12,152 @@
|
|
|
12
12
|
* invalidates the prior snapshot (a diff across pages would be meaningless), so the next snapshot
|
|
13
13
|
* returns full — never a misleading cross-page delta.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Identity for comparison, ref for action.
|
|
17
|
+
*
|
|
18
|
+
* The delta path used `normalizeLines` from the baseline layer, which strips the ref marker on
|
|
19
|
+
* purpose — a ref stored in a baseline would make every later diff noisy. Correct there, wrong here:
|
|
20
|
+
* every delta line came back as `- button "Row actions"` with no ref, and acting needs a ref, so the
|
|
21
|
+
* full snapshot had to be taken anyway. The diff call cost tokens and bought nothing. One helper, two
|
|
22
|
+
* callers, opposite requirements — so this path keeps the ref-stripped line as the KEY and emits the
|
|
23
|
+
* ORIGINAL line with its ref.
|
|
24
|
+
*
|
|
25
|
+
* Focus is stripped from the key too. It rides in the line as `[focused]`, alone or comma-joined with
|
|
26
|
+
* other states, so a focus move used to surface as one line removed and its twin added — structural
|
|
27
|
+
* change reported where nothing had been added at all. Focus is a property OF a line, so it gets its
|
|
28
|
+
* own field. Only focus is exempt: `disabled` moving is a real change and must still show.
|
|
29
|
+
*/
|
|
30
|
+
/** The ref marker, as `formatLine` writes it. Bounded quantifier: `\s*` backtracks polynomially. */
|
|
31
|
+
const REF_MARKER = /\s?\(ref=e\d+\)/;
|
|
32
|
+
/** The state bracket — ` [disabled,checked,expanded,focused]` — and the one state that is not structural. */
|
|
33
|
+
const STATE_BRACKET = /\s\[([a-z,]+)\]$/;
|
|
34
|
+
const FOCUSED_STATE = 'focused';
|
|
35
|
+
/** True when this line carries focus. */
|
|
36
|
+
function isFocused(line) {
|
|
37
|
+
return true === STATE_BRACKET.exec(line)?.[1]?.split(',').includes(FOCUSED_STATE);
|
|
38
|
+
}
|
|
39
|
+
/** The same line with focus removed — and the bracket dropped when focus was all it held. */
|
|
40
|
+
function withoutFocus(line) {
|
|
41
|
+
const match = STATE_BRACKET.exec(line);
|
|
42
|
+
const states = match?.[1];
|
|
43
|
+
if (null === match || undefined === match || undefined === states)
|
|
44
|
+
return line;
|
|
45
|
+
const rest = states.split(',').filter((s) => s !== FOCUSED_STATE);
|
|
46
|
+
const head = line.slice(0, match.index);
|
|
47
|
+
return 0 === rest.length ? head : `${head} [${rest.join(',')}]`;
|
|
48
|
+
}
|
|
49
|
+
/** What makes two lines "the same element" for diffing: no ref, no focus, no surrounding space. */
|
|
50
|
+
function deltaKey(line) {
|
|
51
|
+
return withoutFocus(line.replace(REF_MARKER, '')).trim();
|
|
52
|
+
}
|
|
53
|
+
function deltaLines(tree) {
|
|
54
|
+
return tree
|
|
55
|
+
.split('\n')
|
|
56
|
+
.map((line) => ({ key: deltaKey(line), text: withoutFocus(line).trim() }))
|
|
57
|
+
.filter((line) => line.key.length > 0);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Multiset diff on the key, emitting the original text.
|
|
61
|
+
*
|
|
62
|
+
* Multiset rather than set because duplicate labels are the norm — twelve identical `Row actions`
|
|
63
|
+
* buttons — and the count is the only thing that says how many left. Emitting the ORIGINAL text is
|
|
64
|
+
* what finally tells them apart: with refs, "which twelve" has an answer.
|
|
65
|
+
*/
|
|
66
|
+
/** Extract the ref marker value from a text line, or undefined if absent. */
|
|
67
|
+
function refOf(text) {
|
|
68
|
+
return REF_MARKER.exec(text)?.[0]?.trim();
|
|
69
|
+
}
|
|
70
|
+
/** Extract the role (first word after the bullet) from a snapshot line — the structural type that must match. */
|
|
71
|
+
function roleOf(text) {
|
|
72
|
+
return (text
|
|
73
|
+
.trimStart()
|
|
74
|
+
.replace(/^-\s*/, '')
|
|
75
|
+
.split(/[\s"(]/)[0] ?? '');
|
|
76
|
+
}
|
|
77
|
+
function diffKeyed(prev, next) {
|
|
78
|
+
const bucket = (lines) => {
|
|
79
|
+
const map = new Map();
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
const existing = map.get(line.key);
|
|
82
|
+
if (existing === undefined)
|
|
83
|
+
map.set(line.key, [line.text]);
|
|
84
|
+
else
|
|
85
|
+
existing.push(line.text);
|
|
86
|
+
}
|
|
87
|
+
return map;
|
|
88
|
+
};
|
|
89
|
+
const before = bucket(prev);
|
|
90
|
+
const after = bucket(next);
|
|
91
|
+
const removed = [];
|
|
92
|
+
const added = [];
|
|
93
|
+
for (const key of new Set([...before.keys(), ...after.keys()])) {
|
|
94
|
+
const was = before.get(key) ?? [];
|
|
95
|
+
const now = after.get(key) ?? [];
|
|
96
|
+
// Pair the SAME REF first. Twelve identical `Row actions` buttons are one key, and the ref is the
|
|
97
|
+
// only thing that says which of them left — an end-of-list surplus would name an arbitrary one.
|
|
98
|
+
const survived = new Set(now);
|
|
99
|
+
const wentOrChanged = was.filter((text) => !survived.has(text));
|
|
100
|
+
const arrivedOrChanged = now.filter((text) => !new Set(was).has(text));
|
|
101
|
+
// Whatever is left over on both sides is the SAME element with a re-minted ref (a re-render, or a
|
|
102
|
+
// navigation that restarts the sequence). Pairing those off is what stops a ref change reading as
|
|
103
|
+
// a removal plus an addition; only the count difference is a real structural change.
|
|
104
|
+
const paired = Math.min(wentOrChanged.length, arrivedOrChanged.length);
|
|
105
|
+
removed.push(...wentOrChanged.slice(paired));
|
|
106
|
+
added.push(...arrivedOrChanged.slice(paired));
|
|
107
|
+
}
|
|
108
|
+
return { removed, added };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Same ref AND same role in both added and removed = value change, not a structural
|
|
112
|
+
* arrival/departure. Refs alone are not stable identities — they are re-minted on re-render — so a
|
|
113
|
+
* removed button and an added textbox sharing a ref must stay in their respective lists.
|
|
114
|
+
*/
|
|
115
|
+
function extractChanged(added, removed) {
|
|
116
|
+
const addedByRef = new Map();
|
|
117
|
+
for (const [i, text] of added.entries()) {
|
|
118
|
+
const ref = refOf(text);
|
|
119
|
+
if (ref !== undefined)
|
|
120
|
+
addedByRef.set(ref, { idx: i, text, role: roleOf(text) });
|
|
121
|
+
}
|
|
122
|
+
const changedIndicesInAdded = new Set();
|
|
123
|
+
const changedIndicesInRemoved = new Set();
|
|
124
|
+
const changed = [];
|
|
125
|
+
for (const [i, text] of removed.entries()) {
|
|
126
|
+
const ref = refOf(text);
|
|
127
|
+
if (ref === undefined)
|
|
128
|
+
continue;
|
|
129
|
+
const match = addedByRef.get(ref);
|
|
130
|
+
if (match !== undefined && match.role === roleOf(text)) {
|
|
131
|
+
changed.push(match.text);
|
|
132
|
+
changedIndicesInAdded.add(match.idx);
|
|
133
|
+
changedIndicesInRemoved.add(i);
|
|
134
|
+
addedByRef.delete(ref);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
added: added.filter((_, i) => !changedIndicesInAdded.has(i)),
|
|
139
|
+
removed: removed.filter((_, i) => !changedIndicesInRemoved.has(i)),
|
|
140
|
+
changed,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Where focus sits now: the line to show, plus the identity to compare it BY.
|
|
145
|
+
*
|
|
146
|
+
* These have to be two different things. Comparing the rendered lines said focus had moved whenever
|
|
147
|
+
* the focused element merely changed — typing into a focused textbox rewrites its `[value=...]`, so
|
|
148
|
+
* `from` and `to` came back naming the same element with different text. Focus identity is the
|
|
149
|
+
* ELEMENT, so the comparison is on the ref, and the line is only what gets displayed. Where there is
|
|
150
|
+
* no ref to compare (a non-interactive line), the ref-stripped key stands in.
|
|
151
|
+
*
|
|
152
|
+
* Read from the raw tree so a line the diff filters out cannot silently swallow the focus report.
|
|
153
|
+
*/
|
|
154
|
+
function focusedLine(tree) {
|
|
155
|
+
const focused = tree.split('\n').find((line) => isFocused(line));
|
|
156
|
+
if (focused === undefined)
|
|
157
|
+
return undefined;
|
|
158
|
+
const text = withoutFocus(focused).trim();
|
|
159
|
+
return { id: REF_MARKER.exec(focused)?.[0]?.trim() ?? deltaKey(focused), text };
|
|
160
|
+
}
|
|
16
161
|
export const SnapshotDeltaMode = {
|
|
17
162
|
FULL: 'full',
|
|
18
163
|
DELTA: 'delta',
|
|
@@ -22,12 +167,32 @@ export const SnapshotDeltaMode = {
|
|
|
22
167
|
export function snapshotDelta(prevTree, nextTree) {
|
|
23
168
|
if (prevTree === undefined)
|
|
24
169
|
return { mode: SnapshotDeltaMode.FULL };
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
170
|
+
const raw = diffKeyed(deltaLines(prevTree), deltaLines(nextTree));
|
|
171
|
+
const { added, removed, changed } = extractChanged(raw.added, raw.removed);
|
|
172
|
+
const before = focusedLine(prevTree);
|
|
173
|
+
const now = focusedLine(nextTree);
|
|
174
|
+
// Reported only on a MOVE. Repeating "focus is still here" every turn is the noise the delta exists
|
|
175
|
+
// to remove, and an absent field is how the agent knows nothing happened.
|
|
176
|
+
const focusChanged = before?.id === now?.id
|
|
177
|
+
? undefined
|
|
178
|
+
: {
|
|
179
|
+
...(before === undefined ? {} : { from: before.text }),
|
|
180
|
+
...(now === undefined ? {} : { to: now.text }),
|
|
181
|
+
};
|
|
182
|
+
const moved = focusChanged === undefined ? {} : { focusChanged };
|
|
183
|
+
if (0 === added.length && 0 === removed.length && 0 === changed.length) {
|
|
184
|
+
return { mode: SnapshotDeltaMode.UNCHANGED, ...moved };
|
|
185
|
+
}
|
|
28
186
|
return {
|
|
29
187
|
mode: SnapshotDeltaMode.DELTA,
|
|
30
|
-
delta: {
|
|
188
|
+
delta: {
|
|
189
|
+
added,
|
|
190
|
+
removed,
|
|
191
|
+
changed,
|
|
192
|
+
addedCount: added.length,
|
|
193
|
+
removedCount: removed.length,
|
|
194
|
+
},
|
|
195
|
+
...moved,
|
|
31
196
|
};
|
|
32
197
|
}
|
|
33
198
|
const DEFAULT_MAX_ENTRIES = 50;
|
|
@@ -38,6 +203,10 @@ export class SnapshotCache {
|
|
|
38
203
|
constructor(max = DEFAULT_MAX_ENTRIES) {
|
|
39
204
|
this.#max = max;
|
|
40
205
|
}
|
|
206
|
+
/** True when an entry exists for this key (regardless of route match). */
|
|
207
|
+
has(key) {
|
|
208
|
+
return this.#map.has(key);
|
|
209
|
+
}
|
|
41
210
|
/** Last tree for this key IF the route still matches; undefined when absent or route changed. */
|
|
42
211
|
recall(key, route) {
|
|
43
212
|
const entry = this.#map.get(key);
|
|
@@ -96,17 +265,33 @@ export function applySnapshotDelta(raw, opts, cache) {
|
|
|
96
265
|
return raw;
|
|
97
266
|
}
|
|
98
267
|
const prev = cache.recall(key, route);
|
|
268
|
+
const hadEntry = cache.has(key);
|
|
99
269
|
cache.remember(key, route, tree);
|
|
100
270
|
const decision = snapshotDelta(prev, tree);
|
|
101
|
-
if (decision.mode === SnapshotDeltaMode.FULL)
|
|
102
|
-
|
|
271
|
+
if (decision.mode === SnapshotDeltaMode.FULL) {
|
|
272
|
+
const reason = hadEntry ? 'route changed' : 'first snapshot for this route';
|
|
273
|
+
return { ...r, mode: SnapshotDeltaMode.FULL, reason };
|
|
274
|
+
}
|
|
103
275
|
// The walk stops at a node cap and returns a DOCUMENT-ORDER PREFIX, so two capped snapshots of a
|
|
104
276
|
// large page are identical whenever the change happened past the cap — and "unchanged" is then a
|
|
105
277
|
// statement about the cap, not about the page. Carried through on both branches: a delta computed
|
|
106
278
|
// over a prefix is real but not exhaustive either.
|
|
107
279
|
const capped = true === r['truncated'] ? { truncated: true, note: CAPPED_DIFF_NOTE } : {};
|
|
280
|
+
// Spread onto BOTH branches. A value computed correctly and declared nowhere on the way out is the
|
|
281
|
+
// defect shape that hit this release five separate times: nothing throws, no test reddens, the
|
|
282
|
+
// field is simply absent forever.
|
|
283
|
+
const moved = decision.focusChanged === undefined ? {} : { focusChanged: decision.focusChanged };
|
|
108
284
|
if (decision.mode === SnapshotDeltaMode.UNCHANGED) {
|
|
109
|
-
return { mode: SnapshotDeltaMode.UNCHANGED, status: r['status'], ...capped };
|
|
285
|
+
return { mode: SnapshotDeltaMode.UNCHANGED, status: r['status'], ...moved, ...capped };
|
|
110
286
|
}
|
|
111
|
-
|
|
287
|
+
const { changed, ...structuralDelta } = decision.delta;
|
|
288
|
+
const changedField = changed.length > 0 ? { changed, changedCount: changed.length } : {};
|
|
289
|
+
return {
|
|
290
|
+
mode: SnapshotDeltaMode.DELTA,
|
|
291
|
+
delta: structuralDelta,
|
|
292
|
+
...changedField,
|
|
293
|
+
status: r['status'],
|
|
294
|
+
...moved,
|
|
295
|
+
...capped,
|
|
296
|
+
};
|
|
112
297
|
}
|
package/dist/tools/tool-kit.d.ts
CHANGED
|
@@ -15,10 +15,18 @@ import type { FlowStore } from '../flows/flows.js';
|
|
|
15
15
|
import type { ProjectStore } from '../project/project-store.js';
|
|
16
16
|
import type { AnnotationStore } from '../flows/annotation-store.js';
|
|
17
17
|
import type { BrowserPool } from '../pool/browser-pool.js';
|
|
18
|
+
import type { ChromiumProbe } from '../cli/chromium-hint.js';
|
|
18
19
|
export interface ToolDeps {
|
|
19
20
|
sessions: SessionManager;
|
|
20
21
|
/** shared one-browser/N-context pool for headless leases. undefined ⇒ lease tools report unavailable. */
|
|
21
22
|
pool?: BrowserPool;
|
|
23
|
+
/**
|
|
24
|
+
* Probe for a launchable Chromium, so the lease path can refuse at the FIRST call with the install
|
|
25
|
+
* fix rather than failing several calls in with a message that reads as "the app is not running".
|
|
26
|
+
* Optional: absent ⇒ no preflight — every existing test construction of ToolDeps keeps working, and
|
|
27
|
+
* the fake-pool lease tests are deliberately runnable without a real Chromium.
|
|
28
|
+
*/
|
|
29
|
+
browserProbe?: () => Promise<ChromiumProbe>;
|
|
22
30
|
baselines: BaselineStore;
|
|
23
31
|
recordings: RecordingStore;
|
|
24
32
|
/** on-disk anchored-flow store (.reticle/flows/). */
|
|
@@ -40,6 +40,36 @@ export declare const TOOL_SURFACE: {
|
|
|
40
40
|
readonly DEFAULT: "default";
|
|
41
41
|
/** Every tool advertised directly, WITH output schemas. A verification switch — see above. */
|
|
42
42
|
readonly ALL: "all";
|
|
43
|
+
/**
|
|
44
|
+
* The smallest surface that can still produce a VERDICT. Not a profile — a cost switch.
|
|
45
|
+
*
|
|
46
|
+
* Measured on the wire: a verification that names its own target costs one `act_and_wait` call,
|
|
47
|
+
* and 5,480 of its 5,909 tokens are the advertised surface re-sent for that single turn. The
|
|
48
|
+
* answers cost 430. So on the path where the caller already knows what to assert, almost the
|
|
49
|
+
* entire bill is the menu, and the menu is the thing to cut.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately NOT the default, and now MEASURED rather than inherited. Run over the same 30-bug
|
|
52
|
+
* set that the default surface scores 23/27 detection and 2/29 false alarms on:
|
|
53
|
+
*
|
|
54
|
+
* detection 24/28 (held)
|
|
55
|
+
* FALSE ALARMS 7/30 (was 2/29 — TRIPLED)
|
|
56
|
+
* tokens 113,599 per run (was 179,959 — 37% cheaper)
|
|
57
|
+
*
|
|
58
|
+
* The five new false alarms are not scattered. They are `mutation-leak` and
|
|
59
|
+
* `generate-blast-filter` (state), `kpi-deploys-tamper` (business-logic), `debounce-broken`
|
|
60
|
+
* (timing) and `route-stuck-deployments` (routing) — precisely the classes whose evidence lives in
|
|
61
|
+
* `reticle_state`, `reticle_network` and `reticle_observe`, which this surface does not advertise.
|
|
62
|
+
* Strip the observation tools and the model stops observing: it reaches for the verdict without
|
|
63
|
+
* the evidence and calls a healthy build broken.
|
|
64
|
+
*
|
|
65
|
+
* So the retired `dynamic` finding is confirmed, with a sharper mechanism than "accuracy drops".
|
|
66
|
+
* 37% of the tokens is not worth trading for the one metric this product actually wins on, and
|
|
67
|
+
* this must not become the default on the strength of the token number alone.
|
|
68
|
+
*
|
|
69
|
+
* It remains correct for a caller who has ALREADY decided what to assert — there the assertion
|
|
70
|
+
* supplies the evidence the surface would otherwise have to go and find.
|
|
71
|
+
*/
|
|
72
|
+
readonly VERIFY: "verify";
|
|
43
73
|
};
|
|
44
74
|
export type ToolSurface = (typeof TOOL_SURFACE)[keyof typeof TOOL_SURFACE];
|
|
45
75
|
/**
|
|
@@ -49,6 +79,8 @@ export type ToolSurface = (typeof TOOL_SURFACE)[keyof typeof TOOL_SURFACE];
|
|
|
49
79
|
* users to shop among alternatives that did not meaningfully differ.
|
|
50
80
|
*/
|
|
51
81
|
export declare const ADVERTISE_ALL_ENV = "RETICLE_ADVERTISE_ALL_TOOLS";
|
|
82
|
+
/** Opt into the smallest verdict-capable surface. Read by the DAEMON at startup, like the others. */
|
|
83
|
+
export declare const VERIFY_SURFACE_ENV = "RETICLE_VERIFY_SURFACE";
|
|
52
84
|
/**
|
|
53
85
|
* The retired setting, still read so nobody's shell profile breaks.
|
|
54
86
|
*
|
|
@@ -58,6 +90,34 @@ export declare const ADVERTISE_ALL_ENV = "RETICLE_ADVERTISE_ALL_TOOLS";
|
|
|
58
90
|
*/
|
|
59
91
|
export declare const TOOL_PROFILE_ENV = "RETICLE_TOOL_PROFILE";
|
|
60
92
|
export declare const CORE_TOOL_NAMES: ReadonlySet<string>;
|
|
93
|
+
/**
|
|
94
|
+
* The extended surface: what `all` advertises BEYOND the default set.
|
|
95
|
+
*
|
|
96
|
+
* `all` used to mean "every tool in the registry", and that is no longer allowed to be true. Cursor
|
|
97
|
+
* enforces a limit of 40 tools across every connected MCP server COMBINED, so a server advertising
|
|
98
|
+
* 48 by itself can push a user's other servers out or be dropped wholesale. The budget is a count,
|
|
99
|
+
* so no amount of trimming parameter prose buys anything back — only advertising fewer names does.
|
|
100
|
+
*
|
|
101
|
+
* Everything omitted stays in the registry, stays catalogued by `reticle_tools`, and stays callable
|
|
102
|
+
* by name through `reticle_run { tool, args }`. The cost is one discovery hop on the cold tail, which
|
|
103
|
+
* is the same trade the default surface has always made, applied one level further out.
|
|
104
|
+
*
|
|
105
|
+
* What it costs that is NOT free: `all` is the only surface that carries `outputSchema`, which is
|
|
106
|
+
* what makes the MCP layer validate tool OUTPUT — the check that once caught `reticle_verify_change`
|
|
107
|
+
* returning a payload its own schema rejected. The tools left off this list no longer get that
|
|
108
|
+
* validation from the wire. That defect class is now uncovered for them, and it is written down here
|
|
109
|
+
* rather than discovered later.
|
|
110
|
+
*
|
|
111
|
+
* Chosen as the capabilities an agent plausibly reaches for once the verify loop is not enough:
|
|
112
|
+
* orientation, the record/replay flow loop, visual evidence, and the three fault-injection controls.
|
|
113
|
+
*/
|
|
114
|
+
export declare const EXTENDED_TOOL_NAMES: ReadonlySet<string>;
|
|
115
|
+
/**
|
|
116
|
+
* The verify surface: one acting tool that returns a verdict, plus the two meta-tools that reach
|
|
117
|
+
* everything else. `act_and_wait` can resolve its own target, so no query tool is needed to name an
|
|
118
|
+
* element — that round trip was half the token cost of a verification.
|
|
119
|
+
*/
|
|
120
|
+
export declare const VERIFY_TOOL_NAMES: ReadonlySet<string>;
|
|
61
121
|
/**
|
|
62
122
|
* Which surface is live.
|
|
63
123
|
*
|
|
@@ -40,6 +40,36 @@ export const TOOL_SURFACE = {
|
|
|
40
40
|
DEFAULT: 'default',
|
|
41
41
|
/** Every tool advertised directly, WITH output schemas. A verification switch — see above. */
|
|
42
42
|
ALL: 'all',
|
|
43
|
+
/**
|
|
44
|
+
* The smallest surface that can still produce a VERDICT. Not a profile — a cost switch.
|
|
45
|
+
*
|
|
46
|
+
* Measured on the wire: a verification that names its own target costs one `act_and_wait` call,
|
|
47
|
+
* and 5,480 of its 5,909 tokens are the advertised surface re-sent for that single turn. The
|
|
48
|
+
* answers cost 430. So on the path where the caller already knows what to assert, almost the
|
|
49
|
+
* entire bill is the menu, and the menu is the thing to cut.
|
|
50
|
+
*
|
|
51
|
+
* Deliberately NOT the default, and now MEASURED rather than inherited. Run over the same 30-bug
|
|
52
|
+
* set that the default surface scores 23/27 detection and 2/29 false alarms on:
|
|
53
|
+
*
|
|
54
|
+
* detection 24/28 (held)
|
|
55
|
+
* FALSE ALARMS 7/30 (was 2/29 — TRIPLED)
|
|
56
|
+
* tokens 113,599 per run (was 179,959 — 37% cheaper)
|
|
57
|
+
*
|
|
58
|
+
* The five new false alarms are not scattered. They are `mutation-leak` and
|
|
59
|
+
* `generate-blast-filter` (state), `kpi-deploys-tamper` (business-logic), `debounce-broken`
|
|
60
|
+
* (timing) and `route-stuck-deployments` (routing) — precisely the classes whose evidence lives in
|
|
61
|
+
* `reticle_state`, `reticle_network` and `reticle_observe`, which this surface does not advertise.
|
|
62
|
+
* Strip the observation tools and the model stops observing: it reaches for the verdict without
|
|
63
|
+
* the evidence and calls a healthy build broken.
|
|
64
|
+
*
|
|
65
|
+
* So the retired `dynamic` finding is confirmed, with a sharper mechanism than "accuracy drops".
|
|
66
|
+
* 37% of the tokens is not worth trading for the one metric this product actually wins on, and
|
|
67
|
+
* this must not become the default on the strength of the token number alone.
|
|
68
|
+
*
|
|
69
|
+
* It remains correct for a caller who has ALREADY decided what to assert — there the assertion
|
|
70
|
+
* supplies the evidence the surface would otherwise have to go and find.
|
|
71
|
+
*/
|
|
72
|
+
VERIFY: 'verify',
|
|
43
73
|
};
|
|
44
74
|
/**
|
|
45
75
|
* The switch that turns on the full, output-schema-carrying surface. A boolean, not a menu.
|
|
@@ -48,6 +78,8 @@ export const TOOL_SURFACE = {
|
|
|
48
78
|
* users to shop among alternatives that did not meaningfully differ.
|
|
49
79
|
*/
|
|
50
80
|
export const ADVERTISE_ALL_ENV = 'RETICLE_ADVERTISE_ALL_TOOLS';
|
|
81
|
+
/** Opt into the smallest verdict-capable surface. Read by the DAEMON at startup, like the others. */
|
|
82
|
+
export const VERIFY_SURFACE_ENV = 'RETICLE_VERIFY_SURFACE';
|
|
51
83
|
/**
|
|
52
84
|
* The retired setting, still read so nobody's shell profile breaks.
|
|
53
85
|
*
|
|
@@ -145,6 +177,51 @@ export const CORE_TOOL_NAMES = new Set([
|
|
|
145
177
|
// because there is nowhere else those two calls are ever named.
|
|
146
178
|
ReticleTool.SESSION,
|
|
147
179
|
]);
|
|
180
|
+
/**
|
|
181
|
+
* The extended surface: what `all` advertises BEYOND the default set.
|
|
182
|
+
*
|
|
183
|
+
* `all` used to mean "every tool in the registry", and that is no longer allowed to be true. Cursor
|
|
184
|
+
* enforces a limit of 40 tools across every connected MCP server COMBINED, so a server advertising
|
|
185
|
+
* 48 by itself can push a user's other servers out or be dropped wholesale. The budget is a count,
|
|
186
|
+
* so no amount of trimming parameter prose buys anything back — only advertising fewer names does.
|
|
187
|
+
*
|
|
188
|
+
* Everything omitted stays in the registry, stays catalogued by `reticle_tools`, and stays callable
|
|
189
|
+
* by name through `reticle_run { tool, args }`. The cost is one discovery hop on the cold tail, which
|
|
190
|
+
* is the same trade the default surface has always made, applied one level further out.
|
|
191
|
+
*
|
|
192
|
+
* What it costs that is NOT free: `all` is the only surface that carries `outputSchema`, which is
|
|
193
|
+
* what makes the MCP layer validate tool OUTPUT — the check that once caught `reticle_verify_change`
|
|
194
|
+
* returning a payload its own schema rejected. The tools left off this list no longer get that
|
|
195
|
+
* validation from the wire. That defect class is now uncovered for them, and it is written down here
|
|
196
|
+
* rather than discovered later.
|
|
197
|
+
*
|
|
198
|
+
* Chosen as the capabilities an agent plausibly reaches for once the verify loop is not enough:
|
|
199
|
+
* orientation, the record/replay flow loop, visual evidence, and the three fault-injection controls.
|
|
200
|
+
*/
|
|
201
|
+
export const EXTENDED_TOOL_NAMES = new Set([
|
|
202
|
+
// Demoted from the default set to make room under the cap. It was added as an explicit bet that
|
|
203
|
+
// orientation replaces exploratory snapshots, and the measurement that would have settled that
|
|
204
|
+
// bet was never run — so when the budget became a hard count, the unproven entry is the one that
|
|
205
|
+
// gives way. Still one `reticle_run` hop from any agent that wants it.
|
|
206
|
+
ReticleTool.CAPABILITIES,
|
|
207
|
+
ReticleTool.FLOW_SAVE,
|
|
208
|
+
ReticleTool.FLOW_REPLAY,
|
|
209
|
+
ReticleTool.FLOW_VERIFY,
|
|
210
|
+
ReticleTool.RECORD,
|
|
211
|
+
ReticleTool.SCREENSHOT,
|
|
212
|
+
ReticleTool.VISUAL_DIFF,
|
|
213
|
+
ReticleTool.CLOCK,
|
|
214
|
+
ReticleTool.NETWORK_MOCK,
|
|
215
|
+
ReticleTool.STORAGE,
|
|
216
|
+
ReticleTool.VERIFY_CHANGE,
|
|
217
|
+
ReticleTool.CRAWL,
|
|
218
|
+
]);
|
|
219
|
+
/**
|
|
220
|
+
* The verify surface: one acting tool that returns a verdict, plus the two meta-tools that reach
|
|
221
|
+
* everything else. `act_and_wait` can resolve its own target, so no query tool is needed to name an
|
|
222
|
+
* element — that round trip was half the token cost of a verification.
|
|
223
|
+
*/
|
|
224
|
+
export const VERIFY_TOOL_NAMES = new Set([ReticleTool.ACT_AND_WAIT]);
|
|
148
225
|
/** Is the truthy form of a boolean env var set? `1`, `true`, `yes` — anything else is off. */
|
|
149
226
|
function envFlagOn(raw) {
|
|
150
227
|
if (raw === undefined)
|
|
@@ -159,6 +236,8 @@ function envFlagOn(raw) {
|
|
|
159
236
|
* switch decides, and the retired setting is honoured last so an old shell profile still works.
|
|
160
237
|
*/
|
|
161
238
|
export function resolveToolSurface(explicit) {
|
|
239
|
+
if (explicit === TOOL_SURFACE.VERIFY)
|
|
240
|
+
return TOOL_SURFACE.VERIFY;
|
|
162
241
|
if (explicit === TOOL_SURFACE.ALL)
|
|
163
242
|
return TOOL_SURFACE.ALL;
|
|
164
243
|
if (explicit === TOOL_SURFACE.DEFAULT)
|
|
@@ -166,6 +245,8 @@ export function resolveToolSurface(explicit) {
|
|
|
166
245
|
const retiredExplicit = explicit === undefined ? undefined : RETIRED_PROFILE_VALUES[explicit];
|
|
167
246
|
if (retiredExplicit !== undefined)
|
|
168
247
|
return retiredExplicit;
|
|
248
|
+
if (envFlagOn(process.env[VERIFY_SURFACE_ENV]))
|
|
249
|
+
return TOOL_SURFACE.VERIFY;
|
|
169
250
|
if (envFlagOn(process.env[ADVERTISE_ALL_ENV]))
|
|
170
251
|
return TOOL_SURFACE.ALL;
|
|
171
252
|
const retiredEnv = RETIRED_PROFILE_VALUES[process.env[TOOL_PROFILE_ENV] ?? ''];
|
|
@@ -211,7 +292,11 @@ export function describeToolSurface(active, requested) {
|
|
|
211
292
|
export function filterTools(tools, surface) {
|
|
212
293
|
// CORE_TOOL_NAMES is what it always really was: the set advertised directly. It was never the
|
|
213
294
|
// interesting thing about the `core` PROFILE, whose only distinction was a second name for this.
|
|
295
|
+
if (surface === TOOL_SURFACE.VERIFY)
|
|
296
|
+
return tools.filter((t) => VERIFY_TOOL_NAMES.has(t.name));
|
|
214
297
|
if (surface === TOOL_SURFACE.DEFAULT)
|
|
215
298
|
return tools.filter((t) => CORE_TOOL_NAMES.has(t.name));
|
|
216
|
-
|
|
299
|
+
// `all` is the extended surface, not the whole registry — the cap is a hard budget shared with
|
|
300
|
+
// every other MCP server the user has connected. surface-sizes.test.ts enforces it.
|
|
301
|
+
return tools.filter((t) => CORE_TOOL_NAMES.has(t.name) || EXTENDED_TOOL_NAMES.has(t.name));
|
|
217
302
|
}
|
package/dist/tools/tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { QueryBy, ReticleCommand, SnapshotMode } from '@reticlehq/core';
|
|
2
|
+
import { NoSessionAction, QueryBy, ReticleCommand, SnapshotMode } from '@reticlehq/core';
|
|
3
3
|
import { ReticleTool } from './tool-names.js';
|
|
4
4
|
import { withSizeCost } from '../session/output-budget.js';
|
|
5
5
|
import { applySnapshotDelta, SnapshotCache } from './snapshot-delta.js';
|
|
@@ -82,6 +82,15 @@ const RAW_TOOLS = [
|
|
|
82
82
|
.string()
|
|
83
83
|
.optional()
|
|
84
84
|
.describe('Present ONLY when `sessions` is empty: why nothing is connected, and the next action that fixes it. An empty list is never the end of the road — read this before concluding the app cannot be driven.'),
|
|
85
|
+
next_action: z
|
|
86
|
+
.object({
|
|
87
|
+
action: z.nativeEnum(NoSessionAction),
|
|
88
|
+
command: z.string().optional(),
|
|
89
|
+
port: z.number().optional(),
|
|
90
|
+
reason: z.string(),
|
|
91
|
+
})
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("Present ONLY when `sessions` is empty: the same answer as `why`, executable. `command` is the LITERAL command to run, sourced from this project's own package.json scripts and lockfile — it is absent, never guessed, when the project declares no dev script. `action` is one of start_dev_server | run_init | open_app | reopen_app."),
|
|
85
94
|
},
|
|
86
95
|
handler: async (deps) => {
|
|
87
96
|
const provider = deps.realInput;
|
|
@@ -97,7 +106,14 @@ const RAW_TOOLS = [
|
|
|
97
106
|
// rather than only when a later tool fails for want of a session.
|
|
98
107
|
if (0 === sessions.length) {
|
|
99
108
|
const why = deps.sessions.noSessionHint();
|
|
100
|
-
|
|
109
|
+
// The executable half. `why` is for the human reading the transcript; this is the one the
|
|
110
|
+
// agent acts on, so it never has to parse a paragraph to find a command inside it.
|
|
111
|
+
const next = deps.sessions.noSessionNextAction();
|
|
112
|
+
return {
|
|
113
|
+
sessions,
|
|
114
|
+
...(why === undefined ? {} : { why }),
|
|
115
|
+
...(next === undefined ? {} : { next_action: next }),
|
|
116
|
+
};
|
|
101
117
|
}
|
|
102
118
|
return { sessions };
|
|
103
119
|
},
|
|
@@ -126,11 +142,18 @@ const RAW_TOOLS = [
|
|
|
126
142
|
.string()
|
|
127
143
|
.optional()
|
|
128
144
|
.describe('Indented ARIA tree of every element on the page (or the scoped subtree).'),
|
|
129
|
-
status: z
|
|
145
|
+
status: z
|
|
146
|
+
.object({
|
|
147
|
+
route: z.string(),
|
|
148
|
+
title: z.string().optional(),
|
|
149
|
+
visibleDialogs: z.array(z.string()).optional(),
|
|
150
|
+
overlayHidingPage: z.string().optional(),
|
|
151
|
+
})
|
|
152
|
+
.optional(),
|
|
130
153
|
mode: z
|
|
131
154
|
.string()
|
|
132
155
|
.optional()
|
|
133
|
-
.describe('delta | unchanged
|
|
156
|
+
.describe('full | delta | unchanged — which kind of diff response this is.'),
|
|
134
157
|
delta: z
|
|
135
158
|
.object({
|
|
136
159
|
added: z.array(z.string()),
|
|
@@ -140,6 +163,15 @@ const RAW_TOOLS = [
|
|
|
140
163
|
})
|
|
141
164
|
.optional()
|
|
142
165
|
.describe('Only present on a diff:true call that found changes.'),
|
|
166
|
+
changed: z
|
|
167
|
+
.array(z.string())
|
|
168
|
+
.optional()
|
|
169
|
+
.describe('Elements whose value changed in place (same ref, different content) — not a structural add/remove.'),
|
|
170
|
+
changedCount: z.number().optional().describe('Number of elements in `changed`.'),
|
|
171
|
+
reason: z
|
|
172
|
+
.string()
|
|
173
|
+
.optional()
|
|
174
|
+
.describe('Why mode is "full": first snapshot for this route, or route changed.'),
|
|
143
175
|
cost: z
|
|
144
176
|
.object({ bytes: z.number(), tokens: z.number() })
|
|
145
177
|
.optional()
|
|
@@ -358,16 +390,37 @@ const RAW_TOOLS = [
|
|
|
358
390
|
})
|
|
359
391
|
.partial()
|
|
360
392
|
.optional(),
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
393
|
+
scroll: z
|
|
394
|
+
.object({
|
|
395
|
+
scrollTop: z.number(),
|
|
396
|
+
scrollHeight: z.number(),
|
|
397
|
+
clientHeight: z.number(),
|
|
398
|
+
overflowY: z.string(),
|
|
399
|
+
})
|
|
400
|
+
.optional(),
|
|
401
|
+
// Theme compliance vs the app's design tokens: { colorToken, colorTokens, backgroundToken,
|
|
402
|
+
// backgroundTokens, offTheme, tokenCount, themeScope }. The plural fields carry EVERY token
|
|
403
|
+
// matching the resolved colour; the singular ones abstain (null) when several tokens share it,
|
|
404
|
+
// because returning an arbitrary winner was the defect (#313). `themeScope` names the theme
|
|
405
|
+
// that was active at capture, so two inspects minutes apart are comparable at all.
|
|
406
|
+
// Kept as unknown — the structured-content serializer can truncate a large inspect payload's
|
|
407
|
+
// fields to strings, which a strict shape would reject; the full object is always present in
|
|
408
|
+
// the text content the agent reads.
|
|
365
409
|
theme: z.unknown().optional(),
|
|
366
410
|
/**
|
|
367
411
|
* Where this element is written, as `file:line`. Present when the app is built with the
|
|
368
412
|
* Reticle build plugin in dev; absent in production builds.
|
|
369
413
|
*/
|
|
370
414
|
source: z.string().optional(),
|
|
415
|
+
/**
|
|
416
|
+
* Why `source` is missing, when it is.
|
|
417
|
+
*
|
|
418
|
+
* Distinguishes "this element has no stamp" from "nothing on this page has one, so the
|
|
419
|
+
* stamping loader is not running" — the second means `file:line` is unavailable for the
|
|
420
|
+
* whole session and the fix is a build-config change, which used to be discoverable only by
|
|
421
|
+
* reading the adapter's own source.
|
|
422
|
+
*/
|
|
423
|
+
sourceUnavailable: z.string().optional(),
|
|
371
424
|
/**
|
|
372
425
|
* Component identity from the framework adapter (@reticlehq/react).
|
|
373
426
|
*
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* What to say when an agent calls a real Reticle tool that this profile does not advertise.
|
|
3
3
|
*
|
|
4
|
-
* The default
|
|
5
|
-
* is re-sent on every turn, and
|
|
6
|
-
* fully callable through `reticle_run`,
|
|
4
|
+
* The default surface advertises 18 of 48 tools, and the extended one 30. That is deliberate — every
|
|
5
|
+
* advertised tool's schema is re-sent on every turn, and editors budget MCP tools as a COUNT shared
|
|
6
|
+
* across every connected server — and everything omitted stays fully callable through `reticle_run`,
|
|
7
|
+
* which `surface-reachability.test.ts` guards.
|
|
7
8
|
*
|
|
8
9
|
* The gap was in the ERROR. The MCP SDK answers `Tool <name> not found`, which is indistinguishable
|
|
9
10
|
* from "this tool does not exist", so an agent that trusts it stops trying. Reported from a real
|
|
@@ -14,7 +15,7 @@
|
|
|
14
15
|
* and the switch that makes it stop being necessary.
|
|
15
16
|
*/
|
|
16
17
|
import { ReticleTool } from './tool-names.js';
|
|
17
|
-
import {
|
|
18
|
+
import { ADVERTISE_ALL_ENV } from './tool-surface.js';
|
|
18
19
|
import { mergedNameRedirect, mergedNameMessage } from './merged-name-redirect.js';
|
|
19
20
|
/**
|
|
20
21
|
* Guidance for `name`, or undefined when there is nothing useful to add — the tool IS advertised (so
|
|
@@ -34,6 +35,8 @@ export function unadvertisedToolHelp(name, advertised, known) {
|
|
|
34
35
|
`away. It is NOT missing: invoke it with ` +
|
|
35
36
|
`${ReticleTool.RUN} { tool: "${name}", args: { ... } }. ` +
|
|
36
37
|
`Call ${ReticleTool.TOOLS} { names: ["${name}"] } for its parameters. ` +
|
|
37
|
-
`If you need it repeatedly,
|
|
38
|
-
`
|
|
38
|
+
`If you need it repeatedly, start the daemon with ${ADVERTISE_ALL_ENV}=1 for the extended ` +
|
|
39
|
+
`surface. That is read by the DAEMON at startup, so it takes effect on the next daemon, and it ` +
|
|
40
|
+
`advertises MORE tools rather than all of them — editors budget MCP tools as a shared count, so ` +
|
|
41
|
+
`${ReticleTool.RUN} stays the way to reach the rest.`);
|
|
39
42
|
}
|