@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
package/dist/init/snippets.js
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
* so the runner never inlines free strings.
|
|
4
4
|
*/
|
|
5
5
|
import { RETICLE_DEFAULT_PORT, bridgeWsUrl } from '@reticlehq/core';
|
|
6
|
+
import { UiLibrary } from './detect.js';
|
|
7
|
+
import { SERVER_VERSION } from '../version/server-version.js';
|
|
8
|
+
/**
|
|
9
|
+
* The SDK as one import a plain page can actually resolve.
|
|
10
|
+
*
|
|
11
|
+
* `init` used to tell static-HTML users that a page with no build step could not load the SDK, and
|
|
12
|
+
* to go and stand up a bundler. That is true of a BARE specifier and false of a URL, and the
|
|
13
|
+
* difference was the whole road for every server-rendered app we hear from: FastAPI, Flask, Django,
|
|
14
|
+
* Streamlit, Rails. Proven end to end before this shipped, on a page served by `python3 -m
|
|
15
|
+
* http.server`: a session connected, a snapshot returned, and two act_and_wait calls came back
|
|
16
|
+
* `verified: "yes"`.
|
|
17
|
+
*
|
|
18
|
+
* jsDelivr rather than esm.sh, measured: a third of the requests and a third of the bytes for the
|
|
19
|
+
* same result. `/+esm` is what makes it work, and the bare package URL does NOT: every file in
|
|
20
|
+
* `dist` carries bare specifiers for `@reticlehq/core` and `@testing-library/dom`, so an unbundled
|
|
21
|
+
* entry point dies on the first import. That is also why adding `unpkg`/`jsdelivr` fields to
|
|
22
|
+
* package.json would not help.
|
|
23
|
+
*
|
|
24
|
+
* PINNED to this server's version on purpose. A floating import upgrades the page SDK underneath a
|
|
25
|
+
* daemon that did not move, which is `version_skew` arriving by a route nothing checks.
|
|
26
|
+
*/
|
|
27
|
+
const CDN_SDK_URL = `https://cdn.jsdelivr.net/npm/@reticlehq/browser@${SERVER_VERSION}/+esm`;
|
|
6
28
|
/**
|
|
7
29
|
* The connect argument literal: a non-default port adds a `url`, and a projectId is always passed
|
|
8
30
|
* (so the app is identifiable across port changes). Empty string only when neither applies.
|
|
@@ -16,18 +38,76 @@ function connectArg(port, projectId) {
|
|
|
16
38
|
parts.push(`projectId: '${projectId}'`);
|
|
17
39
|
return parts.length > 0 ? `{ ${parts.join(', ')} }` : '';
|
|
18
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* The same literal, with the pairing token folded in.
|
|
43
|
+
*
|
|
44
|
+
* The token belongs INSIDE the call the user pastes. Every other stack has a build step to inline
|
|
45
|
+
* it (the Vite plugin's `define`, Next's NEXT_PUBLIC_*, Astro's config, CRA's .env); the hand-wired
|
|
46
|
+
* paths have none, so `init` inlines the literal it already read. Without it the bridge closes the
|
|
47
|
+
* socket with AUTH_FAILED and no session ever appears: see Bridge's hello handler.
|
|
48
|
+
*
|
|
49
|
+
* An empty token is omitted rather than emitted as `token: ''`. A daemon that could not write to
|
|
50
|
+
* $HOME runs without auth and trusts loopback, and an empty string would fail the comparison against
|
|
51
|
+
* one that does hold a token.
|
|
52
|
+
*/
|
|
53
|
+
export function connectArgWithToken(port, projectId, pairingToken) {
|
|
54
|
+
const base = connectArg(port, projectId);
|
|
55
|
+
if (pairingToken === undefined || 0 === pairingToken.length)
|
|
56
|
+
return base;
|
|
57
|
+
const inner = base.length > 0 ? base.slice(1, -1).trim() : '';
|
|
58
|
+
return `{ ${[inner, `token: '${pairingToken}'`].filter((p) => p.length > 0).join(', ')} }`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Which SDK package the GENERATED code should import, and whether `install()` applies.
|
|
62
|
+
*
|
|
63
|
+
* This has to agree with `frameworkPackages`, and it did not. That function was changed so a Vue or
|
|
64
|
+
* Svelte app installs `@reticlehq/browser` instead of the React adapter — correctly — while every
|
|
65
|
+
* generated connect snippet still said `import('@reticlehq/react')`. A SvelteKit app would have
|
|
66
|
+
* installed the sensor and then run a hook importing a package that is not there.
|
|
67
|
+
*
|
|
68
|
+
* `install()` is the React adapter's, not the sensor's: `@reticlehq/browser` exports `reticle` and no
|
|
69
|
+
* `install`, so swapping the specifier alone would trade a missing module for a missing export.
|
|
70
|
+
*/
|
|
71
|
+
export function sdkImport(uiLibrary) {
|
|
72
|
+
const react = uiLibrary !== UiLibrary.VUE && uiLibrary !== UiLibrary.SVELTE;
|
|
73
|
+
return react
|
|
74
|
+
? { specifier: '@reticlehq/react', usesInstall: true }
|
|
75
|
+
: { specifier: '@reticlehq/browser', usesInstall: false };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The framework plugin to show ALONGSIDE reticle() in the example, so the ordering is clear.
|
|
79
|
+
*
|
|
80
|
+
* It used to be `react()` unconditionally, which is what a Vue app was shown — a plugin it does not
|
|
81
|
+
* have, four lines after `init` had correctly detected Vue and said so. The example exists to show
|
|
82
|
+
* that `reticle()` goes last, not to tell anyone which UI framework they are using.
|
|
83
|
+
*/
|
|
84
|
+
function frameworkPluginExample(uiLibrary) {
|
|
85
|
+
switch (uiLibrary) {
|
|
86
|
+
case UiLibrary.VUE:
|
|
87
|
+
return 'vue()';
|
|
88
|
+
case UiLibrary.SVELTE:
|
|
89
|
+
return 'svelte()';
|
|
90
|
+
case UiLibrary.REACT:
|
|
91
|
+
case UiLibrary.PREACT:
|
|
92
|
+
return 'react()';
|
|
93
|
+
default:
|
|
94
|
+
// Nothing detected: name no plugin rather than invent one. The reader keeps whatever they have.
|
|
95
|
+
return '/* your existing plugins */';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
19
98
|
/** The Vite-config snippet printed when we can't safely auto-patch the config. */
|
|
20
|
-
export function viteManual(port) {
|
|
99
|
+
export function viteManual(port, uiLibrary = UiLibrary.UNKNOWN) {
|
|
21
100
|
const call = port === undefined ? 'reticle()' : `reticle({ port: ${String(port)} })`;
|
|
22
101
|
return `Add the Reticle plugin to your Vite config:
|
|
23
102
|
|
|
24
103
|
import { reticle } from '@reticlehq/vite-plugin';
|
|
25
104
|
|
|
26
105
|
export default defineConfig({
|
|
27
|
-
plugins: [
|
|
106
|
+
plugins: [${frameworkPluginExample(uiLibrary)}, ${call}],
|
|
28
107
|
});
|
|
29
108
|
|
|
30
|
-
|
|
109
|
+
Keep \`reticle()\` LAST so it sees the output of your other plugins. It only applies during \`vite\`
|
|
110
|
+
(dev) — it is dropped from \`vite build\`.`;
|
|
31
111
|
}
|
|
32
112
|
/** Next.js config wrap — always printed (we never auto-rewrite next.config). */
|
|
33
113
|
export function nextConfigManual(configFile) {
|
|
@@ -45,21 +125,27 @@ export function nextConfigManual(configFile) {
|
|
|
45
125
|
* as `NEXT_PUBLIC_RETICLE_TOKEN`. This file used to connect with only a projectId, so every Next
|
|
46
126
|
* setup ended at `bridge refused the connection: authentication failed` and no session ever appeared.
|
|
47
127
|
*/
|
|
48
|
-
export function nextReticleDevFile(port, projectId, testids = [], stores = []) {
|
|
128
|
+
export function nextReticleDevFile(port, projectId, testids = [], stores = [], found = []) {
|
|
49
129
|
const base = connectArg(port, projectId);
|
|
50
130
|
const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
|
|
51
131
|
const ids = testids.map((t) => `'${t}'`).join(', ');
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
132
|
+
// Same rule as the Vite module: a store we found is registered outright, and the commented hint
|
|
133
|
+
// survives only for the libraries we can name but not wire.
|
|
134
|
+
const storeImports = found.map((s) => `import { ${s.ident} } from '${s.importPath}';`).join('\n');
|
|
135
|
+
const storeBlock = found.length > 0
|
|
136
|
+
? found.map((s) => ` registerStore('${s.key}', ${s.ident});`).join('\n')
|
|
137
|
+
: 0 === stores.length
|
|
138
|
+
? ' // No state library detected. If you add one, register it here — see node_modules/@reticlehq/server/docs/usage.md.'
|
|
139
|
+
: stores.map((h) => ` // import your store, then: ${h}`).join('\n');
|
|
55
140
|
return `'use client';
|
|
56
141
|
import { useEffect } from 'react';
|
|
142
|
+
${storeImports.length > 0 ? storeImports : ''}
|
|
57
143
|
|
|
58
144
|
/** Dev-only: connect Reticle + install the React adapter, after hydration. */
|
|
59
145
|
export function ReticleDev() {
|
|
60
146
|
useEffect(() => {
|
|
61
147
|
if (process.env.NODE_ENV !== 'development') return;
|
|
62
|
-
void import('@reticlehq/react').then(({ reticle, install, registerCapabilities }) => {
|
|
148
|
+
void import('@reticlehq/react').then(({ reticle, install, registerCapabilities${found.length > 0 ? ', registerStore' : ''} }) => {
|
|
63
149
|
install();
|
|
64
150
|
// Both provided by withReticle() in next.config. The bridge rejects a connect with no token;
|
|
65
151
|
// the root makes source paths repo-relative instead of absolute.
|
|
@@ -73,9 +159,9 @@ export function ReticleDev() {
|
|
|
73
159
|
// form wires \`subscribe\` too, so every mutation emits a diff; the getter form is read-only.
|
|
74
160
|
${storeBlock}
|
|
75
161
|
registerCapabilities({
|
|
76
|
-
testids: [${ids}],${0 === testids.length ? ' // none found
|
|
162
|
+
testids: [${ids}],${0 === testids.length ? ' // none found; add data-testid to your key elements' : ''}
|
|
77
163
|
signals: [], // names you pass to reticle.signal()
|
|
78
|
-
stores: [], // the keys you registered above
|
|
164
|
+
stores: [${found.map((s) => `'${s.key}'`).join(', ')}], // the keys you registered above
|
|
79
165
|
});
|
|
80
166
|
});
|
|
81
167
|
}, []);
|
|
@@ -171,15 +257,29 @@ Start the daemon BEFORE \`astro dev\`, so the token file exists when the config
|
|
|
171
257
|
* breaks the module everything else hangs off. So the store lines are generated COMMENTED, naming
|
|
172
258
|
* the libraries actually found in package.json, with the exact call to uncomment.
|
|
173
259
|
*/
|
|
174
|
-
export function viteDevModuleFile(testids, stores) {
|
|
260
|
+
export function viteDevModuleFile(testids, stores, found = [], uiLibrary = UiLibrary.REACT) {
|
|
261
|
+
// The specifier has to be the package `frameworkPackages` installed. This file is the Vite path —
|
|
262
|
+
// the commonest install there is — and it was hardcoded to `@reticlehq/react` while a Vue or
|
|
263
|
+
// Svelte app was being given `@reticlehq/browser`, so the generated file imported something that
|
|
264
|
+
// was not there. Caught by running `init` against a pristine Vue app, not by any gate.
|
|
265
|
+
//
|
|
266
|
+
// The sensor exports `registerCapabilities` and `registerStore` (and the store adapters) just as
|
|
267
|
+
// the React kit does; the only thing it lacks is `install()`, which this file never called.
|
|
268
|
+
const sdk = sdkImport(uiLibrary);
|
|
175
269
|
const ids = testids.map((t) => `'${t}'`).join(', ');
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
270
|
+
// A store we FOUND is imported and registered outright — the whole point of the file. The hints
|
|
271
|
+
// stay only for the libraries we can name but not wire (they need an argument we cannot infer).
|
|
272
|
+
const storeImports = found.map((s) => `import { ${s.ident} } from '${s.importPath}';`).join('\n');
|
|
273
|
+
const storeBlock = found.length > 0
|
|
274
|
+
? found.map((s) => ` registerStore('${s.key}', ${s.ident});`).join('\n')
|
|
275
|
+
: 0 === stores.length
|
|
276
|
+
? ' // No state library detected. If you add one, register it here — see node_modules/@reticlehq/server/docs/usage.md.'
|
|
277
|
+
: stores.map((h) => ` // import your store, then: ${h}`).join('\n');
|
|
278
|
+
const registerImport = found.length > 0 ? 'registerCapabilities, registerStore' : 'registerCapabilities';
|
|
279
|
+
return `// Dev-only. Imported automatically by @reticlehq/vite-plugin, so you do not need to import it.
|
|
180
280
|
// Self-guards on import.meta.env.DEV, so it is a no-op in a production build.
|
|
181
|
-
import {
|
|
182
|
-
|
|
281
|
+
import { ${registerImport} } from '${sdk.specifier}';
|
|
282
|
+
${storeImports.length > 0 ? `${storeImports}\n` : ''}
|
|
183
283
|
if (import.meta.env.DEV) {
|
|
184
284
|
// ── Start with ONE flow. ─────────────────────────────────────────────────────────────────────
|
|
185
285
|
// You do not need to describe the whole app to get value, and trying to is the slow path. Register
|
|
@@ -193,9 +293,9 @@ if (import.meta.env.DEV) {
|
|
|
193
293
|
${storeBlock}
|
|
194
294
|
|
|
195
295
|
registerCapabilities({
|
|
196
|
-
testids: [${ids}],${0 === testids.length ? ' // none found
|
|
296
|
+
testids: [${ids}],${0 === testids.length ? ' // none found; add data-testid to your key elements' : ''}
|
|
197
297
|
signals: [], // names you pass to reticle.signal()
|
|
198
|
-
stores: [], // the keys you registered above
|
|
298
|
+
stores: [${found.map((s) => `'${s.key}'`).join(', ')}], // the keys you registered above
|
|
199
299
|
});
|
|
200
300
|
}
|
|
201
301
|
`;
|
|
@@ -216,23 +316,49 @@ export const NEXT_LAYOUT_MANUAL = `Mount <ReticleDev /> in your root layout (app
|
|
|
216
316
|
* where a bare `@reticlehq/react` import resolves. A bare import in a plain index.html does NOT resolve in
|
|
217
317
|
* the browser, so we never tell a bundled app to do that (the old advice silently failed for CRA).
|
|
218
318
|
*/
|
|
219
|
-
|
|
220
|
-
|
|
319
|
+
/**
|
|
320
|
+
* The whole install, for a page with no build step, as one block a person can paste.
|
|
321
|
+
*
|
|
322
|
+
* Extracted so the `no package.json` exit can print the SAME snippet `htmlManual` offers. That exit
|
|
323
|
+
* is the one every server-rendered app reaches (FastAPI, Flask, Django, Rails, Streamlit), it is
|
|
324
|
+
* where `init` stops, and until now it stopped with an explanation instead of an answer. A message
|
|
325
|
+
* that says "add the snippet below" and then prints no snippet is the same defect wearing the
|
|
326
|
+
* opposite sign, so the two share a builder rather than a copy.
|
|
327
|
+
*/
|
|
328
|
+
export function staticPageSnippet(connectArgLiteral) {
|
|
329
|
+
return ` <script type="module">
|
|
330
|
+
import { reticle } from '${CDN_SDK_URL}';
|
|
331
|
+
reticle.connect(${connectArgLiteral});
|
|
332
|
+
</script>`;
|
|
333
|
+
}
|
|
334
|
+
export function htmlManual(port, projectId, pairingToken) {
|
|
335
|
+
const withToken = connectArgWithToken(port, projectId, pairingToken);
|
|
336
|
+
const tokenNote = pairingToken === undefined || 0 === pairingToken.length
|
|
337
|
+
? ''
|
|
338
|
+
: `\n\n The \`token\` is this machine's pairing token, read from ~/.reticle/pairing-token. Keep it: the
|
|
339
|
+
bridge REJECTS a connect without it ("authentication failed") and no session appears. It is
|
|
340
|
+
per-machine and local-only, so do not commit it — a teammate's daemon mints their own.`;
|
|
221
341
|
return `No Vite/Next plugin detected — wire the dev-only connect by hand. Pick the form for your setup:
|
|
222
342
|
|
|
223
343
|
• Bundled app (Create React App, webpack, Parcel, Vue/Svelte CLI, etc.) — add to your ENTRY module
|
|
224
344
|
(e.g. src/index.js or src/main.js), where '@reticlehq/react' resolves through your bundler:
|
|
225
345
|
|
|
226
|
-
if (
|
|
346
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
227
347
|
void import('@reticlehq/react').then(({ reticle, install }) => {
|
|
228
348
|
install();
|
|
229
|
-
reticle.connect(${
|
|
349
|
+
reticle.connect(${withToken});
|
|
230
350
|
});
|
|
231
|
-
}
|
|
351
|
+
}${tokenNote}
|
|
352
|
+
|
|
353
|
+
• Plain HTML with NO build step (FastAPI, Flask, Django, Rails, Streamlit, a hand-written page) —
|
|
354
|
+
paste this into the page, in a template you only serve in development. There is nothing to
|
|
355
|
+
install: no npm, no bundler, no package.json.
|
|
356
|
+
|
|
357
|
+
${staticPageSnippet(withToken)}
|
|
232
358
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
359
|
+
Serving the app on something other than localhost (a hosts-file alias, a LAN IP, a container, a
|
|
360
|
+
tunnel)? Add \`allowNonLocalhost: true\` to the connect. Without it the SDK refuses to dial and says
|
|
361
|
+
so in the browser console only, so from here it looks exactly like nothing happened.`;
|
|
236
362
|
}
|
|
237
363
|
export const NEXT_RETICLE_DEV_PATH = 'app/reticle-dev.tsx';
|
|
238
364
|
export const SVELTEKIT_HOOKS_PATH = 'src/hooks.client.ts';
|
|
@@ -247,7 +373,15 @@ export const SVELTEKIT_HOOKS_PATH = 'src/hooks.client.ts';
|
|
|
247
373
|
* here is the one thing this project exists not to do.
|
|
248
374
|
*/
|
|
249
375
|
export function unverifiedUiLibraryNote(library) {
|
|
250
|
-
|
|
376
|
+
// Preact is not in the same position as Vue or Svelte and must not be told it is. The React
|
|
377
|
+
// adapter reaches Preact through `preact/compat`, which is what `docs/frameworks.mdx` has always
|
|
378
|
+
// said, so telling a Preact reader they get no component identity contradicts our own docs and
|
|
379
|
+
// talks them out of a package that is the right one for them. It is still ungated, which is the
|
|
380
|
+
// honest caveat, and #129 is the issue for closing that.
|
|
381
|
+
const identity = 'preact' === library
|
|
382
|
+
? 'React component identity — component names and stacks — comes from `@reticlehq/react`, which reaches Preact through `preact/compat`. That path is not covered by a CI gate here, so treat it as expected-to-work rather than proven.'
|
|
383
|
+
: 'What `@reticlehq/react` adds and you will NOT get is React component identity: component names and component stacks.';
|
|
384
|
+
return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here. ${'vue' === library ? 'Source `file:line` does NOT come through: the build plugin stamps JSX and, separately, Svelte components, and a Vue single-file component is neither — measured, a Svelte counter reports `src/lib/Counter.svelte:5` and the same drive on Vue reports no source at all.' : 'Source `file:line` does too — the build plugin stamps it for this library (measured on preact and svelte).'} ${identity} ${'vue' === library ? 'The install gate scaffolds a Vue app from scratch on every change, so this SETUP is proven; no gate drives a Vue app to a verdict, so the drive is not.' : `No CI gate covers ${library}.`} Driven on every change: Vite + React, Next.js, Remix, Astro. If something doesn't work, please open an issue.`;
|
|
251
385
|
}
|
|
252
386
|
export const UNVERIFIED_FRAMEWORK_NOTE = 'Reticle has no SvelteKit app and no CI gate for one, so this wiring is untested — it may work, but nothing proves it and nothing will tell us if it breaks. Supported and gated today: Vite + React, Next.js, Remix and Astro. If the hook does not register a session, please open an issue.';
|
|
253
387
|
/**
|
|
@@ -255,14 +389,18 @@ export const UNVERIFIED_FRAMEWORK_NOTE = 'Reticle has no SvelteKit app and no CI
|
|
|
255
389
|
* never triggers Vite's index.html injection (verified), so the standard plugin can't auto-connect —
|
|
256
390
|
* a client hook is the reliable path. SvelteKit runs src/hooks.client.ts on the client at startup.
|
|
257
391
|
*/
|
|
258
|
-
export function svelteKitHooksFile(port, projectId) {
|
|
392
|
+
export function svelteKitHooksFile(port, projectId, uiLibrary = UiLibrary.SVELTE) {
|
|
393
|
+
// SvelteKit is Svelte, so this defaults to the sensor rather than the React adapter — and the
|
|
394
|
+
// import here MUST match what `frameworkPackages` installed, or the hook loads a package that is
|
|
395
|
+
// not in node_modules. See sdkImport.
|
|
396
|
+
const sdk = sdkImport(uiLibrary);
|
|
259
397
|
const base = connectArg(port, projectId);
|
|
260
398
|
const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
|
|
261
399
|
return `// Dev-only: connect Reticle on the client. SvelteKit renders via app.html, so the Vite-plugin
|
|
262
400
|
// index.html injection doesn't fire — connect from this client hook instead.
|
|
263
401
|
if (import.meta.env.DEV) {
|
|
264
|
-
void import('
|
|
265
|
-
install();
|
|
402
|
+
void import('${sdk.specifier}').then(({ reticle${sdk.usesInstall ? ', install' : ''} }) => {
|
|
403
|
+
${sdk.usesInstall ? 'install();' : '// No React adapter here: the sensor has no install() to call.'}
|
|
266
404
|
// The bridge requires the pairing token even on localhost. Nothing in a browser can read the
|
|
267
405
|
// file it lives in, so @reticlehq/vite-plugin inlines it here at build time. Without it the
|
|
268
406
|
// console reads "bridge refused the connection: authentication failed" and no session appears.
|
|
@@ -282,6 +420,57 @@ declare const __RETICLE_ROOT__: string | undefined;
|
|
|
282
420
|
declare const __RETICLE_SDK_VERSION__: string | undefined;
|
|
283
421
|
`;
|
|
284
422
|
}
|
|
423
|
+
/** Where a Nuxt dev-only client plugin belongs. `.client` keeps it out of SSR; Nuxt auto-registers it. */
|
|
424
|
+
export const NUXT_PLUGIN_PATH = 'app/plugins/reticle.client.ts';
|
|
425
|
+
/**
|
|
426
|
+
* The Nuxt recipe, written out in full because every trap in it is one somebody actually hit.
|
|
427
|
+
*
|
|
428
|
+
* Reported from the field, in the order they were hit: `init` classified a Nuxt 4 app as `html`, so
|
|
429
|
+
* it installed a package named `@reticlehq/react` (with `react` in its peer dependencies) into a Vue
|
|
430
|
+
* codebase — the reporter only continued after auditing our dist to confirm there are no React
|
|
431
|
+
* imports at runtime, which most people will not do. It then handed over a snippet guarded on
|
|
432
|
+
* `window.location.hostname === 'localhost'`, which fails twice over in Nuxt: `window` does not
|
|
433
|
+
* exist during SSR, and the dev host here was a hosts-file alias (required for the backend's
|
|
434
|
+
* white-label origin detection), so the guard was false and the connect never ran — no error, no log
|
|
435
|
+
* line, nothing to debug. And nothing said a running dev server does not pick up a new plugin.
|
|
436
|
+
*
|
|
437
|
+
* So: `import.meta.dev` (build-time, host-independent) instead of a hostname check, `.client.ts`
|
|
438
|
+
* instead of an SSR guard, the framework-neutral sensor instead of the React kit, the non-localhost
|
|
439
|
+
* flag named up front, and the restart said out loud.
|
|
440
|
+
*/
|
|
441
|
+
export function nuxtManual(port, projectId) {
|
|
442
|
+
const base = connectArg(port, projectId);
|
|
443
|
+
const fields = '' === base ? '' : base.slice(1, -1).trim();
|
|
444
|
+
const connect = '' === fields ? 'reticle.connect()' : `reticle.connect({ ${fields} })`;
|
|
445
|
+
return `Nuxt owns its own Vite instance and renders its own HTML, so there is no vite.config to patch
|
|
446
|
+
and no index.html to inject into. Wire it with a dev-only CLIENT plugin, which is Nuxt's own idiom:
|
|
447
|
+
|
|
448
|
+
1. Create ${NUXT_PLUGIN_PATH}:
|
|
449
|
+
|
|
450
|
+
export default defineNuxtPlugin(() => {
|
|
451
|
+
// import.meta.dev is the correct guard: it is resolved at build time, so it does not care
|
|
452
|
+
// what hostname you develop on. Do NOT guard on window.location.hostname === 'localhost' —
|
|
453
|
+
// that is false on any hosts-file alias or LAN address, and window does not exist in SSR.
|
|
454
|
+
if (!import.meta.dev) return
|
|
455
|
+
void import('@reticlehq/browser').then(({ reticle }) => {
|
|
456
|
+
${connect}
|
|
457
|
+
})
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
The .client.ts suffix is load-bearing: it is what keeps this out of the server bundle.
|
|
461
|
+
|
|
462
|
+
2. Restart the dev server. A dev server that is already running does not pick up a new plugin —
|
|
463
|
+
it will not appear in .nuxt/plugins/client.mjs, and the app will come up with no SDK at all.
|
|
464
|
+
|
|
465
|
+
3. If your dev host is anything other than localhost (a hosts-file alias, a LAN IP, a tunnel), add
|
|
466
|
+
allowNonLocalhost: true to that connect call. Without it the SDK loads and then refuses, and the
|
|
467
|
+
only sign is one line in the browser console.
|
|
468
|
+
|
|
469
|
+
The package is @reticlehq/browser — the framework-neutral sensor. DOM, network, console, routing and
|
|
470
|
+
source file:line all work in Vue. What you do not get is React component identity, which is the only
|
|
471
|
+
thing the React adapter adds. There is no Nuxt app in this project's CI, so this path is UNVERIFIED:
|
|
472
|
+
if something does not work, please open an issue.`;
|
|
473
|
+
}
|
|
285
474
|
/**
|
|
286
475
|
* Root-level project config for Reticle. Written by `reticle init`; read by `reticle mcp` for the port
|
|
287
476
|
* and by tooling for the stable projectId (the app's identity across port changes).
|
package/dist/init/vite-config.js
CHANGED
|
@@ -15,6 +15,16 @@ function reticlePluginCall(port) {
|
|
|
15
15
|
const PLUGINS_ARRAY = /plugins\s*:\s*\[/;
|
|
16
16
|
/** Matches an ES import statement (used to place our import after the last one). */
|
|
17
17
|
const IMPORT_LINE = /^import\s.+from\s+['"][^'"]+['"];?\s*$/gm;
|
|
18
|
+
/**
|
|
19
|
+
* The opening `{` of the exported config object — `defineConfig({`, or a bare `export default {`.
|
|
20
|
+
* Used only when there is no `plugins` array to extend: the object is right there, so adding the
|
|
21
|
+
* key is the same edit as extending the array, and bailing sent a user whose config merely set
|
|
22
|
+
* `server.port` to a manual paste for a change we can make correctly.
|
|
23
|
+
*
|
|
24
|
+
* A config built by a call (`defineConfig(buildOptions())`) has no literal to extend and still
|
|
25
|
+
* bails — the rule stays "only edit a shape we can see whole".
|
|
26
|
+
*/
|
|
27
|
+
const CONFIG_OBJECT = /(export\s+default\s+(?:defineConfig\s*\(\s*)?)\{/;
|
|
18
28
|
/** Alias kept so existing call sites read in Vite terms; the vocabulary is shared (see patch-kind). */
|
|
19
29
|
export const VitePatchKind = PatchKind;
|
|
20
30
|
const NO_PLUGINS_REASON = "couldn't find a `plugins: [...]` array to extend";
|
|
@@ -41,12 +51,28 @@ function insertPlugin(source, port) {
|
|
|
41
51
|
return `${match}${reticlePluginCall(port)},${separator}`;
|
|
42
52
|
});
|
|
43
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Add a whole `plugins: [reticle()]` key to a config object that has none, matching the layout of
|
|
56
|
+
* the object it lands in: a multi-line object gets its own indented line, a one-liner stays inline.
|
|
57
|
+
*/
|
|
58
|
+
function insertPluginsKey(source, port) {
|
|
59
|
+
return source.replace(CONFIG_OBJECT, (_match, prefix, offset) => {
|
|
60
|
+
const rest = source.slice(offset + _match.length);
|
|
61
|
+
const multiline = /^\s*\n/.test(rest);
|
|
62
|
+
const indent = /^\s*\n(\s*)\S/.exec(rest)?.[1] ?? ' ';
|
|
63
|
+
const key = `plugins: [${reticlePluginCall(port)}],`;
|
|
64
|
+
return multiline ? `${prefix}{\n${indent}${key}` : `${prefix}{ ${key}`;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
44
67
|
export function patchViteConfig(source, port) {
|
|
45
68
|
if (source.includes(RETICLE_MARKER)) {
|
|
46
69
|
return { kind: VitePatchKind.ALREADY };
|
|
47
70
|
}
|
|
48
|
-
if (
|
|
49
|
-
return { kind: VitePatchKind.
|
|
71
|
+
if (PLUGINS_ARRAY.test(source)) {
|
|
72
|
+
return { kind: VitePatchKind.APPLY, code: insertImport(insertPlugin(source, port)) };
|
|
73
|
+
}
|
|
74
|
+
if (CONFIG_OBJECT.test(source)) {
|
|
75
|
+
return { kind: VitePatchKind.APPLY, code: insertImport(insertPluginsKey(source, port)) };
|
|
50
76
|
}
|
|
51
|
-
return { kind: VitePatchKind.
|
|
77
|
+
return { kind: VitePatchKind.MANUAL, reason: NO_PLUGINS_REASON };
|
|
52
78
|
}
|
|
@@ -1,2 +1,21 @@
|
|
|
1
|
+
import type { MockRule } from './network-mock.js';
|
|
1
2
|
import type { ToolDef } from '../tools/tools.js';
|
|
3
|
+
/**
|
|
4
|
+
* Narrow the validated tool args into MockRule[], omitting undefined keys (exactOptionalPropertyTypes).
|
|
5
|
+
*
|
|
6
|
+
* THROWS on a malformed rule rather than falling back to `[]`, because `[]` is not a neutral value
|
|
7
|
+
* here — it is the documented way to clear every active mock. A rule with a wrong field name used to
|
|
8
|
+
* produce exactly the call that turns mocking OFF, and the handler then reported `applied: true`
|
|
9
|
+
* with `count: 0`. For a tool whose entire job is forcing an error state, that means the agent
|
|
10
|
+
* checks the app's failure handling against the real backend, sees the happy path, and reports that
|
|
11
|
+
* the error state works.
|
|
12
|
+
*
|
|
13
|
+
* Same rule as the action arguments: an argument that could not be understood is refused by name,
|
|
14
|
+
* never reinterpreted into a destructive default. A valueless `fill` used to wipe the field and
|
|
15
|
+
* report ok; it throws now, for this reason.
|
|
16
|
+
*
|
|
17
|
+
* `undefined` and `[]` still mean "clear" — those are deliberate, documented requests, not failures
|
|
18
|
+
* to parse.
|
|
19
|
+
*/
|
|
20
|
+
export declare function toRules(value: unknown): MockRule[];
|
|
2
21
|
export declare const NETWORK_MOCK_TOOLS: ToolDef[];
|
|
@@ -26,11 +26,36 @@ const ruleShape = z.object({
|
|
|
26
26
|
.optional()
|
|
27
27
|
.describe('Simulate a network failure (offline) instead of a response.'),
|
|
28
28
|
});
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Narrow the validated tool args into MockRule[], omitting undefined keys (exactOptionalPropertyTypes).
|
|
31
|
+
*
|
|
32
|
+
* THROWS on a malformed rule rather than falling back to `[]`, because `[]` is not a neutral value
|
|
33
|
+
* here — it is the documented way to clear every active mock. A rule with a wrong field name used to
|
|
34
|
+
* produce exactly the call that turns mocking OFF, and the handler then reported `applied: true`
|
|
35
|
+
* with `count: 0`. For a tool whose entire job is forcing an error state, that means the agent
|
|
36
|
+
* checks the app's failure handling against the real backend, sees the happy path, and reports that
|
|
37
|
+
* the error state works.
|
|
38
|
+
*
|
|
39
|
+
* Same rule as the action arguments: an argument that could not be understood is refused by name,
|
|
40
|
+
* never reinterpreted into a destructive default. A valueless `fill` used to wipe the field and
|
|
41
|
+
* report ok; it throws now, for this reason.
|
|
42
|
+
*
|
|
43
|
+
* `undefined` and `[]` still mean "clear" — those are deliberate, documented requests, not failures
|
|
44
|
+
* to parse.
|
|
45
|
+
*/
|
|
46
|
+
export function toRules(value) {
|
|
47
|
+
if (value === undefined)
|
|
33
48
|
return [];
|
|
49
|
+
const parsed = z.array(ruleShape).safeParse(value);
|
|
50
|
+
if (!parsed.success) {
|
|
51
|
+
const problem = parsed.error.issues
|
|
52
|
+
.map((i) => `${0 === i.path.length ? 'mocks' : `mocks.${i.path.join('.')}`}: ${i.message}`)
|
|
53
|
+
.join('; ');
|
|
54
|
+
throw new Error(`reticle_network_mock could not read \`mocks\` (${problem}). Nothing was applied and mocking ` +
|
|
55
|
+
'was left unchanged — an unreadable rule is NOT treated as a request to clear. A rule is ' +
|
|
56
|
+
'{ urlContains, method?, status?, body?, contentType?, delayMs?, abort? }; pass `clear: true` ' +
|
|
57
|
+
'or an empty array if you meant to turn mocking off.');
|
|
58
|
+
}
|
|
34
59
|
return parsed.data.map((r) => {
|
|
35
60
|
const rule = { urlContains: r.urlContains };
|
|
36
61
|
if (r.method !== undefined)
|
package/dist/license/license.js
CHANGED
|
@@ -161,7 +161,7 @@ export function describeLicense(now, env = process.env, baked = BAKED_ISSUER_PUB
|
|
|
161
161
|
}
|
|
162
162
|
: {
|
|
163
163
|
status: 'eval',
|
|
164
|
-
detail: 'evaluation mode
|
|
164
|
+
detail: 'evaluation mode: enterprise features run free (no issuer key configured)',
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
const publicKey = loadPublicKey(pem);
|
package/dist/mcp/mcp-proxy.d.ts
CHANGED
|
@@ -7,6 +7,45 @@ export declare const MAX_RECONNECT_ATTEMPTS: number;
|
|
|
7
7
|
export declare function reconnectDelayMs(attempt: number): number;
|
|
8
8
|
/** JSON-RPC id the proxy uses for its own replayed `initialize` — never one the client could send. */
|
|
9
9
|
export declare const RECONNECT_INITIALIZE_ID = "__reticle_proxy_reinit";
|
|
10
|
+
/**
|
|
11
|
+
* How much the proxy may append between size checks.
|
|
12
|
+
*
|
|
13
|
+
* Statting the file on every line would be a syscall per log line on a process that logs from a
|
|
14
|
+
* reconnect loop. Counting what we wrote costs nothing and is exact enough, because this process is
|
|
15
|
+
* the only writer of its own log. Well under the cap, so the cap is a cap rather than a suggestion.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PROXY_LOG_CHECK_BYTES: number;
|
|
18
|
+
/**
|
|
19
|
+
* Keep the proxy log under the same cap as the daemon log, without a stat per write.
|
|
20
|
+
*
|
|
21
|
+
* Returns the new "bytes since the last check" counter, so the accounting is testable without a
|
|
22
|
+
* filesystem. Rotation itself is `rotateDaemonLog` — the proxy log had no cap at all while the
|
|
23
|
+
* daemon log on the SAME machine stayed small, which is the whole of this defect.
|
|
24
|
+
*/
|
|
25
|
+
export declare function accountProxyLogWrite(bytesSinceCheck: number, written: number, path: string, deps: {
|
|
26
|
+
fileSize(p: string): number;
|
|
27
|
+
renameFile(from: string, to: string): void;
|
|
28
|
+
}): number;
|
|
29
|
+
/**
|
|
30
|
+
* Reclaim a proxy log that a previous process already let run away. Returns the bytes reclaimed.
|
|
31
|
+
*
|
|
32
|
+
* Reported from the field: one machine's `proxy-4400.log` reached a third of a 460GB disk and broke
|
|
33
|
+
* builds, Docker and ordinary shell commands with ENOSPC. Nothing in Reticle degraded first, so the
|
|
34
|
+
* failure surfaced as the operating system refusing to write files. Files like that exist on real
|
|
35
|
+
* machines now, and the cap alone does not help them: the user should not have to find one with `du`.
|
|
36
|
+
*
|
|
37
|
+
* TRUNCATED IN PLACE, never renamed or unlinked. A rename moves the bytes without reclaiming a byte,
|
|
38
|
+
* and a file that a running process still holds open keeps its blocks allocated after an unlink
|
|
39
|
+
* until that handle closes — which is why the reporter recovered with `: > ~/.reticle/proxy-4400.log`
|
|
40
|
+
* and why that is the operation to copy here.
|
|
41
|
+
*
|
|
42
|
+
* Best-effort, in the same spirit as `rotateDaemonLog`: refusing to start the MCP server over
|
|
43
|
+
* housekeeping is strictly worse than a large file.
|
|
44
|
+
*/
|
|
45
|
+
export declare function recoverOversizedProxyLog(path: string, deps: {
|
|
46
|
+
fileSize(p: string): number;
|
|
47
|
+
truncateFile(p: string): void;
|
|
48
|
+
}, max?: number): number;
|
|
10
49
|
/** Name the proxy log after the port it serves. Called before anything can log. */
|
|
11
50
|
export declare function setProxyLogPort(port: number): void;
|
|
12
51
|
/** The proxy's own log file, so a silent drop leaves a readable trace the agent can go read. */
|
|
@@ -56,9 +95,36 @@ export declare class PendingRequests {
|
|
|
56
95
|
* daemon under a live client left 20 of 20 concurrent calls hanging until the client's own timeout,
|
|
57
96
|
* with the MCP server still perfectly alive. An agent cannot tell that apart from "still working".
|
|
58
97
|
*/
|
|
59
|
-
export declare function streamLossReplies(pending: PendingRequests, reason: string): string[];
|
|
60
|
-
/**
|
|
61
|
-
|
|
98
|
+
export declare function streamLossReplies(pending: PendingRequests, reason: string, nextStep?: string): string[];
|
|
99
|
+
/**
|
|
100
|
+
* The one shape every "the transport ate your call" answer takes, whichever leg died.
|
|
101
|
+
*
|
|
102
|
+
* `nextStep` is appended when the caller knows one. Without it this reply describes a condition and
|
|
103
|
+
* stops there, which is all a first-run user got out of their first ever tool call.
|
|
104
|
+
*/
|
|
105
|
+
export declare function transportLossReply(id: unknown, reason: string, nextStep?: string): string;
|
|
106
|
+
/**
|
|
107
|
+
* How long a request may sit in the queue, unsent, before the proxy answers it itself.
|
|
108
|
+
*
|
|
109
|
+
* The second population the stress test found. A request that was FORWARDED and lost is failed when
|
|
110
|
+
* the stream drops; one that never got forwarded — queued while dormant or reconnecting — was
|
|
111
|
+
* waiting for a session that, with a squatter holding the port, never arrives. It then hung until
|
|
112
|
+
* the client's own timeout, or forever if the client has none.
|
|
113
|
+
*
|
|
114
|
+
* Comfortably longer than a daemon restart (~1-2s) so a normal blip still flushes and succeeds, and
|
|
115
|
+
* well under the 60s an MCP client typically allows, so the answer comes from us with a reason
|
|
116
|
+
* rather than from their timer with none.
|
|
117
|
+
*/
|
|
118
|
+
export declare const QUEUE_WAIT_MS = 20000;
|
|
119
|
+
/** Retry a first connect, or report it? Pure, so the window can be held against the queue's. */
|
|
120
|
+
export declare function shouldRetryFirstConnect(error: unknown, elapsedMs: number): boolean;
|
|
121
|
+
/**
|
|
122
|
+
* What to do about it, carried on the reply the queue expiry sends.
|
|
123
|
+
*
|
|
124
|
+
* A bare -32001 on the first call after an install says nothing a user can act on: not whether to
|
|
125
|
+
* wait, not whether to retry, not whether the install itself is broken.
|
|
126
|
+
*/
|
|
127
|
+
export declare function queueExpiryNextStep(port: number): string;
|
|
62
128
|
/**
|
|
63
129
|
* Makes a reconnect invisible to the client.
|
|
64
130
|
*
|