@reticlehq/server 2.8.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/SKILL.md +130 -813
- 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 +16 -1
- package/dist/capsule/causal-summary.js +2 -1
- package/dist/cli/cli-doctor.js +41 -13
- package/dist/cli/cli-flow-commands.js +8 -8
- package/dist/cli/cli-launch.d.ts +0 -17
- package/dist/cli/cli-launch.js +0 -28
- package/dist/cli/cli-port.d.ts +24 -0
- package/dist/cli/cli-port.js +66 -35
- package/dist/cli/cli-verify.d.ts +21 -0
- package/dist/cli/cli-verify.js +45 -1
- 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.js +6 -3
- 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/status-next-action.d.ts +37 -0
- package/dist/cli/status-next-action.js +61 -0
- package/dist/cli.js +65 -70
- package/dist/events/contradictions.d.ts +32 -0
- package/dist/events/contradictions.js +9 -5
- 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 -91
- package/dist/events/predicate-eval.js +85 -326
- 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 +127 -5
- 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-replay-run.js +8 -1
- package/dist/flows/flow-tools.js +9 -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 +4 -0
- 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 +9 -0
- package/dist/honesty/unsettled.js +7 -3
- package/dist/honesty/verified.d.ts +29 -3
- package/dist/honesty/verified.js +51 -8
- 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 +44 -10
- package/dist/init/agent-rules.d.ts +20 -0
- package/dist/init/agent-rules.js +36 -0
- package/dist/init/astro-patch.js +27 -1
- package/dist/init/confirm.d.ts +21 -3
- package/dist/init/confirm.js +32 -5
- package/dist/init/connect-steps.d.ts +2 -0
- package/dist/init/connect-steps.js +50 -0
- 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/plan-framework.d.ts +10 -0
- package/dist/init/plan-framework.js +25 -1
- package/dist/init/plan.d.ts +7 -2
- package/dist/init/plan.js +66 -30
- package/dist/init/run.js +11 -5
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +12 -4
- package/dist/init/snippets.d.ts +1 -1
- package/dist/init/snippets.js +35 -7
- package/dist/init/vite-config.js +24 -2
- package/dist/input/network-mock-tools.js +8 -1
- 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/mcp/mcp-proxy.d.ts +5 -66
- package/dist/mcp/mcp-proxy.js +180 -163
- package/dist/mcp/mcp.d.ts +10 -1
- package/dist/mcp/mcp.js +47 -27
- package/dist/mcp/proxy-handshake.d.ts +82 -1
- package/dist/mcp/proxy-handshake.js +109 -2
- package/dist/mcp/proxy-lifecycle.d.ts +10 -1
- package/dist/mcp/proxy-lifecycle.js +11 -2
- 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/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 +48 -2
- package/dist/session/no-session-diagnosis.js +176 -20
- 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 +165 -11
- package/dist/session/pending-commands.d.ts +12 -0
- package/dist/session/pending-commands.js +13 -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/bug-found.d.ts +18 -0
- package/dist/telemetry/bug-found.js +14 -0
- package/dist/telemetry/daemon-telemetry.js +24 -0
- package/dist/telemetry/install-source.d.ts +20 -0
- package/dist/telemetry/install-source.js +34 -0
- package/dist/telemetry/instrumentation-stall.d.ts +50 -0
- package/dist/telemetry/instrumentation-stall.js +90 -0
- package/dist/telemetry/session-metrics.d.ts +8 -0
- package/dist/telemetry/session-metrics.js +10 -0
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +13 -2
- package/dist/tools/act-tools.js +166 -29
- package/dist/tools/assert-verdict.js +32 -7
- package/dist/tools/browser-tools.js +10 -0
- package/dist/tools/dynamic-tools.js +16 -7
- package/dist/tools/error-recovery.js +9 -5
- package/dist/tools/invoke-tool.js +20 -3
- package/dist/tools/lease-hint.d.ts +30 -8
- package/dist/tools/lease-hint.js +65 -14
- package/dist/tools/lease-tools.d.ts +11 -0
- package/dist/tools/lease-tools.js +141 -96
- package/dist/tools/observe-tools.js +9 -3
- package/dist/tools/react-grace.d.ts +4 -0
- package/dist/tools/react-grace.js +6 -3
- package/dist/tools/read-tools.js +47 -1
- 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 +15 -0
- package/dist/tools/settle-in-flight.js +42 -2
- package/dist/tools/snapshot-delta.d.ts +3 -0
- package/dist/tools/snapshot-delta.js +67 -6
- package/dist/tools/tool-kit.d.ts +8 -0
- package/dist/tools/tool-surface.d.ts +60 -0
- package/dist/tools/tool-surface.js +85 -19
- package/dist/tools/tools.js +45 -4
- package/dist/tools/unadvertised-help.js +9 -6
- package/docs/README.md +1 -2
- package/docs/actions.mdx +41 -8
- package/docs/agent-cheatsheet.md +4 -2
- package/docs/architecture.md +6 -0
- package/docs/benchmarks.md +60 -12
- package/docs/best-practices.mdx +11 -6
- package/docs/capabilities.mdx +201 -54
- package/docs/cli/affected.mdx +6 -4
- package/docs/cli/capsules.mdx +9 -8
- package/docs/cli/cloud.mdx +30 -4
- package/docs/cli/doctor.mdx +41 -8
- package/docs/cli/drive.mdx +34 -15
- package/docs/cli/gate.mdx +6 -6
- package/docs/cli/help.mdx +8 -6
- package/docs/cli/hunt.mdx +18 -2
- package/docs/cli/identify.mdx +16 -2
- package/docs/cli/init.mdx +35 -8
- package/docs/cli/kill.mdx +5 -5
- package/docs/cli/license.mdx +1 -1
- package/docs/cli/open.mdx +6 -0
- package/docs/cli/restart.mdx +6 -6
- package/docs/cli/serve.mdx +10 -3
- package/docs/cli/status.mdx +29 -5
- package/docs/cli/stop.mdx +8 -0
- package/docs/cli/verify.mdx +20 -1
- package/docs/cli/version.mdx +1 -1
- package/docs/cli/watch.mdx +9 -2
- package/docs/cli.mdx +44 -22
- package/docs/debugging.md +3 -1
- package/docs/deploy-checks.md +30 -1
- package/docs/desktop.mdx +276 -80
- package/docs/docs.json +13 -12
- package/docs/enterprise.md +2 -0
- package/docs/faq.mdx +49 -35
- package/docs/fixtures.md +2 -0
- package/docs/flows.md +3 -1
- package/docs/for-agents.md +3 -3
- package/docs/frameworks.mdx +46 -7
- package/docs/gate-plan.md +4 -2
- package/docs/gates.md +7 -5
- package/docs/getting-started.md +53 -12
- package/docs/human-control.md +2 -0
- package/docs/index.mdx +43 -1
- package/docs/install-agentic.mdx +13 -4
- package/docs/install-manual.mdx +6 -4
- package/docs/instrumentation.mdx +7 -1
- package/docs/integration-patterns.md +3 -1
- package/docs/local-registry.md +4 -2
- package/docs/multi-agent-testing.md +3 -1
- package/docs/packages/babel-plugin.mdx +15 -6
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/core.mdx +26 -16
- package/docs/packages/electron.mdx +5 -7
- package/docs/packages/eslint-plugin.mdx +5 -3
- package/docs/packages/next.mdx +3 -5
- package/docs/packages/react.mdx +3 -3
- package/docs/packages/server.mdx +2 -2
- package/docs/packages/tauri.mdx +6 -6
- package/docs/packages/test.mdx +18 -7
- package/docs/packages/vite-plugin.mdx +4 -6
- package/docs/packages.mdx +14 -13
- package/docs/platform-integration.md +2 -0
- package/docs/predicates.mdx +76 -7
- package/docs/quickstart.mdx +51 -5
- package/docs/recipes.mdx +62 -22
- package/docs/skill-file.mdx +3 -1
- package/docs/state-management.mdx +2 -0
- package/docs/system-map.md +3 -1
- package/docs/telemetry-contract.md +16 -5
- package/docs/telemetry-events.mdx +63 -18
- package/docs/telemetry.md +8 -5
- package/docs/testing.md +35 -1
- package/docs/token-efficiency.md +8 -2
- package/docs/tools-act-and-wait.mdx +64 -14
- package/docs/tools-act-sequence.mdx +34 -17
- package/docs/tools-act.mdx +24 -11
- package/docs/tools-assert.mdx +69 -10
- package/docs/tools-console.mdx +11 -7
- package/docs/tools-inspect.mdx +11 -8
- package/docs/tools-navigate.mdx +41 -5
- package/docs/tools-network.mdx +42 -9
- package/docs/tools-observe.mdx +66 -10
- package/docs/tools-overview.mdx +26 -23
- package/docs/tools-query.mdx +52 -8
- package/docs/tools-session-and-feedback.mdx +30 -9
- package/docs/tools-sessions.mdx +39 -10
- package/docs/tools-snapshot.mdx +57 -3
- package/docs/tools-state.mdx +24 -1
- package/docs/tools-tools-and-run.mdx +40 -9
- package/docs/tools-wait-for.mdx +34 -15
- package/docs/troubleshooting.mdx +80 -10
- package/docs/usage.md +43 -21
- package/docs/vs-chrome-devtools-mcp.mdx +41 -0
- package/docs/vs-playwright-mcp.mdx +42 -2
- package/docs/vs-screenshots.mdx +42 -1
- package/docs/what-is-reticle.mdx +1 -1
- package/docs/why-reticle.mdx +3 -1
- package/package.json +5 -5
- package/docs/desktop-apps.md +0 -265
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** One candidate the browser returned for a target query. */
|
|
2
|
+
export interface TargetCandidate {
|
|
3
|
+
ref?: unknown;
|
|
4
|
+
role?: unknown;
|
|
5
|
+
name?: unknown;
|
|
6
|
+
visible?: unknown;
|
|
7
|
+
}
|
|
8
|
+
/** What the resolution produced: a ref to act on, or the reason there isn't one. */
|
|
9
|
+
export type TargetResolution = {
|
|
10
|
+
readonly kind: 'ref';
|
|
11
|
+
readonly ref: string;
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: 'error';
|
|
14
|
+
readonly message: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Turn a target QUERY into the single ref an action can be performed on.
|
|
18
|
+
*
|
|
19
|
+
* `act_and_wait` took only a `ref`, so every verification cost a `reticle_query` turn first just to
|
|
20
|
+
* learn one string. The advertised tool surface is re-sent on every turn, so that extra turn was not
|
|
21
|
+
* a small cost: measured on the wire, a two-turn verification spent 10,756 of its 11,235 tokens on
|
|
22
|
+
* schema and 479 on the actual answers. Removing the turn removes half the schema bill.
|
|
23
|
+
*
|
|
24
|
+
* Ambiguity is an ERROR, never a pick. Choosing "the first match" would let a verification act on a
|
|
25
|
+
* different element than the author meant and still report success, which is the false green this
|
|
26
|
+
* product exists to prevent — and the failure is invisible, because the verdict describes the
|
|
27
|
+
* element it DID act on. The message names the candidates so the caller can narrow rather than guess.
|
|
28
|
+
*
|
|
29
|
+
* Invisible matches are filtered before counting: a hidden duplicate is a common way for a
|
|
30
|
+
* legitimately unambiguous query to look ambiguous, and refusing on it would push callers back to
|
|
31
|
+
* the two-turn path for no gain.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveTargetRef(candidates: readonly unknown[]): TargetResolution;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { asRecord } from './tools-helpers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Turn a target QUERY into the single ref an action can be performed on.
|
|
4
|
+
*
|
|
5
|
+
* `act_and_wait` took only a `ref`, so every verification cost a `reticle_query` turn first just to
|
|
6
|
+
* learn one string. The advertised tool surface is re-sent on every turn, so that extra turn was not
|
|
7
|
+
* a small cost: measured on the wire, a two-turn verification spent 10,756 of its 11,235 tokens on
|
|
8
|
+
* schema and 479 on the actual answers. Removing the turn removes half the schema bill.
|
|
9
|
+
*
|
|
10
|
+
* Ambiguity is an ERROR, never a pick. Choosing "the first match" would let a verification act on a
|
|
11
|
+
* different element than the author meant and still report success, which is the false green this
|
|
12
|
+
* product exists to prevent — and the failure is invisible, because the verdict describes the
|
|
13
|
+
* element it DID act on. The message names the candidates so the caller can narrow rather than guess.
|
|
14
|
+
*
|
|
15
|
+
* Invisible matches are filtered before counting: a hidden duplicate is a common way for a
|
|
16
|
+
* legitimately unambiguous query to look ambiguous, and refusing on it would push callers back to
|
|
17
|
+
* the two-turn path for no gain.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveTargetRef(candidates) {
|
|
20
|
+
const all = candidates.map((c) => asRecord(c));
|
|
21
|
+
const visible = all.filter((c) => c.visible !== false);
|
|
22
|
+
const usable = visible.length > 0 ? visible : all;
|
|
23
|
+
if (0 === usable.length) {
|
|
24
|
+
return {
|
|
25
|
+
kind: 'error',
|
|
26
|
+
message: 'target matched no element. Nothing was acted on and no verdict is possible — widen the ' +
|
|
27
|
+
'query, or take a reticle_snapshot to see what is actually on the page.',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (usable.length > 1) {
|
|
31
|
+
const named = usable
|
|
32
|
+
.slice(0, 5)
|
|
33
|
+
.map((c) => {
|
|
34
|
+
const role = 'string' === typeof c.role ? c.role : '?';
|
|
35
|
+
const name = 'string' === typeof c.name ? c.name : '';
|
|
36
|
+
const ref = 'string' === typeof c.ref ? c.ref : '?';
|
|
37
|
+
return name.length > 0 ? `${ref} (${role} "${name}")` : `${ref} (${role})`;
|
|
38
|
+
})
|
|
39
|
+
.join(', ');
|
|
40
|
+
return {
|
|
41
|
+
kind: 'error',
|
|
42
|
+
message: `target matched ${String(usable.length)} elements and an action must not guess between ` +
|
|
43
|
+
`them: ${named}. Narrow the query (add role/name/testid or a scope), or pass an explicit ` +
|
|
44
|
+
'`ref` from reticle_query.',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const only = usable[0];
|
|
48
|
+
const ref = only === undefined ? undefined : only.ref;
|
|
49
|
+
if ('string' !== typeof ref || 0 === ref.length) {
|
|
50
|
+
return { kind: 'error', message: 'target matched an element with no usable ref.' };
|
|
51
|
+
}
|
|
52
|
+
return { kind: 'ref', ref };
|
|
53
|
+
}
|
|
@@ -62,6 +62,21 @@ export declare function inFlightRequestLabels(events: readonly {
|
|
|
62
62
|
type: string;
|
|
63
63
|
data: Record<string, unknown>;
|
|
64
64
|
}[]): string[];
|
|
65
|
+
/**
|
|
66
|
+
* Endpoints called MORE THAN ONCE in this window, busiest first, as "METHOD url ×N".
|
|
67
|
+
*
|
|
68
|
+
* The other half of an honest `unsettled`. A retrying query against a dead backend leaves nothing in
|
|
69
|
+
* flight — every attempt comes back, then the next one fires — so the verdict fell through to "what
|
|
70
|
+
* kept the page busy was its own churn (a poll, a timer, an animation)", which names three things
|
|
71
|
+
* and points at none of them. Measured in the field: on an app with a background retry loop
|
|
72
|
+
* `act_and_wait` could never return a pass, and nothing in the verdict said which endpoint to look
|
|
73
|
+
* at. This does not judge the repetition — a poll is legitimate — it reports it, which is all an
|
|
74
|
+
* agent needs to tell "my assertion was wrong" from "this app never quiets".
|
|
75
|
+
*/
|
|
76
|
+
export declare function repeatedRequestLabels(events: readonly {
|
|
77
|
+
type: string;
|
|
78
|
+
data: Record<string, unknown>;
|
|
79
|
+
}[]): string[];
|
|
65
80
|
export interface SettleResult {
|
|
66
81
|
/** True when nothing this window started is still outstanding. */
|
|
67
82
|
settled: boolean;
|
|
@@ -27,7 +27,16 @@
|
|
|
27
27
|
* - still in flight when the budget genuinely runs out → `unsettled`, as before, but now an honest
|
|
28
28
|
* limit instead of an early exit.
|
|
29
29
|
*/
|
|
30
|
-
import { EventType } from '@reticlehq/core';
|
|
30
|
+
import { EventType, isDevToolingUrl } from '@reticlehq/core';
|
|
31
|
+
/**
|
|
32
|
+
* The dev toolchain talking about ITSELF is not the app finishing its work.
|
|
33
|
+
*
|
|
34
|
+
* `evalSettled` and `findContradictions` both drop it already; this file read the raw window, so the
|
|
35
|
+
* exclusion was cosmetic exactly where it decides a verdict. Reported from a real drive: a
|
|
36
|
+
* deliberately disabled control — no state change, no storage change, zero application requests —
|
|
37
|
+
* was graded on the strength of a Next webpack hot-update the same payload printed as ignored.
|
|
38
|
+
*/
|
|
39
|
+
const isDevTooling = (data) => isDevToolingUrl('string' === typeof data['url'] ? data['url'] : undefined);
|
|
31
40
|
/** How often to re-read the buffer. Short enough to return promptly, long enough not to spin. */
|
|
32
41
|
const POLL_MS = 50;
|
|
33
42
|
const idOf = (data) => 'string' === typeof data['id'] ? data['id'] : undefined;
|
|
@@ -49,7 +58,7 @@ export function inFlightRequestIds(events) {
|
|
|
49
58
|
}
|
|
50
59
|
const open = [];
|
|
51
60
|
for (const e of events) {
|
|
52
|
-
if (e.type !== EventType.NET_PENDING)
|
|
61
|
+
if (e.type !== EventType.NET_PENDING || isDevTooling(e.data))
|
|
53
62
|
continue;
|
|
54
63
|
const id = idOf(e.data);
|
|
55
64
|
if (id !== undefined && !settled.has(id) && !open.includes(id))
|
|
@@ -81,6 +90,37 @@ export function inFlightRequestLabels(events) {
|
|
|
81
90
|
}
|
|
82
91
|
return labels;
|
|
83
92
|
}
|
|
93
|
+
/** Enough to recognise the loop; a busy dashboard must not spend the verdict on a URL list. */
|
|
94
|
+
const MAX_REPEATED = 3;
|
|
95
|
+
/** One call is traffic. Two to the same endpoint inside one window is a repeat. */
|
|
96
|
+
const MIN_REPEATS = 2;
|
|
97
|
+
/**
|
|
98
|
+
* Endpoints called MORE THAN ONCE in this window, busiest first, as "METHOD url ×N".
|
|
99
|
+
*
|
|
100
|
+
* The other half of an honest `unsettled`. A retrying query against a dead backend leaves nothing in
|
|
101
|
+
* flight — every attempt comes back, then the next one fires — so the verdict fell through to "what
|
|
102
|
+
* kept the page busy was its own churn (a poll, a timer, an animation)", which names three things
|
|
103
|
+
* and points at none of them. Measured in the field: on an app with a background retry loop
|
|
104
|
+
* `act_and_wait` could never return a pass, and nothing in the verdict said which endpoint to look
|
|
105
|
+
* at. This does not judge the repetition — a poll is legitimate — it reports it, which is all an
|
|
106
|
+
* agent needs to tell "my assertion was wrong" from "this app never quiets".
|
|
107
|
+
*/
|
|
108
|
+
export function repeatedRequestLabels(events) {
|
|
109
|
+
const counts = new Map();
|
|
110
|
+
for (const e of events) {
|
|
111
|
+
if (e.type !== EventType.NET_REQUEST || isDevTooling(e.data))
|
|
112
|
+
continue;
|
|
113
|
+
const method = 'string' === typeof e.data['method'] ? e.data['method'] : 'request';
|
|
114
|
+
const url = 'string' === typeof e.data['url'] ? e.data['url'] : 'an unreported url';
|
|
115
|
+
const key = `${method} ${url}`;
|
|
116
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
117
|
+
}
|
|
118
|
+
return [...counts.entries()]
|
|
119
|
+
.filter(([, count]) => count >= MIN_REPEATS)
|
|
120
|
+
.sort(([, a], [, b]) => b - a)
|
|
121
|
+
.slice(0, MAX_REPEATED)
|
|
122
|
+
.map(([key, count]) => `${key} ×${String(count)}`);
|
|
123
|
+
}
|
|
84
124
|
/**
|
|
85
125
|
* Wait, bounded by `budgetMs`, for the requests this window started to come back.
|
|
86
126
|
*
|
|
@@ -21,6 +21,7 @@ export type SnapshotDeltaMode = (typeof SnapshotDeltaMode)[keyof typeof Snapshot
|
|
|
21
21
|
interface SnapshotDelta {
|
|
22
22
|
added: string[];
|
|
23
23
|
removed: string[];
|
|
24
|
+
changed: string[];
|
|
24
25
|
addedCount: number;
|
|
25
26
|
removedCount: number;
|
|
26
27
|
}
|
|
@@ -45,6 +46,8 @@ export declare function snapshotDelta(prevTree: string | undefined, nextTree: st
|
|
|
45
46
|
export declare class SnapshotCache {
|
|
46
47
|
#private;
|
|
47
48
|
constructor(max?: number);
|
|
49
|
+
/** True when an entry exists for this key (regardless of route match). */
|
|
50
|
+
has(key: string): boolean;
|
|
48
51
|
/** Last tree for this key IF the route still matches; undefined when absent or route changed. */
|
|
49
52
|
recall(key: string, route: string): string | undefined;
|
|
50
53
|
remember(key: string, route: string, tree: string): void;
|
|
@@ -63,6 +63,17 @@ function deltaLines(tree) {
|
|
|
63
63
|
* buttons — and the count is the only thing that says how many left. Emitting the ORIGINAL text is
|
|
64
64
|
* what finally tells them apart: with refs, "which twelve" has an answer.
|
|
65
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
|
+
}
|
|
66
77
|
function diffKeyed(prev, next) {
|
|
67
78
|
const bucket = (lines) => {
|
|
68
79
|
const map = new Map();
|
|
@@ -96,6 +107,39 @@ function diffKeyed(prev, next) {
|
|
|
96
107
|
}
|
|
97
108
|
return { removed, added };
|
|
98
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
|
+
}
|
|
99
143
|
/**
|
|
100
144
|
* Where focus sits now: the line to show, plus the identity to compare it BY.
|
|
101
145
|
*
|
|
@@ -123,7 +167,8 @@ export const SnapshotDeltaMode = {
|
|
|
123
167
|
export function snapshotDelta(prevTree, nextTree) {
|
|
124
168
|
if (prevTree === undefined)
|
|
125
169
|
return { mode: SnapshotDeltaMode.FULL };
|
|
126
|
-
const
|
|
170
|
+
const raw = diffKeyed(deltaLines(prevTree), deltaLines(nextTree));
|
|
171
|
+
const { added, removed, changed } = extractChanged(raw.added, raw.removed);
|
|
127
172
|
const before = focusedLine(prevTree);
|
|
128
173
|
const now = focusedLine(nextTree);
|
|
129
174
|
// Reported only on a MOVE. Repeating "focus is still here" every turn is the noise the delta exists
|
|
@@ -135,12 +180,18 @@ export function snapshotDelta(prevTree, nextTree) {
|
|
|
135
180
|
...(now === undefined ? {} : { to: now.text }),
|
|
136
181
|
};
|
|
137
182
|
const moved = focusChanged === undefined ? {} : { focusChanged };
|
|
138
|
-
if (0 === added.length && 0 === removed.length) {
|
|
183
|
+
if (0 === added.length && 0 === removed.length && 0 === changed.length) {
|
|
139
184
|
return { mode: SnapshotDeltaMode.UNCHANGED, ...moved };
|
|
140
185
|
}
|
|
141
186
|
return {
|
|
142
187
|
mode: SnapshotDeltaMode.DELTA,
|
|
143
|
-
delta: {
|
|
188
|
+
delta: {
|
|
189
|
+
added,
|
|
190
|
+
removed,
|
|
191
|
+
changed,
|
|
192
|
+
addedCount: added.length,
|
|
193
|
+
removedCount: removed.length,
|
|
194
|
+
},
|
|
144
195
|
...moved,
|
|
145
196
|
};
|
|
146
197
|
}
|
|
@@ -152,6 +203,10 @@ export class SnapshotCache {
|
|
|
152
203
|
constructor(max = DEFAULT_MAX_ENTRIES) {
|
|
153
204
|
this.#max = max;
|
|
154
205
|
}
|
|
206
|
+
/** True when an entry exists for this key (regardless of route match). */
|
|
207
|
+
has(key) {
|
|
208
|
+
return this.#map.has(key);
|
|
209
|
+
}
|
|
155
210
|
/** Last tree for this key IF the route still matches; undefined when absent or route changed. */
|
|
156
211
|
recall(key, route) {
|
|
157
212
|
const entry = this.#map.get(key);
|
|
@@ -210,10 +265,13 @@ export function applySnapshotDelta(raw, opts, cache) {
|
|
|
210
265
|
return raw;
|
|
211
266
|
}
|
|
212
267
|
const prev = cache.recall(key, route);
|
|
268
|
+
const hadEntry = cache.has(key);
|
|
213
269
|
cache.remember(key, route, tree);
|
|
214
270
|
const decision = snapshotDelta(prev, tree);
|
|
215
|
-
if (decision.mode === SnapshotDeltaMode.FULL)
|
|
216
|
-
|
|
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
|
+
}
|
|
217
275
|
// The walk stops at a node cap and returns a DOCUMENT-ORDER PREFIX, so two capped snapshots of a
|
|
218
276
|
// large page are identical whenever the change happened past the cap — and "unchanged" is then a
|
|
219
277
|
// statement about the cap, not about the page. Carried through on both branches: a delta computed
|
|
@@ -226,9 +284,12 @@ export function applySnapshotDelta(raw, opts, cache) {
|
|
|
226
284
|
if (decision.mode === SnapshotDeltaMode.UNCHANGED) {
|
|
227
285
|
return { mode: SnapshotDeltaMode.UNCHANGED, status: r['status'], ...moved, ...capped };
|
|
228
286
|
}
|
|
287
|
+
const { changed, ...structuralDelta } = decision.delta;
|
|
288
|
+
const changedField = changed.length > 0 ? { changed, changedCount: changed.length } : {};
|
|
229
289
|
return {
|
|
230
290
|
mode: SnapshotDeltaMode.DELTA,
|
|
231
|
-
delta:
|
|
291
|
+
delta: structuralDelta,
|
|
292
|
+
...changedField,
|
|
232
293
|
status: r['status'],
|
|
233
294
|
...moved,
|
|
234
295
|
...capped,
|
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
|
*
|
|
@@ -144,26 +176,52 @@ export const CORE_TOOL_NAMES = new Set([
|
|
|
144
176
|
// deleting the instruction from the lease and the pause hint — deletes the handback protocol,
|
|
145
177
|
// because there is nowhere else those two calls are ever named.
|
|
146
178
|
ReticleTool.SESSION,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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.
|
|
165
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,
|
|
166
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]);
|
|
167
225
|
/** Is the truthy form of a boolean env var set? `1`, `true`, `yes` — anything else is off. */
|
|
168
226
|
function envFlagOn(raw) {
|
|
169
227
|
if (raw === undefined)
|
|
@@ -178,6 +236,8 @@ function envFlagOn(raw) {
|
|
|
178
236
|
* switch decides, and the retired setting is honoured last so an old shell profile still works.
|
|
179
237
|
*/
|
|
180
238
|
export function resolveToolSurface(explicit) {
|
|
239
|
+
if (explicit === TOOL_SURFACE.VERIFY)
|
|
240
|
+
return TOOL_SURFACE.VERIFY;
|
|
181
241
|
if (explicit === TOOL_SURFACE.ALL)
|
|
182
242
|
return TOOL_SURFACE.ALL;
|
|
183
243
|
if (explicit === TOOL_SURFACE.DEFAULT)
|
|
@@ -185,6 +245,8 @@ export function resolveToolSurface(explicit) {
|
|
|
185
245
|
const retiredExplicit = explicit === undefined ? undefined : RETIRED_PROFILE_VALUES[explicit];
|
|
186
246
|
if (retiredExplicit !== undefined)
|
|
187
247
|
return retiredExplicit;
|
|
248
|
+
if (envFlagOn(process.env[VERIFY_SURFACE_ENV]))
|
|
249
|
+
return TOOL_SURFACE.VERIFY;
|
|
188
250
|
if (envFlagOn(process.env[ADVERTISE_ALL_ENV]))
|
|
189
251
|
return TOOL_SURFACE.ALL;
|
|
190
252
|
const retiredEnv = RETIRED_PROFILE_VALUES[process.env[TOOL_PROFILE_ENV] ?? ''];
|
|
@@ -230,7 +292,11 @@ export function describeToolSurface(active, requested) {
|
|
|
230
292
|
export function filterTools(tools, surface) {
|
|
231
293
|
// CORE_TOOL_NAMES is what it always really was: the set advertised directly. It was never the
|
|
232
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));
|
|
233
297
|
if (surface === TOOL_SURFACE.DEFAULT)
|
|
234
298
|
return tools.filter((t) => CORE_TOOL_NAMES.has(t.name));
|
|
235
|
-
|
|
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));
|
|
236
302
|
}
|