@reticlehq/server 2.12.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +67 -109
- package/dist/bridge/auth-failure-reason.d.ts +6 -2
- package/dist/bridge/auth-failure-reason.js +15 -8
- package/dist/bridge/bridge.d.ts +18 -1
- package/dist/bridge/bridge.js +247 -164
- package/dist/bridge/pairing-token.d.ts +5 -0
- package/dist/bridge/pairing-token.js +28 -0
- package/dist/bridge/protocol-skew.d.ts +37 -0
- package/dist/bridge/protocol-skew.js +45 -0
- package/dist/capsule/capsule-store.d.ts +6 -6
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/cli/cli-doctor.js +31 -1
- package/dist/cli/cli-launch.d.ts +11 -1
- package/dist/cli/cli-launch.js +59 -10
- package/dist/cli/cli-parse.d.ts +15 -1
- package/dist/cli/cli-parse.js +212 -8
- package/dist/cli/cli-verify.d.ts +1 -0
- package/dist/cli/cli-verify.js +3 -2
- package/dist/cli/cloud-cli.js +428 -259
- package/dist/cli/cloud-keystore.d.ts +37 -0
- package/dist/cli/cloud-keystore.js +97 -0
- package/dist/cli/cloud-kit.d.ts +37 -0
- package/dist/cli/cloud-kit.js +121 -0
- package/dist/cli/cloud-login.d.ts +17 -0
- package/dist/cli/cloud-login.js +183 -0
- package/dist/cli/cloud-session.d.ts +48 -0
- package/dist/cli/cloud-session.js +53 -0
- package/dist/cli/config-discovery.js +1 -0
- package/dist/cli/daemon-start-options.d.ts +32 -0
- package/dist/cli/daemon-start-options.js +38 -0
- package/dist/cli/device-flow.d.ts +76 -0
- package/dist/cli/device-flow.js +56 -0
- package/dist/cli/doctor-rows.d.ts +9 -0
- package/dist/cli/doctor-rows.js +9 -0
- package/dist/cli/drive-attach.d.ts +13 -0
- package/dist/cli/drive-attach.js +18 -0
- package/dist/cli/drive-command.js +7 -3
- package/dist/cli/mcp-command.js +51 -20
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/status-next-action.d.ts +14 -0
- package/dist/cli/status-next-action.js +4 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +134 -80
- package/dist/cloud/cloud-config.d.ts +22 -1
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +5 -5
- package/dist/cloud/cloud-sync.js +33 -2
- package/dist/cloud/sync-cycle.d.ts +110 -0
- package/dist/cloud/sync-cycle.js +270 -0
- package/dist/cloud/sync-daemon.d.ts +69 -0
- package/dist/cloud/sync-daemon.js +260 -0
- package/dist/cloud/sync-disk.d.ts +9 -0
- package/dist/cloud/sync-disk.js +191 -0
- package/dist/cloud/sync-hash.d.ts +2 -0
- package/dist/cloud/sync-hash.js +35 -0
- package/dist/crawl/crawl-tools.js +5 -3
- package/dist/crawl/crawl.d.ts +3 -0
- package/dist/crawl/crawl.js +5 -0
- package/dist/daemon/daemon-resolve.d.ts +43 -0
- package/dist/daemon/daemon-resolve.js +69 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +18 -0
- package/dist/events/contradictions.js +134 -21
- package/dist/events/declared.d.ts +7 -4
- package/dist/events/declared.js +15 -6
- package/dist/events/echo-mismatch.js +64 -14
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/predicate-eval.js +53 -5
- package/dist/events/predicate-precheck.d.ts +8 -0
- package/dist/events/predicate-precheck.js +41 -0
- package/dist/events/predicate-route.d.ts +14 -0
- package/dist/events/predicate-route.js +36 -8
- package/dist/events/predicate.d.ts +5 -0
- package/dist/events/predicate.js +37 -5
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/flake.d.ts +6 -6
- package/dist/flows/flow-memory-consult.d.ts +55 -0
- package/dist/flows/flow-memory-consult.js +38 -0
- package/dist/flows/flow-replay-run.d.ts +18 -7
- package/dist/flows/flow-replay-run.js +239 -22
- package/dist/flows/flow-replay.js +11 -2
- package/dist/flows/flow-step-runners.d.ts +8 -0
- package/dist/flows/flow-step-runners.js +8 -2
- package/dist/flows/flow-store-for-session.d.ts +31 -0
- package/dist/flows/flow-store-for-session.js +33 -0
- package/dist/flows/flow-tools.js +58 -32
- package/dist/flows/flows.d.ts +8 -0
- package/dist/flows/flows.js +468 -2
- package/dist/flows/heal-run.js +5 -2
- package/dist/flows/predicate-to-expect.d.ts +17 -12
- package/dist/flows/predicate-to-expect.js +30 -12
- package/dist/flows/recordings.d.ts +13 -1
- package/dist/flows/recordings.js +17 -2
- package/dist/flows/replay.d.ts +22 -2
- package/dist/flows/replay.js +28 -2
- package/dist/flows/verify-change-tools.js +5 -2
- package/dist/honesty/blind-spots.d.ts +55 -2
- package/dist/honesty/blind-spots.js +63 -3
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +9 -0
- package/dist/honesty/instrumentation-gaps.js +29 -2
- package/dist/honesty/verified.d.ts +2 -1
- package/dist/honesty/verified.js +16 -1
- package/dist/http-server.js +12 -0
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +15 -1
- package/dist/impact/impact-store.js +43 -2
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +13 -6
- package/dist/init/agent-rules.js +23 -12
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -1
- package/dist/init/capabilities.js +22 -2
- package/dist/init/confirm.d.ts +31 -7
- package/dist/init/confirm.js +48 -8
- package/dist/init/cra.d.ts +20 -4
- package/dist/init/cra.js +57 -17
- package/dist/init/csp-check.js +17 -2
- package/dist/init/csp-doctor.d.ts +8 -1
- package/dist/init/csp-doctor.js +8 -3
- package/dist/init/dev-script.d.ts +3 -1
- package/dist/init/dev-script.js +8 -5
- package/dist/init/format-generated.d.ts +18 -0
- package/dist/init/format-generated.js +39 -0
- package/dist/init/install-hint.d.ts +9 -0
- package/dist/init/install-hint.js +29 -0
- package/dist/init/mcp-clients.d.ts +7 -0
- package/dist/init/mcp-clients.js +7 -0
- package/dist/init/mcp.d.ts +1 -1
- package/dist/init/mcp.js +1 -1
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.js +81 -45
- package/dist/init/plan.d.ts +20 -7
- package/dist/init/plan.js +20 -45
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/run.d.ts +32 -9
- package/dist/init/run.js +141 -119
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +41 -25
- package/dist/init/vite-config.d.ts +1 -1
- package/dist/init/vite-config.js +39 -25
- package/dist/init/workspace-apps.d.ts +17 -10
- package/dist/init/workspace-apps.js +110 -0
- package/dist/input/network-mock-tools.js +3 -6
- package/dist/input/real-input.js +25 -5
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +10 -19
- package/dist/intent/inline-intent.js +91 -1
- package/dist/intent/intent-shard-store.d.ts +72 -0
- package/dist/intent/intent-shard-store.js +203 -0
- package/dist/intent/intent-shard.d.ts +411 -0
- package/dist/intent/intent-shard.js +176 -0
- package/dist/intent/intent-store.d.ts +12 -0
- package/dist/intent/intent-store.js +21 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.js +65 -2
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +15 -0
- package/dist/license/license-env.d.ts +29 -0
- package/dist/license/license-env.js +148 -0
- package/dist/loopback-agent.d.ts +31 -0
- package/dist/loopback-agent.js +57 -0
- package/dist/mcp/attach-memory.d.ts +18 -2
- package/dist/mcp/attach-memory.js +50 -7
- package/dist/mcp/client-identity.d.ts +31 -0
- package/dist/mcp/client-identity.js +51 -0
- package/dist/mcp/mcp-post-transport.d.ts +26 -0
- package/dist/mcp/mcp-post-transport.js +109 -0
- package/dist/mcp/mcp-proxy.d.ts +2 -1
- package/dist/mcp/mcp-proxy.js +52 -62
- package/dist/mcp/mcp.js +7 -1
- package/dist/mcp/proxy-backoff.d.ts +11 -0
- package/dist/mcp/proxy-backoff.js +13 -0
- package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
- package/dist/mcp/proxy-daemon-probe.js +7 -1
- package/dist/mcp/proxy-handshake.d.ts +23 -0
- package/dist/mcp/proxy-handshake.js +31 -0
- package/dist/mcp/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +12 -0
- package/dist/memory/memory-tools.d.ts +2 -0
- package/dist/memory/memory-tools.js +82 -0
- package/dist/memory/project-memory.d.ts +34 -0
- package/dist/memory/project-memory.js +96 -0
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/pool/browser-pool.d.ts +63 -4
- package/dist/pool/browser-pool.js +113 -4
- package/dist/pool/playwright-launcher.js +9 -0
- package/dist/project/project-tools.js +3 -3
- package/dist/project/reticle-dir.d.ts +15 -2
- package/dist/project/reticle-dir.js +21 -4
- package/dist/runs/run-store.d.ts +10 -0
- package/dist/runs/run-store.js +10 -0
- package/dist/runs/verification-sync.d.ts +0 -11
- package/dist/runs/verification-sync.js +22 -1
- package/dist/runs/verify-server.d.ts +8 -1
- package/dist/runs/verify-server.js +14 -2
- package/dist/session/command-timeout.d.ts +5 -2
- package/dist/session/command-timeout.js +5 -4
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/no-session-diagnosis.d.ts +23 -0
- package/dist/session/no-session-diagnosis.js +58 -17
- package/dist/session/no-session-next-action.d.ts +7 -0
- package/dist/session/no-session-next-action.js +42 -3
- package/dist/session/no-session-watch.d.ts +10 -6
- package/dist/session/no-session-watch.js +51 -19
- package/dist/session/pending-commands.d.ts +11 -1
- package/dist/session/pending-commands.js +12 -2
- package/dist/session/resolve-within.d.ts +34 -0
- package/dist/session/resolve-within.js +53 -0
- package/dist/session/session-info.d.ts +18 -0
- package/dist/session/session-info.js +11 -0
- package/dist/session/session-manager.d.ts +14 -0
- package/dist/session/session-manager.js +61 -3
- package/dist/session/session-mirror.d.ts +16 -0
- package/dist/session/session-mirror.js +22 -0
- package/dist/session/session-recommendation.d.ts +19 -5
- package/dist/session/session-recommendation.js +25 -9
- package/dist/session/session-successor.js +36 -1
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +22 -2
- package/dist/session/session.js +87 -7
- package/dist/session/wall-clock.d.ts +12 -0
- package/dist/session/wall-clock.js +14 -0
- package/dist/setup/agent-configs.d.ts +96 -0
- package/dist/setup/agent-configs.js +333 -0
- package/dist/setup/agent-io.d.ts +9 -0
- package/dist/setup/agent-io.js +16 -0
- package/dist/setup/agent-writer.d.ts +51 -0
- package/dist/setup/agent-writer.js +115 -0
- package/dist/setup/approval-migration.d.ts +47 -0
- package/dist/setup/approval-migration.js +68 -0
- package/dist/setup/auto-approve.d.ts +85 -0
- package/dist/setup/auto-approve.js +150 -0
- package/dist/setup/bridge-port.d.ts +15 -0
- package/dist/setup/bridge-port.js +24 -0
- package/dist/setup/desktop-shape.d.ts +57 -0
- package/dist/setup/desktop-shape.js +63 -0
- package/dist/setup/dev-server-wait.d.ts +60 -0
- package/dist/setup/dev-server-wait.js +65 -0
- package/dist/setup/drive-agent.d.ts +71 -0
- package/dist/setup/drive-agent.js +171 -0
- package/dist/setup/drive-plan.d.ts +68 -0
- package/dist/setup/drive-plan.js +130 -0
- package/dist/setup/ensure-daemon.d.ts +29 -0
- package/dist/setup/ensure-daemon.js +58 -0
- package/dist/setup/init-runtime.d.ts +47 -0
- package/dist/setup/init-runtime.js +160 -0
- package/dist/setup/interrupt.d.ts +25 -0
- package/dist/setup/interrupt.js +40 -0
- package/dist/setup/license-key.d.ts +39 -0
- package/dist/setup/license-key.js +72 -0
- package/dist/setup/listeners.d.ts +37 -0
- package/dist/setup/listeners.js +85 -0
- package/dist/setup/node-effects.d.ts +60 -0
- package/dist/setup/node-effects.js +210 -0
- package/dist/setup/page-probe.d.ts +39 -0
- package/dist/setup/page-probe.js +49 -0
- package/dist/setup/relaunch.d.ts +36 -0
- package/dist/setup/relaunch.js +57 -0
- package/dist/setup/remaining-steps.d.ts +19 -0
- package/dist/setup/remaining-steps.js +31 -0
- package/dist/setup/run-setup.d.ts +99 -0
- package/dist/setup/run-setup.js +187 -0
- package/dist/setup/session-pick.d.ts +47 -0
- package/dist/setup/session-pick.js +51 -0
- package/dist/setup/setup-command.d.ts +45 -0
- package/dist/setup/setup-command.js +199 -0
- package/dist/setup/setup-options.d.ts +74 -0
- package/dist/setup/setup-options.js +67 -0
- package/dist/setup/transcripts.d.ts +16 -0
- package/dist/setup/transcripts.js +43 -0
- package/dist/status-payload.d.ts +14 -1
- package/dist/status-payload.js +23 -1
- package/dist/telemetry/daemon-telemetry.d.ts +8 -1
- package/dist/telemetry/daemon-telemetry.js +52 -5
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +9 -1
- package/dist/telemetry/feedback-context.js +60 -29
- package/dist/telemetry/feedback-local.d.ts +15 -0
- package/dist/telemetry/feedback-local.js +56 -0
- package/dist/telemetry/feedback.d.ts +11 -0
- package/dist/telemetry/feedback.js +19 -6
- package/dist/telemetry/init-telemetry.d.ts +2 -0
- package/dist/telemetry/init-telemetry.js +2 -0
- package/dist/telemetry/install-source.d.ts +14 -0
- package/dist/telemetry/install-source.js +30 -0
- package/dist/telemetry/license-activation.d.ts +7 -1
- package/dist/telemetry/license-activation.js +9 -5
- package/dist/telemetry/mcp-connection.js +7 -0
- package/dist/telemetry/project-profile.d.ts +12 -1
- package/dist/telemetry/project-profile.js +11 -6
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/session-metrics.d.ts +12 -0
- package/dist/telemetry/session-metrics.js +35 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +3 -1
- package/dist/telemetry/tool-refused.js +34 -2
- package/dist/tools/act-capsule.d.ts +10 -0
- package/dist/tools/act-capsule.js +8 -2
- package/dist/tools/act-danger.d.ts +2 -1
- package/dist/tools/act-danger.js +11 -3
- package/dist/tools/act-observation.d.ts +41 -0
- package/dist/tools/act-observation.js +36 -0
- package/dist/tools/act-preflight.d.ts +20 -0
- package/dist/tools/act-preflight.js +98 -0
- package/dist/tools/act-sequence-retry.d.ts +45 -0
- package/dist/tools/act-sequence-retry.js +58 -0
- package/dist/tools/act-sequence-tool.d.ts +13 -0
- package/dist/tools/act-sequence-tool.js +135 -0
- package/dist/tools/act-target.d.ts +14 -0
- package/dist/tools/act-target.js +47 -0
- package/dist/tools/act-tools.d.ts +21 -1
- package/dist/tools/act-tools.js +100 -213
- package/dist/tools/assert-verdict.js +22 -16
- package/dist/tools/browser-tools.js +6 -2
- package/dist/tools/dynamic-tools.js +24 -5
- package/dist/tools/error-recovery.d.ts +1 -0
- package/dist/tools/error-recovery.js +8 -0
- package/dist/tools/invoke-tool.js +59 -9
- package/dist/tools/lease-tools.d.ts +30 -4
- package/dist/tools/lease-tools.js +169 -5
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +47 -40
- package/dist/tools/read-tools.js +28 -7
- package/dist/tools/real-input-attempt.d.ts +10 -0
- package/dist/tools/real-input-attempt.js +59 -1
- package/dist/tools/reconcile-tools.js +4 -4
- package/dist/tools/tool-kit.d.ts +12 -0
- package/dist/tools/tool-kit.js +6 -1
- package/dist/tools/tool-names.d.ts +2 -0
- package/dist/tools/tool-names.js +2 -0
- package/dist/tools/tools-helpers.d.ts +19 -0
- package/dist/tools/tools-helpers.js +46 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +56 -8
- package/dist/update/reticle-deps.d.ts +9 -0
- package/dist/update/reticle-deps.js +27 -0
- package/dist/update/sdk-sync.d.ts +1 -2
- package/dist/update/sdk-sync.js +1 -21
- package/dist/update/update-nudge.d.ts +18 -0
- package/dist/update/update-nudge.js +20 -0
- package/dist/version/peer-announce.js +4 -2
- package/dist/version/sdk-fix.d.ts +47 -0
- package/dist/version/sdk-fix.js +127 -0
- package/dist/version/version-nudge.d.ts +14 -0
- package/dist/version/version-nudge.js +21 -0
- package/dist/version/version-skew.d.ts +38 -3
- package/dist/version/version-skew.js +83 -7
- package/dist/visual/visual-store.d.ts +6 -6
- package/dist/visual/visual-store.js +18 -16
- package/dist/visual/visual-tools.js +57 -12
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +1 -1
- package/docs/getting-started.md +19 -3
- package/docs/index.mdx +9 -6
- package/docs/install-agentic.mdx +38 -12
- package/docs/install-manual.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/usage.md +1 -1
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { EventType, FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, RecordedFlowSchema, ReplayStatus, RunKind, RunStatus, } from '@reticlehq/core';
|
|
2
|
-
import { asString } from '../tools/tools-helpers.js';
|
|
1
|
+
import { EventType, FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, RecordedFlowSchema, ReplayStatus, ReticleCommand, RunKind, RunStatus, } from '@reticlehq/core';
|
|
2
|
+
import { asRecord, asString } from '../tools/tools-helpers.js';
|
|
3
|
+
import { carryReticleIdentity } from '../tools/lease-tools.js';
|
|
3
4
|
import { replayFlow } from './flow-replay.js';
|
|
5
|
+
import { anchorQueryArgs } from './flow-step-runners.js';
|
|
6
|
+
import { queryRefs } from './replay.js';
|
|
4
7
|
import { assertSuccess, dynamicTestids, successLabel, SUCCESS_STEP_TOOL } from './flow-success.js';
|
|
5
8
|
import { buildDecision, unverifiableReason } from './decision.js';
|
|
6
9
|
import { classifyFlowAssertions } from './flow-classify.js';
|
|
@@ -16,7 +19,9 @@ import { EnvelopeStore } from '../journal/envelope-store.js';
|
|
|
16
19
|
import { homedir } from 'node:os';
|
|
17
20
|
import { cloudFetch, syncRunRecordToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
|
|
18
21
|
import { resolveProjectCloud } from '../cloud/cloud-config.js';
|
|
22
|
+
import { consultSubjectFor, selectConsulted } from './flow-memory-consult.js';
|
|
19
23
|
import { log } from '../log.js';
|
|
24
|
+
import { flowsForSession } from './flow-store-for-session.js';
|
|
20
25
|
export function latestRecordedFlow(events) {
|
|
21
26
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
22
27
|
const event = events[i];
|
|
@@ -54,10 +59,12 @@ function replayToRunStatus(status) {
|
|
|
54
59
|
}
|
|
55
60
|
/**
|
|
56
61
|
* Append a flow-replay outcome to .reticle/project.json (never throws into replay) and, when logged in,
|
|
57
|
-
* best-effort mirror it to Reticle
|
|
62
|
+
* best-effort mirror it to Reticle so the team's server-side regression history stays current. The
|
|
58
63
|
* cloud push is fire-and-forget: not logged in → skipped, a network failure is logged and swallowed.
|
|
59
64
|
*/
|
|
60
|
-
async function recordReplayRun(deps, name, status, driftSteps, durationMs, projectId
|
|
65
|
+
async function recordReplayRun(deps, name, status, driftSteps, durationMs, projectId,
|
|
66
|
+
/** The APP's `.reticle`, resolved from the session — never the daemon's own. */
|
|
67
|
+
recordRoot) {
|
|
61
68
|
const runStatus = replayToRunStatus(status);
|
|
62
69
|
await deps.project.recordRun({
|
|
63
70
|
kind: RunKind.FLOW_REPLAY,
|
|
@@ -67,7 +74,10 @@ async function recordReplayRun(deps, name, status, driftSteps, durationMs, proje
|
|
|
67
74
|
durationMs,
|
|
68
75
|
});
|
|
69
76
|
// Per-project cloud: push memory outcomes only when cloud is attached AND memory sync is enabled.
|
|
70
|
-
|
|
77
|
+
// Rooted at the APP, not the daemon — see consultProjectMemory. This call had the same defect and
|
|
78
|
+
// it was worse here: a replay outcome that should have reached the dashboard silently did not,
|
|
79
|
+
// because the daemon's own directory has no link file.
|
|
80
|
+
const cloud = await resolveProjectCloud(deps.fs, recordRoot, homedir(), process.env);
|
|
71
81
|
if (null === cloud.config || !cloud.policy.memory)
|
|
72
82
|
return; // not attached / memory disabled → local only
|
|
73
83
|
const result = await syncRunRecordToCloud({ kind: RunKind.FLOW_REPLAY, name, status: runStatus, at: deps.now(), durationMs }, projectId, cloud.config, cloudFetch);
|
|
@@ -75,27 +85,212 @@ async function recordReplayRun(deps, name, status, driftSteps, durationMs, proje
|
|
|
75
85
|
log('cloud-run-record-sync-failed', { flow: name, status: result.status, error: result.error });
|
|
76
86
|
}
|
|
77
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* The route the tab is on now: the last observed route.change, falling back to the pathname of the
|
|
90
|
+
* session's own URL. The fallback matters — a tab that hard-loaded its page emits no route event
|
|
91
|
+
* (the route observer only sees pushState/replaceState/popstate), but the HELLO url still names
|
|
92
|
+
* where it sits, and without it a wrong-page replay reported plain drift with no hint at all.
|
|
93
|
+
*/
|
|
94
|
+
function currentPathOf(session) {
|
|
95
|
+
const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
|
|
96
|
+
const data = routes.at(-1)?.data ?? {};
|
|
97
|
+
// pathname + hash, because `startPath` is compared against this and must stay NAVIGABLE. Reading
|
|
98
|
+
// the pathname alone made both sides `/` on a hash router — always "same path", so the hint never
|
|
99
|
+
// fired however far the tab had drifted, on the router desktop renderers use by default.
|
|
100
|
+
const observed = asString(data['pathname']) ?? asString(data['to']);
|
|
101
|
+
if (observed !== undefined)
|
|
102
|
+
return `${observed}${asString(data['hash']) ?? ''}`;
|
|
103
|
+
if (session.url === undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
try {
|
|
106
|
+
const parsed = new URL(session.url);
|
|
107
|
+
return `${parsed.pathname}${parsed.hash}`;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Pathname equality up to a trailing slash — a router normalising one must not read as "elsewhere". */
|
|
114
|
+
function samePath(a, b) {
|
|
115
|
+
return a.replace(/\/$/, '') === b.replace(/\/$/, '');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Ask the project what it already knows about this flow.
|
|
119
|
+
*
|
|
120
|
+
* Best-effort in the strongest sense: an unlinked project, a disabled memory policy, an offline
|
|
121
|
+
* laptop and a server that answers nonsense all reach the same place — the verdict returns without
|
|
122
|
+
* a memory block. A verification that FAILED because the knowledge lookup failed would be a worse
|
|
123
|
+
* product than one that never looked, and this is the path every replay takes.
|
|
124
|
+
*
|
|
125
|
+
* The read is also what makes the coverage map's fetch counts mean anything: they were zero across
|
|
126
|
+
* the entire corpus, not because memory is useless but because consulting it was a separate act
|
|
127
|
+
* nobody performed. Now the platform performs it.
|
|
128
|
+
*/
|
|
129
|
+
async function consultProjectMemory(deps, flow, root) {
|
|
130
|
+
const subject = consultSubjectFor(flow);
|
|
131
|
+
if (subject === undefined)
|
|
132
|
+
return undefined;
|
|
133
|
+
try {
|
|
134
|
+
// The APP's root, not the daemon's. `deps.reticleRoot` is wherever the daemon was launched,
|
|
135
|
+
// which for a user-scoped MCP registration is almost never the project being verified — so the
|
|
136
|
+
// link file it reads is the wrong one, or absent, and every project silently reads as
|
|
137
|
+
// "not attached". Same class of bug as the flow store resolving per daemon instead of per
|
|
138
|
+
// session, and it is invisible: the feature simply never appears.
|
|
139
|
+
const cloud = await resolveProjectCloud(deps.fs, root, homedir(), process.env);
|
|
140
|
+
if (null === cloud.config || !cloud.policy.memory)
|
|
141
|
+
return undefined;
|
|
142
|
+
const url = `${cloud.config.url}/v1/memory?subject=${encodeURIComponent(subject)}`;
|
|
143
|
+
const res = await cloudFetch(url, {
|
|
144
|
+
method: 'GET',
|
|
145
|
+
headers: { authorization: `Bearer ${cloud.config.apiKey}` },
|
|
146
|
+
});
|
|
147
|
+
if (200 !== res.status)
|
|
148
|
+
return undefined;
|
|
149
|
+
// `cloudFetch` hands back a real `Response`, so the body is a METHOD. Reading `res.json` as a
|
|
150
|
+
// property yields the function itself, `.entries` on it is undefined, and the whole feature
|
|
151
|
+
// fails silently to "the project knows nothing" — which is indistinguishable from the honest
|
|
152
|
+
// empty case and is why this took a live drive to notice at all.
|
|
153
|
+
const body = (await res.json());
|
|
154
|
+
const entries = body?.entries;
|
|
155
|
+
if (!Array.isArray(entries))
|
|
156
|
+
return undefined;
|
|
157
|
+
const picked = selectConsulted(entries);
|
|
158
|
+
return 0 === picked.length ? undefined : picked;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// See the note above: never the reason a verdict fails to return.
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
78
165
|
/**
|
|
79
166
|
* When a flow records the page its journey started on (`startPath`) and the tab is currently on a
|
|
80
167
|
* different route, step 1 drifts for a reason that has nothing to do with the app regressing — the
|
|
81
168
|
* anchor simply isn't on this page yet. Detect that so the decision says "navigate there first"
|
|
82
169
|
* instead of a mystifying "a step no longer matches". Returns undefined when the routes agree or the
|
|
83
|
-
* current route is unobservable
|
|
84
|
-
*
|
|
85
|
-
*
|
|
170
|
+
* current route is unobservable — never a false alarm. Replay normally never gets here on a
|
|
171
|
+
* wrong page (arriveAtStartPath navigates before step 1); this is the fallback for when that
|
|
172
|
+
* navigation was refused or the SDK never reconnected in the window.
|
|
86
173
|
*/
|
|
87
174
|
export function startPathMismatchHint(flow, session) {
|
|
88
175
|
const startPath = flow.startPath;
|
|
89
176
|
if (startPath === undefined || 0 === startPath.length)
|
|
90
177
|
return undefined;
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const data = last?.data ?? {};
|
|
94
|
-
const current = asString(data['pathname']) ?? asString(data['to']);
|
|
95
|
-
if (current === undefined || current === startPath)
|
|
178
|
+
const current = currentPathOf(session);
|
|
179
|
+
if (current === undefined || samePath(current, startPath))
|
|
96
180
|
return undefined;
|
|
97
181
|
return `this flow's journey starts on ${startPath} but the tab is on ${current} — navigate there (reticle_navigate { url: "${startPath}" }), then replay`;
|
|
98
182
|
}
|
|
183
|
+
/** How long replay waits for the SDK to reconnect on the start page before falling back to the hint. */
|
|
184
|
+
const START_PATH_ARRIVAL_TIMEOUT_MS = 5_000;
|
|
185
|
+
const START_PATH_POLL_MS = 100;
|
|
186
|
+
const REAL_ARRIVAL_CLOCK = {
|
|
187
|
+
now: () => Date.now(),
|
|
188
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* The session `oldId` denotes right now — itself, or the tombstone successor `resolve` rebinds to
|
|
192
|
+
* after a full-document navigation — but only once it actually sits on `target`. Undefined while
|
|
193
|
+
* the tab is still travelling (or in the gap between teardown and the successor's HELLO, when
|
|
194
|
+
* `resolve` throws). Keyed to the navigated tab's own identity on purpose: matching "any session at
|
|
195
|
+
* the target page" would happily hand replay an unrelated tab that was already sitting there.
|
|
196
|
+
*/
|
|
197
|
+
function arrivedSuccessor(sessions, oldId, target) {
|
|
198
|
+
try {
|
|
199
|
+
const candidate = sessions.resolve(oldId);
|
|
200
|
+
const path = currentPathOf(candidate);
|
|
201
|
+
return path !== undefined && samePath(path, target) ? candidate : undefined;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Replay's half of the FlowFile contract: navigate to the flow's `startPath` before step 1.
|
|
209
|
+
*
|
|
210
|
+
* A full-page load tears down the session socket, so the navigation must happen here — before any
|
|
211
|
+
* step runs — and the replay continues on the session the SDK reconnects as (found via the same
|
|
212
|
+
* tombstone rebind that lets `resolve(oldId)` answer after any navigation). Best-effort by design:
|
|
213
|
+
* when the flow carries no startPath, the tab is already there, the current route is unobservable,
|
|
214
|
+
* the navigation is refused, or the SDK never reconnects in the window, it returns undefined and
|
|
215
|
+
* replay proceeds on the connected session as before — with startPathMismatchHint turning any
|
|
216
|
+
* resulting drift into an actionable next move rather than a mystifying one.
|
|
217
|
+
*/
|
|
218
|
+
/**
|
|
219
|
+
* Can step 1 start from where the tab already is?
|
|
220
|
+
*
|
|
221
|
+
* `startPathMismatchHint` states the premise the navigation below exists to serve: on the wrong
|
|
222
|
+
* route "the anchor simply isn't on this page yet". That is the condition worth a page load — and
|
|
223
|
+
* it was never the condition checked. `arriveAtStartPath` fired on a route mismatch ALONE, so a
|
|
224
|
+
* flow whose first anchor lives somewhere persistent (a sidebar, a header, a nav rail — present on
|
|
225
|
+
* every route) was navigated for a problem it did not have.
|
|
226
|
+
*
|
|
227
|
+
* A navigation is a full page load, which tears the session down and brings the app back in its
|
|
228
|
+
* cold state. For the ordinary case of a flow recorded after signing in, that cold state is the
|
|
229
|
+
* LOGIN SCREEN: step 1 then reports its anchor missing and names the component that holds it, which
|
|
230
|
+
* is a correct sentence about a file that is completely fine. Found by the benchmark, on two flows
|
|
231
|
+
* with identical steps where the first replayed clean and the second — run after it, with the tab
|
|
232
|
+
* drifted off `/` — did not.
|
|
233
|
+
*
|
|
234
|
+
* So the question is asked directly. A best-effort step that can leave the caller WORSE off than
|
|
235
|
+
* skipping it is not best-effort; checking first is what makes the description true.
|
|
236
|
+
*
|
|
237
|
+
* Unresolvable either way (no anchor we can query, a failed query) reads as "cannot tell", and the
|
|
238
|
+
* navigation goes ahead — the pre-existing behaviour, and the safe direction for a check whose whole
|
|
239
|
+
* job is to avoid making things worse.
|
|
240
|
+
*/
|
|
241
|
+
async function firstStepResolvesHere(session, flow) {
|
|
242
|
+
const first = flow.steps?.[0];
|
|
243
|
+
if (first === undefined)
|
|
244
|
+
return false;
|
|
245
|
+
const args = anchorQueryArgs(first.anchor);
|
|
246
|
+
if (null === args)
|
|
247
|
+
return false;
|
|
248
|
+
try {
|
|
249
|
+
return 0 < queryRefs(await session.command(ReticleCommand.QUERY, args)).length;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export async function arriveAtStartPath(sessions, session, flow, timeoutMs = START_PATH_ARRIVAL_TIMEOUT_MS, clock = REAL_ARRIVAL_CLOCK) {
|
|
256
|
+
const target = flow.startPath;
|
|
257
|
+
if (target === undefined || 0 === target.length)
|
|
258
|
+
return undefined;
|
|
259
|
+
const current = currentPathOf(session);
|
|
260
|
+
if (current === undefined || samePath(current, target))
|
|
261
|
+
return undefined;
|
|
262
|
+
// The route differs — but that only matters if it stops the flow starting. See above.
|
|
263
|
+
if (await firstStepResolvesHere(session, flow))
|
|
264
|
+
return undefined;
|
|
265
|
+
let destination;
|
|
266
|
+
try {
|
|
267
|
+
// startPath is a pathname (a host belongs to the machine, not the journey) — resolve it
|
|
268
|
+
// against the tab's own URL to get something the browser can be sent to.
|
|
269
|
+
destination = new URL(target, session.url).toString();
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return undefined; // no usable base URL — nowhere to navigate from
|
|
273
|
+
}
|
|
274
|
+
// A leased tab is addressed by URL params, so navigating without them would strand the lease.
|
|
275
|
+
const url = carryReticleIdentity(session.url, destination);
|
|
276
|
+
try {
|
|
277
|
+
const outcome = await session.command(ReticleCommand.NAVIGATE, { url });
|
|
278
|
+
if (!outcome.ok || true !== asRecord(outcome.result)['ok'])
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
const deadline = clock.now() + timeoutMs;
|
|
285
|
+
for (;;) {
|
|
286
|
+
const arrived = arrivedSuccessor(sessions, session.id, target);
|
|
287
|
+
if (arrived !== undefined)
|
|
288
|
+
return arrived;
|
|
289
|
+
if (clock.now() >= deadline)
|
|
290
|
+
return undefined;
|
|
291
|
+
await clock.sleep(START_PATH_POLL_MS);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
99
294
|
/**
|
|
100
295
|
* Replay one named flow end to end: load → re-resolve+run each step → assert the success oracle →
|
|
101
296
|
* status + decision. Shared by reticle_flow_replay (single flow) and reticle_flow_verify (whole suite) so
|
|
@@ -114,9 +309,11 @@ export async function replayNamedFlow(deps, args) {
|
|
|
114
309
|
catch {
|
|
115
310
|
projectId = undefined;
|
|
116
311
|
}
|
|
117
|
-
|
|
312
|
+
// The app's store, not the daemon's: this load answering `flow_not_found` for a flow plainly on
|
|
313
|
+
// disk is what made replay unusable from a daemon started outside the project.
|
|
314
|
+
const loaded = await flowsForSession(deps, projectId).flows.load(name, projectId);
|
|
118
315
|
if (!loaded.ok) {
|
|
119
|
-
await recordReplayRun(deps, name, ReplayStatus.ERROR, 0, deps.now() - startedAt, projectId);
|
|
316
|
+
await recordReplayRun(deps, name, ReplayStatus.ERROR, 0, deps.now() - startedAt, projectId, sessionRoot(deps, asString(args['sessionId'])));
|
|
120
317
|
return {
|
|
121
318
|
name,
|
|
122
319
|
status: ReplayStatus.ERROR,
|
|
@@ -127,13 +324,19 @@ export async function replayNamedFlow(deps, args) {
|
|
|
127
324
|
// What this flow is FOR, from the shared ledger — so a failure can report the business outcome
|
|
128
325
|
// that stopped being true before the step that stopped being green. Undefined when nothing
|
|
129
326
|
// declared one, which the decision then says plainly rather than inventing a goal from step names.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
327
|
+
/*
|
|
328
|
+
* The APP's `.reticle`, resolved once. Everything below that reaches the project's own files —
|
|
329
|
+
* the intent ledger, the cloud link, the memory consultation — takes THIS, not `deps.reticleRoot`,
|
|
330
|
+
* which is wherever the daemon happened to be launched.
|
|
331
|
+
*/
|
|
332
|
+
const replayRoot = sessionRoot(deps, asString(args['sessionId']));
|
|
333
|
+
const intents = new IntentStore(deps.fs, replayRoot, { now: deps.now });
|
|
133
334
|
const intentSaid = await flowIntentStatement(intents, loaded.value);
|
|
134
|
-
const
|
|
135
|
-
//
|
|
136
|
-
//
|
|
335
|
+
const connected = deps.sessions.resolve(asString(args['sessionId']));
|
|
336
|
+
// The FlowFile contract: replay navigates to the flow's startPath before step 1, and the steps run
|
|
337
|
+
// on the session the SDK reconnects as. When arrival can't be confirmed, replay proceeds on the
|
|
338
|
+
// connected session — and the hint below turns the wrong-page drift into an actionable next move.
|
|
339
|
+
const session = (await arriveAtStartPath(deps.sessions, connected, loaded.value)) ?? connected;
|
|
137
340
|
const startPathHint = startPathMismatchHint(loaded.value, session);
|
|
138
341
|
// Floor the success oracle at the start of THIS replay so a stale signal from a prior run
|
|
139
342
|
// in the same session can't fake a pass.
|
|
@@ -157,7 +360,7 @@ export async function replayNamedFlow(deps, args) {
|
|
|
157
360
|
const driftSteps = steps.filter((s) => s.drift !== undefined).length;
|
|
158
361
|
const allOk = steps.every((s) => s.ok);
|
|
159
362
|
const status = driftSteps > 0 ? ReplayStatus.DRIFT : allOk ? ReplayStatus.OK : ReplayStatus.ERROR;
|
|
160
|
-
await recordReplayRun(deps, name, status, driftSteps, deps.now() - startedAt, projectId);
|
|
363
|
+
await recordReplayRun(deps, name, status, driftSteps, deps.now() - startedAt, projectId, replayRoot);
|
|
161
364
|
// Anti-reward-hacking baseline: record what this flow asserted ONLY when it passed clean. A
|
|
162
365
|
// failing run must never become the baseline a later weakening is measured against.
|
|
163
366
|
if (status === ReplayStatus.OK) {
|
|
@@ -182,6 +385,16 @@ export async function replayNamedFlow(deps, args) {
|
|
|
182
385
|
}
|
|
183
386
|
// Push-default: the deviation report over this drive's segments, learned across runs. Best-effort.
|
|
184
387
|
const deviation = await computeReplayDeviation(deps, session, replayFloor);
|
|
388
|
+
/*
|
|
389
|
+
* What the team already knows about this flow, fetched on the agent's behalf.
|
|
390
|
+
*
|
|
391
|
+
* Attached to the FAILING path as well as the clean one, deliberately: a drift is exactly the
|
|
392
|
+
* moment somebody needs to know what this feature is supposed to do and who established it. A
|
|
393
|
+
* knowledge base you only see when everything is already fine is decoration.
|
|
394
|
+
*/
|
|
395
|
+
// No projectId argument: the API key is already bound to one project server-side, so passing a
|
|
396
|
+
// second opinion about which project this is would only create a way for the two to disagree.
|
|
397
|
+
const knows = await consultProjectMemory(deps, loaded.value, replayRoot);
|
|
185
398
|
const failed = steps.find((step) => !step.ok && step.drift === undefined);
|
|
186
399
|
if (failed !== undefined) {
|
|
187
400
|
const errored = {
|
|
@@ -194,9 +407,13 @@ export async function replayNamedFlow(deps, args) {
|
|
|
194
407
|
applyStartPathHint(errored, startPathHint);
|
|
195
408
|
if (deviation !== undefined)
|
|
196
409
|
errored.deviation = deviation;
|
|
410
|
+
if (knows !== undefined)
|
|
411
|
+
errored.knows = knows;
|
|
197
412
|
return errored;
|
|
198
413
|
}
|
|
199
414
|
const result = { name, status, steps };
|
|
415
|
+
if (knows !== undefined)
|
|
416
|
+
result.knows = knows;
|
|
200
417
|
// A green that cannot go red is not a pass. `reticle_flow_verify` already refuses to count these,
|
|
201
418
|
// via this same function -- a single-flow caller saw a bare `ok` and had no way to learn the flow
|
|
202
419
|
// asserts nothing. Derived from the same helper on purpose: two copies of this judgement would
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { span } from '../trace.js';
|
|
2
|
+
import { routePathOf } from '../events/predicate-route.js';
|
|
2
3
|
import { AnchorKind, DriftReason, EventType, ReticleCommand, QueryBy, PredicateKind, } from '@reticlehq/core';
|
|
3
4
|
import { asRecord, asString } from '../tools/tools-helpers.js';
|
|
4
5
|
import { replayActionArgs, ambiguousTestidNote, queryRefs } from './replay.js';
|
|
@@ -198,8 +199,14 @@ function currentRoute(session) {
|
|
|
198
199
|
if (last === undefined)
|
|
199
200
|
return undefined;
|
|
200
201
|
const data = last.data ?? {};
|
|
202
|
+
// The ROUTER's path. This field answers "which page did this step run on", and the document
|
|
203
|
+
// pathname is `/` on every page of a hash-routed app — so a whole desktop replay reported `/` for
|
|
204
|
+
// every step. Sixth place the same reading was wrong; see routePathOf.
|
|
201
205
|
const pathname = asString(data['pathname']) ?? asString(data['to']);
|
|
202
|
-
|
|
206
|
+
if (pathname === undefined || 0 === pathname.length)
|
|
207
|
+
return undefined;
|
|
208
|
+
const routed = routePathOf(pathname, asString(data['hash']) ?? '');
|
|
209
|
+
return routed.length > 0 ? routed : undefined;
|
|
203
210
|
}
|
|
204
211
|
/** Pathname only (drop origin + query) so a net URL stays terse in the journey. */
|
|
205
212
|
function trimUrl(url) {
|
|
@@ -294,7 +301,9 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
|
|
|
294
301
|
act = await session.command(ReticleCommand.ACT, {
|
|
295
302
|
ref,
|
|
296
303
|
action: step.action ?? '',
|
|
297
|
-
|
|
304
|
+
// `value` is the anchor's own name — the field this step types into — so a redacted fill can
|
|
305
|
+
// be supplied from RETICLE_SECRET_<FIELD> without the flow carrying the secret.
|
|
306
|
+
args: replayActionArgs(step.args, confirmDangerous, value),
|
|
298
307
|
});
|
|
299
308
|
}
|
|
300
309
|
finally {
|
|
@@ -30,6 +30,14 @@ export declare function runComponentStep(session: FlowReplaySession, step: FlowS
|
|
|
30
30
|
*/
|
|
31
31
|
export declare function isDegradedAnchor(anchor: FlowAnchor): boolean;
|
|
32
32
|
export declare function degradedStepResult(step: FlowStep, index: number, label: string): FlowStepResult;
|
|
33
|
+
/**
|
|
34
|
+
* QUERY args for an element anchor — null when the anchor addresses no element.
|
|
35
|
+
*
|
|
36
|
+
* Exported for `arriveAtStartPath`, which has to ask "can step 1 resolve where we already are?"
|
|
37
|
+
* before deciding to navigate. Sharing this mapping rather than repeating it keeps that question and
|
|
38
|
+
* the step runner's own resolution asking the same thing of the same anchor.
|
|
39
|
+
*/
|
|
40
|
+
export declare function anchorQueryArgs(anchor: FlowAnchor): Record<string, unknown> | null;
|
|
33
41
|
/**
|
|
34
42
|
* Run an act_sequence step: resolve every sub-step's OWN anchor, then dispatch the whole thing as one
|
|
35
43
|
* ACT_SEQUENCE — the same shape `replayProgram` already uses, so a recorded sequence and a saved one
|
|
@@ -181,8 +181,14 @@ export function degradedStepResult(step, index, label) {
|
|
|
181
181
|
},
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
|
-
/**
|
|
185
|
-
|
|
184
|
+
/**
|
|
185
|
+
* QUERY args for an element anchor — null when the anchor addresses no element.
|
|
186
|
+
*
|
|
187
|
+
* Exported for `arriveAtStartPath`, which has to ask "can step 1 resolve where we already are?"
|
|
188
|
+
* before deciding to navigate. Sharing this mapping rather than repeating it keeps that question and
|
|
189
|
+
* the step runner's own resolution asking the same thing of the same anchor.
|
|
190
|
+
*/
|
|
191
|
+
export function anchorQueryArgs(anchor) {
|
|
186
192
|
if (anchor.kind === AnchorKind.TESTID)
|
|
187
193
|
return { by: QueryBy.TESTID, value: anchor.value };
|
|
188
194
|
if (anchor.kind === AnchorKind.COMPONENT)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flow store a SESSION's flows belong in.
|
|
3
|
+
*
|
|
4
|
+
* `deps.flows` is bound to the daemon's own root at construction, which is right only when the
|
|
5
|
+
* daemon happens to have been started in the project it is driving. A user-scoped MCP registration
|
|
6
|
+
* is the common case and the editor starts it wherever it likes, so "where the daemon was launched"
|
|
7
|
+
* and "which app is being verified" are usually different directories.
|
|
8
|
+
*
|
|
9
|
+
* This used to live inside flow-tools and was applied at exactly ONE of ten call sites. The result
|
|
10
|
+
* is the half-applied state `sessionRoot` warns about in its own comment, reached in practice:
|
|
11
|
+
*
|
|
12
|
+
* - the HUD listed the daemon repo's flows while driving somebody else's app — Electron and Tauri
|
|
13
|
+
* flows shown over a React dashboard, none of which existed in the checkout being verified
|
|
14
|
+
* - `flow_replay` answered `flow_not_found` for a flow sitting on disk, because it looked in the
|
|
15
|
+
* daemon's root while `reticle verify` (which roots itself at cwd) replayed the same flow fine
|
|
16
|
+
* - and the two save paths disagreed with each other, so where a flow landed depended on which
|
|
17
|
+
* tool wrote it
|
|
18
|
+
*
|
|
19
|
+
* Reported as "running the daemon from here but taking the directory from there", which is exactly
|
|
20
|
+
* what it was.
|
|
21
|
+
*
|
|
22
|
+
* Rebinding is cheap — a FlowStore is a filesystem port, a root and a clock — and the common case
|
|
23
|
+
* allocates nothing: when no resolver is wired, or it names the root already in use, `deps.flows` is
|
|
24
|
+
* returned unchanged.
|
|
25
|
+
*/
|
|
26
|
+
import { FlowStore } from './flows.js';
|
|
27
|
+
import type { ToolDeps } from '../tools/tool-kit.js';
|
|
28
|
+
export declare function flowsForSession(deps: ToolDeps, projectId: string | undefined): {
|
|
29
|
+
flows: FlowStore;
|
|
30
|
+
root: string;
|
|
31
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flow store a SESSION's flows belong in.
|
|
3
|
+
*
|
|
4
|
+
* `deps.flows` is bound to the daemon's own root at construction, which is right only when the
|
|
5
|
+
* daemon happens to have been started in the project it is driving. A user-scoped MCP registration
|
|
6
|
+
* is the common case and the editor starts it wherever it likes, so "where the daemon was launched"
|
|
7
|
+
* and "which app is being verified" are usually different directories.
|
|
8
|
+
*
|
|
9
|
+
* This used to live inside flow-tools and was applied at exactly ONE of ten call sites. The result
|
|
10
|
+
* is the half-applied state `sessionRoot` warns about in its own comment, reached in practice:
|
|
11
|
+
*
|
|
12
|
+
* - the HUD listed the daemon repo's flows while driving somebody else's app — Electron and Tauri
|
|
13
|
+
* flows shown over a React dashboard, none of which existed in the checkout being verified
|
|
14
|
+
* - `flow_replay` answered `flow_not_found` for a flow sitting on disk, because it looked in the
|
|
15
|
+
* daemon's root while `reticle verify` (which roots itself at cwd) replayed the same flow fine
|
|
16
|
+
* - and the two save paths disagreed with each other, so where a flow landed depended on which
|
|
17
|
+
* tool wrote it
|
|
18
|
+
*
|
|
19
|
+
* Reported as "running the daemon from here but taking the directory from there", which is exactly
|
|
20
|
+
* what it was.
|
|
21
|
+
*
|
|
22
|
+
* Rebinding is cheap — a FlowStore is a filesystem port, a root and a clock — and the common case
|
|
23
|
+
* allocates nothing: when no resolver is wired, or it names the root already in use, `deps.flows` is
|
|
24
|
+
* returned unchanged.
|
|
25
|
+
*/
|
|
26
|
+
import { FlowStore } from './flows.js';
|
|
27
|
+
export function flowsForSession(deps, projectId) {
|
|
28
|
+
const resolved = deps.artifactRootFor?.(projectId);
|
|
29
|
+
if (resolved === undefined || resolved.root === deps.reticleRoot) {
|
|
30
|
+
return { flows: deps.flows, root: deps.reticleRoot };
|
|
31
|
+
}
|
|
32
|
+
return { flows: new FlowStore(deps.fs, resolved.root, { now: deps.now }), root: resolved.root };
|
|
33
|
+
}
|