@reticlehq/server 2.6.0 → 2.8.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 +251 -33
- package/dist/bridge/bridge.d.ts +6 -1
- package/dist/bridge/bridge.js +27 -4
- package/dist/capsule/causal-summary.d.ts +20 -0
- package/dist/capsule/causal-summary.js +31 -6
- package/dist/capsule/divergence.js +7 -1
- package/dist/cli/chromium-hint.d.ts +68 -0
- package/dist/cli/chromium-hint.js +145 -0
- package/dist/cli/cli-doctor.js +31 -36
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-launch.d.ts +18 -0
- package/dist/cli/cli-launch.js +32 -1
- package/dist/cli/cli-parse.d.ts +16 -1
- package/dist/cli/cli-parse.js +32 -9
- package/dist/cli/cli-port.d.ts +8 -0
- package/dist/cli/cli-port.js +36 -1
- package/dist/cli/cli-verify.js +2 -2
- package/dist/cli/doctor-daemon-line.js +10 -1
- package/dist/cli/doctor-sessions-line.d.ts +12 -0
- package/dist/cli/doctor-sessions-line.js +66 -0
- package/dist/cli/port-holder.d.ts +10 -0
- package/dist/cli/port-holder.js +26 -2
- package/dist/cli.js +139 -23
- 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/daemon/daemon-resilience.js +2 -2
- package/dist/daemon/loopback-alias.d.ts +32 -0
- package/dist/daemon/loopback-alias.js +80 -0
- package/dist/events/contradictions.d.ts +14 -0
- package/dist/events/contradictions.js +26 -17
- package/dist/events/predicate-eval.d.ts +26 -3
- package/dist/events/predicate-eval.js +284 -95
- package/dist/events/predicate-parse.d.ts +1 -1
- package/dist/events/predicate-parse.js +18 -4
- package/dist/events/predicate.js +170 -18
- package/dist/events/ring-buffer.d.ts +8 -0
- package/dist/events/ring-buffer.js +34 -1
- package/dist/flows/flow-classify.js +1 -1
- package/dist/flows/flow-tools.js +12 -4
- package/dist/flows/heal-run.js +1 -1
- package/dist/honesty/blind-spots.js +6 -2
- package/dist/honesty/honesty.d.ts +11 -0
- package/dist/honesty/honesty.js +9 -1
- package/dist/honesty/unsettled.d.ts +40 -0
- package/dist/honesty/unsettled.js +78 -0
- package/dist/honesty/verified.d.ts +7 -0
- package/dist/honesty/verified.js +27 -4
- package/dist/http-server.js +11 -1
- package/dist/index.js +29 -2
- package/dist/init/agent-rules.d.ts +16 -0
- package/dist/init/agent-rules.js +96 -17
- package/dist/init/astro-patch.d.ts +2 -1
- package/dist/init/astro-patch.js +11 -5
- package/dist/init/capabilities.d.ts +22 -0
- package/dist/init/capabilities.js +76 -0
- package/dist/init/confirm.d.ts +96 -0
- package/dist/init/confirm.js +138 -0
- package/dist/init/cra.js +3 -2
- package/dist/init/detect.d.ts +8 -0
- package/dist/init/detect.js +15 -0
- package/dist/init/non-js-project.d.ts +27 -0
- package/dist/init/non-js-project.js +84 -0
- package/dist/init/plan-framework.d.ts +9 -0
- package/dist/init/plan-framework.js +72 -16
- package/dist/init/plan.d.ts +40 -4
- package/dist/init/plan.js +183 -43
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +181 -46
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +46 -6
- package/dist/init/snippets.d.ts +64 -5
- package/dist/init/snippets.js +220 -31
- package/dist/init/vite-config.js +29 -3
- package/dist/input/network-mock-tools.d.ts +19 -0
- package/dist/input/network-mock-tools.js +29 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +69 -3
- package/dist/mcp/mcp-proxy.js +215 -23
- package/dist/mcp/mcp.js +58 -7
- package/dist/mcp/proxy-lifecycle.d.ts +20 -1
- package/dist/mcp/proxy-lifecycle.js +22 -1
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/pool/playwright-launcher.js +10 -2
- 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/session/live-control-tools.js +39 -2
- package/dist/session/live-control.d.ts +7 -9
- package/dist/session/live-control.js +34 -1
- package/dist/session/no-session-diagnosis.d.ts +23 -4
- package/dist/session/no-session-diagnosis.js +128 -44
- package/dist/session/no-session-watch.js +12 -0
- package/dist/session/ref-provenance.d.ts +54 -0
- package/dist/session/ref-provenance.js +61 -0
- package/dist/session/review-store.d.ts +19 -0
- package/dist/session/review-store.js +34 -5
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +9 -0
- package/dist/session/session-manager.js +11 -0
- package/dist/session/session.d.ts +8 -0
- package/dist/session/session.js +12 -0
- package/dist/status-payload.d.ts +13 -1
- package/dist/status-payload.js +6 -1
- package/dist/telemetry/app-instrumented.d.ts +37 -0
- package/dist/telemetry/app-instrumented.js +54 -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 +0 -20
- package/dist/telemetry/bug-found.js +38 -21
- package/dist/telemetry/cli-telemetry.js +7 -1
- package/dist/telemetry/daemon-telemetry.d.ts +4 -4
- package/dist/telemetry/daemon-telemetry.js +8 -4
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/init-telemetry.js +9 -1
- package/dist/telemetry/install-source.d.ts +36 -0
- package/dist/telemetry/install-source.js +40 -0
- package/dist/telemetry/run-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.d.ts +3 -3
- package/dist/telemetry/session-metrics.js +9 -4
- package/dist/telemetry/telemetry.d.ts +7 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +72 -8
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +77 -11
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +25 -1
- package/dist/tools/contract-tools.js +4 -0
- package/dist/tools/dynamic-tools.js +1 -1
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +70 -5
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +149 -32
- package/dist/tools/lease-tools.d.ts +29 -1
- package/dist/tools/lease-tools.js +58 -5
- package/dist/tools/navigate-result.js +1 -1
- package/dist/tools/observe-tools.js +1 -1
- package/dist/tools/observed-nothing.js +1 -1
- package/dist/tools/react-grace.d.ts +48 -0
- package/dist/tools/react-grace.js +87 -0
- package/dist/tools/read-tools.js +20 -4
- package/dist/tools/settle-in-flight.d.ts +79 -0
- package/dist/tools/settle-in-flight.js +106 -0
- package/dist/tools/snapshot-delta.d.ts +7 -0
- package/dist/tools/snapshot-delta.js +130 -6
- package/dist/tools/tool-kit.d.ts +52 -5
- package/dist/tools/tool-kit.js +55 -24
- package/dist/tools/tool-surface.js +28 -9
- package/dist/tools/tools.js +38 -4
- package/dist/visual/capture-cleanup.d.ts +16 -0
- package/dist/visual/capture-cleanup.js +39 -0
- package/dist/visual/visual-tools.d.ts +18 -0
- package/dist/visual/visual-tools.js +24 -5
- package/docs/README.md +56 -11
- package/docs/actions.mdx +87 -0
- package/docs/agent-cheatsheet.md +102 -26
- package/docs/architecture.md +46 -35
- package/docs/benchmarks.md +76 -72
- package/docs/best-practices.mdx +211 -0
- package/docs/capabilities.mdx +387 -0
- package/docs/cli/affected.mdx +51 -0
- package/docs/cli/capsules.mdx +69 -0
- package/docs/cli/cloud.mdx +127 -0
- package/docs/cli/doctor.mdx +76 -0
- package/docs/cli/drive.mdx +55 -0
- package/docs/cli/feedback.mdx +67 -0
- package/docs/cli/gate.mdx +64 -0
- package/docs/cli/help.mdx +92 -0
- package/docs/cli/hunt.mdx +57 -0
- package/docs/cli/identify.mdx +62 -0
- package/docs/cli/init.mdx +94 -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 +68 -0
- package/docs/cli/restart.mdx +60 -0
- package/docs/cli/rollback.mdx +35 -0
- package/docs/cli/serve.mdx +62 -0
- package/docs/cli/status.mdx +64 -0
- package/docs/cli/stop.mdx +56 -0
- package/docs/cli/telemetry.mdx +57 -0
- package/docs/cli/update.mdx +52 -0
- package/docs/cli/verify.mdx +57 -0
- package/docs/cli/version.mdx +39 -0
- package/docs/cli/watch.mdx +55 -0
- package/docs/cli.mdx +219 -0
- package/docs/debugging.md +13 -9
- package/docs/deploy-checks.md +15 -11
- package/docs/desktop-apps.md +49 -40
- package/docs/desktop.mdx +188 -0
- package/docs/docs.json +363 -0
- package/docs/enterprise.md +20 -16
- package/docs/faq.mdx +88 -0
- package/docs/fixtures.md +11 -7
- package/docs/flows.md +26 -22
- package/docs/for-agents.md +71 -0
- package/docs/frameworks.mdx +199 -0
- package/docs/gate-plan.md +45 -41
- package/docs/gates.md +23 -19
- package/docs/getting-started.md +76 -65
- package/docs/human-control.md +22 -18
- package/docs/index.mdx +101 -0
- package/docs/install-agentic.mdx +215 -0
- package/docs/install-manual.mdx +194 -0
- package/docs/instrumentation.mdx +232 -0
- package/docs/integration-patterns.md +38 -34
- package/docs/local-registry.md +16 -12
- package/docs/multi-agent-testing.md +16 -12
- package/docs/packages/babel-plugin.mdx +70 -0
- package/docs/packages/browser.mdx +111 -0
- package/docs/packages/core.mdx +106 -0
- package/docs/packages/electron.mdx +87 -0
- package/docs/packages/eslint-plugin.mdx +84 -0
- package/docs/packages/next.mdx +91 -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 +108 -0
- package/docs/packages/vite-plugin.mdx +96 -0
- package/docs/packages.mdx +217 -0
- package/docs/platform-integration.md +33 -26
- package/docs/predicates.mdx +179 -0
- package/docs/quickstart.mdx +211 -0
- package/docs/recipes.mdx +242 -0
- package/docs/skill-file.mdx +105 -0
- package/docs/state-management.mdx +197 -0
- package/docs/style.css +14 -0
- package/docs/system-map.md +47 -32
- package/docs/telemetry-contract.md +101 -46
- package/docs/telemetry-events.mdx +430 -0
- package/docs/telemetry.md +63 -50
- package/docs/testing.md +10 -6
- package/docs/token-efficiency.md +30 -26
- package/docs/tools-act-and-wait.mdx +123 -0
- package/docs/tools-act-sequence.mdx +120 -0
- package/docs/tools-act.mdx +82 -0
- package/docs/tools-assert.mdx +70 -0
- package/docs/tools-console.mdx +59 -0
- package/docs/tools-inspect.mdx +85 -0
- package/docs/tools-navigate.mdx +63 -0
- package/docs/tools-network.mdx +86 -0
- package/docs/tools-observe.mdx +89 -0
- package/docs/tools-overview.mdx +157 -0
- package/docs/tools-query.mdx +82 -0
- package/docs/tools-session-and-feedback.mdx +91 -0
- package/docs/tools-sessions.mdx +65 -0
- package/docs/tools-snapshot.mdx +75 -0
- package/docs/tools-state.mdx +81 -0
- package/docs/tools-tools-and-run.mdx +125 -0
- package/docs/tools-wait-for.mdx +66 -0
- package/docs/troubleshooting.mdx +193 -0
- package/docs/usage.md +175 -153
- package/docs/vs-chrome-devtools-mcp.mdx +82 -0
- package/docs/vs-playwright-mcp.mdx +85 -0
- package/docs/vs-screenshots.mdx +96 -0
- package/docs/what-is-reticle.mdx +63 -0
- package/docs/why-reticle.mdx +107 -0
- package/package.json +2 -2
- 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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which tab a ref came out of, so the call that spends it cannot be routed to a different one.
|
|
3
|
+
*
|
|
4
|
+
* Reported from the field: three tabs of one project were connected, a snapshot was taken against a
|
|
5
|
+
* named session, and the act that followed omitted `sessionId`. Auto-selection picked a different
|
|
6
|
+
* tab, the ref legitimately did not exist there, and the refusal read "that ref is stale: refs are
|
|
7
|
+
* invalidated whenever the DOM re-renders" — a confident diagnosis of the wrong thing, and one that
|
|
8
|
+
* accuses the app under test. The recovery text then sends the agent to re-snapshot, which mints a
|
|
9
|
+
* fresh ref in whichever tab is chosen next, so the lesson it teaches is wrong either way.
|
|
10
|
+
*
|
|
11
|
+
* The quiet outcome is the worse one. Refs are minted from a per-document block, so the same number
|
|
12
|
+
* is a real, live, DIFFERENT element in the tab that was guessed: the identical call on a luckier day
|
|
13
|
+
* drives the wrong tab and returns a green about a page nobody asked about. That is a false green
|
|
14
|
+
* with extra steps, and it is the failure class this product exists to remove.
|
|
15
|
+
*
|
|
16
|
+
* Every `sessionId` description on the surface already promises that Reticle "refuses rather than
|
|
17
|
+
* guesses when ambiguous". This is the fact that makes the ambiguity visible: the tab a ref was
|
|
18
|
+
* handed out of is known at the moment it is handed out. Resolution is otherwise untouched — a call
|
|
19
|
+
* that names a session, carries no ref, or lands on the tab it looked at resolves silently, which is
|
|
20
|
+
* the cost the surface wording was rewritten to avoid.
|
|
21
|
+
*
|
|
22
|
+
* ponytail: one slot, not a per-ref map. It answers the reported shape — look at one tab, act on
|
|
23
|
+
* what you saw — for the cost of an assignment. A drive that interleaves refs from several tabs
|
|
24
|
+
* inside one turn needs per-ref provenance, and that is the upgrade if it ever shows up.
|
|
25
|
+
*/
|
|
26
|
+
/** Record that a tool handed out refs from this session. */
|
|
27
|
+
export declare function noteRefsMinted(sessionId: string): void;
|
|
28
|
+
export declare function refsLastMintedIn(): string | undefined;
|
|
29
|
+
/** Test seam: the module holds one slot for the process, so a test has to be able to clear it. */
|
|
30
|
+
export declare function forgetRefProvenance(): void;
|
|
31
|
+
interface RefRouting {
|
|
32
|
+
/** The ref this call is about to spend, if it carries one. */
|
|
33
|
+
ref: string | undefined;
|
|
34
|
+
/** The `sessionId` the CALLER passed — when present, nothing was guessed. */
|
|
35
|
+
explicitSessionId: string | undefined;
|
|
36
|
+
/** The session resolution actually picked. */
|
|
37
|
+
chosenSessionId: string;
|
|
38
|
+
/**
|
|
39
|
+
* The connected sessions, read ONLY once a refusal is certain. A thunk because listing them walks
|
|
40
|
+
* every session and builds a health block each time, and the answer is wanted on the rare path.
|
|
41
|
+
*/
|
|
42
|
+
connected: () => readonly {
|
|
43
|
+
sessionId: string;
|
|
44
|
+
url: string;
|
|
45
|
+
}[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The refusal for a ref that belongs to another tab, or undefined when there is no ambiguity.
|
|
49
|
+
*
|
|
50
|
+
* Silent when the minting tab is no longer connected: that ref really is dead, and the stale-ref
|
|
51
|
+
* refusal that already exists is the honest answer for it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function wrongTabRefusal(routing: RefRouting): string | undefined;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which tab a ref came out of, so the call that spends it cannot be routed to a different one.
|
|
3
|
+
*
|
|
4
|
+
* Reported from the field: three tabs of one project were connected, a snapshot was taken against a
|
|
5
|
+
* named session, and the act that followed omitted `sessionId`. Auto-selection picked a different
|
|
6
|
+
* tab, the ref legitimately did not exist there, and the refusal read "that ref is stale: refs are
|
|
7
|
+
* invalidated whenever the DOM re-renders" — a confident diagnosis of the wrong thing, and one that
|
|
8
|
+
* accuses the app under test. The recovery text then sends the agent to re-snapshot, which mints a
|
|
9
|
+
* fresh ref in whichever tab is chosen next, so the lesson it teaches is wrong either way.
|
|
10
|
+
*
|
|
11
|
+
* The quiet outcome is the worse one. Refs are minted from a per-document block, so the same number
|
|
12
|
+
* is a real, live, DIFFERENT element in the tab that was guessed: the identical call on a luckier day
|
|
13
|
+
* drives the wrong tab and returns a green about a page nobody asked about. That is a false green
|
|
14
|
+
* with extra steps, and it is the failure class this product exists to remove.
|
|
15
|
+
*
|
|
16
|
+
* Every `sessionId` description on the surface already promises that Reticle "refuses rather than
|
|
17
|
+
* guesses when ambiguous". This is the fact that makes the ambiguity visible: the tab a ref was
|
|
18
|
+
* handed out of is known at the moment it is handed out. Resolution is otherwise untouched — a call
|
|
19
|
+
* that names a session, carries no ref, or lands on the tab it looked at resolves silently, which is
|
|
20
|
+
* the cost the surface wording was rewritten to avoid.
|
|
21
|
+
*
|
|
22
|
+
* ponytail: one slot, not a per-ref map. It answers the reported shape — look at one tab, act on
|
|
23
|
+
* what you saw — for the cost of an assignment. A drive that interleaves refs from several tabs
|
|
24
|
+
* inside one turn needs per-ref provenance, and that is the upgrade if it ever shows up.
|
|
25
|
+
*/
|
|
26
|
+
/** The session the last ref-bearing result was produced from. Undefined until something mints one. */
|
|
27
|
+
let mintedIn;
|
|
28
|
+
/** Record that a tool handed out refs from this session. */
|
|
29
|
+
export function noteRefsMinted(sessionId) {
|
|
30
|
+
mintedIn = sessionId;
|
|
31
|
+
}
|
|
32
|
+
export function refsLastMintedIn() {
|
|
33
|
+
return mintedIn;
|
|
34
|
+
}
|
|
35
|
+
/** Test seam: the module holds one slot for the process, so a test has to be able to clear it. */
|
|
36
|
+
export function forgetRefProvenance() {
|
|
37
|
+
mintedIn = undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The refusal for a ref that belongs to another tab, or undefined when there is no ambiguity.
|
|
41
|
+
*
|
|
42
|
+
* Silent when the minting tab is no longer connected: that ref really is dead, and the stale-ref
|
|
43
|
+
* refusal that already exists is the honest answer for it.
|
|
44
|
+
*/
|
|
45
|
+
export function wrongTabRefusal(routing) {
|
|
46
|
+
if (routing.ref === undefined || routing.explicitSessionId !== undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
const source = mintedIn;
|
|
49
|
+
if (source === undefined || source === routing.chosenSessionId)
|
|
50
|
+
return undefined;
|
|
51
|
+
const connected = routing.connected();
|
|
52
|
+
if (!connected.some((s) => s.sessionId === source))
|
|
53
|
+
return undefined;
|
|
54
|
+
const listed = connected.map((s) => `'${s.sessionId}' (${s.url})`).join(', ');
|
|
55
|
+
return (`ref '${routing.ref}' was handed out by session '${source}', and this call named no sessionId so ` +
|
|
56
|
+
`it resolved to '${routing.chosenSessionId}'. A ref only means something in the tab it was taken ` +
|
|
57
|
+
'in — refs are minted per document, so the same one can point at a different element in another ' +
|
|
58
|
+
'tab — and Reticle refuses rather than guessing which tab you meant. Connected right now: ' +
|
|
59
|
+
`${listed}. Pass sessionId: '${source}' to act on the tab you looked at, or re-read the tab you ` +
|
|
60
|
+
'actually want first. Nothing was driven.');
|
|
61
|
+
}
|
|
@@ -21,6 +21,15 @@ export interface ReviewMark {
|
|
|
21
21
|
at: number;
|
|
22
22
|
status: MarkStatus;
|
|
23
23
|
}
|
|
24
|
+
/** Tests only — assertions on `m1` must not depend on which test ran first. */
|
|
25
|
+
export declare function resetMarkIdsForTest(): void;
|
|
26
|
+
/** What `resolve` actually did, including WHICH mark — so a caller can verify it hit the right one. */
|
|
27
|
+
export interface ResolveOutcome {
|
|
28
|
+
resolved: boolean;
|
|
29
|
+
id: string;
|
|
30
|
+
/** The retired mark's note. Absent when nothing was resolved. */
|
|
31
|
+
note?: string;
|
|
32
|
+
}
|
|
24
33
|
export declare class ReviewStore {
|
|
25
34
|
#private;
|
|
26
35
|
/** Store a new mark (status pending) stamped with the caller-supplied session-relative time. */
|
|
@@ -36,4 +45,14 @@ export declare class ReviewStore {
|
|
|
36
45
|
* false for an unknown id or an already-resolved mark (so resolve is idempotent).
|
|
37
46
|
*/
|
|
38
47
|
resolve(id: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve, and say WHICH mark was retired.
|
|
50
|
+
*
|
|
51
|
+
* The bare boolean is what made the misattribution silent: `{"resolved":true}` is equally
|
|
52
|
+
* consistent with "I retired the bug you fixed" and "I retired somebody else's". Echoing the note
|
|
53
|
+
* lets a caller check the answer against what it believed it was resolving, which is exactly what
|
|
54
|
+
* the agent that hit this asked for — it had been re-reading the whole list after every resolve
|
|
55
|
+
* and hand-diffing the notes to get the same assurance.
|
|
56
|
+
*/
|
|
57
|
+
resolveDetail(id: string): ResolveOutcome;
|
|
39
58
|
}
|
|
@@ -11,14 +11,31 @@ import { MarkStatus } from '@reticlehq/core';
|
|
|
11
11
|
*/
|
|
12
12
|
/** Prefix on review-mark ids (m1, m2, …) — distinguishes them from command ids. */
|
|
13
13
|
const MARK_ID_PREFIX = 'm';
|
|
14
|
+
/**
|
|
15
|
+
* Shared by every store in the process, so an id is never reissued.
|
|
16
|
+
*
|
|
17
|
+
* The sequence used to live on the instance, which looked right and was not: a store is created per
|
|
18
|
+
* Session, and a Session is recreated on every page reload and socket reattach. Each new store began
|
|
19
|
+
* again at `m1`, so an id an agent was still holding silently came to denote a DIFFERENT mark — and
|
|
20
|
+
* `resolve` retired that one instead, reporting success. Reported from the field by an agent that
|
|
21
|
+
* closed a bug nobody had fixed while the two it had fixed vanished unrecorded.
|
|
22
|
+
*
|
|
23
|
+
* Process-wide is deliberately not "forever": a daemon restart resets it, but a restart also
|
|
24
|
+
* destroys every session, so any id from before it resolves to nothing and `resolve` correctly
|
|
25
|
+
* returns false. The identity echo below covers what remains.
|
|
26
|
+
*/
|
|
27
|
+
let nextMarkId = 0;
|
|
28
|
+
/** Tests only — assertions on `m1` must not depend on which test ran first. */
|
|
29
|
+
export function resetMarkIdsForTest() {
|
|
30
|
+
nextMarkId = 0;
|
|
31
|
+
}
|
|
14
32
|
export class ReviewStore {
|
|
15
33
|
#marks = [];
|
|
16
|
-
#seq = 0;
|
|
17
34
|
/** Store a new mark (status pending) stamped with the caller-supplied session-relative time. */
|
|
18
35
|
add(data, at) {
|
|
19
|
-
|
|
36
|
+
nextMarkId += 1;
|
|
20
37
|
const mark = {
|
|
21
|
-
id: `${MARK_ID_PREFIX}${String(
|
|
38
|
+
id: `${MARK_ID_PREFIX}${String(nextMarkId)}`,
|
|
22
39
|
note: data.note,
|
|
23
40
|
anchor: data.anchor,
|
|
24
41
|
strategy: data.strategy,
|
|
@@ -51,10 +68,22 @@ export class ReviewStore {
|
|
|
51
68
|
* false for an unknown id or an already-resolved mark (so resolve is idempotent).
|
|
52
69
|
*/
|
|
53
70
|
resolve(id) {
|
|
71
|
+
return this.resolveDetail(id).resolved;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolve, and say WHICH mark was retired.
|
|
75
|
+
*
|
|
76
|
+
* The bare boolean is what made the misattribution silent: `{"resolved":true}` is equally
|
|
77
|
+
* consistent with "I retired the bug you fixed" and "I retired somebody else's". Echoing the note
|
|
78
|
+
* lets a caller check the answer against what it believed it was resolving, which is exactly what
|
|
79
|
+
* the agent that hit this asked for — it had been re-reading the whole list after every resolve
|
|
80
|
+
* and hand-diffing the notes to get the same assurance.
|
|
81
|
+
*/
|
|
82
|
+
resolveDetail(id) {
|
|
54
83
|
const mark = this.#marks.find((m) => m.id === id);
|
|
55
84
|
if (mark === undefined || mark.status === MarkStatus.RESOLVED)
|
|
56
|
-
return false;
|
|
85
|
+
return { resolved: false, id };
|
|
57
86
|
mark.status = MarkStatus.RESOLVED;
|
|
58
|
-
return true;
|
|
87
|
+
return { resolved: true, id, note: mark.note };
|
|
59
88
|
}
|
|
60
89
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Any edit here is a per-session tax on every user: say it in as few words as still work.
|
|
10
10
|
*/
|
|
11
|
-
const SESSION_LEASE_NOTICE = 'When you stop driving, call reticle_session {action:"yield", mode:"waiting"|"ask"}
|
|
11
|
+
const SESSION_LEASE_NOTICE = 'When you stop driving, call reticle_session {action:"yield", mode:"waiting"|"ask"}. The panel reads "live" until you do. {action:"end"} when the task is done; it revives on your next action.';
|
|
12
12
|
export function buildSessionLease(sessionId, openedAt) {
|
|
13
13
|
return { sessionId, opened_at: openedAt, IMPORTANT: SESSION_LEASE_NOTICE };
|
|
14
14
|
}
|
|
@@ -26,6 +26,15 @@ export declare class SessionManager {
|
|
|
26
26
|
count(): number;
|
|
27
27
|
/** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
|
|
28
28
|
setNoSessionHint(hint: (() => string | undefined) | undefined): void;
|
|
29
|
+
/**
|
|
30
|
+
* The same diagnosis, for a caller that is ASKING about sessions rather than failing to get one.
|
|
31
|
+
*
|
|
32
|
+
* `reticle_sessions` is the first thing an agent reaches for and it returned a bare empty array —
|
|
33
|
+
* confidently, with nothing to act on. An agent cannot tell an empty list apart from a daemon that
|
|
34
|
+
* is down, an app with no SDK, or a tab that was closed, so it stops there. The diagnosis was
|
|
35
|
+
* already computed for the error path; this exposes it to the read path too.
|
|
36
|
+
*/
|
|
37
|
+
noSessionHint(): string | undefined;
|
|
29
38
|
everConnected(): boolean;
|
|
30
39
|
/** Record a bridge-initiated close so `resolve` can explain a session that vanished. */
|
|
31
40
|
noteClosure(reason: string, at: number): void;
|
|
@@ -161,6 +161,17 @@ export class SessionManager {
|
|
|
161
161
|
setNoSessionHint(hint) {
|
|
162
162
|
this.#noSessionHint = hint;
|
|
163
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* The same diagnosis, for a caller that is ASKING about sessions rather than failing to get one.
|
|
166
|
+
*
|
|
167
|
+
* `reticle_sessions` is the first thing an agent reaches for and it returned a bare empty array —
|
|
168
|
+
* confidently, with nothing to act on. An agent cannot tell an empty list apart from a daemon that
|
|
169
|
+
* is down, an app with no SDK, or a tab that was closed, so it stops there. The diagnosis was
|
|
170
|
+
* already computed for the error path; this exposes it to the read path too.
|
|
171
|
+
*/
|
|
172
|
+
noSessionHint() {
|
|
173
|
+
return this.#noSessionHint?.();
|
|
174
|
+
}
|
|
164
175
|
/** Whether any session has connected since this daemon booted — half the diagnosis. */
|
|
165
176
|
#everConnected = false;
|
|
166
177
|
everConnected() {
|
|
@@ -135,6 +135,12 @@ export declare class Session {
|
|
|
135
135
|
total: number;
|
|
136
136
|
dropped: number;
|
|
137
137
|
};
|
|
138
|
+
/**
|
|
139
|
+
* Did the buffer lose scarce evidence from a window opened at `cursor`? The input to whether a
|
|
140
|
+
* verdict's capture was clean — see `RingBuffer.lostSince`, and never the raw drop counter, which
|
|
141
|
+
* moves for the age and churn evictions that every live page produces continuously.
|
|
142
|
+
*/
|
|
143
|
+
lostSince(cursor: number): boolean;
|
|
138
144
|
onEvent(listener: (event: ReticleEvent) => void): () => void;
|
|
139
145
|
onDisconnect(listener: () => void): () => void;
|
|
140
146
|
/** Send a command to the browser and await its reply (or time out). */
|
|
@@ -159,6 +165,8 @@ export declare class Session {
|
|
|
159
165
|
drainInbox(): InboxMessage[];
|
|
160
166
|
/** Diagnostic read of the inbox depth (does not clear). */
|
|
161
167
|
inboxSize(): number;
|
|
168
|
+
/** Everything the human has said this session, delivered or not. Never clears. */
|
|
169
|
+
inboxHistory(): readonly InboxMessage[];
|
|
162
170
|
/** Human marks still awaiting a fix (oldest first). Reading does not consume — resolveMark does. */
|
|
163
171
|
pendingMarks(): ReviewMark[];
|
|
164
172
|
/** Full mark history (pending + resolved), oldest first. */
|
package/dist/session/session.js
CHANGED
|
@@ -370,6 +370,14 @@ export class Session {
|
|
|
370
370
|
bufferHealth() {
|
|
371
371
|
return this.#buffer.bufferHealth();
|
|
372
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Did the buffer lose scarce evidence from a window opened at `cursor`? The input to whether a
|
|
375
|
+
* verdict's capture was clean — see `RingBuffer.lostSince`, and never the raw drop counter, which
|
|
376
|
+
* moves for the age and churn evictions that every live page produces continuously.
|
|
377
|
+
*/
|
|
378
|
+
lostSince(cursor) {
|
|
379
|
+
return this.#buffer.lostSince(cursor);
|
|
380
|
+
}
|
|
373
381
|
onEvent(listener) {
|
|
374
382
|
this.#listeners.add(listener);
|
|
375
383
|
return () => this.#listeners.delete(listener);
|
|
@@ -470,6 +478,10 @@ export class Session {
|
|
|
470
478
|
inboxSize() {
|
|
471
479
|
return this.#live.size();
|
|
472
480
|
}
|
|
481
|
+
/** Everything the human has said this session, delivered or not. Never clears. */
|
|
482
|
+
inboxHistory() {
|
|
483
|
+
return this.#live.history();
|
|
484
|
+
}
|
|
473
485
|
// ── Human review marks: the "annotate the bug where you see it" inbox (server-owned) ──────────
|
|
474
486
|
/** Human marks still awaiting a fix (oldest first). Reading does not consume — resolveMark does. */
|
|
475
487
|
pendingMarks() {
|
package/dist/status-payload.d.ts
CHANGED
|
@@ -14,6 +14,18 @@ interface StatusPayload {
|
|
|
14
14
|
contract: string;
|
|
15
15
|
sessionCount: number;
|
|
16
16
|
sessions: SessionInfo[];
|
|
17
|
+
/**
|
|
18
|
+
* Why nothing is connected — present ONLY when `sessionCount` is 0.
|
|
19
|
+
*
|
|
20
|
+
* `reticle status` is the most-run command in the field, and with no sessions it answered
|
|
21
|
+
* `sessionCount: 0` and stopped. That is the same dead end `reticle_sessions` used to be for
|
|
22
|
+
* agents, at the same point in the funnel — the daemon is up, the agent is attached, and the app
|
|
23
|
+
* never arrived — and it is where most installs stop. Agents got the diagnosis in 2.7.0; a human
|
|
24
|
+
* running the command we tell them to run in `init`'s closing line deserves the same sentence.
|
|
25
|
+
*/
|
|
26
|
+
why?: string;
|
|
17
27
|
}
|
|
18
|
-
export declare function statusPayload(sessionCount: number, sessions: SessionInfo[]
|
|
28
|
+
export declare function statusPayload(sessionCount: number, sessions: SessionInfo[],
|
|
29
|
+
/** The no-session diagnosis, injected so this stays pure and the port probe stays off this path. */
|
|
30
|
+
why?: string): StatusPayload;
|
|
19
31
|
export {};
|
package/dist/status-payload.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
|
|
2
2
|
import { SERVER_VERSION } from './version/server-version.js';
|
|
3
|
-
export function statusPayload(sessionCount, sessions
|
|
3
|
+
export function statusPayload(sessionCount, sessions,
|
|
4
|
+
/** The no-session diagnosis, injected so this stays pure and the port probe stays off this path. */
|
|
5
|
+
why) {
|
|
4
6
|
return {
|
|
5
7
|
running: true,
|
|
6
8
|
version: SERVER_VERSION,
|
|
7
9
|
contract: CONTRACT_FINGERPRINT,
|
|
8
10
|
sessionCount,
|
|
9
11
|
sessions,
|
|
12
|
+
// Only when there is nothing to explain away: a diagnosis printed beside a live session would
|
|
13
|
+
// contradict it.
|
|
14
|
+
...(0 === sessionCount && why !== undefined ? { why } : {}),
|
|
10
15
|
};
|
|
11
16
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When an app carrying the SDK first reaches this daemon.
|
|
3
|
+
*
|
|
4
|
+
* This is the step the whole funnel turns on, and the one nothing could measure. Registering the MCP
|
|
5
|
+
* server and instrumenting an app are separate acts, done by different commands, and the data says
|
|
6
|
+
* almost everybody does the first and almost nobody does the second — but proving that required
|
|
7
|
+
* inferring it from a window counter that resets on every flush, and which therefore reported fewer
|
|
8
|
+
* instrumented users than there were users calling tools.
|
|
9
|
+
*
|
|
10
|
+
* Once per daemon run, on the FIRST app connect only. A page that reloads, or an app that opens five
|
|
11
|
+
* tabs, must not read as five instrumented installs — the question is "did this install ever get an
|
|
12
|
+
* app wired", and it has one answer per run.
|
|
13
|
+
*
|
|
14
|
+
* Names, never values: whether `init` had been run and whether an agent was attached — never the
|
|
15
|
+
* project path, the URL, or anything from the page. The stack is deliberately NOT repeated here;
|
|
16
|
+
* `project_profiled` already carries it on the same daemon run, so the two join on sessionId.
|
|
17
|
+
*/
|
|
18
|
+
export declare function markInstrumentationClock(now: number): void;
|
|
19
|
+
export interface InstrumentationFacts {
|
|
20
|
+
/** Whether this project has a projectId stamped — i.e. `init` has run here. */
|
|
21
|
+
initialized: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether an MCP client was attached when the app arrived.
|
|
24
|
+
*
|
|
25
|
+
* Both halves present is the only state in which Reticle can do anything, and it is worth being
|
|
26
|
+
* able to see the two orders apart: an app that connects with no agent attached is a person
|
|
27
|
+
* setting up, while an agent that has been waiting is a session about to do work.
|
|
28
|
+
*/
|
|
29
|
+
agentAttached: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Report the first instrumented app of this daemon run. Best-effort and idempotent — a metric must
|
|
33
|
+
* never be the reason a session fails to register.
|
|
34
|
+
*/
|
|
35
|
+
export declare function reportAppInstrumented(facts: InstrumentationFacts, now?: () => number): void;
|
|
36
|
+
/** Tests only. */
|
|
37
|
+
export declare function resetAppInstrumented(): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When an app carrying the SDK first reaches this daemon.
|
|
3
|
+
*
|
|
4
|
+
* This is the step the whole funnel turns on, and the one nothing could measure. Registering the MCP
|
|
5
|
+
* server and instrumenting an app are separate acts, done by different commands, and the data says
|
|
6
|
+
* almost everybody does the first and almost nobody does the second — but proving that required
|
|
7
|
+
* inferring it from a window counter that resets on every flush, and which therefore reported fewer
|
|
8
|
+
* instrumented users than there were users calling tools.
|
|
9
|
+
*
|
|
10
|
+
* Once per daemon run, on the FIRST app connect only. A page that reloads, or an app that opens five
|
|
11
|
+
* tabs, must not read as five instrumented installs — the question is "did this install ever get an
|
|
12
|
+
* app wired", and it has one answer per run.
|
|
13
|
+
*
|
|
14
|
+
* Names, never values: whether `init` had been run and whether an agent was attached — never the
|
|
15
|
+
* project path, the URL, or anything from the page. The stack is deliberately NOT repeated here;
|
|
16
|
+
* `project_profiled` already carries it on the same daemon run, so the two join on sessionId.
|
|
17
|
+
*/
|
|
18
|
+
import { TelemetryEventKind } from '@reticlehq/core';
|
|
19
|
+
import { getTelemetry } from './telemetry.js';
|
|
20
|
+
let reported = false;
|
|
21
|
+
/** Set at daemon start so the first connect can report how long the install sat un-instrumented. */
|
|
22
|
+
let daemonStartedAt;
|
|
23
|
+
export function markInstrumentationClock(now) {
|
|
24
|
+
daemonStartedAt = now;
|
|
25
|
+
reported = false;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Report the first instrumented app of this daemon run. Best-effort and idempotent — a metric must
|
|
29
|
+
* never be the reason a session fails to register.
|
|
30
|
+
*/
|
|
31
|
+
export function reportAppInstrumented(facts, now = () => Date.now()) {
|
|
32
|
+
if (reported)
|
|
33
|
+
return;
|
|
34
|
+
reported = true;
|
|
35
|
+
try {
|
|
36
|
+
void getTelemetry().emit(TelemetryEventKind.APP_INSTRUMENTED, {
|
|
37
|
+
instrumentation: {
|
|
38
|
+
initialized: facts.initialized,
|
|
39
|
+
agentAttached: facts.agentAttached,
|
|
40
|
+
// How long the daemon ran before an app showed up. Large values are the interesting ones:
|
|
41
|
+
// Reticle was up, the agent had the tools, and nothing was wired for that whole time.
|
|
42
|
+
msToFirstApp: daemonStartedAt === undefined ? 0 : Math.max(0, now() - daemonStartedAt),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* never let a metric interfere with a page connecting */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Tests only. */
|
|
51
|
+
export function resetAppInstrumented() {
|
|
52
|
+
reported = false;
|
|
53
|
+
daemonStartedAt = undefined;
|
|
54
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AutomationHint } from '@reticlehq/core';
|
|
2
|
+
export interface AutomationInput {
|
|
3
|
+
env: NodeJS.ProcessEnv;
|
|
4
|
+
fileExists: (path: string) => boolean;
|
|
5
|
+
hasTty: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The most specific signal that fires, or undefined.
|
|
9
|
+
*
|
|
10
|
+
* Ordered most specific first: a container inside a Codespace with no terminal is one fact about the
|
|
11
|
+
* run, and sending the weakest of the three would waste the field. Omitted rather than sent as a
|
|
12
|
+
* "none" value, so its presence is itself the signal — the convention every other advisory field
|
|
13
|
+
* here follows.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveAutomationHint(input: AutomationInput): AutomationHint | undefined;
|
|
16
|
+
/** The real reading, taken once per process. */
|
|
17
|
+
export declare function currentAutomationHint(env: NodeJS.ProcessEnv): AutomationHint | undefined;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An ADVISORY marker that a run looks automated, for the runs `ci` cannot see.
|
|
3
|
+
*
|
|
4
|
+
* `ci` is a single environment variable, which is exactly right for a GitHub Actions runner and
|
|
5
|
+
* blind to everything else: our own gate has run from a datacenter and landed in the user data
|
|
6
|
+
* indistinguishable from somebody at a laptop.
|
|
7
|
+
*
|
|
8
|
+
* ADVISORY, and never a filter. A real person works inside a container, inside a Codespace, and over
|
|
9
|
+
* an ssh session with no terminal attached, and every one of them is a user. Nothing may drop a row
|
|
10
|
+
* because this is set; it exists so a surprising number can be looked at from a second angle, and a
|
|
11
|
+
* hint that starts being used as an exclusion has stopped being a hint. Absent means "nothing here
|
|
12
|
+
* looked automated", never "a human was present".
|
|
13
|
+
*
|
|
14
|
+
* WHAT WAS REJECTED, so nobody re-proposes it:
|
|
15
|
+
* - IP address, ASN or datacenter geolocation. Not knowable client-side, and it is a stronger
|
|
16
|
+
* identifier than anything else we collect. The one signal that would actually separate a cloud
|
|
17
|
+
* sandbox from a laptop is the one we will not collect.
|
|
18
|
+
* - Hostname patterns (`runner-*`, `ip-10-*`). A hostname is user data, and matching names is
|
|
19
|
+
* guesswork dressed as a measurement — a guessed dimension and a measured one are
|
|
20
|
+
* indistinguishable once they are in the same column.
|
|
21
|
+
* - Process ancestry. Costs a spawn on a path that must never tax a command, and reads the name of
|
|
22
|
+
* somebody else's program to classify our own user.
|
|
23
|
+
* - `npm_config_user_agent`. Says which package manager ran us, which every route shares.
|
|
24
|
+
* - An absent `$DISPLAY`. Every Linux server, every ssh session, and a large share of genuine
|
|
25
|
+
* developer machines trip it; it separates nothing.
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync } from 'node:fs';
|
|
28
|
+
import { AutomationHint } from '@reticlehq/core';
|
|
29
|
+
/** Container runtimes drop one of these at the filesystem root. */
|
|
30
|
+
const CONTAINER_MARKERS = ['/.dockerenv', '/run/.containerenv'];
|
|
31
|
+
/** Hosted dev environments that declare themselves. Self-declared, never inferred. */
|
|
32
|
+
const HOSTED_WORKSPACE_ENV = [
|
|
33
|
+
'CODESPACES',
|
|
34
|
+
'GITPOD_WORKSPACE_ID',
|
|
35
|
+
'CLOUD_SHELL',
|
|
36
|
+
'REMOTE_CONTAINERS',
|
|
37
|
+
];
|
|
38
|
+
/**
|
|
39
|
+
* The most specific signal that fires, or undefined.
|
|
40
|
+
*
|
|
41
|
+
* Ordered most specific first: a container inside a Codespace with no terminal is one fact about the
|
|
42
|
+
* run, and sending the weakest of the three would waste the field. Omitted rather than sent as a
|
|
43
|
+
* "none" value, so its presence is itself the signal — the convention every other advisory field
|
|
44
|
+
* here follows.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveAutomationHint(input) {
|
|
47
|
+
if (CONTAINER_MARKERS.some((m) => input.fileExists(m)))
|
|
48
|
+
return AutomationHint.CONTAINER;
|
|
49
|
+
if (HOSTED_WORKSPACE_ENV.some((k) => (input.env[k] ?? '') !== ''))
|
|
50
|
+
return AutomationHint.HOSTED_WORKSPACE;
|
|
51
|
+
if (!input.hasTty)
|
|
52
|
+
return AutomationHint.NO_TTY;
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
/** The real reading, taken once per process. */
|
|
56
|
+
export function currentAutomationHint(env) {
|
|
57
|
+
return resolveAutomationHint({
|
|
58
|
+
env,
|
|
59
|
+
fileExists: (p) => {
|
|
60
|
+
try {
|
|
61
|
+
return existsSync(p);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false; // an unreadable root is not evidence of anything
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
// Both ends, because a piped stdout alone is how `reticle status | jq` looks, and that is a
|
|
68
|
+
// person typing.
|
|
69
|
+
hasTty: true === process.stdout.isTTY || true === process.stdin.isTTY,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -6,24 +6,4 @@ import { type BugFound } from '@reticlehq/core';
|
|
|
6
6
|
* nothing about sessions and should stay that way. The emission site owns the dedup flag.
|
|
7
7
|
*/
|
|
8
8
|
export type BugCandidate = Omit<BugFound, 'repeat'>;
|
|
9
|
-
/**
|
|
10
|
-
* `bug.attribution` was REMOVED, deliberately, and this note is here so it is not re-added casually.
|
|
11
|
-
*
|
|
12
|
-
* It shipped twice and was wrong both times. First it stamped `app` on every contradiction, including
|
|
13
|
-
* `request-never-settled` — which Reticle raises from the ABSENCE of a settle event, so a dev-overlay
|
|
14
|
-
* poll manufactured it with the app doing nothing wrong. That was fixed, and a real drive then found
|
|
15
|
-
* it still inverted: across two full runs, EVERY `attribution: 'app'` was a misattribution, while the
|
|
16
|
-
* one defect that genuinely was a bad agent predicate carried none. One session would have published
|
|
17
|
-
* "2 defects in the app" against a true count of 0.
|
|
18
|
-
*
|
|
19
|
-
* A metric that is confidently wrong about WHOSE fault a defect is, is worse than no metric: it is
|
|
20
|
-
* the number a founder steers on, and it points at the customer. Counting nothing is recoverable;
|
|
21
|
-
* publishing a false accusation about somebody's product is not.
|
|
22
|
-
*
|
|
23
|
-
* Bringing it back needs evidence that actually separates the cases. It does not exist in the payload
|
|
24
|
-
* today: `element.present` covers "the button is missing", "the API is down" and "the agent mistyped
|
|
25
|
-
* a testid" identically, and `signal.absent` covers both a signal the app never fired and a ref
|
|
26
|
-
* Reticle drove into nothing. Until a verdict carries the reason it came out that way, the honest
|
|
27
|
-
* output is no owner at all.
|
|
28
|
-
*/
|
|
29
9
|
export declare function bugsInResult(toolName: string, result: Record<string, unknown>): BugCandidate[];
|