@reticlehq/server 2.8.0 → 2.10.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/capsule-store.d.ts +2 -2
- 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-parse.d.ts +1 -1
- package/dist/cli/cli-parse.js +1 -1
- 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/crawl/crawl.d.ts +7 -2
- package/dist/crawl/crawl.js +52 -9
- package/dist/ee/audit-log.js +2 -2
- package/dist/events/body-failures.d.ts +2 -2
- package/dist/events/contradiction-folds.d.ts +45 -0
- package/dist/events/contradiction-folds.js +42 -0
- package/dist/events/contradictions.d.ts +51 -1
- package/dist/events/contradictions.js +26 -7
- package/dist/events/declared.d.ts +45 -0
- package/dist/events/declared.js +73 -0
- package/dist/events/echo-mismatch.d.ts +9 -3
- 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/events/stale-response.d.ts +2 -3
- package/dist/events/stale-response.js +49 -2
- package/dist/events/unit-mismatch.d.ts +2 -2
- 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/flows/verify-change-tools.js +1 -1
- 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 +67 -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 +25 -0
- package/dist/index.js +60 -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 +16 -2
- package/dist/init/mcp.js +23 -1
- 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/license/license.d.ts +69 -4
- package/dist/license/license.js +84 -15
- package/dist/mcp/mcp-proxy.d.ts +7 -93
- package/dist/mcp/mcp-proxy.js +183 -250
- package/dist/mcp/mcp.d.ts +27 -2
- package/dist/mcp/mcp.js +77 -36
- package/dist/mcp/proxy-daemon-probe.d.ts +8 -0
- package/dist/mcp/proxy-daemon-probe.js +48 -0
- package/dist/mcp/proxy-handshake.d.ts +82 -1
- package/dist/mcp/proxy-handshake.js +110 -3
- 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/mcp/sse-frame-parser.d.ts +19 -0
- package/dist/mcp/sse-frame-parser.js +41 -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 +55 -2
- package/dist/session/no-session-diagnosis.js +199 -22
- 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 +172 -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-readiness.js +1 -1
- 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 +29 -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 +57 -0
- package/dist/telemetry/instrumentation-stall.js +103 -0
- package/dist/telemetry/license-activation.d.ts +48 -0
- package/dist/telemetry/license-activation.js +57 -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 +23 -3
- 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 +17 -8
- package/dist/tools/error-recovery.js +10 -6
- package/dist/tools/feedback-tools.js +31 -6
- package/dist/tools/invoke-tool.d.ts +1 -1
- package/dist/tools/invoke-tool.js +30 -8
- 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 +38 -3
- package/dist/tools/tool-names.d.ts +2 -0
- package/dist/tools/tool-names.js +2 -0
- package/dist/tools/tool-surface.d.ts +60 -0
- package/dist/tools/tool-surface.js +84 -19
- package/dist/tools/tools.js +61 -5
- 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 +6 -4
- package/docs/architecture.md +6 -0
- package/docs/benchmarks.md +60 -12
- package/docs/best-practices.mdx +11 -6
- package/docs/capabilities.mdx +204 -57
- 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 +20 -4
- 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 +31 -2
- package/docs/desktop.mdx +276 -80
- package/docs/docs.json +13 -12
- package/docs/enterprise.md +9 -3
- package/docs/faq.mdx +49 -35
- package/docs/fixtures.md +2 -0
- package/docs/flows.md +7 -5
- package/docs/for-agents.md +3 -3
- package/docs/frameworks.mdx +46 -7
- package/docs/gate-plan.md +4 -2
- package/docs/gates.md +8 -6
- 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 +63 -23
- 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 +33 -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 +35 -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 +55 -24
- package/docs/tools-wait-for.mdx +34 -15
- package/docs/troubleshooting.mdx +80 -10
- package/docs/usage.md +46 -24
- 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
|
@@ -9,5 +9,73 @@
|
|
|
9
9
|
* the probe refreshes in the background and the resolve path reads a cached answer. A stale answer
|
|
10
10
|
* is fine here — it is a hint in an error message, not a decision.
|
|
11
11
|
*/
|
|
12
|
+
/**
|
|
13
|
+
* What the probe actually learned about a port. Three states, because two cannot express this.
|
|
14
|
+
*
|
|
15
|
+
* A bare TCP connect is not enough (macOS AirPlay Receiver holds 5000 on every Mac and would read as
|
|
16
|
+
* the user's dev server), and a document check alone is not enough either (a compiling SSR server
|
|
17
|
+
* reads as absent). The thing in between — accepted the connection, said nothing in time — is a
|
|
18
|
+
* listener we cannot classify, and reporting that honestly is the point.
|
|
19
|
+
*/
|
|
20
|
+
export declare const PortState: {
|
|
21
|
+
/** Nothing accepted a connection on either loopback family. */
|
|
22
|
+
readonly CLOSED: "closed";
|
|
23
|
+
/** Accepted a connection, then answered nothing within the budget. Something IS there. */
|
|
24
|
+
readonly CONNECTED_NO_ANSWER: "connected-no-answer";
|
|
25
|
+
/** Answered with a document: a dev server serving an app. */
|
|
26
|
+
readonly SERVES_DOCUMENT: "serves-document";
|
|
27
|
+
};
|
|
28
|
+
export type PortState = (typeof PortState)[keyof typeof PortState];
|
|
29
|
+
/** The probe's answer for one port, however it was obtained. */
|
|
30
|
+
export declare function classifyPort(port: number, probe: (port: number) => Promise<PortState>): Promise<PortState>;
|
|
31
|
+
/**
|
|
32
|
+
* `localhost`, not `127.0.0.1` — so BOTH address families are tried.
|
|
33
|
+
*
|
|
34
|
+
* Measured: a plain `vite --port 4311` on this machine listens on `[::1]` only, and a probe pinned to
|
|
35
|
+
* the IPv4 loopback cannot see it. The old TCP probe had the same blind spot, which meant the
|
|
36
|
+
* diagnostic could miss the very dev server it exists to find while still reporting an unrelated
|
|
37
|
+
* service that happens to bind both stacks (macOS AirPlay does).
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Both loopback addresses, BY ADDRESS — never by the name `localhost`.
|
|
41
|
+
*
|
|
42
|
+
* A dev server started with `--host` binds the wildcards `0.0.0.0` and `[::]` rather than the
|
|
43
|
+
* loopbacks, and a wildcard bind serves only the family it belongs to. Asking for `localhost` hands
|
|
44
|
+
* the choice of family to the resolver: on Windows it prefers `::1`, so a `0.0.0.0` listener is
|
|
45
|
+
* invisible and the scan reports "nothing is listening" about a dev server that is plainly running.
|
|
46
|
+
* Reported from the field on exactly that setup.
|
|
47
|
+
*
|
|
48
|
+
* `[::1]` is also how a `[::]` bind is reached, and on most stacks a `[::]` bind additionally
|
|
49
|
+
* accepts v4-mapped connections — so between the two, every ordinary bind is covered.
|
|
50
|
+
*/
|
|
51
|
+
export declare const PROBE_HOSTS: readonly string[];
|
|
52
|
+
/**
|
|
53
|
+
* Is anything serving a document on this port, on EITHER loopback family?
|
|
54
|
+
*
|
|
55
|
+
* "Either" and not "both": one answer is enough, and requiring both would make the wildcard binds
|
|
56
|
+
* this exists to see fail the check again. A family that errors (no IPv6 stack at all) is an
|
|
57
|
+
* absence, never a rejection — a diagnostic must not throw at the tool it is diagnosing.
|
|
58
|
+
*/
|
|
59
|
+
export declare function anyFamilyServes(port: number, probe?: (port: number, host: string) => Promise<boolean>): Promise<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* Every candidate port, split by what the probe could actually establish.
|
|
62
|
+
*
|
|
63
|
+
* `slow` is the set that accepted a connection and then said nothing inside the budget. Those ports
|
|
64
|
+
* are NOT dev servers as far as this check knows, and they are emphatically not absent — an SSR
|
|
65
|
+
* framework compiling its first route lands here every time. Reported separately so the diagnosis
|
|
66
|
+
* can stop telling people to start a server that is already running.
|
|
67
|
+
*/
|
|
68
|
+
export declare function probeDevServerStates(ports?: readonly number[], probe?: (port: number) => Promise<PortState>): Promise<{
|
|
69
|
+
serving: number[];
|
|
70
|
+
slow: number[];
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* The strongest state either loopback family reports.
|
|
74
|
+
*
|
|
75
|
+
* Serving a document beats a bare connection beats nothing, so a server bound to only one family is
|
|
76
|
+
* still seen at its best — the same "either, not both" rule `anyFamilyServes` follows, extended to
|
|
77
|
+
* three states.
|
|
78
|
+
*/
|
|
79
|
+
export declare function anyFamilyState(port: number, probe?: (port: number, host: string) => Promise<PortState>): Promise<PortState>;
|
|
12
80
|
/** Every candidate port serving a document, in ascending order. Never rejects. */
|
|
13
81
|
export declare function probeDevServers(ports?: readonly number[], probe?: (port: number) => Promise<boolean>): Promise<number[]>;
|
|
@@ -12,8 +12,37 @@
|
|
|
12
12
|
import { request } from 'node:http';
|
|
13
13
|
import { DEV_SERVER_PORTS } from '../cli/cli-port.js';
|
|
14
14
|
import { looksLikeDevServer } from './looks-like-dev-server.js';
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Give up fast, but do not call giving up an absence.
|
|
17
|
+
*
|
|
18
|
+
* This budget suits Vite handing back a static index.html. It does NOT suit an SSR framework
|
|
19
|
+
* compiling a route on the first request: Nuxt and Next routinely take seconds cold, and treating
|
|
20
|
+
* that as "nothing is there" told a reporter to start a dev server that was already serving 57KB of
|
|
21
|
+
* HTML — advice that would have hit their dev lock with a second `nuxt dev`.
|
|
22
|
+
*
|
|
23
|
+
* So the budget stays short and the TIMEOUT changed meaning instead. See PortState.
|
|
24
|
+
*/
|
|
16
25
|
const HTTP_PROBE_TIMEOUT_MS = 400;
|
|
26
|
+
/**
|
|
27
|
+
* What the probe actually learned about a port. Three states, because two cannot express this.
|
|
28
|
+
*
|
|
29
|
+
* A bare TCP connect is not enough (macOS AirPlay Receiver holds 5000 on every Mac and would read as
|
|
30
|
+
* the user's dev server), and a document check alone is not enough either (a compiling SSR server
|
|
31
|
+
* reads as absent). The thing in between — accepted the connection, said nothing in time — is a
|
|
32
|
+
* listener we cannot classify, and reporting that honestly is the point.
|
|
33
|
+
*/
|
|
34
|
+
export const PortState = {
|
|
35
|
+
/** Nothing accepted a connection on either loopback family. */
|
|
36
|
+
CLOSED: 'closed',
|
|
37
|
+
/** Accepted a connection, then answered nothing within the budget. Something IS there. */
|
|
38
|
+
CONNECTED_NO_ANSWER: 'connected-no-answer',
|
|
39
|
+
/** Answered with a document: a dev server serving an app. */
|
|
40
|
+
SERVES_DOCUMENT: 'serves-document',
|
|
41
|
+
};
|
|
42
|
+
/** The probe's answer for one port, however it was obtained. */
|
|
43
|
+
export function classifyPort(port, probe) {
|
|
44
|
+
return probe(port).catch(() => PortState.CLOSED);
|
|
45
|
+
}
|
|
17
46
|
/**
|
|
18
47
|
* `localhost`, not `127.0.0.1` — so BOTH address families are tried.
|
|
19
48
|
*
|
|
@@ -22,7 +51,19 @@ const HTTP_PROBE_TIMEOUT_MS = 400;
|
|
|
22
51
|
* diagnostic could miss the very dev server it exists to find while still reporting an unrelated
|
|
23
52
|
* service that happens to bind both stacks (macOS AirPlay does).
|
|
24
53
|
*/
|
|
25
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Both loopback addresses, BY ADDRESS — never by the name `localhost`.
|
|
56
|
+
*
|
|
57
|
+
* A dev server started with `--host` binds the wildcards `0.0.0.0` and `[::]` rather than the
|
|
58
|
+
* loopbacks, and a wildcard bind serves only the family it belongs to. Asking for `localhost` hands
|
|
59
|
+
* the choice of family to the resolver: on Windows it prefers `::1`, so a `0.0.0.0` listener is
|
|
60
|
+
* invisible and the scan reports "nothing is listening" about a dev server that is plainly running.
|
|
61
|
+
* Reported from the field on exactly that setup.
|
|
62
|
+
*
|
|
63
|
+
* `[::1]` is also how a `[::]` bind is reached, and on most stacks a `[::]` bind additionally
|
|
64
|
+
* accepts v4-mapped connections — so between the two, every ordinary bind is covered.
|
|
65
|
+
*/
|
|
66
|
+
export const PROBE_HOSTS = ['127.0.0.1', '::1'];
|
|
26
67
|
/**
|
|
27
68
|
* Does this port serve a DOCUMENT — i.e. is it a dev server rather than merely something listening?
|
|
28
69
|
*
|
|
@@ -30,23 +71,77 @@ const PROBE_HOST = 'localhost';
|
|
|
30
71
|
* port 5000) as the user's dev server. See looks-like-dev-server for what it actually answers and
|
|
31
72
|
* why "a socket accepted" is the wrong question.
|
|
32
73
|
*/
|
|
33
|
-
function
|
|
74
|
+
function probeState(port, host) {
|
|
34
75
|
return new Promise((resolve) => {
|
|
76
|
+
// Whether the SOCKET came up is tracked separately from whether a response did, because that is
|
|
77
|
+
// the whole difference between "nothing is there" and "something is there and still compiling".
|
|
78
|
+
let connected = false;
|
|
35
79
|
const req = request({ host, port, path: '/', method: 'GET', timeout: HTTP_PROBE_TIMEOUT_MS }, (res) => {
|
|
36
80
|
const answer = looksLikeDevServer(res.statusCode ?? 0, res.headers['content-type']);
|
|
37
81
|
res.resume(); // drain, so the socket closes rather than lingering
|
|
38
|
-
resolve(answer);
|
|
82
|
+
resolve(answer ? PortState.SERVES_DOCUMENT : PortState.CLOSED);
|
|
83
|
+
});
|
|
84
|
+
req.once('socket', (socket) => {
|
|
85
|
+
socket.once('connect', () => {
|
|
86
|
+
connected = true;
|
|
87
|
+
});
|
|
39
88
|
});
|
|
40
89
|
req.once('timeout', () => {
|
|
41
90
|
req.destroy();
|
|
42
|
-
resolve(
|
|
91
|
+
resolve(connected ? PortState.CONNECTED_NO_ANSWER : PortState.CLOSED);
|
|
43
92
|
});
|
|
44
|
-
req.once('error', () => resolve(
|
|
93
|
+
req.once('error', () => resolve(PortState.CLOSED));
|
|
45
94
|
req.end();
|
|
46
95
|
});
|
|
47
96
|
}
|
|
97
|
+
/** Kept for callers that only need the yes/no. A slow listener is NOT a dev server, but it is not absent. */
|
|
98
|
+
function servesDocument(port, host) {
|
|
99
|
+
return probeState(port, host).then((state) => state === PortState.SERVES_DOCUMENT);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Is anything serving a document on this port, on EITHER loopback family?
|
|
103
|
+
*
|
|
104
|
+
* "Either" and not "both": one answer is enough, and requiring both would make the wildcard binds
|
|
105
|
+
* this exists to see fail the check again. A family that errors (no IPv6 stack at all) is an
|
|
106
|
+
* absence, never a rejection — a diagnostic must not throw at the tool it is diagnosing.
|
|
107
|
+
*/
|
|
108
|
+
export async function anyFamilyServes(port, probe = servesDocument) {
|
|
109
|
+
const answers = await Promise.all(PROBE_HOSTS.map((host) => probe(port, host).catch(() => false)));
|
|
110
|
+
return answers.some((up) => up);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Every candidate port, split by what the probe could actually establish.
|
|
114
|
+
*
|
|
115
|
+
* `slow` is the set that accepted a connection and then said nothing inside the budget. Those ports
|
|
116
|
+
* are NOT dev servers as far as this check knows, and they are emphatically not absent — an SSR
|
|
117
|
+
* framework compiling its first route lands here every time. Reported separately so the diagnosis
|
|
118
|
+
* can stop telling people to start a server that is already running.
|
|
119
|
+
*/
|
|
120
|
+
export async function probeDevServerStates(ports = [...DEV_SERVER_PORTS], probe = (p) => anyFamilyState(p)) {
|
|
121
|
+
const states = await Promise.all(ports.map((p) => classifyPort(p, probe).then((state) => ({ port: p, state }))));
|
|
122
|
+
const pick = (want) => states
|
|
123
|
+
.filter((s) => s.state === want)
|
|
124
|
+
.map((s) => s.port)
|
|
125
|
+
.sort((a, b) => a - b);
|
|
126
|
+
return { serving: pick(PortState.SERVES_DOCUMENT), slow: pick(PortState.CONNECTED_NO_ANSWER) };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The strongest state either loopback family reports.
|
|
130
|
+
*
|
|
131
|
+
* Serving a document beats a bare connection beats nothing, so a server bound to only one family is
|
|
132
|
+
* still seen at its best — the same "either, not both" rule `anyFamilyServes` follows, extended to
|
|
133
|
+
* three states.
|
|
134
|
+
*/
|
|
135
|
+
export async function anyFamilyState(port, probe = probeState) {
|
|
136
|
+
const answers = await Promise.all(PROBE_HOSTS.map((host) => probe(port, host).catch(() => PortState.CLOSED)));
|
|
137
|
+
if (answers.includes(PortState.SERVES_DOCUMENT))
|
|
138
|
+
return PortState.SERVES_DOCUMENT;
|
|
139
|
+
if (answers.includes(PortState.CONNECTED_NO_ANSWER))
|
|
140
|
+
return PortState.CONNECTED_NO_ANSWER;
|
|
141
|
+
return PortState.CLOSED;
|
|
142
|
+
}
|
|
48
143
|
/** Every candidate port serving a document, in ascending order. Never rejects. */
|
|
49
|
-
export async function probeDevServers(ports = [...DEV_SERVER_PORTS], probe = (p) =>
|
|
144
|
+
export async function probeDevServers(ports = [...DEV_SERVER_PORTS], probe = (p) => anyFamilyServes(p)) {
|
|
50
145
|
const results = await Promise.all(ports.map((p) => probe(p).then((up) => (up ? p : null))));
|
|
51
146
|
return results.filter((p) => p !== null).sort((a, b) => a - b);
|
|
52
147
|
}
|
|
@@ -36,6 +36,32 @@ export declare class LastAct {
|
|
|
36
36
|
* cannot forget to clean up state that was never written.
|
|
37
37
|
*/
|
|
38
38
|
markActed(cursor: number, action: string | undefined, mutatedWithin: number | undefined): void;
|
|
39
|
+
/**
|
|
40
|
+
* A navigation — by URL or by reload — moves the floor without being an act.
|
|
41
|
+
*
|
|
42
|
+
* Reported from the field: an agent reloaded the page, restarted its API, then asserted four
|
|
43
|
+
* strings that were on the screen. Every clause passed and the verdict came back `contradicted`,
|
|
44
|
+
* citing hundreds of failed requests, every one against a resource that no longer existed. The
|
|
45
|
+
* failures were real once; they belonged to the document the reload destroyed.
|
|
46
|
+
*
|
|
47
|
+
* The floor was set only by act, act_sequence and act_and_wait, so navigating left it where it was
|
|
48
|
+
* — or unset, which the caller reads as "judge the whole session". `queryEvents` is journal-backed,
|
|
49
|
+
* so the whole session is durable history that outlives the page. A navigation has to move this or
|
|
50
|
+
* every rule reading the window inherits evidence from a page that is gone.
|
|
51
|
+
*
|
|
52
|
+
* Deliberately NOT `markActed`: a navigation records no act effect. The "this click did nothing"
|
|
53
|
+
* check reads an action with no measured mutation, and a reload written there would be accused of
|
|
54
|
+
* being a dead click.
|
|
55
|
+
*/
|
|
56
|
+
markNavigated(cursor: number): void;
|
|
57
|
+
/**
|
|
58
|
+
* The evaluation floor: the most recent thing that changed the page, whichever kind it was.
|
|
59
|
+
*
|
|
60
|
+
* `Math.max` rather than last-writer-wins, because the two are recorded by different tools and
|
|
61
|
+
* their order is the agent's, not ours: an act then a reload must floor at the reload, and a reload
|
|
62
|
+
* then an act must floor at the act. Either one going backwards would re-admit the evidence this
|
|
63
|
+
* exists to exclude.
|
|
64
|
+
*/
|
|
39
65
|
cursor(): number | undefined;
|
|
40
66
|
markSource(source: string | undefined): void;
|
|
41
67
|
source(): string | undefined;
|
package/dist/session/last-act.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export class LastAct {
|
|
2
2
|
#cursor;
|
|
3
|
+
/** The cursor of the most recent navigation, kept apart from an act's so neither overwrites it. */
|
|
4
|
+
#navigated;
|
|
3
5
|
#source;
|
|
4
6
|
#effect;
|
|
5
7
|
/**
|
|
@@ -20,8 +22,40 @@ export class LastAct {
|
|
|
20
22
|
this.#cursor = cursor;
|
|
21
23
|
this.#effect = { action, mutatedWithin };
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* A navigation — by URL or by reload — moves the floor without being an act.
|
|
27
|
+
*
|
|
28
|
+
* Reported from the field: an agent reloaded the page, restarted its API, then asserted four
|
|
29
|
+
* strings that were on the screen. Every clause passed and the verdict came back `contradicted`,
|
|
30
|
+
* citing hundreds of failed requests, every one against a resource that no longer existed. The
|
|
31
|
+
* failures were real once; they belonged to the document the reload destroyed.
|
|
32
|
+
*
|
|
33
|
+
* The floor was set only by act, act_sequence and act_and_wait, so navigating left it where it was
|
|
34
|
+
* — or unset, which the caller reads as "judge the whole session". `queryEvents` is journal-backed,
|
|
35
|
+
* so the whole session is durable history that outlives the page. A navigation has to move this or
|
|
36
|
+
* every rule reading the window inherits evidence from a page that is gone.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately NOT `markActed`: a navigation records no act effect. The "this click did nothing"
|
|
39
|
+
* check reads an action with no measured mutation, and a reload written there would be accused of
|
|
40
|
+
* being a dead click.
|
|
41
|
+
*/
|
|
42
|
+
markNavigated(cursor) {
|
|
43
|
+
this.#navigated = cursor;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The evaluation floor: the most recent thing that changed the page, whichever kind it was.
|
|
47
|
+
*
|
|
48
|
+
* `Math.max` rather than last-writer-wins, because the two are recorded by different tools and
|
|
49
|
+
* their order is the agent's, not ours: an act then a reload must floor at the reload, and a reload
|
|
50
|
+
* then an act must floor at the act. Either one going backwards would re-admit the evidence this
|
|
51
|
+
* exists to exclude.
|
|
52
|
+
*/
|
|
23
53
|
cursor() {
|
|
24
|
-
|
|
54
|
+
if (this.#cursor === undefined)
|
|
55
|
+
return this.#navigated;
|
|
56
|
+
if (this.#navigated === undefined)
|
|
57
|
+
return this.#cursor;
|
|
58
|
+
return Math.max(this.#cursor, this.#navigated);
|
|
25
59
|
}
|
|
26
60
|
markSource(source) {
|
|
27
61
|
this.#source = source;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { noteEmptyRead } from '../tools/observed-nothing.js';
|
|
3
|
-
import { AGENT_ASK_NOTICE, AGENT_WAITING_NOTICE, PresenterTone, SessionState, } from '@reticlehq/core';
|
|
3
|
+
import { AGENT_ASK_NOTICE, AGENT_WAITING_NOTICE, PresenterTone, SessionState, YIELD_WITHOUT_SESSION_NOTE, } from '@reticlehq/core';
|
|
4
4
|
import { ReticleTool } from '../tools/tool-names.js';
|
|
5
5
|
import { sessionIdShape } from '../tools/tool-kit.js';
|
|
6
6
|
import { asNumber, asString } from '../tools/tools-helpers.js';
|
|
@@ -9,6 +9,20 @@ import { recoveryFor } from '../tools/error-recovery.js';
|
|
|
9
9
|
/** Default + ceiling for the readiness wait — keep it short so a truly-missing app fails fast. */
|
|
10
10
|
const WAIT_READY_DEFAULT_MS = 5000;
|
|
11
11
|
const WAIT_READY_MAX_MS = 30000;
|
|
12
|
+
/**
|
|
13
|
+
* Is this a turn ending with nothing attached, rather than a call about a specific tab?
|
|
14
|
+
*
|
|
15
|
+
* Only true when the caller named NO session and none is connected. Ending a turn is a statement
|
|
16
|
+
* about the agent, not about a tab, so it must not fail merely because no app was ever wired — that
|
|
17
|
+
* made a call documented as MANDATORY fail in the most common state a daemon is in, and the agents
|
|
18
|
+
* that hit it stopped calling it and wrote the gap into prose instead.
|
|
19
|
+
*
|
|
20
|
+
* Naming a session that does not exist stays an error. That is a mistake about WHICH tab you are
|
|
21
|
+
* talking to, and swallowing it would hide a real one.
|
|
22
|
+
*/
|
|
23
|
+
function endingTurnWithNothingAttached(deps, id) {
|
|
24
|
+
return id === undefined && 0 === deps.sessions.count();
|
|
25
|
+
}
|
|
12
26
|
/**
|
|
13
27
|
* Live-control agent tools: the agent's side of the human-in-the-loop control surface.
|
|
14
28
|
*
|
|
@@ -29,9 +43,19 @@ export const LIVE_CONTROL_TOOLS = [
|
|
|
29
43
|
'(calm, terminal) and shows the optional `summary` on the panel. If you are just finishing a ' +
|
|
30
44
|
'turn or waiting on the human, call reticle_session{action:"yield"} instead (revivable). Idempotent.',
|
|
31
45
|
inputSchema: { summary: z.string().optional(), ...sessionIdShape },
|
|
32
|
-
|
|
46
|
+
// `sessionId` is optional because there may genuinely not be one — see the no-op below. An
|
|
47
|
+
// empty string would read as a real id in a log, which is worse than its absence.
|
|
48
|
+
outputSchema: {
|
|
49
|
+
ended: z.boolean(),
|
|
50
|
+
sessionId: z.string().optional(),
|
|
51
|
+
note: z.string().optional(),
|
|
52
|
+
},
|
|
33
53
|
handler: (deps, args) => {
|
|
34
|
-
const
|
|
54
|
+
const requested = asString(args['sessionId']);
|
|
55
|
+
if (endingTurnWithNothingAttached(deps, requested)) {
|
|
56
|
+
return Promise.resolve({ ended: true, note: YIELD_WITHOUT_SESSION_NOTE });
|
|
57
|
+
}
|
|
58
|
+
const session = deps.sessions.resolve(requested);
|
|
35
59
|
// One PRESENTER push for the transition; the optional summary rides the same push.
|
|
36
60
|
session.setState(SessionState.ENDED, asString(args['summary']));
|
|
37
61
|
return Promise.resolve({ ended: true, sessionId: session.id });
|
|
@@ -55,10 +79,23 @@ export const LIVE_CONTROL_TOOLS = [
|
|
|
55
79
|
.describe('For mode:"ask", the question to show the human on the panel.'),
|
|
56
80
|
...sessionIdShape,
|
|
57
81
|
},
|
|
58
|
-
outputSchema: {
|
|
82
|
+
outputSchema: {
|
|
83
|
+
yielded: z.boolean(),
|
|
84
|
+
mode: z.string(),
|
|
85
|
+
sessionId: z.string().optional(),
|
|
86
|
+
note: z.string().optional(),
|
|
87
|
+
},
|
|
59
88
|
handler: (deps, args) => {
|
|
60
|
-
const
|
|
89
|
+
const requested = asString(args['sessionId']);
|
|
61
90
|
const ask = asString(args['mode']) === PresenterTone.ASK;
|
|
91
|
+
if (endingTurnWithNothingAttached(deps, requested)) {
|
|
92
|
+
return Promise.resolve({
|
|
93
|
+
yielded: true,
|
|
94
|
+
mode: ask ? PresenterTone.ASK : PresenterTone.WAITING,
|
|
95
|
+
note: YIELD_WITHOUT_SESSION_NOTE,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const session = deps.sessions.resolve(requested);
|
|
62
99
|
const note = asString(args['note']);
|
|
63
100
|
const tone = ask ? PresenterTone.ASK : PresenterTone.WAITING;
|
|
64
101
|
const text = ask && note !== undefined && note.trim().length > 0
|
|
@@ -18,13 +18,22 @@
|
|
|
18
18
|
* Pure: everything it needs is passed in, so the probe that discovers listening ports stays out of
|
|
19
19
|
* the hot resolve() path and this stays unit-testable.
|
|
20
20
|
*/
|
|
21
|
-
interface NoSessionFacts {
|
|
21
|
+
export interface NoSessionFacts {
|
|
22
22
|
/** Whether ANY session has connected to this daemon since it booted. */
|
|
23
23
|
everConnected: boolean;
|
|
24
24
|
/** Whether this project has been through `reticle init` (a .reticle.json / projectId is present). */
|
|
25
25
|
initialized: boolean;
|
|
26
26
|
/** Localhost ports with something listening that looks like a dev server. */
|
|
27
27
|
listening: readonly number[];
|
|
28
|
+
/**
|
|
29
|
+
* Ports that ACCEPTED a connection and then answered nothing inside the probe budget.
|
|
30
|
+
*
|
|
31
|
+
* Not dev servers as far as the probe knows, and absolutely not absent. An SSR framework
|
|
32
|
+
* compiling its first route lands here every time, and calling that "nothing is listening" told a
|
|
33
|
+
* reporter to start a Nuxt server that was already serving — advice that would have hit their dev
|
|
34
|
+
* lock. Named in the message so the reader can tell the two apart.
|
|
35
|
+
*/
|
|
36
|
+
slowListeners?: readonly number[];
|
|
28
37
|
/** The port this daemon is on — half of the mismatch the old message asked about. */
|
|
29
38
|
port: number;
|
|
30
39
|
/**
|
|
@@ -53,6 +62,51 @@ interface NoSessionFacts {
|
|
|
53
62
|
* no `.reticle.json` at all and is working perfectly.
|
|
54
63
|
*/
|
|
55
64
|
projectPort?: number;
|
|
65
|
+
/**
|
|
66
|
+
* An app HAS connected on this port for this project before — read from durable state, not from
|
|
67
|
+
* this process.
|
|
68
|
+
*
|
|
69
|
+
* `everConnected` is a per-process boolean, and daemons are short-lived by design: they idle out
|
|
70
|
+
* and respawn. So a fresh daemon said it had "never seen one" seconds after its predecessor served
|
|
71
|
+
* a session, and the reader was sent hunting a `.reticle.json` that was never missing. The two
|
|
72
|
+
* claims mean opposite things — one is evidence about an install, the other is a fact about a
|
|
73
|
+
* process that booted four seconds ago — and only this one can tell them apart.
|
|
74
|
+
*/
|
|
75
|
+
previouslyConnected?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* The framework `.reticle.json` declares, when there is one.
|
|
78
|
+
*
|
|
79
|
+
* Used to RANK the causes rather than to print a static differential. Nuxt is the case that made
|
|
80
|
+
* the difference: it does not register a new plugin on HMR, so a dev server older than the wiring
|
|
81
|
+
* is the single most likely cause there — and `reticle init` says so at install time while the
|
|
82
|
+
* hint the agent reads hours later never mentioned it.
|
|
83
|
+
*/
|
|
84
|
+
framework?: string;
|
|
85
|
+
/**
|
|
86
|
+
* `.reticle.json` files found OUTSIDE this daemon's directory — a workspace app, a repo root.
|
|
87
|
+
*
|
|
88
|
+
* Editors launch MCP servers from wherever they like, and the config then sits under the app
|
|
89
|
+
* package while the daemon stands in the user's home. The old message read that as "this project
|
|
90
|
+
* has no SDK" and sent a working install back through `reticle init`.
|
|
91
|
+
*/
|
|
92
|
+
configsElsewhere?: readonly {
|
|
93
|
+
directory: string;
|
|
94
|
+
projectId?: string;
|
|
95
|
+
}[];
|
|
96
|
+
/**
|
|
97
|
+
* Every directory the config search actually checked.
|
|
98
|
+
*
|
|
99
|
+
* "There is no `.reticle.json`" is unfalsifiable without it, and the claim was wrong often enough
|
|
100
|
+
* that a reader deserves to check it.
|
|
101
|
+
*/
|
|
102
|
+
searchedDirectories?: readonly string[];
|
|
103
|
+
/**
|
|
104
|
+
* How long this daemon has been running with no app connected, in milliseconds.
|
|
105
|
+
*
|
|
106
|
+
* Present only when the stall clock is active and no app has arrived. Used to surface "the
|
|
107
|
+
* install never finished" to the user-facing diagnosis rather than only to telemetry.
|
|
108
|
+
*/
|
|
109
|
+
daemonUpMs?: number;
|
|
56
110
|
}
|
|
57
111
|
/**
|
|
58
112
|
* Every branch ends with this, and `recoveryFor` keys on it to suppress the generic no-session
|
|
@@ -61,4 +115,3 @@ interface NoSessionFacts {
|
|
|
61
115
|
*/
|
|
62
116
|
export declare const SELF_RECOVERING_MARKER = "Then call reticle_sessions again \u2014 it will appear within a second of the page loading.";
|
|
63
117
|
export declare function diagnoseNoSession(facts: NoSessionFacts): string;
|
|
64
|
-
export {};
|