@reticlehq/server 3.1.0 → 3.2.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 +17 -18
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +13 -1
- package/dist/command/cli/cli-flow-commands.d.ts +2 -1
- package/dist/command/cli/cli-kill.js +1 -1
- package/dist/command/cli/cli-parse.d.ts +2 -1
- package/dist/command/cli/cli-parse.js +5 -2
- package/dist/command/cli/cli-verify.js +2 -2
- package/dist/command/cli/cloud-cli.js +7 -6
- package/dist/command/cli/cloud-kit.js +2 -6
- package/dist/command/cli/config/config-discovery.d.ts +12 -0
- package/dist/command/cli/config/config-discovery.js +19 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
- package/dist/command/cli/doctor/doctor-rows.js +12 -0
- package/dist/command/cli/drive/drive-command.js +1 -1
- package/dist/command/cli/launch/cli-launch.d.ts +10 -2
- package/dist/command/cli/launch/cli-launch.js +32 -39
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
- package/dist/command/cli/mcp-command.js +5 -3
- package/dist/command/cli/ports/resolve/cli-port.d.ts +2 -1
- package/dist/command/cli/ports/resolve/cli-port.js +5 -1
- package/dist/command/cli/status/status-lines.d.ts +28 -0
- package/dist/command/cli/status/status-lines.js +117 -0
- package/dist/command/cli.d.ts +1 -1
- package/dist/command/cli.js +24 -177
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +48 -0
- package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
- package/dist/command/daemon/binding/loopback-reach.js +63 -0
- package/dist/command/daemon/daemon-resilience.d.ts +13 -1
- package/dist/command/daemon/daemon-resilience.js +12 -1
- package/dist/command/daemon/daemon.d.ts +2 -11
- package/dist/command/daemon/daemon.js +8 -11
- package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
- package/dist/command/daemon/lifetime/heartbeat.js +10 -0
- package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
- package/dist/command/daemon/lifetime/log-cap.js +115 -0
- package/dist/command/setup/agent-notice-memory.d.ts +16 -0
- package/dist/command/setup/agent-notice-memory.js +65 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
- package/dist/command/setup/bringup/dev-server-wait.js +16 -0
- package/dist/command/setup/bringup/ensure-daemon.js +1 -1
- package/dist/command/setup/init/init-runtime.js +13 -1
- package/dist/command/setup/node-effects.js +1 -1
- package/dist/command/setup/probe/page-probe.js +9 -2
- package/dist/command/setup/run-setup.d.ts +25 -7
- package/dist/command/setup/run-setup.js +51 -9
- package/dist/command/setup/setup-command.js +31 -3
- package/dist/command/setup/terminal/confirm.js +2 -1
- package/dist/command/update/updater.d.ts +27 -0
- package/dist/command/update/updater.js +35 -2
- package/dist/features/crawl/crawl-tools.js +3 -1
- package/dist/features/crawl/crawl.js +2 -1
- package/dist/features/harness/drive-report.d.ts +73 -0
- package/dist/features/harness/drive-report.js +196 -0
- package/dist/features/harness/driver.js +29 -8
- package/dist/features/harness/drivers.d.ts +19 -0
- package/dist/features/harness/drivers.js +19 -0
- package/dist/features/harness/fill-values.d.ts +67 -0
- package/dist/features/harness/fill-values.js +153 -0
- package/dist/features/harness/jev-driver.d.ts +188 -0
- package/dist/features/harness/jev-driver.js +664 -0
- package/dist/features/harness/openai-driver.d.ts +46 -0
- package/dist/features/harness/openai-driver.js +197 -0
- package/dist/features/harness/platform-config.d.ts +52 -0
- package/dist/features/harness/platform-config.js +89 -0
- package/dist/features/visual/visual-tools.js +26 -2
- package/dist/index.js +60 -55
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/runs/verification-sync.d.ts +2 -2
- package/dist/judgement/runs/verification-sync.js +9 -2
- package/dist/language/flows/affected-tools.js +6 -3
- package/dist/language/flows/flow-replay-run.d.ts +25 -2
- package/dist/language/flows/flow-replay-run.js +73 -27
- package/dist/language/flows/flow-replay.d.ts +17 -0
- package/dist/language/flows/flow-replay.js +148 -100
- package/dist/language/flows/flow-result.d.ts +18 -1
- package/dist/language/flows/flow-result.js +18 -2
- package/dist/language/flows/flow-store-for-session.d.ts +2 -1
- package/dist/language/flows/flow-tools.js +8 -4
- package/dist/language/flows/flows.d.ts +8 -7
- package/dist/language/flows/flows.js +7 -3
- package/dist/language/flows/narrow-write.d.ts +4 -4
- package/dist/language/flows/replay.js +1 -1
- package/dist/language/flows/suite-selection.d.ts +15 -1
- package/dist/language/flows/suite-selection.js +1 -1
- package/dist/language/flows/verify-change-tools.js +7 -4
- package/dist/memory/cloud/account-state.d.ts +1 -1
- package/dist/memory/cloud/account-state.js +5 -4
- package/dist/memory/cloud/cloud-config.d.ts +9 -0
- package/dist/memory/cloud/cloud-config.js +9 -0
- package/dist/memory/cloud/cloud-sync.d.ts +7 -2
- package/dist/memory/cloud/cloud-sync.js +10 -5
- package/dist/memory/cloud/harness-config.d.ts +10 -0
- package/dist/memory/cloud/harness-config.js +58 -0
- package/dist/memory/cloud/harness-offer.d.ts +44 -0
- package/dist/memory/cloud/harness-offer.js +146 -0
- package/dist/memory/cloud/harness-switch.d.ts +11 -0
- package/dist/memory/cloud/harness-switch.js +53 -0
- package/dist/memory/cloud/sync-daemon.d.ts +22 -2
- package/dist/memory/cloud/sync-daemon.js +24 -0
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +2 -1
- package/dist/memory/impact/impact-store.d.ts +5 -0
- package/dist/memory/impact/impact-store.js +19 -0
- package/dist/memory/intent/intent-shard-store.js +2 -2
- package/dist/memory/intent/intent-shard.d.ts +12 -12
- package/dist/memory/journal/attach-journal.d.ts +11 -0
- package/dist/memory/journal/attach-journal.js +1 -1
- package/dist/memory/journal/journal-query.d.ts +35 -1
- package/dist/memory/journal/journal-query.js +46 -0
- package/dist/memory/journal/journal-recorder.d.ts +50 -2
- package/dist/memory/journal/journal-recorder.js +45 -7
- package/dist/memory/journal/on-disk/retention.d.ts +87 -2
- package/dist/memory/journal/on-disk/retention.js +177 -8
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +9 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.js +9 -2
- package/dist/memory/journal/self-observation.d.ts +3 -0
- package/dist/memory/journal/self-observation.js +67 -0
- package/dist/memory/journal/session-end.d.ts +13 -3
- package/dist/memory/journal/session-end.js +24 -4
- package/dist/memory/journal/session-journal.d.ts +115 -5
- package/dist/memory/journal/session-journal.js +479 -32
- package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
- package/dist/memory/project/artifact-root-resolver.js +89 -0
- package/dist/memory/project/artifact-root.d.ts +39 -2
- package/dist/memory/project/artifact-root.js +58 -1
- package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
- package/dist/memory/project/dir/fill-value-store.js +88 -0
- package/dist/memory/project/dir/reticle-dir.d.ts +14 -4
- package/dist/memory/project/dir/reticle-dir.js +11 -0
- package/dist/memory/project/fs/fs-port.d.ts +22 -1
- package/dist/memory/project/fs/fs-port.js +33 -5
- package/dist/memory/project/learned-routes.d.ts +16 -2
- package/dist/memory/project/learned-routes.js +26 -10
- package/dist/memory/project/project-for-root.d.ts +30 -0
- package/dist/memory/project/project-for-root.js +43 -0
- package/dist/memory/project/project-tools.js +10 -5
- package/dist/memory/project/session-root.d.ts +35 -6
- package/dist/memory/project/session-root.js +70 -8
- package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
- package/dist/portal/bridge/auth-failure-reason.js +15 -1
- package/dist/portal/bridge/bridge.d.ts +6 -1
- package/dist/portal/bridge/bridge.js +36 -2
- package/dist/portal/session/facts/session-replaced.d.ts +19 -0
- package/dist/portal/session/facts/session-replaced.js +22 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -60
- package/dist/portal/session/no-session-next-action.js +3 -2
- package/dist/portal/session/no-session-watch.js +14 -10
- package/dist/portal/session/rebind.d.ts +18 -0
- package/dist/portal/session/rebind.js +38 -0
- package/dist/portal/session/session-manager.d.ts +4 -0
- package/dist/portal/session/session-manager.js +19 -1
- package/dist/portal/session/session.d.ts +21 -10
- package/dist/portal/session/session.js +29 -42
- package/dist/status-payload.d.ts +12 -1
- package/dist/status-payload.js +4 -1
- package/dist/surface/http-server.js +3 -5
- package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
- package/dist/surface/mcp/faults/mcp-outage.js +20 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
- package/dist/surface/mcp/mcp-proxy.js +20 -9
- package/dist/surface/mcp/mcp.d.ts +10 -1
- package/dist/surface/mcp/mcp.js +59 -29
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
- package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
- package/dist/surface/mcp/proxy/proxy-log.js +7 -22
- package/dist/surface/tools/act/act-preflight.js +6 -1
- package/dist/surface/tools/act/sequence-grade.js +13 -0
- package/dist/surface/tools/act-capsule.js +27 -1
- package/dist/surface/tools/assert/assert-verdict.js +44 -17
- package/dist/surface/tools/coverage-tools.js +5 -2
- package/dist/surface/tools/dynamic-tools.js +37 -46
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +15 -0
- package/dist/surface/tools/explore-tools.js +80 -7
- package/dist/surface/tools/feedback-tools.js +49 -2
- package/dist/surface/tools/first-sentence.d.ts +24 -0
- package/dist/surface/tools/first-sentence.js +0 -0
- package/dist/surface/tools/harness-explore.d.ts +125 -1
- package/dist/surface/tools/harness-explore.js +401 -15
- package/dist/surface/tools/harness-plan.d.ts +76 -0
- package/dist/surface/tools/harness-plan.js +156 -0
- package/dist/surface/tools/harness-toolset.js +12 -1
- package/dist/surface/tools/invoke-tool.js +65 -5
- package/dist/surface/tools/live-call-text.d.ts +6 -0
- package/dist/surface/tools/live-call-text.js +36 -2
- package/dist/surface/tools/observe-tools.js +55 -9
- package/dist/surface/tools/read/query-windowed.d.ts +1 -1
- package/dist/surface/tools/read/query-windowed.js +1 -1
- package/dist/surface/tools/read-tools.js +1 -2
- package/dist/surface/tools/tool-kit.d.ts +18 -3
- package/dist/surface/tools/tools-helpers.d.ts +8 -7
- package/dist/surface/tools/tools-helpers.js +11 -13
- package/dist/surface/tools/tools.js +6 -1
- package/dist/telemetry/bug-found.js +32 -2
- package/dist/telemetry/feedback-context.js +3 -3
- package/dist/telemetry/harness-drive.d.ts +6 -0
- package/dist/telemetry/harness-drive.js +39 -0
- package/dist/telemetry/mcp-connection.d.ts +14 -1
- package/dist/telemetry/mcp-connection.js +17 -2
- package/dist/telemetry/onboarding-firsts.d.ts +30 -1
- package/dist/telemetry/onboarding-firsts.js +54 -12
- package/dist/telemetry/verification-of.js +31 -3
- package/docs/agent-cheatsheet.md +7 -3
- package/docs/autodrive.md +42 -2
- package/docs/benchmarks.md +1 -1
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/status.mdx +35 -20
- package/docs/cli.mdx +8 -18
- package/docs/desktop.mdx +1 -1
- package/docs/faq.mdx +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +16 -0
- package/docs/system-map.md +6 -4
- package/docs/telemetry.md +2 -1
- package/docs/token-efficiency.md +2 -2
- package/docs/tools/overview.mdx +1 -1
- package/docs/troubleshooting.mdx +8 -2
- package/docs/usage.md +1 -1
- package/package.json +6 -6
|
@@ -97,6 +97,18 @@ changeUndeclared) {
|
|
|
97
97
|
// rule — part of what happened was not seen — so both belong in `blindSpots`, which is the only
|
|
98
98
|
// input `decideVerified` reads for that.
|
|
99
99
|
const gap = transportGapNote(windowEvents);
|
|
100
|
+
/**
|
|
101
|
+
* The DURABLE half of the same question. `queryEvents` above falls through to the journal once the
|
|
102
|
+
* ring buffer has evicted, and that ledger refuses writes at its byte ceiling — so a window read
|
|
103
|
+
* from it after the ceiling was reached is missing whatever the cap turned away. The in-band
|
|
104
|
+
* marker cannot carry this: it is stamped with a `t`, and any `since` later than that filters it
|
|
105
|
+
* straight back out, leaving a partial answer that looks exactly like a complete one.
|
|
106
|
+
*
|
|
107
|
+
* Called optionally because `Session` is stubbed by cast in a great many suites, and a method
|
|
108
|
+
* added to it therefore arrives as a runtime `undefined` in files that have nothing to do with
|
|
109
|
+
* this change — the cost recorded at the top of `fake-session.ts`, four times over.
|
|
110
|
+
*/
|
|
111
|
+
const ledgerClosed = undefined !== (await session.journalWriteLoss?.());
|
|
100
112
|
// A consumer rule that crashed belongs here too: the engine ran with fewer rules than it claims,
|
|
101
113
|
// so part of what happened may simply not have been looked for.
|
|
102
114
|
const impeachingNotes = [impeaching.note, gap, ...crashedRuleNotes()].filter((n) => n !== undefined);
|
|
@@ -104,6 +116,29 @@ changeUndeclared) {
|
|
|
104
116
|
const outcomeUnread = unreadWriteLabels(windowEvents);
|
|
105
117
|
const stillInFlight = inFlightRequestLabels(windowEvents);
|
|
106
118
|
const effectiveInconclusive = inconclusive ?? (!pass ? session.preconditionFailure?.() : undefined);
|
|
119
|
+
/**
|
|
120
|
+
* Does a green here rest on the window having been COMPLETE?
|
|
121
|
+
*
|
|
122
|
+
* Not a refinement of the two losses below — it is what keeps either of them usable. Scarce loss
|
|
123
|
+
* is recorded for AGE eviction too, so `lostSince(0)` is true on any session past the 60s cutoff,
|
|
124
|
+
* and assert takes a caller-chosen `since` that is often 0. Impeaching every verdict over a wide
|
|
125
|
+
* window would make `unknown` the answer to everything, which is the failure this repo has already
|
|
126
|
+
* paid for once, when `unclean_capture` became the dominant cause of `unknown` in the field. A
|
|
127
|
+
* POSITIVE assertion that passed FOUND its evidence; things lost elsewhere do not unmake it.
|
|
128
|
+
*/
|
|
129
|
+
const restsOnComplete = restsOnCompleteWindow(predicate);
|
|
130
|
+
/**
|
|
131
|
+
* The buffer evicted scarce evidence from this window.
|
|
132
|
+
*
|
|
133
|
+
* assert never consulted the buffer at all, on the reasoning that it "observes an already-open
|
|
134
|
+
* window" — but eviction happens on push regardless of who opened the window, so
|
|
135
|
+
* `{ console, absent: true }` returned `yes` over a window whose evidence was gone, which is
|
|
136
|
+
* absence of evidence read as evidence of absence. The act path needs no `restsOnComplete` guard:
|
|
137
|
+
* its cursor is the action's own.
|
|
138
|
+
*/
|
|
139
|
+
const bufferLost = session.lostSince(since) && restsOnComplete;
|
|
140
|
+
/** The durable ledger refused writes, on a query path that read from it. Same rule, other store. */
|
|
141
|
+
const ledgerLost = ledgerClosed && restsOnComplete;
|
|
107
142
|
const decision = decideVerified({
|
|
108
143
|
pass,
|
|
109
144
|
// What this claim needs to read, against what the page said it can see -- the protocol's
|
|
@@ -127,27 +162,19 @@ changeUndeclared) {
|
|
|
127
162
|
honesty: buildHonestyBlock({
|
|
128
163
|
grade: gradeOfPredicate(predicate),
|
|
129
164
|
attribution: 'window',
|
|
130
|
-
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
// which is absence of evidence read as evidence of absence.
|
|
135
|
-
//
|
|
136
|
-
// The second half of the condition is not a refinement, it is what keeps this usable. Scarce
|
|
137
|
-
// loss is recorded for AGE eviction too, so `lostSince(0)` is true on any session past the 60s
|
|
138
|
-
// cutoff, and assert takes a caller-chosen `since` that is often 0. Impeaching every verdict
|
|
139
|
-
// over a wide window would make `unknown` the answer to everything — the failure this repo has
|
|
140
|
-
// already paid for once, when `unclean_capture` became the dominant cause of `unknown` in the
|
|
141
|
-
// field. The act path needs no such guard: its cursor is the action's own.
|
|
142
|
-
truncated: session.lostSince(since) && restsOnCompleteWindow(predicate),
|
|
165
|
+
truncated: bufferLost,
|
|
166
|
+
// The durable ledger stopped, and the SAME absence rule applies: a positive assertion that
|
|
167
|
+
// passed found its evidence, and events missing from the end of a file do not unmake it.
|
|
168
|
+
ledgerClosed: ledgerLost,
|
|
143
169
|
coveragePartial: Coverage.PARTIAL === statement.coverage,
|
|
144
170
|
...(statement.note === undefined ? {} : { coverageNote: statement.note }),
|
|
145
171
|
...(0 === impeachingNotes.length ? {} : { blindSpots: impeachingNotes }),
|
|
146
|
-
// Which loss, as an enum, beside the prose.
|
|
172
|
+
// Which loss, as an enum, beside the prose. Ours before the page's, and the memory one before
|
|
173
|
+
// the disk one: `verification.uncleanLoss` takes the first, and a window that lost both is
|
|
174
|
+
// more usefully reported as the eviction, which is the one a caller can still drive around.
|
|
147
175
|
losses: [
|
|
148
|
-
...(
|
|
149
|
-
|
|
150
|
-
: []),
|
|
176
|
+
...(bufferLost ? [CaptureLoss.BUFFER_LOSS] : []),
|
|
177
|
+
...(ledgerLost ? [CaptureLoss.JOURNAL_LOSS] : []),
|
|
151
178
|
...(gap === undefined ? [] : [CaptureLoss.TRANSPORT_GAP]),
|
|
152
179
|
...(impeaching.note === undefined ? [] : [CaptureLoss.BLIND_SPOT]),
|
|
153
180
|
],
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { sessionRoot } from '../../memory/project/session-root.js';
|
|
3
|
+
import { projectForRoot } from '../../memory/project/project-for-root.js';
|
|
2
4
|
import { coverageRegressed, observabilityOf } from '@reticlehq/engine/evidence/observability.js';
|
|
3
5
|
import { foldFeatureCapture } from './feature-capture.js';
|
|
4
6
|
import { foldToolHitRate } from './tool-hit-rate.js';
|
|
@@ -153,10 +155,11 @@ export function buildCoverageTools(toolNames) {
|
|
|
153
155
|
// The number, and the floor under it, together. A coverage figure that can only ever be
|
|
154
156
|
// reported and never contradicted is one an agent learns to satisfy rather than to earn.
|
|
155
157
|
const observability = observabilityOf(session.actedRefs(), gaps);
|
|
156
|
-
const
|
|
158
|
+
const project = projectForRoot(deps, sessionRoot(deps, sessionId));
|
|
159
|
+
const best = await project.bestObservability();
|
|
157
160
|
const regressed = coverageRegressed(best, observability);
|
|
158
161
|
if (observability.percent !== undefined) {
|
|
159
|
-
await
|
|
162
|
+
await project.raiseObservability(observability.percent);
|
|
160
163
|
}
|
|
161
164
|
return {
|
|
162
165
|
total: parseControls(tree).length,
|
|
@@ -2,36 +2,12 @@ import { z } from 'zod';
|
|
|
2
2
|
import { runTool } from './invoke-tool.js';
|
|
3
3
|
import { buildErrorPayload } from './error-recovery.js';
|
|
4
4
|
import { mergedNameRedirect, mergedNameMessage, retiredToolNames } from './merged-name-redirect.js';
|
|
5
|
+
import { CATALOGUE_SUMMARY_LENGTH, firstSentence } from './first-sentence.js';
|
|
5
6
|
import { takeVersionSkewOnto } from '../../command/version/version-nudge.js';
|
|
6
7
|
import { ReticleTool } from '@reticlehq/core';
|
|
7
8
|
import { ADVERTISE_ALL_ENV } from './tool-surface.js';
|
|
8
9
|
import { getSessionMetrics } from '../../telemetry/session-metrics.js';
|
|
9
10
|
import { isPredicateParam, predicateGrammar, } from '@reticlehq/engine/question/predicate/predicate-eval.js';
|
|
10
|
-
/**
|
|
11
|
-
* On-demand tool loading for MCP — the answer to the per-turn tool-definition tax.
|
|
12
|
-
*
|
|
13
|
-
* A normal MCP server advertises every tool's full description + schema, and the client re-sends
|
|
14
|
-
* all of them to the model on EVERY turn. Measured here: Reticle at ~5.6k–14.6k tokens/turn just for
|
|
15
|
-
* definitions, which dominates a real agent loop and only grows as the tool surface grows.
|
|
16
|
-
*
|
|
17
|
-
* The `dynamic` profile advertises just TWO meta-tools (~hundreds of tokens, fixed regardless of
|
|
18
|
-
* how many real tools exist):
|
|
19
|
-
* reticle_tools — discover. No args ⇒ a compact catalog (name + one-line summary) of every tool.
|
|
20
|
-
* names:[...] ⇒ full description + params for just those tools, loaded on demand.
|
|
21
|
-
* reticle_run — invoke any tool by name. On a bad/unknown call it returns that tool's params as a
|
|
22
|
-
* hint, so the model self-corrects without ever needing the schema up front.
|
|
23
|
-
*
|
|
24
|
-
* The model lists once, loads the 2–3 tools it actually needs, and calls them — paying for tool
|
|
25
|
-
* detail only when used, not every turn. Works with any MCP client (no client-side support needed).
|
|
26
|
-
*/
|
|
27
|
-
/** First sentence (purpose) of a description — keeps the catalog one line per tool. */
|
|
28
|
-
function firstSentence(description) {
|
|
29
|
-
const nl = description.indexOf('\n');
|
|
30
|
-
const base = nl >= 0 ? description.slice(0, nl) : description;
|
|
31
|
-
const dot = base.search(/\.\s/);
|
|
32
|
-
const sentence = dot >= 0 ? base.slice(0, dot + 1) : base;
|
|
33
|
-
return sentence.length > 140 ? `${sentence.slice(0, 139)}…` : sentence;
|
|
34
|
-
}
|
|
35
11
|
/** Compact param list from a tool's zod shape — name/required/description (the description already
|
|
36
12
|
* carries the type and any enum hints, so no JSON-Schema machinery is needed). */
|
|
37
13
|
function paramInfo(shape) {
|
|
@@ -73,6 +49,33 @@ export function buildDynamicTools(allTools, profile,
|
|
|
73
49
|
callable) {
|
|
74
50
|
const reachable = callable === undefined ? allTools : allTools.filter((tool) => callable.has(tool.name));
|
|
75
51
|
const byName = new Map(reachable.map((t) => [t.name, t]));
|
|
52
|
+
const registered = new Map(allTools.map((tool) => [tool.name, tool]));
|
|
53
|
+
/**
|
|
54
|
+
* A name the catalogue cannot hand back as a callable tool.
|
|
55
|
+
*
|
|
56
|
+
* A redirect wins, because the capability moved and the old name is how an older instruction
|
|
57
|
+
* still asks for it. A name that is a real tool on a surface that cannot call it is the other
|
|
58
|
+
* case: `unknown tool` is what a typo gets, and a headline capability must not get the same
|
|
59
|
+
* sentence (#977). A name in neither set is a typo.
|
|
60
|
+
*/
|
|
61
|
+
const explainAbsent = (name) => {
|
|
62
|
+
const moved = mergedNameRedirect(name);
|
|
63
|
+
if (moved !== undefined) {
|
|
64
|
+
return {
|
|
65
|
+
error: mergedNameMessage(name, moved),
|
|
66
|
+
tool: moved.tool,
|
|
67
|
+
...(moved.action === undefined ? {} : { action: moved.action }),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (callable !== undefined && registered.has(name)) {
|
|
71
|
+
return {
|
|
72
|
+
error: `${name} is a Reticle tool and this surface cannot call it. Restart the daemon with ` +
|
|
73
|
+
`${ADVERTISE_ALL_ENV}=1, then call reticle_run { tool: "${name}", args }.`,
|
|
74
|
+
tool: name,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return { error: 'unknown tool' };
|
|
78
|
+
};
|
|
76
79
|
// The profile is a DAEMON-startup decision, so an agent that exported RETICLE_TOOL_PROFILE into its
|
|
77
80
|
// own environment sees no change and has, until now, no way to tell. Reported with the catalog.
|
|
78
81
|
const profileBlock = profile === undefined
|
|
@@ -135,7 +138,7 @@ callable) {
|
|
|
135
138
|
// turns "these are the tools" into "these are the tools I was shown".
|
|
136
139
|
const catalog = reachable.map((t) => ({
|
|
137
140
|
name: t.name,
|
|
138
|
-
summary: firstSentence(t.description),
|
|
141
|
+
summary: firstSentence(t.description, CATALOGUE_SUMMARY_LENGTH),
|
|
139
142
|
}));
|
|
140
143
|
// Names that USED to be tools, against the call that replaces each. Instructions written
|
|
141
144
|
// against an earlier release are a permanent fact of the product — `reticle_crawl` is still
|
|
@@ -171,15 +174,7 @@ callable) {
|
|
|
171
174
|
// `reticle_run` has answered a merged name with its replacement for a while; the tool
|
|
172
175
|
// asked to DISCOVER tools still said "unknown tool", which is the wrong answer in the one
|
|
173
176
|
// place an agent goes to resolve a name it is unsure of. Same redirect, same derivation.
|
|
174
|
-
|
|
175
|
-
return moved === undefined
|
|
176
|
-
? { name: n, error: 'unknown tool' }
|
|
177
|
-
: {
|
|
178
|
-
name: n,
|
|
179
|
-
error: mergedNameMessage(n, moved),
|
|
180
|
-
tool: moved.tool,
|
|
181
|
-
...(moved.action === undefined ? {} : { action: moved.action }),
|
|
182
|
-
};
|
|
177
|
+
return { name: n, ...explainAbsent(n) };
|
|
183
178
|
}),
|
|
184
179
|
...(carriesPredicate ? { predicateGrammar: predicateGrammar() } : {}),
|
|
185
180
|
});
|
|
@@ -190,9 +185,10 @@ callable) {
|
|
|
190
185
|
args: z.record(z.unknown()).optional().describe('Arguments object for that tool.'),
|
|
191
186
|
// Accepted AND FORWARDED, not merely tolerated. reticle_run is the only way to reach an
|
|
192
187
|
// unadvertised tool, so on a machine running several projects it has to be aimable — and
|
|
193
|
-
// `sessionId` is the shape an agent already uses on every other tool. Reported
|
|
194
|
-
//
|
|
195
|
-
// "no browser session for project X" while naming the very session it
|
|
188
|
+
// `sessionId` is the shape an agent already uses on every other tool. Reported from the field
|
|
189
|
+
// against every app it was tried on: it took this key, dropped it, resolved by the daemon's cwd
|
|
190
|
+
// project, and failed with "no browser session for project X" while naming the very session it
|
|
191
|
+
// had been given.
|
|
196
192
|
sessionId: z
|
|
197
193
|
.string()
|
|
198
194
|
.optional()
|
|
@@ -232,14 +228,9 @@ callable) {
|
|
|
232
228
|
getSessionMetrics().recordUnknownTool(name);
|
|
233
229
|
// ...and for 22 of those names the capability DOES exist, it just moved when tools merged.
|
|
234
230
|
// Answering "unknown tool" there is simply wrong; see merged-name-redirect.
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
return
|
|
238
|
-
error: mergedNameMessage(name, moved),
|
|
239
|
-
tool: moved.tool,
|
|
240
|
-
...(moved.action === undefined ? {} : { action: moved.action }),
|
|
241
|
-
};
|
|
242
|
-
}
|
|
231
|
+
const absent = explainAbsent(name);
|
|
232
|
+
if ('unknown tool' !== absent.error)
|
|
233
|
+
return absent;
|
|
243
234
|
return { error: `unknown tool '${name}'`, available: reachable.map((t) => t.name) };
|
|
244
235
|
}
|
|
245
236
|
// The outer aim, forwarded ONLY to a tool that declares a session — injecting it into one that
|
|
@@ -66,6 +66,12 @@ export declare const RECOVERY: {
|
|
|
66
66
|
readonly COMMAND_TIMEOUT: string;
|
|
67
67
|
readonly FLOW_STEP_MISSING: string;
|
|
68
68
|
readonly NO_POOL: string;
|
|
69
|
+
/**
|
|
70
|
+
* The two ways a platform answer can refuse a drive. Neither is a fault, and both were telling the
|
|
71
|
+
* agent that a perfectly understood refusal might be a defect in Reticle — which is how a person
|
|
72
|
+
* who deliberately switched the harness off gets a bug report filed about their own decision.
|
|
73
|
+
*/
|
|
74
|
+
readonly HARNESS_OFF: string;
|
|
69
75
|
};
|
|
70
76
|
/**
|
|
71
77
|
* The actionable next move for a known error message, or undefined when none is recognized.
|
|
@@ -199,6 +199,15 @@ export const RECOVERY = {
|
|
|
199
199
|
chromiumInstallCommand(bundledPlaywrightVersion()) +
|
|
200
200
|
'` if that is what is missing. Meanwhile drive ' +
|
|
201
201
|
'a tab the human already has open — reticle_sessions lists them.',
|
|
202
|
+
/**
|
|
203
|
+
* The two ways a platform answer can refuse a drive. Neither is a fault, and both were telling the
|
|
204
|
+
* agent that a perfectly understood refusal might be a defect in Reticle — which is how a person
|
|
205
|
+
* who deliberately switched the harness off gets a bug report filed about their own decision.
|
|
206
|
+
*/
|
|
207
|
+
HARNESS_OFF: 'A human turned autonomous driving off for this project, or has not claimed the free harness ' +
|
|
208
|
+
'months. This is a deliberate refusal and there is nothing to report: drive the app yourself ' +
|
|
209
|
+
'through the MCP tools — reticle_navigate, then reticle_act_and_wait with an `until` — which is ' +
|
|
210
|
+
'the same verification without a model driving it.',
|
|
202
211
|
};
|
|
203
212
|
/**
|
|
204
213
|
* Which OWNER each recovery belongs to, for `tool_refused`.
|
|
@@ -236,6 +245,8 @@ const REASON_OF = {
|
|
|
236
245
|
WRONG_TARGET: RefusalReason.UNSUPPORTED,
|
|
237
246
|
BAD_ARGUMENTS: RefusalReason.BAD_ARGS,
|
|
238
247
|
INVALID_NAME: RefusalReason.BAD_ARGS,
|
|
248
|
+
// Nothing about the app or the call is wrong; the feature is switched off or unpaid for.
|
|
249
|
+
HARNESS_OFF: RefusalReason.UNSUPPORTED,
|
|
239
250
|
};
|
|
240
251
|
/** Hint text back to its reason. The hints are distinct strings, so this inverts cleanly. */
|
|
241
252
|
const REASON_BY_HINT = new Map(Object.entries(RECOVERY).map(([key, hint]) => [hint, REASON_OF[key]]));
|
|
@@ -248,6 +259,10 @@ const RULES = [
|
|
|
248
259
|
{ match: /no baseline named/i, hint: RECOVERY.MISSING_BASELINE },
|
|
249
260
|
{ match: /no (?:active|compiled) recording named/i, hint: RECOVERY.MISSING_RECORDING },
|
|
250
261
|
{ match: /pairing token is required/i, hint: RECOVERY.TOKEN_REQUIRED },
|
|
262
|
+
{
|
|
263
|
+
match: /Autonomous driving is turned OFF|no harness entitlement/i,
|
|
264
|
+
hint: RECOVERY.HARNESS_OFF,
|
|
265
|
+
},
|
|
251
266
|
// Three conditions the daemon understands perfectly and still asked for a bug report about. Each
|
|
252
267
|
// needs its own rule: none of them contains "no browser session connected" (the scope miss is
|
|
253
268
|
// "no browser session FOR project 'x'", which is the opposite claim — sessions exist).
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
* them deterministically with no model in the loop at all.
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
|
-
import { ReticleTool } from '@reticlehq/core';
|
|
14
|
+
import { ReticleTool, asRecord } from '@reticlehq/core';
|
|
15
15
|
import { stepCountSchema } from './args/numeric-bounds.js';
|
|
16
|
-
import { exploreApp, harnessAvailable, MSG_NO_HARNESS_KEY } from './harness-explore.js';
|
|
16
|
+
import { exploreApp, harnessAvailable, withLinkedCredential, MSG_NO_HARNESS_KEY, } from './harness-explore.js';
|
|
17
|
+
import { DRIVER_NAMES } from '../../features/harness/drivers.js';
|
|
18
|
+
import { describeDrive, replayedFlows } from '../../features/harness/drive-report.js';
|
|
17
19
|
import { StopReason } from '../../features/harness/harness.js';
|
|
18
20
|
export const EXPLORE_TOOLS = [
|
|
19
21
|
{
|
|
@@ -27,6 +29,10 @@ export const EXPLORE_TOOLS = [
|
|
|
27
29
|
maxSteps: stepCountSchema
|
|
28
30
|
.optional()
|
|
29
31
|
.describe('Ceiling on model turns. Bounds cost, not value — the drive is graded however it ends.'),
|
|
32
|
+
driver: z
|
|
33
|
+
.enum(DRIVER_NAMES)
|
|
34
|
+
.optional()
|
|
35
|
+
.describe('Which model drives. An unconfigured one is an error, never a substitution, so an A/B cannot measure the same driver twice. Omit for the default.'),
|
|
30
36
|
sessionId: z
|
|
31
37
|
.string()
|
|
32
38
|
.optional()
|
|
@@ -35,9 +41,24 @@ export const EXPLORE_TOOLS = [
|
|
|
35
41
|
// Everything the handler returns has to be declared, or a schema-aware client never sees it.
|
|
36
42
|
outputSchema: {
|
|
37
43
|
stopReason: z.string(),
|
|
44
|
+
/** Which driver actually drove, so a comparison can never misattribute its own result. */
|
|
45
|
+
driver: z.string(),
|
|
38
46
|
steps: z.number(),
|
|
39
47
|
savedFlows: z.array(z.string()),
|
|
40
|
-
/**
|
|
48
|
+
/** Flows that already existed and were driven and written again. A second run's ordinary result. */
|
|
49
|
+
rewroteFlows: z.array(z.string()),
|
|
50
|
+
/**
|
|
51
|
+
* What the drive set out to do, read from `.reticle` BEFORE it started — every recorded
|
|
52
|
+
* journey with the consequence that must still hold, and the declared intent nobody has
|
|
53
|
+
* tested. Returned so the caller can see the drive was aimed rather than wandering.
|
|
54
|
+
*/
|
|
55
|
+
plan: z.object({ summary: z.string(), steps: z.array(z.unknown()) }),
|
|
56
|
+
/**
|
|
57
|
+
* What the drive did, derived from the calls it made and the verdicts the engine returned.
|
|
58
|
+
*
|
|
59
|
+
* Not the driver's narration. A driver that cannot write a sentence used to leave this empty,
|
|
60
|
+
* and a driver that can is the one witness with a reason to round "unknown" up to "worked".
|
|
61
|
+
*/
|
|
41
62
|
summary: z.string(),
|
|
42
63
|
/** Present when the drive broke: a model that would not answer, a wedged browser. */
|
|
43
64
|
error: z.string().optional(),
|
|
@@ -52,28 +73,80 @@ export const EXPLORE_TOOLS = [
|
|
|
52
73
|
note: z.string().optional(),
|
|
53
74
|
},
|
|
54
75
|
handler: async (deps, args) => {
|
|
55
|
-
|
|
76
|
+
// The credential `reticle link` already filed counts as configured, so somebody who has
|
|
77
|
+
// signed in and linked does not also have to export a key by hand.
|
|
78
|
+
const env = await withLinkedCredential(deps, process.env);
|
|
79
|
+
if (!harnessAvailable(env))
|
|
56
80
|
throw new Error(MSG_NO_HARNESS_KEY);
|
|
57
81
|
const persona = args['persona'];
|
|
58
82
|
const maxSteps = args['maxSteps'];
|
|
59
83
|
const sessionId = args['sessionId'];
|
|
60
|
-
const
|
|
84
|
+
const driver = args['driver'];
|
|
85
|
+
const { drive, savedFlows, rewroteFlows, driverName, plan } = await exploreApp(deps, env, {
|
|
61
86
|
...('string' === typeof persona ? { focus: persona } : {}),
|
|
62
87
|
...('number' === typeof maxSteps ? { maxSteps } : {}),
|
|
63
88
|
...('string' === typeof sessionId ? { sessionId } : {}),
|
|
89
|
+
...('string' === typeof driver ? { driverName: driver } : {}),
|
|
64
90
|
});
|
|
65
91
|
return {
|
|
66
92
|
stopReason: drive.stopReason,
|
|
93
|
+
driver: driverName,
|
|
67
94
|
steps: drive.steps,
|
|
68
95
|
savedFlows: [...savedFlows],
|
|
69
|
-
|
|
96
|
+
rewroteFlows: [...rewroteFlows],
|
|
97
|
+
plan: { summary: plan.summary, steps: [...plan.steps] },
|
|
98
|
+
// Derived, not narrated. The driver's own `summary` is appended only when it said
|
|
99
|
+
// something — it is the one part of this a model authored, so it goes last and is labelled.
|
|
100
|
+
summary: [
|
|
101
|
+
describeDrive(drive.toolCalls, [...savedFlows, ...rewroteFlows]),
|
|
102
|
+
...(0 === drive.summary.length ? [] : [`The driver's own account: ${drive.summary}`]),
|
|
103
|
+
].join('\n'),
|
|
70
104
|
...(drive.error === undefined ? {} : { error: drive.error }),
|
|
71
105
|
usage: drive.usage,
|
|
72
|
-
|
|
106
|
+
// The note only fires when the drive left NOTHING behind. A rewritten flow is a flow: it
|
|
107
|
+
// replays, it proves what it asserts, and telling its author that "nothing will replay" is
|
|
108
|
+
// a lie this tool used to tell on every second run.
|
|
109
|
+
/*
|
|
110
|
+
* The note fires only when the run left NOTHING behind — and a run that REPLAYED left
|
|
111
|
+
* plenty. Sixteen recorded journeys re-proved for zero model tokens is the cheap half of
|
|
112
|
+
* the plan doing its job, and telling its author to "raise maxSteps" reads as a failure.
|
|
113
|
+
*/
|
|
114
|
+
...(0 === savedFlows.length &&
|
|
115
|
+
0 === rewroteFlows.length &&
|
|
116
|
+
0 === replayedFlows(drive.toolCalls).length
|
|
117
|
+
? { note: `${NOTHING_RECORDED[drive.stopReason]}${failureDetail(drive)}` }
|
|
118
|
+
: {}),
|
|
73
119
|
};
|
|
74
120
|
},
|
|
75
121
|
},
|
|
76
122
|
];
|
|
123
|
+
/**
|
|
124
|
+
* The tool failure behind an empty drive, when there was one.
|
|
125
|
+
*
|
|
126
|
+
* "The drive ran out of steps before saving a flow" is true of a drive that explored happily and ran
|
|
127
|
+
* long, and ALSO of one that reached its save and was refused — and those need opposite responses.
|
|
128
|
+
* Without this the two are indistinguishable from outside the daemon, which cost a full debugging
|
|
129
|
+
* session: a driver whose `reticle_record{stop}` was rejected for a missing argument retried until
|
|
130
|
+
* the budget ended, and the only thing the caller ever saw was the advice to raise `maxSteps`.
|
|
131
|
+
*/
|
|
132
|
+
function failureDetail(drive) {
|
|
133
|
+
const failed = drive.toolCalls.filter((call) => call.isError);
|
|
134
|
+
const last = failed[failed.length - 1];
|
|
135
|
+
if (last !== undefined) {
|
|
136
|
+
const message = asRecord(last.result)['error'];
|
|
137
|
+
return ` ${String(failed.length)} tool call(s) failed during the drive; the last was ${last.name}: ${'string' === typeof message ? message : 'no message'}`;
|
|
138
|
+
}
|
|
139
|
+
// Nothing threw, so the interesting case left is a save that was ACCEPTED and wrote nothing —
|
|
140
|
+
// an empty recording reports success, and from outside the daemon that is indistinguishable from
|
|
141
|
+
// a drive that never reached its save at all.
|
|
142
|
+
const saves = drive.toolCalls.filter((call) => ReticleTool.FLOW_SAVE === call.name);
|
|
143
|
+
const save = saves[saves.length - 1];
|
|
144
|
+
if (save !== undefined) {
|
|
145
|
+
const result = asRecord(save.result);
|
|
146
|
+
return ` The flow save was accepted but kept nothing: ${JSON.stringify({ stepCount: result['stepCount'], empty: result['empty'], warning: result['warning'] })}.`;
|
|
147
|
+
}
|
|
148
|
+
return ` The drive never reached a flow save (${String(drive.toolCalls.length)} tool calls made).`;
|
|
149
|
+
}
|
|
77
150
|
/**
|
|
78
151
|
* What an empty drive means, said rather than left for the caller to infer.
|
|
79
152
|
*
|
|
@@ -55,6 +55,15 @@ function sessionFacts(deps, sessionId) {
|
|
|
55
55
|
return { driven };
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
/** Said when a report arrives with no body at all. Names both accepted fields so the retry can work. */
|
|
59
|
+
const NO_BODY_REASON = 'the report has no body — send it as `text` (or `summary`, which is the same field). Nothing was filed.';
|
|
60
|
+
/** `text`, or the `summary` alias, whichever arrived. Empty when neither did — the handler refuses. */
|
|
61
|
+
function feedbackBody(args) {
|
|
62
|
+
const text = asString(args['text'])?.trim();
|
|
63
|
+
if (text !== undefined && '' !== text)
|
|
64
|
+
return text;
|
|
65
|
+
return asString(args['summary'])?.trim() ?? '';
|
|
66
|
+
}
|
|
58
67
|
export const FEEDBACK_TOOLS = [
|
|
59
68
|
{
|
|
60
69
|
name: ReticleTool.FEEDBACK,
|
|
@@ -72,8 +81,22 @@ export const FEEDBACK_TOOLS = [
|
|
|
72
81
|
.describe('bug = a Reticle tool returned a wrong or broken result; gap = Reticle could not observe something you needed at all; ambiguity = the verification ran but you could not tell pass from fail; feature_request = something that does not exist and would have helped; improvement = something that exists but is awkward or inefficient; experience = something worked notably well and is worth keeping (pair it with `rating`).'),
|
|
73
82
|
text: z
|
|
74
83
|
.string()
|
|
75
|
-
.
|
|
84
|
+
.optional()
|
|
76
85
|
.describe('For a failure: the root-cause analysis — the call you made, what you expected, what you got, and why Reticle is at fault. For a request: what you want, in your own words.'),
|
|
86
|
+
/**
|
|
87
|
+
* `text` under the name agents reach for unprompted.
|
|
88
|
+
*
|
|
89
|
+
* Incident: field reports of a call carrying a summary and a trace coming back
|
|
90
|
+
* "String must contain at least 1 character(s)" and being lost. The body had been put in
|
|
91
|
+
* `summary` — a field this tool never had. The MCP SDK validates input BEFORE the handler, so
|
|
92
|
+
* a required `text` made those calls unreachable and unanswerable: no handler ran, nothing
|
|
93
|
+
* could explain the rejection, and the report was dropped.
|
|
94
|
+
*
|
|
95
|
+
* So `text` is optional and this is its alias; the handler takes whichever arrived and refuses
|
|
96
|
+
* — with both names in the reason — only when neither did. Widening the schema is what buys
|
|
97
|
+
* the chance to say anything at all.
|
|
98
|
+
*/
|
|
99
|
+
summary: z.string().optional().describe('Alias for `text`; send either.'),
|
|
77
100
|
need: z
|
|
78
101
|
.string()
|
|
79
102
|
.optional()
|
|
@@ -124,10 +147,26 @@ export const FEEDBACK_TOOLS = [
|
|
|
124
147
|
model: 'claude-opus-4',
|
|
125
148
|
},
|
|
126
149
|
handler: async (deps, args) => {
|
|
150
|
+
// Refuse here rather than in core's schema: core validates a REPORT, which has one body field
|
|
151
|
+
// and knows nothing about the `summary` alias, so its rejection reads "String must contain at
|
|
152
|
+
// least 1 character(s)" and names neither field. That sentence is what agents were given when
|
|
153
|
+
// their report was dropped, and it is unactionable — the agent cannot tell which of eight
|
|
154
|
+
// arguments was wrong.
|
|
155
|
+
const body = feedbackBody(args);
|
|
156
|
+
if ('' === body) {
|
|
157
|
+
return {
|
|
158
|
+
sent: false,
|
|
159
|
+
accepted: false,
|
|
160
|
+
reason: NO_BODY_REASON,
|
|
161
|
+
redacted: [],
|
|
162
|
+
context: undefined,
|
|
163
|
+
note: 'NOT accepted (see reason). Tell the human what you found so it is not lost.',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
127
166
|
const receipt = await submitFeedback({
|
|
128
167
|
source: FeedbackSource.AGENT,
|
|
129
168
|
kind: asString(args['kind']),
|
|
130
|
-
text:
|
|
169
|
+
text: body,
|
|
131
170
|
...optionalText(args, 'trace'),
|
|
132
171
|
...optionalText(args, 'need'),
|
|
133
172
|
...optionalText(args, 'impact'),
|
|
@@ -187,6 +226,14 @@ export const VERDICT_TOOLS = new Set([
|
|
|
187
226
|
// inflate the count.
|
|
188
227
|
ReticleTool.VERIFY,
|
|
189
228
|
ReticleTool.RECONCILE,
|
|
229
|
+
// Replaying ONE saved flow proves a journey still works, which is a verification by any reading a
|
|
230
|
+
// user would give the word — and it was in neither the numerator nor the denominator. It matched
|
|
231
|
+
// no name tripwire (`/assert|verify/`), it declares no `verified` field for the shape guard to
|
|
232
|
+
// catch, and its own status vocabulary spells itself `ok | drift | error`, so the three
|
|
233
|
+
// independent things that could have noticed all said nothing. `verificationOf` maps that
|
|
234
|
+
// vocabulary; `error` stays UNKNOWN, because a flow that could not load is not a verdict about
|
|
235
|
+
// anybody's app.
|
|
236
|
+
ReticleTool.FLOW_REPLAY,
|
|
190
237
|
]);
|
|
191
238
|
/**
|
|
192
239
|
* Verifications between re-offers of the prompt, and how many times we will ever offer it.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The opening sentence of a tool description, for the two surfaces that show a summary.
|
|
3
|
+
*
|
|
4
|
+
* There were two copies of this, and only one of them knew about abbreviations. The MCP surface's
|
|
5
|
+
* copy masks `e.g.` and friends before looking for a sentence end; the tool catalogue's private
|
|
6
|
+
* copy did not, so `reticle_session` reached every agent as
|
|
7
|
+
*
|
|
8
|
+
* Drive or inspect the SESSION itself, chosen by action: "tune" adjusts the presenter session (e.g.
|
|
9
|
+
*
|
|
10
|
+
* and stopped there. Everything after the abbreviation was cut, including `"yield" hands the tab
|
|
11
|
+
* back to the human and is MANDATORY before you stop driving`. The catalogue is the surface an
|
|
12
|
+
* agent reads to find out what exists, so the one instruction the product calls mandatory was
|
|
13
|
+
* missing from the only place it would have been discovered.
|
|
14
|
+
*
|
|
15
|
+
* This is the same defect the MCP copy already carried a long comment about, reappearing in a
|
|
16
|
+
* second copy written without the fix. So there is one implementation now, and the thing the two
|
|
17
|
+
* callers genuinely disagree about -- how much room a line gets -- is a parameter rather than a
|
|
18
|
+
* reason to fork the logic again.
|
|
19
|
+
*/
|
|
20
|
+
/** What the MCP surface allows a one-line description. */
|
|
21
|
+
export declare const MCP_SUMMARY_LENGTH = 160;
|
|
22
|
+
/** What the catalogue allows, which is tighter: it prints one line per tool, for every tool. */
|
|
23
|
+
export declare const CATALOGUE_SUMMARY_LENGTH = 140;
|
|
24
|
+
export declare function firstSentence(description: string, maxLength?: number): string;
|
|
Binary file
|