@reticlehq/server 3.3.0 → 3.4.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/LICENSE-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +12 -14
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +22 -0
- package/dist/command/cli/adhoc-suite.js +90 -19
- package/dist/command/cli/adhoc-verdict.d.ts +35 -0
- package/dist/command/cli/adhoc-verdict.js +90 -3
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +4 -0
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/cli-doctor.js +7 -5
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +21 -3
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +24 -3
- package/dist/command/cli/cli-verify.js +54 -11
- package/dist/command/cli/cloud-cli.js +5 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.js +21 -3
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/launch/cli-launch.d.ts +8 -0
- package/dist/command/cli/launch/cli-launch.js +21 -9
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
- package/dist/command/cli/ports/resolve/cli-port.js +125 -15
- package/dist/command/cli/setup-mcp-cli.js +13 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +62 -10
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +39 -2
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +80 -44
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +26 -1
- package/dist/command/setup/run-setup.js +96 -13
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.js +3 -3
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.js +171 -157
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +6 -0
- package/dist/features/harness/fill-values.js +3 -0
- package/dist/features/harness/jev-driver.js +30 -13
- package/dist/features/harness/openai-driver.js +4 -7
- package/dist/features/harness/platform-config.js +5 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.js +40 -11
- package/dist/judgement/domain/domain-model.js +3 -1
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-replay-run.d.ts +2 -3
- package/dist/language/flows/flow-replay-run.js +13 -47
- package/dist/language/flows/flow-replay.d.ts +22 -8
- package/dist/language/flows/flow-replay.js +55 -9
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +10 -1
- package/dist/language/flows/flows.js +90 -14
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +42 -6
- package/dist/language/flows/verify-change-tools.js +5 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -1
- package/dist/memory/cloud/cloud-sync.js +6 -9
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +68 -3
- package/dist/memory/cloud/sync-cycle.js +298 -55
- package/dist/memory/cloud/sync-daemon.js +24 -20
- package/dist/memory/cloud/sync-disk.js +16 -2
- package/dist/memory/intent/intent-shard.d.ts +10 -32
- package/dist/memory/intent/intent-shard.js +0 -10
- package/dist/memory/journal/drive-flow.d.ts +18 -13
- package/dist/memory/journal/drive-flow.js +45 -37
- package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
- package/dist/memory/journal/session-end.d.ts +9 -1
- package/dist/memory/journal/session-end.js +24 -12
- package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +4 -2
- package/dist/portal/bridge/bridge.d.ts +8 -0
- package/dist/portal/bridge/bridge.js +43 -2
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +29 -4
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +20 -13
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/session-health.js +3 -2
- package/dist/portal/session/session-manager.d.ts +20 -6
- package/dist/portal/session/session-manager.js +19 -3
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session.d.ts +5 -0
- package/dist/portal/session/session.js +15 -19
- package/dist/surface/http-server.d.ts +2 -1
- package/dist/surface/http-server.js +4 -2
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +2 -2
- package/dist/surface/mcp/mcp.js +4 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/server-instructions.js +9 -7
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +46 -1
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +26 -50
- package/dist/surface/tools/assert/assert-grade.js +12 -1
- package/dist/surface/tools/assert/assert-verdict.js +2 -2
- package/dist/surface/tools/browser-tools.js +14 -2
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/error-recovery.js +4 -4
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +7 -6
- package/dist/surface/tools/lease-tools.js +15 -6
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +9 -1
- package/dist/surface/tools/tools.js +4 -0
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +0 -4
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/wire-journal.d.ts +3 -0
- package/dist/wire-journal.js +4 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +4 -2
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +14 -4
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +9 -0
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +21 -2
- package/docs/frameworks.mdx +29 -10
- package/docs/getting-started.md +3 -3
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +2 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +50 -5
- package/docs/quickstart.mdx +2 -2
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +1 -1
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +7 -6
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +2 -2
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +10 -9
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/phenomena/phenomena.d.ts +0 -27
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { z } from 'zod';
|
|
23
23
|
import { type Intent } from '@reticlehq/core/artifacts';
|
|
24
|
-
import type { FileSystemPort } from '../project/fs/fs-port.js';
|
|
25
24
|
/**
|
|
26
25
|
* How settled a record is.
|
|
27
26
|
*
|
|
@@ -52,12 +51,12 @@ declare const IntentRecordSchema: z.ZodObject<{
|
|
|
52
51
|
flow: z.ZodOptional<z.ZodString>;
|
|
53
52
|
files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
54
53
|
}, "strip", z.ZodTypeAny, {
|
|
55
|
-
flow?: string | undefined;
|
|
56
54
|
route?: string | undefined;
|
|
55
|
+
flow?: string | undefined;
|
|
57
56
|
files?: string[] | undefined;
|
|
58
57
|
}, {
|
|
59
|
-
flow?: string | undefined;
|
|
60
58
|
route?: string | undefined;
|
|
59
|
+
flow?: string | undefined;
|
|
61
60
|
files?: string[] | undefined;
|
|
62
61
|
}>>;
|
|
63
62
|
provenBy: z.ZodOptional<z.ZodObject<{
|
|
@@ -101,8 +100,8 @@ declare const IntentRecordSchema: z.ZodObject<{
|
|
|
101
100
|
statement: string;
|
|
102
101
|
declaredAt: number;
|
|
103
102
|
surface?: {
|
|
104
|
-
flow?: string | undefined;
|
|
105
103
|
route?: string | undefined;
|
|
104
|
+
flow?: string | undefined;
|
|
106
105
|
files?: string[] | undefined;
|
|
107
106
|
} | undefined;
|
|
108
107
|
source?: string | undefined;
|
|
@@ -126,8 +125,8 @@ declare const IntentRecordSchema: z.ZodObject<{
|
|
|
126
125
|
statement: string;
|
|
127
126
|
declaredAt: number;
|
|
128
127
|
surface?: {
|
|
129
|
-
flow?: string | undefined;
|
|
130
128
|
route?: string | undefined;
|
|
129
|
+
flow?: string | undefined;
|
|
131
130
|
files?: string[] | undefined;
|
|
132
131
|
} | undefined;
|
|
133
132
|
source?: string | undefined;
|
|
@@ -159,12 +158,12 @@ export declare const IntentShardSchema: z.ZodObject<{
|
|
|
159
158
|
flow: z.ZodOptional<z.ZodString>;
|
|
160
159
|
files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
161
160
|
}, "strip", z.ZodTypeAny, {
|
|
162
|
-
flow?: string | undefined;
|
|
163
161
|
route?: string | undefined;
|
|
162
|
+
flow?: string | undefined;
|
|
164
163
|
files?: string[] | undefined;
|
|
165
164
|
}, {
|
|
166
|
-
flow?: string | undefined;
|
|
167
165
|
route?: string | undefined;
|
|
166
|
+
flow?: string | undefined;
|
|
168
167
|
files?: string[] | undefined;
|
|
169
168
|
}>>;
|
|
170
169
|
provenBy: z.ZodOptional<z.ZodObject<{
|
|
@@ -208,8 +207,8 @@ export declare const IntentShardSchema: z.ZodObject<{
|
|
|
208
207
|
statement: string;
|
|
209
208
|
declaredAt: number;
|
|
210
209
|
surface?: {
|
|
211
|
-
flow?: string | undefined;
|
|
212
210
|
route?: string | undefined;
|
|
211
|
+
flow?: string | undefined;
|
|
213
212
|
files?: string[] | undefined;
|
|
214
213
|
} | undefined;
|
|
215
214
|
source?: string | undefined;
|
|
@@ -233,8 +232,8 @@ export declare const IntentShardSchema: z.ZodObject<{
|
|
|
233
232
|
statement: string;
|
|
234
233
|
declaredAt: number;
|
|
235
234
|
surface?: {
|
|
236
|
-
flow?: string | undefined;
|
|
237
235
|
route?: string | undefined;
|
|
236
|
+
flow?: string | undefined;
|
|
238
237
|
files?: string[] | undefined;
|
|
239
238
|
} | undefined;
|
|
240
239
|
source?: string | undefined;
|
|
@@ -262,8 +261,8 @@ export declare const IntentShardSchema: z.ZodObject<{
|
|
|
262
261
|
statement: string;
|
|
263
262
|
declaredAt: number;
|
|
264
263
|
surface?: {
|
|
265
|
-
flow?: string | undefined;
|
|
266
264
|
route?: string | undefined;
|
|
265
|
+
flow?: string | undefined;
|
|
267
266
|
files?: string[] | undefined;
|
|
268
267
|
} | undefined;
|
|
269
268
|
source?: string | undefined;
|
|
@@ -291,8 +290,8 @@ export declare const IntentShardSchema: z.ZodObject<{
|
|
|
291
290
|
statement: string;
|
|
292
291
|
declaredAt: number;
|
|
293
292
|
surface?: {
|
|
294
|
-
flow?: string | undefined;
|
|
295
293
|
route?: string | undefined;
|
|
294
|
+
flow?: string | undefined;
|
|
296
295
|
files?: string[] | undefined;
|
|
297
296
|
} | undefined;
|
|
298
297
|
source?: string | undefined;
|
|
@@ -311,24 +310,6 @@ export declare const IntentShardSchema: z.ZodObject<{
|
|
|
311
310
|
}>;
|
|
312
311
|
}>;
|
|
313
312
|
export type IntentShard = z.infer<typeof IntentShardSchema>;
|
|
314
|
-
/** One line per intent: enough to decide whether to open the shard, and no more. */
|
|
315
|
-
declare const IntentIndexEntrySchema: z.ZodObject<{
|
|
316
|
-
id: z.ZodString;
|
|
317
|
-
subject: z.ZodString;
|
|
318
|
-
statement: z.ZodString;
|
|
319
|
-
status: z.ZodEnum<["proposed", "agreed", "proved", "stale"]>;
|
|
320
|
-
}, "strip", z.ZodTypeAny, {
|
|
321
|
-
status: "proved" | "proposed" | "agreed" | "stale";
|
|
322
|
-
id: string;
|
|
323
|
-
subject: string;
|
|
324
|
-
statement: string;
|
|
325
|
-
}, {
|
|
326
|
-
status: "proved" | "proposed" | "agreed" | "stale";
|
|
327
|
-
id: string;
|
|
328
|
-
subject: string;
|
|
329
|
-
statement: string;
|
|
330
|
-
}>;
|
|
331
|
-
export type IntentIndexEntry = z.infer<typeof IntentIndexEntrySchema>;
|
|
332
313
|
export declare const IntentIndexSchema: z.ZodObject<{
|
|
333
314
|
version: z.ZodLiteral<1>;
|
|
334
315
|
entries: z.ZodArray<z.ZodObject<{
|
|
@@ -366,7 +347,6 @@ export declare const IntentIndexSchema: z.ZodObject<{
|
|
|
366
347
|
}>;
|
|
367
348
|
export type IntentIndex = z.infer<typeof IntentIndexSchema>;
|
|
368
349
|
export declare const emptyShard: (subject: string) => IntentShard;
|
|
369
|
-
export declare const emptyIndex: () => IntentIndex;
|
|
370
350
|
/** The verification lifecycle a legacy record carried, mapped onto the settledness axis. */
|
|
371
351
|
export declare const statusFromState: (state: Intent["state"]) => IntentStatus;
|
|
372
352
|
/**
|
|
@@ -383,6 +363,4 @@ export declare const indexFrom: (shards: readonly IntentShard[]) => IntentIndex;
|
|
|
383
363
|
export declare const shardsFrom: (records: readonly IntentRecord[]) => IntentShard[];
|
|
384
364
|
/** Serialise byte-stably, with keys sorted so a re-save of unchanged data is a no-op diff. */
|
|
385
365
|
export declare const serialise: (value: unknown) => string;
|
|
386
|
-
/** Read + validate one JSON file, failing soft: a hand-merged file with a conflict marker is real. */
|
|
387
|
-
export declare const readJsonFile: <T>(fs: FileSystemPort, path: string, parse: (raw: unknown) => T, fallback: T) => Promise<T>;
|
|
388
366
|
export {};
|
|
@@ -96,7 +96,6 @@ export const emptyShard = (subject) => ({
|
|
|
96
96
|
subject,
|
|
97
97
|
intents: {},
|
|
98
98
|
});
|
|
99
|
-
export const emptyIndex = () => ({ version: INTENT_SHARD_VERSION, entries: [] });
|
|
100
99
|
/** The verification lifecycle a legacy record carried, mapped onto the settledness axis. */
|
|
101
100
|
export const statusFromState = (state) => 'proved' === state ? IntentStatus.PROVED : IntentStatus.AGREED;
|
|
102
101
|
/**
|
|
@@ -142,12 +141,3 @@ const sortKeys = (value) => {
|
|
|
142
141
|
return value;
|
|
143
142
|
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)));
|
|
144
143
|
};
|
|
145
|
-
/** Read + validate one JSON file, failing soft: a hand-merged file with a conflict marker is real. */
|
|
146
|
-
export const readJsonFile = async (fs, path, parse, fallback) => {
|
|
147
|
-
try {
|
|
148
|
-
return parse(JSON.parse(await fs.readFile(path)));
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
return fallback;
|
|
152
|
-
}
|
|
153
|
-
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FlowName, Predicate } from '@reticlehq/core';
|
|
1
|
+
import type { FlowFile, FlowName, Predicate } from '@reticlehq/core';
|
|
2
2
|
/**
|
|
3
3
|
* What this module needs a recorded step to BE, named structurally rather than imported.
|
|
4
4
|
*
|
|
@@ -15,6 +15,10 @@ export interface TapeStep {
|
|
|
15
15
|
expect?: Predicate;
|
|
16
16
|
/** Recorder-internal, and the field journeys are cut on. Never written to the saved flow. */
|
|
17
17
|
route?: string;
|
|
18
|
+
page?: string;
|
|
19
|
+
endPage?: string;
|
|
20
|
+
/** The intent the agent declared on this action. The first one names the journey. */
|
|
21
|
+
intent?: string;
|
|
18
22
|
}
|
|
19
23
|
/** The compiled shape a flow store accepts. Structural, for the same reason as `TapeStep`. */
|
|
20
24
|
export interface DriveProgram {
|
|
@@ -22,6 +26,7 @@ export interface DriveProgram {
|
|
|
22
26
|
version: number;
|
|
23
27
|
steps: TapeStep[];
|
|
24
28
|
startPath?: string;
|
|
29
|
+
author?: FlowFile['author'];
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
32
|
* What a session drove, saved as a replayable flow — without anybody asking for it.
|
|
@@ -36,21 +41,21 @@ export interface DriveProgram {
|
|
|
36
41
|
*/
|
|
37
42
|
/** The one condition on saving, and the reason the whole thing is safe. */
|
|
38
43
|
export declare function carriesAnAssertion(steps: readonly TapeStep[]): boolean;
|
|
44
|
+
/** Every auto-saved flow's name starts here, which is what lets a merge tell them from hand-named ones. */
|
|
45
|
+
export declare const DRIVE_FLOW_PREFIX = "drive-";
|
|
39
46
|
/**
|
|
40
|
-
* A
|
|
47
|
+
* A readable name for a journey, led by WHY it was driven.
|
|
41
48
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* because `presenter-controls.ts` renders it straight into the replay button's `textContent` — so
|
|
46
|
-
* the label a person actually sees was `drive-sdc991872-6d66-4adf-8780-f931c62905f9`, twice, for two
|
|
47
|
-
* different journeys. A session id is a fact about the socket, not about the feature.
|
|
49
|
+
* The agent's own `intent` on the action is the best name there is — it is the sentence a person
|
|
50
|
+
* would use — so it leads when one was declared. Without one, what the journey proved names it (the
|
|
51
|
+
* strongest claim first), and the route follows so two journeys stay apart.
|
|
48
52
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
53
|
+
* No session id. It used to trail every name so two sessions never overwrote each other, which also
|
|
54
|
+
* made every session's drive of the same journey its own file. Two DIFFERENT journeys that land on
|
|
55
|
+
* one name are now told apart at save by their shape, and two copies of the SAME journey merge —
|
|
56
|
+
* see FlowStore.saveJourney.
|
|
52
57
|
*/
|
|
53
|
-
export declare function driveFlowName(
|
|
58
|
+
export declare function driveFlowName(route?: string, steps?: readonly TapeStep[]): FlowName;
|
|
54
59
|
export interface DriveFlowOutcome {
|
|
55
60
|
/** The flows that were written — one per journey the session contained. */
|
|
56
61
|
readonly saved?: readonly string[];
|
|
@@ -65,7 +70,7 @@ export interface DriveFlowOutcome {
|
|
|
65
70
|
readonly unprovenSteps?: number;
|
|
66
71
|
}
|
|
67
72
|
/** A flow per journey, so two journeys in one session are two regression tests rather than one. */
|
|
68
|
-
export declare function driveFlowsFrom(
|
|
73
|
+
export declare function driveFlowsFrom(tape: {
|
|
69
74
|
steps: readonly TapeStep[];
|
|
70
75
|
startPath?: string;
|
|
71
76
|
} | undefined): {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PredicateKind, REPLAY_PROGRAM_VERSION, asFlowName } from '@reticlehq/core';
|
|
1
|
+
import { ActionType, PredicateKind, QueryBy, REPLAY_PROGRAM_VERSION, asFlowName, defaultIsSensitiveKey, } from '@reticlehq/core';
|
|
2
2
|
import { clauseOfKind } from '@reticlehq/core';
|
|
3
3
|
/**
|
|
4
4
|
* What a session drove, saved as a replayable flow — without anybody asking for it.
|
|
@@ -30,7 +30,7 @@ const segment = (value, max) => value
|
|
|
30
30
|
* DOM-shaped claims are the weakest. Naming a flow after the strongest thing it proves is the same
|
|
31
31
|
* judgement the honesty grade makes, reused where a human reads it.
|
|
32
32
|
*
|
|
33
|
-
* Returns '' when a step claims nothing nameable
|
|
33
|
+
* Returns '' when a step claims nothing nameable.
|
|
34
34
|
*/
|
|
35
35
|
function provedSlug(steps) {
|
|
36
36
|
for (const step of steps) {
|
|
@@ -63,42 +63,29 @@ function provedSlug(steps) {
|
|
|
63
63
|
}
|
|
64
64
|
return '';
|
|
65
65
|
}
|
|
66
|
+
/** Every auto-saved flow's name starts here, which is what lets a merge tell them from hand-named ones. */
|
|
67
|
+
export const DRIVE_FLOW_PREFIX = 'drive-';
|
|
66
68
|
/**
|
|
67
|
-
* A
|
|
69
|
+
* A readable name for a journey, led by WHY it was driven.
|
|
68
70
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* because `presenter-controls.ts` renders it straight into the replay button's `textContent` — so
|
|
73
|
-
* the label a person actually sees was `drive-sdc991872-6d66-4adf-8780-f931c62905f9`, twice, for two
|
|
74
|
-
* different journeys. A session id is a fact about the socket, not about the feature.
|
|
71
|
+
* The agent's own `intent` on the action is the best name there is — it is the sentence a person
|
|
72
|
+
* would use — so it leads when one was declared. Without one, what the journey proved names it (the
|
|
73
|
+
* strongest claim first), and the route follows so two journeys stay apart.
|
|
75
74
|
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
75
|
+
* No session id. It used to trail every name so two sessions never overwrote each other, which also
|
|
76
|
+
* made every session's drive of the same journey its own file. Two DIFFERENT journeys that land on
|
|
77
|
+
* one name are now told apart at save by their shape, and two copies of the SAME journey merge —
|
|
78
|
+
* see FlowStore.saveJourney.
|
|
79
79
|
*/
|
|
80
|
-
export function driveFlowName(
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Minted here, not at the call site. This function is where a drive's flow name comes into
|
|
90
|
-
// existence, and every character it can emit has just been forced through the safe-segment
|
|
91
|
-
// replacements above — so this IS the validated boundary the brand is meant to be created at.
|
|
92
|
-
// Returning `string` pushed a cast onto each caller, which is the hole `flowPath`'s parameter
|
|
93
|
-
// type was added to close.
|
|
94
|
-
// Nothing nameable was proved, so keep the name this function has always produced.
|
|
95
|
-
if ('' === proved) {
|
|
96
|
-
return asFlowName(0 === leg.length ? `drive-${safe}` : `drive-${safe}-${leg}`);
|
|
97
|
-
}
|
|
98
|
-
// Enough of the session to keep two of them apart, and little enough to read past.
|
|
99
|
-
const discriminator = safe.replace(/-/g, '').slice(0, 8);
|
|
100
|
-
const parts = ['drive', proved, ...(0 === leg.length ? [] : [leg]), discriminator];
|
|
101
|
-
return asFlowName(parts.join('-'));
|
|
80
|
+
export function driveFlowName(route, steps = []) {
|
|
81
|
+
const intent = steps.find((step) => step.intent !== undefined)?.intent;
|
|
82
|
+
const leg = segment(route ?? '', 24);
|
|
83
|
+
const why = intent === undefined ? '' : segment(intent, 48);
|
|
84
|
+
const lead = '' !== why ? why : provedSlug(steps);
|
|
85
|
+
// Minted here: every character has just been forced through `segment`, so this IS the validated
|
|
86
|
+
// boundary the brand is meant to be created at.
|
|
87
|
+
const parts = [lead, ...('' !== why || 0 === leg.length ? [] : [leg])].filter((p) => '' !== p);
|
|
88
|
+
return asFlowName(`${DRIVE_FLOW_PREFIX}${0 === parts.length ? 'journey' : parts.join('-')}`);
|
|
102
89
|
}
|
|
103
90
|
/**
|
|
104
91
|
* Turn this session's ambient tape into a flow, when it is worth one.
|
|
@@ -125,18 +112,39 @@ export function driveFlowName(sessionId, route, steps = []) {
|
|
|
125
112
|
*/
|
|
126
113
|
function segmentsByRoute(steps) {
|
|
127
114
|
const out = [];
|
|
115
|
+
let signedIn = false;
|
|
128
116
|
for (const step of steps) {
|
|
129
117
|
const last = out.at(-1);
|
|
130
|
-
|
|
118
|
+
const previous = last?.steps.at(-1);
|
|
119
|
+
// The same journey when the route is unchanged, or when the previous ACTION moved the app here
|
|
120
|
+
// — cart → shipping → payment is one flow, and cutting it left three fragments that each
|
|
121
|
+
// proved a page and none proved the journey. Not after a sign-in: that is the cut this exists
|
|
122
|
+
// for, and a login that navigates onward must still not open the next flow.
|
|
123
|
+
const caused = previous !== undefined && step.route !== undefined && previous.endPage === step.route;
|
|
124
|
+
if (last !== undefined && (last.route === step.route || (caused && !signedIn))) {
|
|
131
125
|
last.steps.push(step);
|
|
126
|
+
signedIn ||= typesASecret(step);
|
|
132
127
|
continue;
|
|
133
128
|
}
|
|
129
|
+
signedIn = typesASecret(step);
|
|
134
130
|
out.push({ ...(step.route === undefined ? {} : { route: step.route }), steps: [step] });
|
|
135
131
|
}
|
|
136
132
|
return out;
|
|
137
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* A fill into a field the network channel would redact — the sign-in a journey must not carry.
|
|
136
|
+
* Read off the recorder's anchor args: a testid names the field by `value`, a role by `name`.
|
|
137
|
+
*/
|
|
138
|
+
function typesASecret(step) {
|
|
139
|
+
const action = step.args['action'];
|
|
140
|
+
if (ActionType.FILL !== action && ActionType.TYPE !== action)
|
|
141
|
+
return false;
|
|
142
|
+
const by = step.args['by'];
|
|
143
|
+
const field = QueryBy.TESTID === by ? step.args['value'] : step.args['name'];
|
|
144
|
+
return 'string' === typeof field && defaultIsSensitiveKey(field);
|
|
145
|
+
}
|
|
138
146
|
/** A flow per journey, so two journeys in one session are two regression tests rather than one. */
|
|
139
|
-
export function driveFlowsFrom(
|
|
147
|
+
export function driveFlowsFrom(tape) {
|
|
140
148
|
if (tape === undefined || 0 === tape.steps.length)
|
|
141
149
|
return { programs: [], outcome: {} };
|
|
142
150
|
const programs = [];
|
|
@@ -148,7 +156,7 @@ export function driveFlowsFrom(sessionId, tape) {
|
|
|
148
156
|
}
|
|
149
157
|
const startPath = segment.route ?? tape.startPath;
|
|
150
158
|
programs.push({
|
|
151
|
-
name: driveFlowName(
|
|
159
|
+
name: driveFlowName(startPath, segment.steps),
|
|
152
160
|
version: REPLAY_PROGRAM_VERSION,
|
|
153
161
|
// The route is recorder-internal and has no business on disk — `startPath` is where the
|
|
154
162
|
// on-disk flow says the same thing, in the field replay actually reads.
|
|
@@ -49,6 +49,8 @@ export const WORKSPACE_TIERS = {
|
|
|
49
49
|
[ReticleDir.AMBIENT_FILE]: localFile,
|
|
50
50
|
[ReticleDir.ENVELOPES_FILE]: localFile,
|
|
51
51
|
[ReticleDir.FLAKE_FILE]: localFile,
|
|
52
|
+
// What THIS machine's drives covered, and the best each level reached — the gate's ratchet.
|
|
53
|
+
[ReticleDir.COVERAGE_FILE]: localFile,
|
|
52
54
|
[ReticleDir.TIERS_FILE]: localFile,
|
|
53
55
|
// The user's own record of what Reticle did for them, on THIS machine.
|
|
54
56
|
[ReticleDir.IMPACT_FILE]: localFile,
|
|
@@ -79,8 +79,16 @@ interface SessionEndDeps {
|
|
|
79
79
|
* this handler follows.
|
|
80
80
|
*/
|
|
81
81
|
flows?: {
|
|
82
|
-
|
|
82
|
+
saveJourney: (program: DriveProgram, projectId: ProjectId | undefined, prefix: string) => Promise<unknown>;
|
|
83
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* The flow store for a session's own `.reticle`. Drive flows used to go to `flows` above — the
|
|
86
|
+
* daemon's launch directory — while replay reads the session's project, so a daemon started
|
|
87
|
+
* anywhere else saved flows nothing could replay. Absent, `flows` is used.
|
|
88
|
+
*/
|
|
89
|
+
flowsAt?: (root: string) => NonNullable<SessionEndDeps['flows']>;
|
|
90
|
+
/** Who is driving — stamped on each saved flow. Injected: teardown does not reach into MCP. */
|
|
91
|
+
author?: () => DriveProgram['author'];
|
|
84
92
|
/**
|
|
85
93
|
* Called when a run artifact is written, so cloud sync can cycle instead of waiting for its timer.
|
|
86
94
|
*
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import { driveFlowsFrom } from './drive-flow.js';
|
|
1
|
+
import { DRIVE_FLOW_PREFIX, driveFlowsFrom, } from './drive-flow.js';
|
|
2
|
+
import { log } from '../../log.js';
|
|
3
|
+
/** What a session's teardown writes to the daemon log when a by-product fails to save. */
|
|
4
|
+
const SESSION_END_LOG = {
|
|
5
|
+
FLOW_SAVE_FAILED: 'reticle_drive_flow_save_failed',
|
|
6
|
+
RUN_RECORD_FAILED: 'reticle_drive_run_record_failed',
|
|
7
|
+
};
|
|
8
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
2
9
|
import { journalActionsPath, sessionDirPath } from '../project/dir/reticle-dir.js';
|
|
3
10
|
import { asSessionId } from '@reticlehq/core';
|
|
4
11
|
import { OnboardingPhase, OnboardingStepStatus, } from '@reticlehq/core/telemetry';
|
|
@@ -78,24 +85,27 @@ export function makeSessionEnd(deps) {
|
|
|
78
85
|
// would buy under a minute of latency for a mutable handle held across two wiring sites.
|
|
79
86
|
// A DRIVE IS A REGRESSION TEST, and it used to need somebody to remember to say so.
|
|
80
87
|
//
|
|
81
|
-
// `record{start}` + `flow_save` is the agent-facing route and agents do not take it:
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
88
|
+
// `record{start}` + `flow_save` is the agent-facing route and agents do not take it: few saved
|
|
89
|
+
// flows are mutation-testable and few steps declare a consequence, while the engine catches
|
|
90
|
+
// nearly every bug it structurally can. The ceiling is how many flows EXIST that could go red,
|
|
91
|
+
// so a drive that is never saved is a regression test paid for and thrown away.
|
|
85
92
|
//
|
|
86
93
|
// Saved only when the tape declared a consequence — see drive-flow.ts for why the alternative is
|
|
87
94
|
// a false-green factory running once per session.
|
|
88
95
|
try {
|
|
89
96
|
await saveDrivenFlow(deps, session);
|
|
90
97
|
}
|
|
91
|
-
catch {
|
|
92
|
-
//
|
|
98
|
+
catch (error) {
|
|
99
|
+
// A flow is a by-product of the session; failing to write one never fails the teardown. It is
|
|
100
|
+
// logged, because a drive that leaves no flow and no reason is otherwise undiagnosable.
|
|
101
|
+
log(SESSION_END_LOG.FLOW_SAVE_FAILED, { error: errorMessage(error) });
|
|
93
102
|
}
|
|
94
103
|
try {
|
|
95
104
|
await recordDriveRun(deps, session);
|
|
96
105
|
}
|
|
97
|
-
catch {
|
|
98
|
-
//
|
|
106
|
+
catch (error) {
|
|
107
|
+
// An artifact is a report ABOUT the session; failing to write one never fails the teardown.
|
|
108
|
+
log(SESSION_END_LOG.RUN_RECORD_FAILED, { error: errorMessage(error) });
|
|
99
109
|
}
|
|
100
110
|
try {
|
|
101
111
|
// Bound the journal on disk HERE, not only at daemon start.
|
|
@@ -196,13 +206,15 @@ export async function recordDriveRun(deps, session) {
|
|
|
196
206
|
/** Persist what this session drove as a replayable flow, when it declared anything provable. */
|
|
197
207
|
async function saveDrivenFlow(deps, session) {
|
|
198
208
|
const takeTape = deps.takeAmbientTape;
|
|
199
|
-
const flows = deps.flows;
|
|
209
|
+
const flows = deps.flowsAt?.(session.artifactRoot ?? deps.reticleRoot) ?? deps.flows;
|
|
200
210
|
if (takeTape === undefined || flows === undefined)
|
|
201
211
|
return;
|
|
202
212
|
// One flow per journey the session contained, not one flow per session — see drive-flow.ts.
|
|
203
|
-
const { programs, outcome } = driveFlowsFrom(
|
|
213
|
+
const { programs, outcome } = driveFlowsFrom(takeTape());
|
|
214
|
+
const author = deps.author?.();
|
|
204
215
|
for (const program of programs) {
|
|
205
|
-
|
|
216
|
+
// Folded into any saved copy of the same journey, so each session does not add a near-copy.
|
|
217
|
+
await flows.saveJourney(author === undefined ? program : { ...program, author }, session.projectId, DRIVE_FLOW_PREFIX);
|
|
206
218
|
}
|
|
207
219
|
// The funnel step that makes the SECOND run cheap.
|
|
208
220
|
//
|
|
@@ -33,6 +33,8 @@ export interface ReticleDirPaths {
|
|
|
33
33
|
capsules: string;
|
|
34
34
|
/**.../.reticle/flake.json (per-flow flake ledger) */
|
|
35
35
|
flake: string;
|
|
36
|
+
/**.../.reticle/coverage.json (app-wide coverage ledger, all six levels) */
|
|
37
|
+
coverage: string;
|
|
36
38
|
/**.../.reticle/assertion-tiers.json (last-passing assertion tiers; anti-reward-hacking baseline) */
|
|
37
39
|
tiers: string;
|
|
38
40
|
}
|
|
@@ -18,6 +18,7 @@ export function reticleDirPaths(root) {
|
|
|
18
18
|
ambient: join(root, ReticleDir.AMBIENT_FILE),
|
|
19
19
|
capsules: join(root, ReticleDir.CAPSULES_SUBDIR),
|
|
20
20
|
flake: join(root, ReticleDir.FLAKE_FILE),
|
|
21
|
+
coverage: join(root, ReticleDir.COVERAGE_FILE),
|
|
21
22
|
tiers: join(root, ReticleDir.TIERS_FILE),
|
|
22
23
|
};
|
|
23
24
|
}
|
|
@@ -38,7 +38,9 @@ function numericDelta(before, after) {
|
|
|
38
38
|
* "what's broken vs before" from the cloud. Absent creds / unreachable → undefined (agent stays local).
|
|
39
39
|
*/
|
|
40
40
|
async function cloudRegression(deps, sessionId) {
|
|
41
|
-
|
|
41
|
+
// The same resolver every caller uses (stored key for a linked repo, else the exported one);
|
|
42
|
+
// the environment alone only for an embedder that wires no linked-credential port.
|
|
43
|
+
const config = await (deps.linkedCloud?.() ?? Promise.resolve(resolveCloudConfig(process.env)));
|
|
42
44
|
if (null === config)
|
|
43
45
|
return undefined;
|
|
44
46
|
let projectId;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* A daemon left running by one project answers the next project's app and rejects it on token, and
|
|
5
5
|
* the SDK printed `bridge refused the connection: authentication failed`. That sends someone to
|
|
6
6
|
* check a token which is perfectly correct — it is simply a DIFFERENT project's token. The two cases
|
|
7
|
-
* need opposite fixes: "your token is wrong" versus "that daemon is not yours,
|
|
7
|
+
* need opposite fixes: "your token is wrong" versus "that daemon is not yours, use your own port".
|
|
8
8
|
*
|
|
9
9
|
* The discriminator is EVIDENCE, not derivation. The daemon and the SDK derive project ids by
|
|
10
10
|
* different schemes (the SDK from its Vite root, the daemon from its own cwd), so comparing those
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* A daemon left running by one project answers the next project's app and rejects it on token, and
|
|
5
5
|
* the SDK printed `bridge refused the connection: authentication failed`. That sends someone to
|
|
6
6
|
* check a token which is perfectly correct — it is simply a DIFFERENT project's token. The two cases
|
|
7
|
-
* need opposite fixes: "your token is wrong" versus "that daemon is not yours,
|
|
7
|
+
* need opposite fixes: "your token is wrong" versus "that daemon is not yours, use your own port".
|
|
8
8
|
*
|
|
9
9
|
* The discriminator is EVIDENCE, not derivation. The daemon and the SDK derive project ids by
|
|
10
10
|
* different schemes (the SDK from its Vite root, the daemon from its own cwd), so comparing those
|
|
@@ -62,7 +62,9 @@ export function authFailureReason(servedProjects, helloProject, helloToken) {
|
|
|
62
62
|
if (helloProject !== undefined &&
|
|
63
63
|
1 === servedProjects.size &&
|
|
64
64
|
!servedProjects.has(helloProject)) {
|
|
65
|
-
|
|
65
|
+
// Not "stop it": that daemon is serving somebody else's app. This project needs its own port,
|
|
66
|
+
// which the CLI and the build plugins both read from `.reticle.json`.
|
|
67
|
+
const reason = `${DIFFERENT_PROJECT_PREFIX} — set a free "port" in .reticle.json, then restart the dev server`;
|
|
66
68
|
return Buffer.byteLength(reason, 'utf8') <= MAX_REASON_BYTES ? reason : PLAIN;
|
|
67
69
|
}
|
|
68
70
|
if (helloToken === undefined || 0 === helloToken.length)
|
|
@@ -38,6 +38,14 @@ export declare const WS_CLOSE_REASON: {
|
|
|
38
38
|
* refusal on this path: nothing about the app is wrong and retrying is the correct response.
|
|
39
39
|
*/
|
|
40
40
|
readonly HANDSHAKE_POOL_FULL: string;
|
|
41
|
+
/**
|
|
42
|
+
* A page's HELLO failed the wire schema for a reason other than its protocol number — the SDK and
|
|
43
|
+
* this daemon are different releases whose contracts no longer agree. The socket is still closed
|
|
44
|
+
* with the short `invalid message` the SDK already prints; this is the sentence the no-session
|
|
45
|
+
* diagnosis records, because "never dialled the bridge" was what `init` said about a page this
|
|
46
|
+
* daemon had just turned away. Same `cause — fix` shape as the protocol-skew reasons.
|
|
47
|
+
*/
|
|
48
|
+
readonly INVALID_HELLO: string;
|
|
41
49
|
};
|
|
42
50
|
interface BridgeOptions {
|
|
43
51
|
port: number;
|
|
@@ -69,6 +69,16 @@ export const WS_CLOSE_REASON = {
|
|
|
69
69
|
'this dial never got to identify itself. Nothing is wrong with the app; the pool drains on its ' +
|
|
70
70
|
'own and reloading the page reconnects. If it persists, something else on this machine is ' +
|
|
71
71
|
'opening sockets to this port.',
|
|
72
|
+
/**
|
|
73
|
+
* A page's HELLO failed the wire schema for a reason other than its protocol number — the SDK and
|
|
74
|
+
* this daemon are different releases whose contracts no longer agree. The socket is still closed
|
|
75
|
+
* with the short `invalid message` the SDK already prints; this is the sentence the no-session
|
|
76
|
+
* diagnosis records, because "never dialled the bridge" was what `init` said about a page this
|
|
77
|
+
* daemon had just turned away. Same `cause — fix` shape as the protocol-skew reasons.
|
|
78
|
+
*/
|
|
79
|
+
INVALID_HELLO: "invalid message: the page's hello does not match this daemon's wire contract — put " +
|
|
80
|
+
'@reticlehq/browser, the build plugin and @reticlehq/server on one release, run `reticle stop`, ' +
|
|
81
|
+
'then restart the dev server',
|
|
72
82
|
};
|
|
73
83
|
/**
|
|
74
84
|
* A dial turned away at the ORIGIN gate, in words the reader can act on.
|
|
@@ -179,6 +189,25 @@ function helloProtocolMismatch(text) {
|
|
|
179
189
|
}
|
|
180
190
|
return null;
|
|
181
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* What a HELLO says about the page it came from, read loosely enough to survive the schema failing.
|
|
194
|
+
* Null when the message is not a hello at all — only an SDK sends one, so only then is a refusal
|
|
195
|
+
* evidence about an app.
|
|
196
|
+
*/
|
|
197
|
+
function peekHelloPage(text) {
|
|
198
|
+
try {
|
|
199
|
+
const json = JSON.parse(text);
|
|
200
|
+
if (json.kind !== MessageKind.HELLO)
|
|
201
|
+
return null;
|
|
202
|
+
return {
|
|
203
|
+
...('string' === typeof json.url ? { url: json.url } : {}),
|
|
204
|
+
...('string' === typeof json.projectId ? { projectId: json.projectId } : {}),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
182
211
|
/** Normalize ws RawData (string | Buffer | Buffer[] | ArrayBuffer) into a UTF-8 string. */
|
|
183
212
|
function rawToString(raw) {
|
|
184
213
|
if ('string' === typeof raw)
|
|
@@ -436,10 +465,19 @@ export class Bridge {
|
|
|
436
465
|
// Told to the AGENT too, not only to the SDK that is about to stop retrying. Without this
|
|
437
466
|
// the next tool call answers "no browser session connected", which reads as "no app is
|
|
438
467
|
// running" — so an SDK too old to connect is invisible.
|
|
439
|
-
this.sessions.noteClosure(reason, this.#clock());
|
|
468
|
+
this.sessions.noteClosure(reason, this.#clock(), peekHelloPage(text) ?? {});
|
|
440
469
|
socket.close(WS_CLOSE.PROTOCOL_MISMATCH[0], reason);
|
|
441
470
|
return;
|
|
442
471
|
}
|
|
472
|
+
// Recorded only for a HELLO on a socket that has none yet: that is a page being turned
|
|
473
|
+
// away, and it used to leave no trace, so `init` reported "never dialled the bridge" about
|
|
474
|
+
// a page this daemon had just refused. A malformed frame on a live session is a different
|
|
475
|
+
// failure and says nothing about whether the app can connect.
|
|
476
|
+
const refused = undefined === session ? peekHelloPage(text) : null;
|
|
477
|
+
if (null !== refused) {
|
|
478
|
+
log('hello_invalid', { ...refused });
|
|
479
|
+
this.sessions.noteClosure(WS_CLOSE_REASON.INVALID_HELLO, this.#clock(), refused);
|
|
480
|
+
}
|
|
443
481
|
socket.close(...WS_CLOSE.INVALID_MESSAGE);
|
|
444
482
|
return;
|
|
445
483
|
}
|
|
@@ -498,7 +536,10 @@ export class Bridge {
|
|
|
498
536
|
presented: parsed.token !== undefined && 0 < parsed.token.length,
|
|
499
537
|
tokenSource: this.#tokenSource,
|
|
500
538
|
});
|
|
501
|
-
this.sessions.noteClosure(reason, this.#clock()
|
|
539
|
+
this.sessions.noteClosure(reason, this.#clock(), {
|
|
540
|
+
url: parsed.url,
|
|
541
|
+
...(parsed.projectId === undefined ? {} : { projectId: parsed.projectId }),
|
|
542
|
+
});
|
|
502
543
|
socket.close(WS_CLOSE.AUTH_FAILED[0], reason);
|
|
503
544
|
return;
|
|
504
545
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* V8 JS coverage on a page Reticle drives — the raw half of "what of the app ever ran".
|
|
4
|
+
*
|
|
5
|
+
* Started once per page with `resetOnNavigation: false`, so a reload keeps counting instead of
|
|
6
|
+
* starting over. A take stops and immediately restarts: the counts it returns are since the last
|
|
7
|
+
* take, and the reader folds them as a union (see features/exhaust/code-coverage.ts).
|
|
8
|
+
*
|
|
9
|
+
* Chromium only. Anything else — or a page that closed — answers false/undefined rather than
|
|
10
|
+
* throwing: coverage is an addition to a drive, never a reason for one to fail.
|
|
11
|
+
*/
|
|
12
|
+
/** One script's coverage — the shape `stopJSCoverage` returns, narrowed to what is read. */
|
|
13
|
+
export interface ScriptCoverage {
|
|
14
|
+
url: string;
|
|
15
|
+
functions: {
|
|
16
|
+
functionName: string;
|
|
17
|
+
ranges: {
|
|
18
|
+
startOffset: number;
|
|
19
|
+
endOffset: number;
|
|
20
|
+
count: number;
|
|
21
|
+
}[];
|
|
22
|
+
}[];
|
|
23
|
+
}
|
|
24
|
+
export declare function startJsCoverage(page: Page, covering: WeakSet<Page>): Promise<boolean>;
|
|
25
|
+
export declare function takeJsCoverage(page: Page, covering: WeakSet<Page>): Promise<ScriptCoverage[] | undefined>;
|