@reticlehq/server 3.3.0 → 3.5.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 +6 -2
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- 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 +24 -4
- 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 +64 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.d.ts +1 -1
- package/dist/command/cli/cloud-login.js +27 -8
- 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 +21 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +79 -11
- 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/dev-server-wait.d.ts +2 -0
- package/dist/command/setup/bringup/dev-server-wait.js +1 -1
- 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 +91 -9
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +86 -45
- 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 +28 -1
- package/dist/command/setup/run-setup.js +103 -15
- 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.d.ts +2 -0
- package/dist/command/setup/setup-mcp.js +17 -8
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/command/version/peer-announce.d.ts +6 -2
- package/dist/command/version/peer-announce.js +8 -6
- package/dist/command/version/version-nudge.d.ts +17 -3
- package/dist/command/version/version-nudge.js +25 -4
- 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 +10 -0
- package/dist/features/harness/fill-values.js +50 -11
- package/dist/features/harness/jev-driver.js +30 -13
- package/dist/features/harness/openai-driver.d.ts +12 -0
- package/dist/features/harness/openai-driver.js +21 -12
- package/dist/features/harness/platform-config.js +5 -6
- package/dist/features/license/license.d.ts +2 -2
- package/dist/index.d.ts +9 -0
- package/dist/index.js +56 -19
- package/dist/judgement/capsule/capsule-store.d.ts +4 -4
- 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 +77 -3
- package/dist/memory/cloud/sync-cycle.js +325 -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/intent/intent-tools.js +8 -3
- package/dist/memory/journal/drive-flow.d.ts +18 -13
- package/dist/memory/journal/drive-flow.js +45 -37
- package/dist/memory/journal/envelope.d.ts +8 -8
- package/dist/memory/journal/journal-query.d.ts +14 -0
- package/dist/memory/journal/journal-query.js +15 -0
- 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/artifact-root-resolver.d.ts +10 -0
- package/dist/memory/project/artifact-root-resolver.js +46 -19
- 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 +12 -3
- package/dist/portal/bridge/bridge.js +49 -3
- 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/input/viewport-tools.js +16 -6
- 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 +10 -8
- package/dist/portal/session/session.js +24 -32
- package/dist/surface/http-server.d.ts +3 -2
- package/dist/surface/http-server.js +8 -5
- 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 +5 -3
- 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/proxy/proxy-log.js +3 -5
- 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 +28 -52
- 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/dynamic-tools.js +29 -2
- package/dist/surface/tools/error-recovery.js +5 -6
- 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 +12 -7
- package/dist/surface/tools/invoke-tool.js +12 -8
- 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/tool-kit.d.ts +10 -0
- package/dist/surface/tools/tools.js +4 -0
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +1 -5
- 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/telemetry/onboarding-firsts.d.ts +2 -2
- package/dist/telemetry/onboarding-firsts.js +2 -2
- 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 -4
- 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 +20 -8
- 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 +22 -7
- 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 +15 -5
- 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 +2 -2
- 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/next.mdx +3 -0
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +4 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +50 -5
- package/docs/quickstart.mdx +12 -10
- 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/act.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 -7
- 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
package/dist/index.js
CHANGED
|
@@ -4,12 +4,12 @@ import { fetchPlatformConfig } from './features/harness/platform-config.js';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { existsSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { homedir } from 'node:os';
|
|
7
|
-
import { linkedCloudPort } from './memory/cloud/cloud-config.js';
|
|
7
|
+
import { linkedCloudPort, platformEnvPort } from './memory/cloud/cloud-config.js';
|
|
8
8
|
import { attachCloudSync } from './memory/cloud/sync-daemon.js';
|
|
9
9
|
import { wireHooks } from './hooks/hook-commands.js';
|
|
10
10
|
import { PROJECT_REGISTRY_FILE, parseProjectRegistry, projectCandidates, } from '@reticlehq/core/artifacts';
|
|
11
11
|
import { discoverProjectConfigs } from './command/cli/config/config-discovery.js';
|
|
12
|
-
import { artifactRootResolver } from './memory/project/artifact-root-resolver.js';
|
|
12
|
+
import { artifactRootResolver, projectDirectoryFor, } from './memory/project/artifact-root-resolver.js';
|
|
13
13
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
14
|
import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, bridgeWsUrl, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST, ReplayStatus, EventType, } from '@reticlehq/core';
|
|
15
15
|
import { originOf } from './portal/session/session-manager.js';
|
|
@@ -21,10 +21,12 @@ import { reportAppInstrumented } from './telemetry/app-instrumented.js';
|
|
|
21
21
|
import { resolveBridgeSecurityWithAutoToken } from './portal/bridge/bridge-security.js';
|
|
22
22
|
import { Bridge } from './portal/bridge/bridge.js';
|
|
23
23
|
import { sdkFixForDirectory } from './command/version/sdk-fix.js';
|
|
24
|
+
import { connectionSkew } from './command/version/version-nudge.js';
|
|
24
25
|
import { SERVER_VERSION } from './command/version/identity/server-version.js';
|
|
25
26
|
import { BaselineStore } from './memory/project/baselines.js';
|
|
26
27
|
import { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
27
28
|
import { initImpact } from './memory/impact/impact-recorder.js';
|
|
29
|
+
import { flowAuthor } from './language/flows/flow-author.js';
|
|
28
30
|
import { FlowStore } from './language/flows/flows.js';
|
|
29
31
|
import { buildFlowChips } from './language/flows/flow-scope.js';
|
|
30
32
|
import { ProjectStore } from './memory/project/project-store.js';
|
|
@@ -42,7 +44,7 @@ import { LEASE_ACQUIRE_TOOL } from './surface/tools/lease-tools.js';
|
|
|
42
44
|
import { runTool } from './surface/tools/invoke-tool.js';
|
|
43
45
|
import { SessionReaper, endAllSessions, MCP_DISCONNECT_SUMMARY, } from './portal/session/session-reaper.js';
|
|
44
46
|
import { wireSessionScope } from './portal/session/no-session-watch.js';
|
|
45
|
-
import { buildIdlePredicate } from './command/daemon/lifetime/daemon-usefulness.js';
|
|
47
|
+
import { buildIdlePredicate, noteSetupHold } from './command/daemon/lifetime/daemon-usefulness.js';
|
|
46
48
|
import { resolveToolSurface } from './surface/tools/tool-surface.js';
|
|
47
49
|
import { statusPayload } from './status-payload.js';
|
|
48
50
|
import { CdpRealInputProvider, LaunchedRealInputProvider } from './portal/input/real-input.js';
|
|
@@ -54,7 +56,7 @@ import { LeaseReaper } from './portal/pool/lease-reaper.js';
|
|
|
54
56
|
import { findProjectConfig, projectIdsAt, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
|
|
55
57
|
import { hasAnyProjectConnectedBefore } from './memory/recall/prior/connection-memory.js';
|
|
56
58
|
import { reticleStateHome } from './command/daemon/daemon.js';
|
|
57
|
-
import {
|
|
59
|
+
import { probeLaunchableChromium } from './launch-chromium.js';
|
|
58
60
|
import { attachJournal } from './wire-journal.js';
|
|
59
61
|
import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
|
|
60
62
|
import { AMBIENT_RECORDING } from './language/flows/recording/tape/recordings.js';
|
|
@@ -80,6 +82,15 @@ export { buildSessionRecommendation } from './portal/session/presence/session-re
|
|
|
80
82
|
export { TOOLS } from './surface/tools/tools.js';
|
|
81
83
|
export { createToolInvoker, UNKNOWN_TOOL_ERROR } from './surface/tools/tool-invoker.js';
|
|
82
84
|
export { runTool, SESSION_BOUND_TOOLS, SESSION_EXEMPT_TOOLS } from './surface/tools/invoke-tool.js';
|
|
85
|
+
/**
|
|
86
|
+
* The port-presence check the CLI commands (`drive`, `verify`, `status`, `doctor`, `kill`) all share,
|
|
87
|
+
* re-exported so a consumer embedding `start()` in its own process can refuse a taken port the same
|
|
88
|
+
* honest way instead of letting a raw `EADDRINUSE` stack out — the gap `@reticlehq/test`'s
|
|
89
|
+
* `bootSession()` had (reticlehq/reticle#1141): it called `start()` directly with no pre-flight probe.
|
|
90
|
+
*/
|
|
91
|
+
export { probePresence, describePresence, PortPresence, } from './command/daemon/binding/port-presence.js';
|
|
92
|
+
export { probeDaemon } from './surface/mcp/proxy/proxy-daemon-probe.js';
|
|
93
|
+
export { fetchStatus } from './command/daemon/binding/daemon-status-probe.js';
|
|
83
94
|
export { BaselineStore, normalizeLines, diffLines } from './memory/project/baselines.js';
|
|
84
95
|
export { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
85
96
|
export { FlowStore, recordedStepToFlowStep } from './language/flows/flows.js';
|
|
@@ -215,12 +226,12 @@ async function resolveRealInput(options, onNavigateError, onNetworkDetail) {
|
|
|
215
226
|
}
|
|
216
227
|
/** Start the Reticle bridge (browser WS endpoint) and, by default, the MCP stdio server. */
|
|
217
228
|
/**
|
|
218
|
-
* The SDK-upgrade sentence for
|
|
219
|
-
* just-edited manifest is what we name.
|
|
220
|
-
* not an app.
|
|
229
|
+
* The SDK-upgrade sentence for the package.json of the project the page announced, evaluated at
|
|
230
|
+
* each HELLO so a just-edited manifest is what we name. The daemon's cwd only when that project's
|
|
231
|
+
* directory is unknown, and the framework-neutral sensor when that is not an app either (#1135).
|
|
221
232
|
*/
|
|
222
|
-
function
|
|
223
|
-
return sdkFixForDirectory(SERVER_VERSION, process.cwd());
|
|
233
|
+
function sdkFixForProject(projectId) {
|
|
234
|
+
return sdkFixForDirectory(SERVER_VERSION, projectDirectoryFor(projectId) ?? process.cwd());
|
|
224
235
|
}
|
|
225
236
|
/**
|
|
226
237
|
* Resolve a session's artifact root, from everything this machine knows about where projects live.
|
|
@@ -263,6 +274,8 @@ function knownProjectRoots() {
|
|
|
263
274
|
}
|
|
264
275
|
return [...roots];
|
|
265
276
|
}
|
|
277
|
+
/** This project's platform credential (stored, else the env key), as the env the platform readers take. */
|
|
278
|
+
const platformEnvFor = (root) => platformEnvPort(createNodeFileSystem(), root ?? join(process.cwd(), ReticleDir.ROOT), homedir(), process.env);
|
|
266
279
|
export async function start(options = {}) {
|
|
267
280
|
const port = options.port ?? RETICLE_DEFAULT_PORT;
|
|
268
281
|
// Open the user's impact record before anything can connect. Not inside the MCP branch: a daemon
|
|
@@ -273,11 +286,11 @@ export async function start(options = {}) {
|
|
|
273
286
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
274
287
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
275
288
|
// to reach for the other.
|
|
276
|
-
config: harnessConfigSource(() => fetchPlatformConfig(
|
|
289
|
+
config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
|
|
277
290
|
});
|
|
278
291
|
const uninstallHooks = wireHooks(options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT), readProjectId(process.cwd()));
|
|
279
292
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
280
|
-
const bridge = new Bridge({ port, sdkFix:
|
|
293
|
+
const bridge = new Bridge({ port, sdkFix: sdkFixForProject, ...security });
|
|
281
294
|
// Server-authoritative liveness: a Node-side reaper (immune to browser throttling) ends sessions
|
|
282
295
|
// whose agent has gone idle, so a forgotten/crashed agent never leaves the HUD "running" forever.
|
|
283
296
|
const reaper = new SessionReaper(bridge.sessions);
|
|
@@ -316,6 +329,7 @@ export async function start(options = {}) {
|
|
|
316
329
|
takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
|
|
317
330
|
reportStep: reportOnboardingStep,
|
|
318
331
|
flows,
|
|
332
|
+
author: flowAuthor,
|
|
319
333
|
});
|
|
320
334
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
321
335
|
attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
|
|
@@ -352,16 +366,23 @@ export async function start(options = {}) {
|
|
|
352
366
|
project,
|
|
353
367
|
fs,
|
|
354
368
|
reticleRoot,
|
|
369
|
+
linkedCloud: linkedCloudPort(fs, reticleRoot, homedir(), process.env),
|
|
355
370
|
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
356
371
|
now,
|
|
357
372
|
bridgePort: port,
|
|
358
|
-
browserProbe:
|
|
373
|
+
browserProbe: probeLaunchableChromium,
|
|
359
374
|
// The daemon's OWN project, so a tool can tell "this session is mine" from "this session
|
|
360
375
|
// belongs to a sibling app under the same daemon". contract_save refuses on the second.
|
|
361
376
|
...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
|
|
362
377
|
};
|
|
363
378
|
const profile = resolveToolSurface(options.toolProfile);
|
|
364
|
-
const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile,
|
|
379
|
+
const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile,
|
|
380
|
+
// A session live RIGHT NOW is stronger evidence than the durable memory, which can be empty or
|
|
381
|
+
// stale for a project the plugin wired without writing `.reticle.json` (see connection-memory.ts's
|
|
382
|
+
// KNOWN LIMIT). Without this OR, that project's `initialize` led with the first-install steps
|
|
383
|
+
// while a real session was already connected — see #1138.
|
|
384
|
+
bridge.sessions.count() > 0 ||
|
|
385
|
+
hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd())));
|
|
365
386
|
// When the agent (the MCP client) disconnects cleanly, end every active session at once so the
|
|
366
387
|
// HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
|
|
367
388
|
// self-ends via SESSION_LIFECYCLE.BRIDGE_LOST_MS — see transport.ts.)
|
|
@@ -404,11 +425,16 @@ export async function startDaemon(options = {}) {
|
|
|
404
425
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
405
426
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
406
427
|
// to reach for the other.
|
|
407
|
-
config: harnessConfigSource(() => fetchPlatformConfig(
|
|
428
|
+
config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
|
|
408
429
|
});
|
|
409
430
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
410
431
|
const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
|
|
411
|
-
const bridge = new Bridge({
|
|
432
|
+
const bridge = new Bridge({
|
|
433
|
+
port,
|
|
434
|
+
server: shared.httpServer,
|
|
435
|
+
sdkFix: sdkFixForProject,
|
|
436
|
+
...security,
|
|
437
|
+
});
|
|
412
438
|
// The daemon owns listen (below), so the real bind error is reported there; absorb bridge.ready's
|
|
413
439
|
// mirror rejection so a port collision can't surface as an unhandled promise rejection.
|
|
414
440
|
void bridge.ready.catch(() => undefined);
|
|
@@ -420,7 +446,12 @@ export async function startDaemon(options = {}) {
|
|
|
420
446
|
// answers WHY rather than just "sessionCount: 0".
|
|
421
447
|
// `verifyPort` rides along so a later `serve --http` can tell whether this daemon already honours
|
|
422
448
|
// the requested `--http-port` instead of silently ignoring the flag (#687).
|
|
423
|
-
shared.attachStatus(() =>
|
|
449
|
+
shared.attachStatus((held) => {
|
|
450
|
+
// `init` holds the daemon it is waiting on, so the idle rule cannot shut it down mid-wait.
|
|
451
|
+
if (held)
|
|
452
|
+
noteSetupHold(Date.now());
|
|
453
|
+
return statusPayload(bridge.sessions.count(), bridge.sessions.list(), bridge.sessions.noSessionHint(), verifyHttp?.port, bridge.sessions.noSessionLead());
|
|
454
|
+
});
|
|
424
455
|
// Agent-independent presence: the daemon outlives any single agent, so when the LAST agent's MCP
|
|
425
456
|
// connection drops (it stopped, or is waiting on the human), end every session and push a clear
|
|
426
457
|
// "go to your terminal" notice to the panel — the human is on the browser and must not lose a typed
|
|
@@ -462,6 +493,7 @@ export async function startDaemon(options = {}) {
|
|
|
462
493
|
takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
|
|
463
494
|
reportStep: reportOnboardingStep,
|
|
464
495
|
flows,
|
|
496
|
+
author: flowAuthor,
|
|
465
497
|
onRunPersisted: () => syncNudge.run?.(),
|
|
466
498
|
});
|
|
467
499
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
@@ -501,7 +533,7 @@ export async function startDaemon(options = {}) {
|
|
|
501
533
|
// console and the panel cannot disagree about a setting they both offer. Nothing is awaited and a
|
|
502
534
|
// failure is not surfaced: the next snapshot re-reads the platform, so a lost write shows up as
|
|
503
535
|
// the switch springing back, which is the truthful outcome.
|
|
504
|
-
bridge.attachHarnessRequest((enabled) => void
|
|
536
|
+
bridge.attachHarnessRequest((enabled) => void platformEnvFor(reticleRoot)().then((env) => writeHarnessSwitch(env, enabled)));
|
|
505
537
|
// Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
|
|
506
538
|
// never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
|
|
507
539
|
// Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
|
|
@@ -533,7 +565,7 @@ export async function startDaemon(options = {}) {
|
|
|
533
565
|
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
534
566
|
now,
|
|
535
567
|
bridgePort: port,
|
|
536
|
-
browserProbe:
|
|
568
|
+
browserProbe: probeLaunchableChromium,
|
|
537
569
|
// A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
|
|
538
570
|
onRunPersisted: () => cloudSync.nudge(),
|
|
539
571
|
};
|
|
@@ -541,7 +573,12 @@ export async function startDaemon(options = {}) {
|
|
|
541
573
|
const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
|
|
542
574
|
// Read per attach, not once: a project that gets wired while this daemon is alive should stop
|
|
543
575
|
// being told to wire itself on the next agent that connects.
|
|
544
|
-
shared.attachMcp(() => createMcpServer(
|
|
576
|
+
shared.attachMcp((peerSkew) => createMcpServer(peerSkew === undefined
|
|
577
|
+
? effectiveDeps
|
|
578
|
+
: { ...effectiveDeps, peerSkew: connectionSkew(peerSkew) }, profile,
|
|
579
|
+
// See the sibling call in `start`: a live session outweighs empty/stale durable memory (#1138).
|
|
580
|
+
bridge.sessions.count() > 0 ||
|
|
581
|
+
hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd()))));
|
|
545
582
|
// `reticle drive <url>` when this daemon already owns the port: it asks HERE instead of trying to
|
|
546
583
|
// bind a port we are holding, and gets the same pooled context an agent's reticle_lease returns —
|
|
547
584
|
// through runTool, so it is counted and reported like any other call rather than being a second,
|
|
@@ -42,10 +42,10 @@ declare const CapsuleSchema: z.ZodObject<{
|
|
|
42
42
|
seen: z.ZodOptional<z.ZodNumber>;
|
|
43
43
|
lastSeenAt: z.ZodOptional<z.ZodNumber>;
|
|
44
44
|
}, "strip", z.ZodTypeAny, {
|
|
45
|
-
expected: string;
|
|
46
|
-
origin: string;
|
|
47
45
|
version: 1;
|
|
48
46
|
id: string;
|
|
47
|
+
origin: string;
|
|
48
|
+
expected: string;
|
|
49
49
|
steps: FlowStep[];
|
|
50
50
|
createdAt: number;
|
|
51
51
|
observed: string;
|
|
@@ -53,10 +53,10 @@ declare const CapsuleSchema: z.ZodObject<{
|
|
|
53
53
|
seen?: number | undefined;
|
|
54
54
|
lastSeenAt?: number | undefined;
|
|
55
55
|
}, {
|
|
56
|
-
expected: string;
|
|
57
|
-
origin: string;
|
|
58
56
|
version: 1;
|
|
59
57
|
id: string;
|
|
58
|
+
origin: string;
|
|
59
|
+
expected: string;
|
|
60
60
|
steps: FlowStep[];
|
|
61
61
|
createdAt: number;
|
|
62
62
|
observed: string;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Pure: no IO, no clock. The tool layer loads flows + contract and calls this.
|
|
14
14
|
*/
|
|
15
|
-
import { AnchorKind, PredicateKind, } from '@reticlehq/core';
|
|
15
|
+
import { AnchorKind, PredicateKind, compareSourceClauses, } from '@reticlehq/core';
|
|
16
16
|
import { classifyFlowAssertions, FlowAssertionGrade } from '../../language/flows/flow-classify.js';
|
|
17
17
|
import { successLabel } from '../../language/flows/flow-success.js';
|
|
18
18
|
import { flowRisk, latestRun, rankByRisk, RiskLevel } from './flow-risk.js';
|
|
@@ -41,6 +41,8 @@ function walkClauses(predicate) {
|
|
|
41
41
|
}
|
|
42
42
|
if (PredicateKind.NOT === predicate.kind)
|
|
43
43
|
return walkClauses(predicate.predicate);
|
|
44
|
+
if (PredicateKind.COMPARE === predicate.kind)
|
|
45
|
+
return compareSourceClauses(predicate);
|
|
44
46
|
return [predicate];
|
|
45
47
|
}
|
|
46
48
|
function signalNamesIn(predicate) {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `missing-signal` while honesty said `no-signal-on-mutation` for the same absence.
|
|
10
10
|
*/
|
|
11
11
|
import { InstrumentationGapKind } from '@reticlehq/core/artifacts';
|
|
12
|
-
/** PascalCase a store name for a `useX
|
|
12
|
+
/** PascalCase a store name for a `useX` hint (cart → Cart). */
|
|
13
13
|
function pascal(name) {
|
|
14
14
|
return 0 === name.length ? name : name[0]?.toUpperCase() + name.slice(1);
|
|
15
15
|
}
|
|
@@ -18,7 +18,9 @@ function insertFor(gap) {
|
|
|
18
18
|
case InstrumentationGapKind.NO_SIGNAL_ON_MUTATION:
|
|
19
19
|
return `reticle.signal('${gap.name}');`;
|
|
20
20
|
case InstrumentationGapKind.NO_STORE_REGISTERED:
|
|
21
|
-
|
|
21
|
+
// The store itself, so its subscribe is wired too. A `() => useX.getState()` getter is
|
|
22
|
+
// readable but never reports a change, which is the state this gap is about (#1146).
|
|
23
|
+
return `registerStore('${gap.name}', use${pascal(gap.name)});`;
|
|
22
24
|
case InstrumentationGapKind.MISSING_TESTID:
|
|
23
25
|
return `data-testid="${gap.name}"`;
|
|
24
26
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* perspective a flow that drifted no longer behaves, so it must not read as green. The actionable
|
|
5
5
|
* "why" is lifted from the replay's decision envelope (or its error) into failureReason.
|
|
6
6
|
*/
|
|
7
|
-
import { ReplayStatus, RunFlowStatus, type FlowReplayResult, type RunFlowResult } from '@reticlehq/core';
|
|
7
|
+
import { ReplayStatus, RunFlowStatus, type FlowFile, type FlowReplayResult, type RunFlowResult } from '@reticlehq/core';
|
|
8
8
|
/** OK → PASS; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
|
|
9
9
|
export declare function runFlowStatusOf(status: ReplayStatus): RunFlowStatus;
|
|
10
10
|
/** Map one replay (plus its measured duration) into a RunFlowResult for the artifact's flows[]. */
|
|
11
|
-
export declare function mapReplayToFlowResult(replay: FlowReplayResult, durationMs: number): RunFlowResult;
|
|
11
|
+
export declare function mapReplayToFlowResult(replay: FlowReplayResult, durationMs: number, flow?: FlowFile): RunFlowResult;
|
|
@@ -4,21 +4,52 @@
|
|
|
4
4
|
* perspective a flow that drifted no longer behaves, so it must not read as green. The actionable
|
|
5
5
|
* "why" is lifted from the replay's decision envelope (or its error) into failureReason.
|
|
6
6
|
*/
|
|
7
|
-
import { ReplayStatus, RunFlowStatus, } from '@reticlehq/core';
|
|
7
|
+
import { ReplayStatus, RUN_RECORDING_MAX_BYTES, RUN_STEP_RESULTS_MAX, RUN_TEXT_MAX, RunFlowStatus, } from '@reticlehq/core';
|
|
8
8
|
import { SUCCESS_STEP_TOOL } from '../../../language/flows/flow-success.js';
|
|
9
9
|
/** OK → PASS; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
|
|
10
10
|
export function runFlowStatusOf(status) {
|
|
11
11
|
return status === ReplayStatus.OK ? RunFlowStatus.PASS : RunFlowStatus.FAIL;
|
|
12
12
|
}
|
|
13
|
+
const clip = (text) => text.length > RUN_TEXT_MAX ? `${text.slice(0, RUN_TEXT_MAX - 1)}…` : text;
|
|
14
|
+
/** One step as synced: what it did, where, and whether it held. */
|
|
15
|
+
function stepResultOf(step) {
|
|
16
|
+
return {
|
|
17
|
+
step: step.step,
|
|
18
|
+
anchor: clip(step.anchor),
|
|
19
|
+
ok: step.ok,
|
|
20
|
+
...(step.page === undefined ? {} : { page: step.page }),
|
|
21
|
+
...(step.endPage === undefined ? {} : { endPage: step.endPage }),
|
|
22
|
+
...(step.consequence === undefined ? {} : { consequence: clip(step.consequence) }),
|
|
23
|
+
...(step.error === undefined ? {} : { error: clip(step.error) }),
|
|
24
|
+
...(step.drift === undefined ? {} : { drift: step.drift.reasonKind }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The flow as recorded, when it is small enough to travel — what lets a teammate replay this run
|
|
29
|
+
* from the cloud. Fill values were already redacted when the flow was saved.
|
|
30
|
+
*/
|
|
31
|
+
function recordingOf(flow) {
|
|
32
|
+
const recording = {
|
|
33
|
+
...(flow.startPath === undefined ? {} : { startPath: flow.startPath }),
|
|
34
|
+
steps: flow.steps,
|
|
35
|
+
};
|
|
36
|
+
return Buffer.byteLength(JSON.stringify(recording)) > RUN_RECORDING_MAX_BYTES
|
|
37
|
+
? { recordingOmitted: true }
|
|
38
|
+
: { recording };
|
|
39
|
+
}
|
|
13
40
|
/** Map one replay (plus its measured duration) into a RunFlowResult for the artifact's flows[]. */
|
|
14
|
-
export function mapReplayToFlowResult(replay, durationMs) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
export function mapReplayToFlowResult(replay, durationMs, flow) {
|
|
42
|
+
// A replay that could not start returns OK with no steps. Nothing ran, so it is SKIPPED — which
|
|
43
|
+
// every reader of a run (the verdict, the gate) already refuses to count as a pass.
|
|
44
|
+
const status = replay.unverifiable === undefined ? runFlowStatusOf(replay.status) : RunFlowStatus.SKIPPED;
|
|
45
|
+
const failureReason = replay.unverifiable !== undefined
|
|
46
|
+
? replay.unverifiable.reason
|
|
47
|
+
: status === RunFlowStatus.FAIL
|
|
48
|
+
? (replay.decision?.whatChanged ??
|
|
49
|
+
replay.decision?.summary ??
|
|
50
|
+
replay.error?.message ??
|
|
51
|
+
'flow failed')
|
|
52
|
+
: undefined;
|
|
22
53
|
// A flow with a success oracle gets a synthetic 'success' step appended by replay; surface its label
|
|
23
54
|
// as the run's `oracle` so the verdict counts this flow as consequence-backed (→ HIGH confidence),
|
|
24
55
|
// not a bare smoke click. Without this, an oracle-backed pass reads as MEDIUM and undersells itself.
|
|
@@ -30,5 +61,9 @@ export function mapReplayToFlowResult(replay, durationMs) {
|
|
|
30
61
|
durationMs,
|
|
31
62
|
...(oracle !== undefined ? { oracle } : {}),
|
|
32
63
|
...(failureReason !== undefined ? { failureReason } : {}),
|
|
64
|
+
...(0 === replay.steps.length
|
|
65
|
+
? {}
|
|
66
|
+
: { stepResults: replay.steps.slice(0, RUN_STEP_RESULTS_MAX).map(stepResultOf) }),
|
|
67
|
+
...(flow === undefined ? {} : recordingOf(flow)),
|
|
33
68
|
};
|
|
34
69
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { type ProjectId, type FlowReplayResult } from '@reticlehq/core';
|
|
1
|
+
import { type ProjectId, type FlowFile, type FlowReplayResult } from '@reticlehq/core';
|
|
2
2
|
import type { ToolDeps } from '../../surface/tools/tool-kit.js';
|
|
3
3
|
/** A replay plus the wall-clock time it took — the shape the verify handler already collects. */
|
|
4
4
|
export interface TimedReplay {
|
|
5
5
|
replay: FlowReplayResult;
|
|
6
6
|
durationMs: number;
|
|
7
|
+
/** The flow file that was replayed, so the synced run can carry its recording. */
|
|
8
|
+
flow?: FlowFile;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
11
|
* Persist the suite's run artifact and best-effort mirror it to the cloud. Never throws into the verify
|
|
@@ -23,7 +23,7 @@ import { rootForProjectId } from '../../memory/project/session-root.js';
|
|
|
23
23
|
const RETICLE_MCP_AGENT_ID = 'reticle-mcp';
|
|
24
24
|
/** Assemble the ReticleVerificationRun artifact for a verify suite (pure — no IO). */
|
|
25
25
|
function assembleRun(deps, timed, projectId) {
|
|
26
|
-
const flows = timed.map((t) => mapReplayToFlowResult(t.replay, t.durationMs));
|
|
26
|
+
const flows = timed.map((t) => mapReplayToFlowResult(t.replay, t.durationMs, t.flow));
|
|
27
27
|
const input = {
|
|
28
28
|
runId: defaultRunId(),
|
|
29
29
|
durationMs: flows.reduce((sum, f) => sum + f.durationMs, 0),
|
|
@@ -22,3 +22,12 @@ export interface AffectedResult {
|
|
|
22
22
|
* a repo-relative change matches an absolute stamp and vice-versa.
|
|
23
23
|
*/
|
|
24
24
|
export declare function affectedFlows(flows: readonly FlowSources[], changedFiles: readonly string[]): AffectedResult;
|
|
25
|
+
export declare function isInteractiveSource(text: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Changed interactive components that no saved flow's manifest names.
|
|
28
|
+
*
|
|
29
|
+
* `affectedFlows` starts from the flows, so a file no flow ever touched affects nothing — and a gate
|
|
30
|
+
* built only on it passed a brand-new feature that had never been driven. This starts from the
|
|
31
|
+
* change instead. `isInteractive` is injected so the decision stays pure; the CLI reads the file.
|
|
32
|
+
*/
|
|
33
|
+
export declare function unflowedFiles(flows: readonly FlowSources[], changedFiles: readonly string[], isInteractive: (file: string) => boolean): string[];
|
|
@@ -33,3 +33,38 @@ export function affectedFlows(flows, changedFiles) {
|
|
|
33
33
|
}
|
|
34
34
|
return { affected, unknownProvenance };
|
|
35
35
|
}
|
|
36
|
+
/** A file a source stamper can put on an element — the only files a flow's manifest can ever name. */
|
|
37
|
+
const COMPONENT_FILE = /\.(?:jsx|tsx|vue|svelte|astro)$/;
|
|
38
|
+
/** Same test-file rule the stampers use, so a test is never demanded a flow. */
|
|
39
|
+
const TEST_FILE = /(?:\.(?:test|spec)\.[^./]+$)|(?:^|\/)__tests__\//;
|
|
40
|
+
/**
|
|
41
|
+
* Something a step could act on: a handler prop (`onClick=`, `@click`, `v-on:`, `on:submit`) or a
|
|
42
|
+
* native control.
|
|
43
|
+
*
|
|
44
|
+
* ponytail: a text heuristic, not a parse. A flow's manifest only names the file of the element a
|
|
45
|
+
* step ACTED on, so a display-only component can never be attributed and demanding a flow for one
|
|
46
|
+
* would block forever. A handler passed in from a parent under another name escapes it; upgrade to
|
|
47
|
+
* recording the source of the element an `expect` matched if that proves common.
|
|
48
|
+
*/
|
|
49
|
+
const INTERACTIVE = /\bon[A-Z]\w*=|@[a-z]+[=.]|\bv-on:|\bon:[a-z]+|<(?:button|input|select|textarea|form|a|summary)[\s>/]/;
|
|
50
|
+
export function isInteractiveSource(text) {
|
|
51
|
+
return INTERACTIVE.test(text);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Changed interactive components that no saved flow's manifest names.
|
|
55
|
+
*
|
|
56
|
+
* `affectedFlows` starts from the flows, so a file no flow ever touched affects nothing — and a gate
|
|
57
|
+
* built only on it passed a brand-new feature that had never been driven. This starts from the
|
|
58
|
+
* change instead. `isInteractive` is injected so the decision stays pure; the CLI reads the file.
|
|
59
|
+
*/
|
|
60
|
+
export function unflowedFiles(flows, changedFiles, isInteractive) {
|
|
61
|
+
const sources = flows.flatMap((flow) => (flow.sources ?? []).map(normalize));
|
|
62
|
+
return changedFiles.filter((file) => {
|
|
63
|
+
const c = normalize(file);
|
|
64
|
+
if (!COMPONENT_FILE.test(c) || TEST_FILE.test(c))
|
|
65
|
+
return false;
|
|
66
|
+
if (sources.some((s) => c === s || c.endsWith(`/${s}`) || s.endsWith(`/${c}`)))
|
|
67
|
+
return false;
|
|
68
|
+
return isInteractive(file);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -22,6 +22,16 @@ interface GateInput {
|
|
|
22
22
|
downgraded?: readonly DowngradedFlow[];
|
|
23
23
|
/** Flows that covered a changed file but no longer exist — coverage deleted rather than satisfied. */
|
|
24
24
|
deleted?: readonly string[];
|
|
25
|
+
/** Changed interactive components no saved flow touches — a change nothing has driven. */
|
|
26
|
+
unflowed?: readonly string[];
|
|
27
|
+
/** Coverage levels below the best they ever reached — something new appeared, nothing covered it. */
|
|
28
|
+
coverageRegressed?: readonly {
|
|
29
|
+
level: string;
|
|
30
|
+
was: number;
|
|
31
|
+
now: number;
|
|
32
|
+
}[];
|
|
33
|
+
/** Changed files the browser loaded and never ran a function of. */
|
|
34
|
+
unexecuted?: readonly string[];
|
|
25
35
|
}
|
|
26
36
|
interface GateResult {
|
|
27
37
|
/** True when every affected, non-flaky flow has a passing artifact AND nothing was weakened/deleted. */
|
|
@@ -34,6 +44,16 @@ interface GateResult {
|
|
|
34
44
|
downgraded: DowngradedFlow[];
|
|
35
45
|
/** Deleted flows that covered changed files — BLOCKING for the same reason. */
|
|
36
46
|
deleted: string[];
|
|
47
|
+
/** Changed interactive components no flow covers — BLOCKING: the change was never driven. */
|
|
48
|
+
unflowed: string[];
|
|
49
|
+
/** BLOCKING: the app grew something nothing has covered. */
|
|
50
|
+
coverageRegressed: {
|
|
51
|
+
level: string;
|
|
52
|
+
was: number;
|
|
53
|
+
now: number;
|
|
54
|
+
}[];
|
|
55
|
+
/** BLOCKING: changed code that never ran in any drive. */
|
|
56
|
+
unexecuted: string[];
|
|
37
57
|
}
|
|
38
58
|
export declare function gateDecision(input: GateInput): GateResult;
|
|
39
59
|
export {};
|
|
@@ -23,11 +23,22 @@ export function gateDecision(input) {
|
|
|
23
23
|
// trivially gameable, which is the exact failure this gate exists to prevent.
|
|
24
24
|
const downgraded = [...(input.downgraded ?? [])];
|
|
25
25
|
const deleted = [...(input.deleted ?? [])];
|
|
26
|
+
const unflowed = [...(input.unflowed ?? [])];
|
|
27
|
+
const coverageRegressed = [...(input.coverageRegressed ?? [])];
|
|
28
|
+
const unexecuted = [...(input.unexecuted ?? [])];
|
|
26
29
|
return {
|
|
27
|
-
pass: 0 === uncovered.length &&
|
|
30
|
+
pass: 0 === uncovered.length &&
|
|
31
|
+
0 === downgraded.length &&
|
|
32
|
+
0 === deleted.length &&
|
|
33
|
+
0 === unflowed.length &&
|
|
34
|
+
0 === coverageRegressed.length &&
|
|
35
|
+
0 === unexecuted.length,
|
|
28
36
|
uncovered,
|
|
29
37
|
quarantined,
|
|
30
38
|
downgraded,
|
|
31
39
|
deleted,
|
|
40
|
+
unflowed,
|
|
41
|
+
coverageRegressed,
|
|
42
|
+
unexecuted,
|
|
32
43
|
};
|
|
33
44
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FlowFile } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* Who is making a flow right now: the agent that announced itself over MCP, and the person signed in
|
|
4
|
+
* to Reticle on this machine. A key handed over in the environment names no person, so none is
|
|
5
|
+
* written — the cloud can still attribute the push by the key that sent it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function flowAuthor(env?: NodeJS.ProcessEnv, home?: string): FlowFile['author'];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { mcpClientIdentity } from '../../surface/mcp/peer/client-identity.js';
|
|
3
|
+
import { readAccountState } from '../../memory/cloud/account-state.js';
|
|
4
|
+
/**
|
|
5
|
+
* Who is making a flow right now: the agent that announced itself over MCP, and the person signed in
|
|
6
|
+
* to Reticle on this machine. A key handed over in the environment names no person, so none is
|
|
7
|
+
* written — the cloud can still attribute the push by the key that sent it.
|
|
8
|
+
*/
|
|
9
|
+
export function flowAuthor(env = process.env, home = homedir()) {
|
|
10
|
+
const agent = mcpClientIdentity().name;
|
|
11
|
+
const account = readAccountState(home, env);
|
|
12
|
+
const person = account.signedIn ? account.email : undefined;
|
|
13
|
+
if (agent === undefined && person === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
return {
|
|
16
|
+
...(agent === undefined ? {} : { agent }),
|
|
17
|
+
...(person === undefined ? {} : { person }),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -215,7 +215,7 @@ export function parseFlowFileText(text) {
|
|
|
215
215
|
ok: false,
|
|
216
216
|
code: FlowErrorCode.WRONG_VERSION,
|
|
217
217
|
detail: `this flow file is version ${String(declared)} and this Reticle reads versions ` +
|
|
218
|
-
`${[...READABLE_FLOW_VERSIONS].join('
|
|
218
|
+
`${[...READABLE_FLOW_VERSIONS].join(', ')}. The file is not damaged, the reader is the ` +
|
|
219
219
|
'wrong one. ' +
|
|
220
220
|
'Upgrade or downgrade Reticle rather than editing the flow.',
|
|
221
221
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type FlowFile, type FlowStep, type Predicate } from '@reticlehq/core';
|
|
2
|
+
/** A short, stable identity for a journey's shape: its start page and its steps. */
|
|
3
|
+
export declare function journeyFingerprint(flow: Pick<FlowFile, 'startPath' | 'steps'>): string;
|
|
4
|
+
/**
|
|
5
|
+
* Fold two copies of one journey into one file.
|
|
6
|
+
*
|
|
7
|
+
* Gap-filling, never overwriting: whatever the kept flow already says — its name, its intent, a
|
|
8
|
+
* step's declared consequence, a quarantine a person wrote — survives, and the other copy only
|
|
9
|
+
* supplies what is missing. So a merge can add an assertion or an intent and can never remove one.
|
|
10
|
+
*/
|
|
11
|
+
export declare function mergeJourney(kept: FlowFile, other: FlowFile): FlowFile;
|
|
12
|
+
/**
|
|
13
|
+
* The name a new journey saves under.
|
|
14
|
+
*
|
|
15
|
+
* Its readable name when that is free or already this journey's; otherwise the same name with the
|
|
16
|
+
* fingerprint's head, so two different journeys that share an intent never overwrite each other.
|
|
17
|
+
*/
|
|
18
|
+
export declare function journeyName(base: string, fingerprint: string, taken: ReadonlyMap<string, string>): string;
|
|
19
|
+
/**
|
|
20
|
+
* What must hold before a journey and what it leaves behind, read from where it started and ended.
|
|
21
|
+
*
|
|
22
|
+
* Routes only: the page is the one piece of state every recorded step knows for certain, and a
|
|
23
|
+
* claim written from a guess about the rest would be a precondition nobody met. Written in the
|
|
24
|
+
* exact form `canFollow` compares by value, so A-then-B is answerable from the two files alone.
|
|
25
|
+
*/
|
|
26
|
+
export declare function routeClaims(startPath: string | undefined, steps: readonly FlowStep[]): {
|
|
27
|
+
requires?: Predicate[];
|
|
28
|
+
ensures?: Predicate[];
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The preconditions only another flow can establish.
|
|
32
|
+
*
|
|
33
|
+
* A route claim on the page the flow starts on is not one of them: replay navigates there itself, so
|
|
34
|
+
* it neither opts the flow out of the reset nor gates the run. That is the claim a saved drive writes
|
|
35
|
+
* for the map (see routeClaims) — reading it as "starts from leftover state" would stop every
|
|
36
|
+
* auto-saved flow from ever being reset.
|
|
37
|
+
*/
|
|
38
|
+
export declare function establishedState(flow: FlowFile): readonly Predicate[];
|
|
39
|
+
/**
|
|
40
|
+
* Is the tab where the flow asked to start? Up to a trailing slash, and up to the query the flow
|
|
41
|
+
* did not ask about.
|
|
42
|
+
*
|
|
43
|
+
* `startPath` is the SPECIFICATION, so it decides what counts. A query it recorded is compared:
|
|
44
|
+
* `?tab=wrap` and `?tab=summary` are different pages, and a replay that starts on the wrong one
|
|
45
|
+
* proves nothing about the right one. A query it did NOT record is ignored: the tab carrying
|
|
46
|
+
* `?next=%2F` on a login page, or the identity params Reticle puts on a leased tab, are not the flow
|
|
47
|
+
* being elsewhere, and navigating to strip them costs a session for nothing.
|
|
48
|
+
*
|
|
49
|
+
* The asymmetry is the whole point and the reason `observed` and `expected` are named rather than
|
|
50
|
+
* `a` and `b`. Comparing with the query on both sides always re-navigated a query-bearing
|
|
51
|
+
* `startPath` (#1059, which killed the session mid-flow); comparing with it on neither side reads a
|
|
52
|
+
* tab on `?tab=summary` as already at `?tab=wrap`.
|
|
53
|
+
*/
|
|
54
|
+
export declare function samePath(observed: string, expected: string): boolean;
|