@reticlehq/server 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +16 -8
- package/dist/chromium-launch-options.d.ts +3 -1
- package/dist/chromium-launch-options.js +2 -1
- package/dist/command/cli/adhoc-verdict.d.ts +1 -0
- package/dist/command/cli/adhoc-verdict.js +6 -3
- package/dist/command/cli/answers/open-note.d.ts +2 -0
- package/dist/command/cli/answers/open-note.js +22 -0
- package/dist/command/cli/cli-flow-commands.js +4 -8
- package/dist/command/cli/cli-parse.d.ts +1 -1
- package/dist/command/cli/cli-parse.js +10 -10
- package/dist/command/cli/cli-verify.d.ts +1 -1
- package/dist/command/cli/cli-verify.js +35 -25
- package/dist/command/cli/cloud-cli.js +51 -12
- package/dist/command/cli/cloud-login.d.ts +8 -0
- package/dist/command/cli/cloud-login.js +36 -2
- package/dist/command/cli/demo/demo-page.js +11 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
- package/dist/command/cli/drive/drive-attach.d.ts +27 -2
- package/dist/command/cli/drive/drive-attach.js +71 -4
- package/dist/command/cli/drive/drive-command.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +13 -7
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
- package/dist/command/cli/ports/port-holder.d.ts +10 -0
- package/dist/command/cli/ports/port-holder.js +24 -0
- package/dist/command/cli/setup-mcp-cli.js +6 -4
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +6 -9
- package/dist/command/cli.d.ts +6 -0
- package/dist/command/cli.js +28 -22
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
- package/dist/command/daemon/daemon-resolve.d.ts +14 -0
- package/dist/command/daemon/daemon-resolve.js +33 -2
- package/dist/command/setup/agent-writer.d.ts +1 -1
- package/dist/command/setup/agent-writer.js +3 -2
- package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
- package/dist/command/setup/bringup/connect-lease.js +1 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +8 -0
- package/dist/command/setup/bringup/dev-server-wait.js +19 -0
- package/dist/command/setup/init/init-port.d.ts +4 -0
- package/dist/command/setup/init/init-port.js +10 -0
- package/dist/command/setup/init/init-runtime.d.ts +10 -0
- package/dist/command/setup/init/init-runtime.js +18 -1
- package/dist/command/setup/node-effects.js +2 -1
- package/dist/command/setup/remaining-steps.js +2 -1
- package/dist/command/setup/run-setup.d.ts +5 -0
- package/dist/command/setup/run-setup.js +35 -27
- package/dist/command/setup/setup-command.d.ts +2 -1
- package/dist/command/setup/setup-command.js +2 -0
- package/dist/command/version/version-skew.js +11 -2
- package/dist/features/crawl/crawl-tools.js +4 -0
- package/dist/features/crawl/crawl.d.ts +6 -0
- package/dist/features/crawl/crawl.js +9 -1
- package/dist/features/exhaust/ledger.d.ts +7 -0
- package/dist/features/exhaust/ledger.js +35 -9
- package/dist/features/harness/app-findings.d.ts +2 -0
- package/dist/features/harness/app-findings.js +52 -0
- package/dist/features/harness/drive-report.d.ts +27 -1
- package/dist/features/harness/drive-report.js +162 -11
- package/dist/features/harness/drivers.d.ts +10 -14
- package/dist/features/harness/drivers.js +10 -14
- package/dist/features/harness/fill-values.d.ts +3 -73
- package/dist/features/harness/fill-values.js +3 -169
- package/dist/features/harness/goals.d.ts +21 -0
- package/dist/features/harness/goals.js +77 -0
- package/dist/features/harness/harness.d.ts +21 -0
- package/dist/features/harness/harness.js +69 -2
- package/dist/features/harness/platform/drive-target.d.ts +30 -0
- package/dist/features/harness/platform/drive-target.js +81 -0
- package/dist/features/harness/platform/local-apps.d.ts +94 -0
- package/dist/features/harness/platform/local-apps.js +204 -0
- package/dist/features/harness/platform/remote-drive.d.ts +180 -0
- package/dist/features/harness/platform/remote-drive.js +365 -0
- package/dist/features/harness/platform/script.d.ts +48 -0
- package/dist/features/harness/platform/script.js +128 -0
- package/dist/features/harness/platform/server-driver.d.ts +41 -0
- package/dist/features/harness/platform/server-driver.js +179 -0
- package/dist/features/harness/platform-config.d.ts +7 -0
- package/dist/features/harness/platform-config.js +14 -2
- package/dist/features/harness/script/app-controls.d.ts +5 -0
- package/dist/features/harness/script/app-controls.js +40 -0
- package/dist/features/harness/script/lane-ids.d.ts +14 -0
- package/dist/features/harness/script/lane-ids.js +16 -0
- package/dist/features/harness/script/script-run.d.ts +41 -0
- package/dist/features/harness/script/script-run.js +299 -0
- package/dist/features/license/license.d.ts +2 -2
- package/dist/features/visual/visual-tools.d.ts +10 -1
- package/dist/features/visual/visual-tools.js +40 -3
- package/dist/hooks/driven-by.d.ts +4 -0
- package/dist/hooks/driven-by.js +16 -0
- package/dist/hooks/hook-commands.js +15 -4
- package/dist/hooks/tool-log.d.ts +9 -0
- package/dist/hooks/tool-log.js +29 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +76 -82
- package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
- package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
- package/dist/judgement/runs/artifact/run-store.js +26 -1
- package/dist/judgement/runs/drive-run.d.ts +22 -0
- package/dist/judgement/runs/drive-run.js +99 -1
- package/dist/judgement/runs/repair-prompt.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +8 -1
- package/dist/judgement/runs/verification-sync.d.ts +3 -2
- package/dist/judgement/runs/verification-sync.js +4 -3
- package/dist/language/flows/change/gate.d.ts +10 -0
- package/dist/language/flows/change/gate.js +22 -0
- package/dist/language/flows/decision.js +34 -0
- package/dist/language/flows/flow-anchor.d.ts +54 -8
- package/dist/language/flows/flow-anchor.js +88 -3
- package/dist/language/flows/flow-cross-step.d.ts +15 -0
- package/dist/language/flows/flow-cross-step.js +21 -0
- package/dist/language/flows/flow-learning.js +8 -11
- package/dist/language/flows/flow-mutate-tools.js +5 -3
- package/dist/language/flows/flow-replay-run.d.ts +23 -9
- package/dist/language/flows/flow-replay-run.js +101 -94
- package/dist/language/flows/flow-replay.d.ts +23 -1
- package/dist/language/flows/flow-replay.js +92 -19
- package/dist/language/flows/flow-scope.js +5 -1
- package/dist/language/flows/flow-step-runners.d.ts +20 -2
- package/dist/language/flows/flow-step-runners.js +236 -42
- package/dist/language/flows/flow-store-for-session.d.ts +2 -2
- package/dist/language/flows/flow-store-for-session.js +2 -2
- package/dist/language/flows/flow-tools.js +58 -34
- package/dist/language/flows/flows.js +5 -2
- package/dist/language/flows/heal-run.js +6 -4
- package/dist/language/flows/recording/flake.d.ts +2 -2
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
- package/dist/language/flows/recording/tape/recordings.js +40 -8
- package/dist/language/flows/replay-from-hud.d.ts +20 -0
- package/dist/language/flows/replay-from-hud.js +51 -0
- package/dist/language/flows/replay.d.ts +9 -5
- package/dist/language/flows/replay.js +27 -9
- package/dist/language/flows/suite/suite-flakes.js +2 -0
- package/dist/language/flows/suite-selection.d.ts +2 -15
- package/dist/language/flows/suite-selection.js +4 -3
- package/dist/language/flows/verify-change-tools.js +8 -4
- package/dist/launch-chromium.d.ts +27 -5
- package/dist/launch-chromium.js +38 -8
- package/dist/memory/cloud/cloud-config.d.ts +12 -0
- package/dist/memory/cloud/cloud-config.js +15 -0
- package/dist/memory/cloud/cloud-sync.d.ts +17 -14
- package/dist/memory/cloud/cloud-sync.js +12 -1
- package/dist/memory/cloud/harness-config.d.ts +26 -1
- package/dist/memory/cloud/harness-config.js +99 -4
- package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
- package/dist/memory/cloud/hud-notices-source.js +128 -0
- package/dist/memory/cloud/memory-scope.d.ts +144 -0
- package/dist/memory/cloud/memory-scope.js +205 -0
- package/dist/memory/cloud/sync-cycle.d.ts +12 -3
- package/dist/memory/cloud/sync-cycle.js +16 -3
- package/dist/memory/cloud/sync-daemon.js +7 -5
- package/dist/memory/cloud/sync-disk.d.ts +1 -1
- package/dist/memory/cloud/sync-disk.js +5 -8
- package/dist/memory/impact/account-watch.d.ts +19 -0
- package/dist/memory/impact/account-watch.js +34 -0
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +12 -2
- package/dist/memory/impact/impact-store.d.ts +16 -0
- package/dist/memory/impact/impact-store.js +142 -26
- package/dist/memory/intent/intent-shard.d.ts +2 -2
- package/dist/memory/intent/intent-tools.js +80 -2
- package/dist/memory/journal/envelope.d.ts +8 -8
- package/dist/memory/journal/journal-recorder.js +7 -1
- package/dist/memory/journal/on-disk/retention.d.ts +3 -0
- package/dist/memory/journal/on-disk/retention.js +5 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
- package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
- package/dist/memory/journal/session-end.js +6 -4
- package/dist/memory/project/artifact-root-resolver.d.ts +26 -1
- package/dist/memory/project/artifact-root-resolver.js +94 -13
- package/dist/memory/project/artifact-root.d.ts +42 -5
- package/dist/memory/project/artifact-root.js +87 -8
- package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
- package/dist/memory/project/dir/reticle-dir.js +3 -1
- package/dist/memory/project/project-tools.js +22 -3
- package/dist/memory/project/session-root.d.ts +24 -1
- package/dist/memory/project/session-root.js +41 -5
- package/dist/memory/project/sync-status.d.ts +39 -0
- package/dist/memory/project/sync-status.js +90 -0
- package/dist/memory/recall/memory-tools.js +2 -0
- package/dist/memory/recall/project-memory.d.ts +19 -0
- package/dist/memory/recall/project-memory.js +45 -6
- package/dist/portal/bridge/bridge.d.ts +3 -1
- package/dist/portal/bridge/bridge.js +19 -2
- package/dist/portal/bridge/pairing-token.d.ts +9 -0
- package/dist/portal/bridge/pairing-token.js +17 -0
- package/dist/portal/input/real-input.d.ts +81 -10
- package/dist/portal/input/real-input.js +207 -22
- package/dist/portal/input/viewport-tools.js +3 -3
- package/dist/portal/pool/browser-pool.d.ts +22 -9
- package/dist/portal/pool/browser-pool.js +70 -35
- package/dist/portal/pool/playwright-launcher.d.ts +13 -0
- package/dist/portal/pool/playwright-launcher.js +17 -1
- package/dist/portal/pool/pool-contract.d.ts +7 -4
- package/dist/portal/session/command-payload.d.ts +8 -0
- package/dist/portal/session/command-payload.js +19 -0
- package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
- package/dist/portal/session/dev-server/dev-command.js +61 -12
- package/dist/portal/session/facts/departure-note.d.ts +22 -0
- package/dist/portal/session/facts/departure-note.js +47 -0
- package/dist/portal/session/facts/session-replaced.d.ts +5 -0
- package/dist/portal/session/facts/session-replaced.js +5 -0
- package/dist/portal/session/first-run-wiring.d.ts +42 -0
- package/dist/portal/session/first-run-wiring.js +132 -0
- package/dist/portal/session/human/notes-ledger.d.ts +20 -0
- package/dist/portal/session/human/notes-ledger.js +55 -0
- package/dist/portal/session/human/review-store.d.ts +12 -0
- package/dist/portal/session/human/review-store.js +18 -10
- package/dist/portal/session/live-control-tools.d.ts +0 -13
- package/dist/portal/session/live-control-tools.js +28 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
- package/dist/portal/session/no-session-diagnosis.js +15 -0
- package/dist/portal/session/no-session-next-action.d.ts +11 -0
- package/dist/portal/session/no-session-next-action.js +31 -5
- package/dist/portal/session/no-session-watch.d.ts +14 -1
- package/dist/portal/session/no-session-watch.js +63 -10
- package/dist/portal/session/presence/session-mirror.js +3 -0
- package/dist/portal/session/serving-directory.d.ts +27 -0
- package/dist/portal/session/serving-directory.js +45 -0
- package/dist/portal/session/session-health.d.ts +7 -0
- package/dist/portal/session/session-health.js +6 -0
- package/dist/portal/session/session-info.d.ts +8 -1
- package/dist/portal/session/session-info.js +6 -0
- package/dist/portal/session/session-manager.d.ts +21 -0
- package/dist/portal/session/session-manager.js +44 -8
- package/dist/portal/session/session-reaper.js +4 -1
- package/dist/portal/session/session-successor.d.ts +5 -0
- package/dist/portal/session/session-tools.js +26 -3
- package/dist/portal/session/session.d.ts +7 -2
- package/dist/portal/session/session.js +21 -18
- package/dist/start-options.d.ts +6 -0
- package/dist/status-payload.d.ts +10 -0
- package/dist/status-payload.js +23 -0
- package/dist/surface/http-server.js +11 -1
- package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
- package/dist/surface/mcp/mcp-proxy.js +36 -5
- package/dist/surface/mcp/mcp.d.ts +11 -6
- package/dist/surface/mcp/mcp.js +27 -13
- package/dist/surface/mcp/server-instructions.d.ts +4 -2
- package/dist/surface/mcp/server-instructions.js +14 -4
- package/dist/surface/mcp/shared-params.js +2 -2
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
- package/dist/surface/tools/act/settle-in-flight.js +25 -3
- package/dist/surface/tools/act-sequence-tool.js +2 -2
- package/dist/surface/tools/act-tools.js +23 -7
- package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +17 -2
- package/dist/surface/tools/browser-tools.js +8 -1
- package/dist/surface/tools/chat-drives.d.ts +26 -0
- package/dist/surface/tools/chat-drives.js +165 -0
- package/dist/surface/tools/empty-session-list.d.ts +41 -0
- package/dist/surface/tools/empty-session-list.js +80 -0
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +37 -4
- package/dist/surface/tools/explore-tools.d.ts +10 -1
- package/dist/surface/tools/explore-tools.js +82 -13
- package/dist/surface/tools/feedback-tools.js +14 -0
- package/dist/surface/tools/harness-explore.d.ts +96 -14
- package/dist/surface/tools/harness-explore.js +227 -206
- package/dist/surface/tools/harness-plan.d.ts +12 -0
- package/dist/surface/tools/harness-plan.js +31 -0
- package/dist/surface/tools/harness-script.d.ts +8 -0
- package/dist/surface/tools/harness-script.js +342 -0
- package/dist/surface/tools/harness-toolset.d.ts +14 -1
- package/dist/surface/tools/harness-toolset.js +77 -6
- package/dist/surface/tools/invoke-tool.js +52 -1
- package/dist/surface/tools/lease-seed.d.ts +29 -0
- package/dist/surface/tools/lease-seed.js +123 -0
- package/dist/surface/tools/lease-tools.d.ts +3 -23
- package/dist/surface/tools/lease-tools.js +30 -131
- package/dist/surface/tools/live-call-text.js +2 -2
- package/dist/surface/tools/next-step.d.ts +19 -0
- package/dist/surface/tools/next-step.js +159 -0
- package/dist/surface/tools/observe-tools.js +2 -4
- package/dist/surface/tools/platform-moment.d.ts +12 -0
- package/dist/surface/tools/platform-moment.js +168 -0
- package/dist/surface/tools/query-hint-schema.d.ts +7 -0
- package/dist/surface/tools/query-hint-schema.js +10 -0
- package/dist/surface/tools/read-tools.js +8 -2
- package/dist/surface/tools/real-input-attempt.js +96 -13
- package/dist/surface/tools/tool-kit.d.ts +15 -1
- package/dist/surface/tools/tool-kit.js +4 -6
- package/dist/surface/tools/tools.js +20 -48
- package/dist/telemetry/hud-metrics.d.ts +2 -0
- package/dist/telemetry/hud-metrics.js +17 -0
- package/dist/telemetry/telemetry.d.ts +12 -0
- package/dist/telemetry/telemetry.js +43 -14
- package/dist/telemetry/verification-of.js +1 -0
- package/dist/wire-journal.js +4 -1
- package/docs/README.md +1 -1
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/autodrive.md +29 -25
- package/docs/cli/doctor.mdx +6 -0
- package/docs/cli/drive.mdx +8 -2
- package/docs/cli/init.mdx +11 -13
- package/docs/cli/status.mdx +5 -1
- package/docs/cli/version.mdx +3 -3
- package/docs/cli.mdx +1 -1
- package/docs/desktop.mdx +5 -1
- package/docs/environment.mdx +3 -7
- package/docs/getting-started.md +13 -11
- package/docs/human-control.md +1 -2
- package/docs/index.mdx +4 -3
- package/docs/install-manual.mdx +2 -2
- package/docs/packages/tauri.mdx +2 -2
- package/docs/packages/vite-plugin.mdx +22 -1
- package/docs/packages.mdx +1 -1
- package/docs/quickstart.mdx +17 -7
- package/docs/telemetry.md +1 -1
- package/docs/tools/session-and-feedback.mdx +9 -9
- package/docs/usage.md +5 -2
- package/docs/what-is-recorded.md +13 -3
- package/docs/what-is-reticle.mdx +3 -1
- package/package.json +8 -12
- package/dist/features/harness/driver.d.ts +0 -50
- package/dist/features/harness/driver.js +0 -236
- package/dist/features/harness/jev-driver.d.ts +0 -188
- package/dist/features/harness/jev-driver.js +0 -678
- package/dist/features/harness/openai-driver.d.ts +0 -58
- package/dist/features/harness/openai-driver.js +0 -206
- package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
- package/dist/memory/project/dir/fill-value-store.js +0 -88
- package/docs/local-registry.md +0 -143
|
@@ -6,7 +6,7 @@ import { emptyFlowRefusal } from './empty-flow.js';
|
|
|
6
6
|
import { aliasParam } from '../../surface/tools/args/alias-args.js';
|
|
7
7
|
import { FlowErrorCode, RecordedSaveError, ReplayStatus, SuiteIsolation, } from '@reticlehq/core';
|
|
8
8
|
import { recordSuiteFlakes } from './suite/suite-flakes.js';
|
|
9
|
-
import { ReticleTool } from '@reticlehq/core';
|
|
9
|
+
import { AppRuntime, ReticleTool } from '@reticlehq/core';
|
|
10
10
|
import { asNumber, asString, mutationTargetsFor, perturbationFor } from '@reticlehq/core';
|
|
11
11
|
import { workerCountSchema } from '../../surface/tools/args/numeric-bounds.js';
|
|
12
12
|
import { log } from '../../log.js';
|
|
@@ -21,7 +21,8 @@ import { recordingBacktrackWarning } from './recording/recording-backtrack.js';
|
|
|
21
21
|
import { isValidFlowName, flowPath } from '../../memory/project/dir/reticle-dir.js';
|
|
22
22
|
import {} from './flows.js';
|
|
23
23
|
import { flowsForSession } from './flow-store-for-session.js';
|
|
24
|
-
import {
|
|
24
|
+
import { rootForTarget, sessionTarget } from '../../memory/project/session-root.js';
|
|
25
|
+
import { originOf } from '../../portal/session/session-manager.js';
|
|
25
26
|
import { resolveSuiteSelection } from './suite-selection.js';
|
|
26
27
|
import { replayNamedFlow, flowErrorMessage, latestRecordedFlow, sessionProjectId, } from './flow-replay-run.js';
|
|
27
28
|
import { persistAndSyncVerificationRun, } from '../../judgement/runs/verification-sync.js';
|
|
@@ -34,19 +35,21 @@ import { replayAndLearn } from './flow-learning.js';
|
|
|
34
35
|
* set). Fire-and-forget: resolves the config, POSTs via the platform fetch, and logs the outcome. Any
|
|
35
36
|
* failure is swallowed so a network hiccup never affects the local save.
|
|
36
37
|
*/
|
|
37
|
-
async function syncSavedFlowToCloud(deps, flow,
|
|
38
|
+
async function syncSavedFlowToCloud(deps, flow, target) {
|
|
38
39
|
// Per-project cloud: sync a saved flow only when cloud is attached AND flow sync is enabled.
|
|
39
40
|
// The link file of the project this flow belongs to — the flow itself was already saved through
|
|
40
41
|
// `flowsForSession`, and reading the credential from a different tree is how one project's work
|
|
41
42
|
// reaches another project's dashboard.
|
|
42
|
-
const cloud = await resolveProjectCloud(deps.fs,
|
|
43
|
+
const cloud = await resolveProjectCloud(deps.fs, rootForTarget(deps, target), homedir(), process.env);
|
|
43
44
|
if (null === cloud.config || !cloud.policy.flows)
|
|
44
45
|
return; // not attached / flows disabled → local only
|
|
45
|
-
const result = await syncFlowToCloud(flow, cloud.config, projectId, cloudFetch);
|
|
46
|
+
const result = await syncFlowToCloud(flow, cloud.config, target.projectId, cloudFetch);
|
|
46
47
|
if (result.outcome !== SyncOutcome.SYNCED) {
|
|
47
48
|
log('cloud-flow-sync-failed', { flow: flow.name, status: result.status, error: result.error });
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
/** A step index no recorded flow reaches; bounds `to` and `at`. */
|
|
52
|
+
const MAX_REPLAY_STEP = 10_000;
|
|
50
53
|
/**
|
|
51
54
|
* The URL a leased context should open: the app's ORIGIN, not the live tab's current location.
|
|
52
55
|
*
|
|
@@ -57,7 +60,10 @@ async function syncSavedFlowToCloud(deps, flow, projectId) {
|
|
|
57
60
|
*/
|
|
58
61
|
export function leasableAppUrl(deps, sessionId) {
|
|
59
62
|
try {
|
|
60
|
-
const
|
|
63
|
+
const session = deps.sessions.resolve(sessionId);
|
|
64
|
+
if (AppRuntime.WEB !== (session.runtime ?? AppRuntime.WEB))
|
|
65
|
+
return undefined; // desktop: no IPC in a lease
|
|
66
|
+
const url = session.url;
|
|
61
67
|
if (typeof url !== 'string' || 0 === url.length)
|
|
62
68
|
return undefined;
|
|
63
69
|
return new URL(url).origin;
|
|
@@ -211,13 +217,14 @@ export const FLOW_TOOLS = [
|
|
|
211
217
|
...(success !== undefined ? { success } : {}),
|
|
212
218
|
...(intent !== undefined ? { intent } : {}),
|
|
213
219
|
};
|
|
214
|
-
const
|
|
220
|
+
const target = sessionTarget(deps, asString(args['sessionId']));
|
|
221
|
+
const { projectId } = target;
|
|
215
222
|
// A zero-step recording is not a flow. Saving it wrote a file the suite reports "unverifiable"
|
|
216
223
|
// forever while telling the agent it had saved a regression test — see empty-flow.
|
|
217
224
|
const emptyRefusal = emptyFlowRefusal(program.steps.length, name);
|
|
218
225
|
if (emptyRefusal !== undefined)
|
|
219
226
|
return Promise.resolve(emptyRefusal);
|
|
220
|
-
const { flows, root } = flowsForSession(deps,
|
|
227
|
+
const { flows, root } = flowsForSession(deps, target);
|
|
221
228
|
const author = flowAuthor();
|
|
222
229
|
const toSave = {
|
|
223
230
|
...program,
|
|
@@ -275,10 +282,11 @@ export const FLOW_TOOLS = [
|
|
|
275
282
|
// (the prior bug) made schema-validating MCP clients reject the result ("expected object,
|
|
276
283
|
// received string") — caught driving the live demo.
|
|
277
284
|
handler: (deps, args) => {
|
|
278
|
-
const
|
|
285
|
+
const target = sessionTarget(deps, asString(args['sessionId']));
|
|
286
|
+
const { projectId } = target;
|
|
279
287
|
// The APP's flows, not the daemon's. Listing where the daemon was launched is what showed a
|
|
280
288
|
// React dashboard a HUD full of Electron and Tauri flows from an unrelated checkout.
|
|
281
|
-
const { flows: store, root } = flowsForSession(deps,
|
|
289
|
+
const { flows: store, root } = flowsForSession(deps, target);
|
|
282
290
|
return store.list(projectId).then((names) => ({
|
|
283
291
|
// `list` deliberately returns invalid names so they are reported rather than silently
|
|
284
292
|
// dropped — but the declared outputSchema requires `path` on EVERY entry, and omitting it made
|
|
@@ -366,16 +374,15 @@ export const FLOW_TOOLS = [
|
|
|
366
374
|
{
|
|
367
375
|
name: ReticleTool.FLOW_REPLAY,
|
|
368
376
|
description: "Replay a git-checked flow from .reticle/flows/<name>.json. RE-RESOLVES each step's semantic " +
|
|
369
|
-
'anchor (testid via reticle_query; signal via predicate) against
|
|
370
|
-
'
|
|
371
|
-
'
|
|
372
|
-
'
|
|
373
|
-
'
|
|
374
|
-
'
|
|
375
|
-
'
|
|
376
|
-
'
|
|
377
|
-
'
|
|
378
|
-
'an action that failed to dispatch.',
|
|
377
|
+
'anchor (testid via reticle_query; signal via predicate) against LIVE DOM. On anchor MISS returns ' +
|
|
378
|
+
'DRIFT { step, anchor, drift:{ reasonKind, reason, nearest } } and stops. ' +
|
|
379
|
+
'Returns { name, status: ok|drift|error|unverifiable, steps:[...] }. ' +
|
|
380
|
+
'Action failures are status:error. ' +
|
|
381
|
+
'Status unverifiable means flow could not be graded (missing secrets, unmet preconditions, lost ' +
|
|
382
|
+
'document) — neither pass nor fail, treat as UNKNOWN. ' +
|
|
383
|
+
'An ok replay of a flow asserting no consequence also carries unverifiable:{reason} — do not ' +
|
|
384
|
+
'treat as proof. ' +
|
|
385
|
+
'Early halt carries halted:{atStep, notAttempted}: `steps` is shorter because replay stopped.',
|
|
379
386
|
inputSchema: {
|
|
380
387
|
flow: z
|
|
381
388
|
.string()
|
|
@@ -392,6 +399,20 @@ export const FLOW_TOOLS = [
|
|
|
392
399
|
.union([z.number().int().nonnegative(), z.string().min(1)])
|
|
393
400
|
.optional()
|
|
394
401
|
.describe('Resume at this step (index or step id). Earlier steps re-run as unchecked, unreported setup; a setup step marked effect:"commits" refuses the resume.'),
|
|
402
|
+
to: z
|
|
403
|
+
.number()
|
|
404
|
+
.int()
|
|
405
|
+
.positive()
|
|
406
|
+
.max(MAX_REPLAY_STEP)
|
|
407
|
+
.optional()
|
|
408
|
+
.describe('Stop before this step (drive only to a point journeys branch from).'),
|
|
409
|
+
at: z
|
|
410
|
+
.number()
|
|
411
|
+
.int()
|
|
412
|
+
.positive()
|
|
413
|
+
.max(MAX_REPLAY_STEP)
|
|
414
|
+
.optional()
|
|
415
|
+
.describe('Continue at this step on the page as it is (after another flow ran `to` it).'),
|
|
395
416
|
sweep: z
|
|
396
417
|
.boolean()
|
|
397
418
|
.optional()
|
|
@@ -412,7 +433,7 @@ export const FLOW_TOOLS = [
|
|
|
412
433
|
// The flow's name — always present in FlowReplayResult (the description promises `{ name, … }`),
|
|
413
434
|
// but omitted here, so a validating profile stripped it and a replay result arrived anonymous.
|
|
414
435
|
name: z.string(),
|
|
415
|
-
status: z.string().describe('ok | drift | error'),
|
|
436
|
+
status: z.string().describe('ok | drift | error | unverifiable'),
|
|
416
437
|
steps: z.array(z.unknown()),
|
|
417
438
|
proposals: z.array(z.unknown()).optional(),
|
|
418
439
|
deviation: z
|
|
@@ -482,8 +503,9 @@ export const FLOW_TOOLS = [
|
|
|
482
503
|
* inherit it as a mystery — the same rule the mutation loop had to earn.
|
|
483
504
|
*/
|
|
484
505
|
const sessionId = asString(args['sessionId']);
|
|
485
|
-
const
|
|
486
|
-
|
|
506
|
+
const target = sessionTarget(deps, sessionId);
|
|
507
|
+
const loaded = await flowsForSession(deps, target)
|
|
508
|
+
.flows.load(asString(args['flowName']) ?? asString(args['flow']) ?? '', target.projectId)
|
|
487
509
|
.catch(() => null);
|
|
488
510
|
const targets = loaded !== null && loaded.ok ? mutationTargetsFor(loaded.value) : [];
|
|
489
511
|
const rules = perturbationFor(seed, targets);
|
|
@@ -576,8 +598,9 @@ export const FLOW_TOOLS = [
|
|
|
576
598
|
handler: async (deps, args) => {
|
|
577
599
|
const sessionId = asString(args['sessionId']);
|
|
578
600
|
// "Replay all" means all of THIS app's flows (+ legacy), not every project's on a shared daemon.
|
|
579
|
-
const
|
|
580
|
-
const
|
|
601
|
+
const target = sessionTarget(deps, sessionId);
|
|
602
|
+
const { projectId } = target;
|
|
603
|
+
const selected = await resolveSuiteSelection(deps, target, args);
|
|
581
604
|
const requested = selected.run;
|
|
582
605
|
/** Named rather than subtracted — a suite that returns fewer than were asked for must say so. */
|
|
583
606
|
const heldBack = {
|
|
@@ -587,7 +610,7 @@ export const FLOW_TOOLS = [
|
|
|
587
610
|
...(selected.unsatisfied === undefined ? {} : { unsatisfied: selected.unsatisfied }),
|
|
588
611
|
};
|
|
589
612
|
// verify:server — hand the whole suite to the hosted runner; it records the verification itself.
|
|
590
|
-
const cloud = await resolveProjectCloud(deps.fs,
|
|
613
|
+
const cloud = await resolveProjectCloud(deps.fs, rootForTarget(deps, target), homedir(), process.env);
|
|
591
614
|
const server = await runServerVerify(deps, cloud, sessionId, requested);
|
|
592
615
|
if (server !== null)
|
|
593
616
|
return server;
|
|
@@ -639,7 +662,7 @@ export const FLOW_TOOLS = [
|
|
|
639
662
|
const parallelRuns = await Promise.all(outcomes.map(async (o, i) => {
|
|
640
663
|
const name = requested[i] ?? '';
|
|
641
664
|
const replay = o.ok && o.value !== undefined ? o.value.replay : leaseFailureReplay(name, o.error);
|
|
642
|
-
const loaded = await flowsForSession(deps,
|
|
665
|
+
const loaded = await flowsForSession(deps, target)
|
|
643
666
|
.flows.load(name, projectId)
|
|
644
667
|
.catch(() => null);
|
|
645
668
|
const flow = loaded !== null && loaded.ok ? loaded.value : undefined;
|
|
@@ -649,8 +672,8 @@ export const FLOW_TOOLS = [
|
|
|
649
672
|
...(parallelRuns[i] ?? { replay: leaseFailureReplay(requested[i] ?? '', o.error) }),
|
|
650
673
|
durationMs: o.ok && o.value !== undefined ? o.value.durationMs : 0,
|
|
651
674
|
}));
|
|
652
|
-
const flaky = await recordSuiteFlakes(deps.fs,
|
|
653
|
-
await persistAndSyncVerificationRun(deps, timed,
|
|
675
|
+
const flaky = await recordSuiteFlakes(deps.fs, rootForTarget(deps, target), parallelRuns);
|
|
676
|
+
await persistAndSyncVerificationRun(deps, timed, target);
|
|
654
677
|
const verdict = buildSuiteVerdict(parallelRuns, selected.knownRoutes, SuiteIsolation.PER_FLOW_LEASE);
|
|
655
678
|
return {
|
|
656
679
|
...verdict,
|
|
@@ -674,7 +697,7 @@ export const FLOW_TOOLS = [
|
|
|
674
697
|
for (const flowName of requested) {
|
|
675
698
|
const start = deps.now();
|
|
676
699
|
const replay = await replayAndLearn(deps, { flowName, sessionId });
|
|
677
|
-
const loaded = await flowsForSession(deps,
|
|
700
|
+
const loaded = await flowsForSession(deps, target)
|
|
678
701
|
.flows.load(flowName, projectId)
|
|
679
702
|
.catch(() => null);
|
|
680
703
|
const flow = loaded !== null && loaded.ok ? loaded.value : undefined;
|
|
@@ -689,9 +712,9 @@ export const FLOW_TOOLS = [
|
|
|
689
712
|
// `reticle flow` on the command line, so an AGENT running this tool a hundred times learned
|
|
690
713
|
// nothing about which flows are intermittent. Same product, same ledger, two surfaces, and the
|
|
691
714
|
// one an agent uses was the blind half.
|
|
692
|
-
const flaky = await recordSuiteFlakes(deps.fs,
|
|
715
|
+
const flaky = await recordSuiteFlakes(deps.fs, rootForTarget(deps, target), runs);
|
|
693
716
|
// Emit the consolidated run artifact (Runs tab) + best-effort cloud push. Never blocks the verdict.
|
|
694
|
-
await persistAndSyncVerificationRun(deps, timed,
|
|
717
|
+
await persistAndSyncVerificationRun(deps, timed, target);
|
|
695
718
|
const verdict = buildSuiteVerdict(runs, selected.knownRoutes, SuiteIsolation.SHARED_SESSION);
|
|
696
719
|
// A flow that has both passed and failed on UNCHANGED code is a different thing from a
|
|
697
720
|
// regression, and an agent that cannot tell them apart either chases a ghost or ignores a real
|
|
@@ -766,12 +789,13 @@ export const FLOW_TOOLS = [
|
|
|
766
789
|
// Resolved like every other path now. This save used to go to the daemon's root while the
|
|
767
790
|
// recorded-flow save resolved per session, so where a flow landed depended on which tool wrote
|
|
768
791
|
// it — and the pair that disagreed were the two ways to save the same thing.
|
|
769
|
-
const
|
|
792
|
+
const target = { projectId: session.projectId, origin: originOf(session.url) };
|
|
793
|
+
const res = await flowsForSession(deps, target).flows.saveFlow(flow, session.projectId);
|
|
770
794
|
if (!res.ok)
|
|
771
795
|
return { error: flowErrorMessage(res.code, res.detail), code: res.code };
|
|
772
796
|
// If logged in to Reticle, mirror the saved flow to the team's regression suite. Best-effort
|
|
773
797
|
// and non-blocking: the flow is already on disk, so a sync failure never fails the save.
|
|
774
|
-
void syncSavedFlowToCloud(deps, flow,
|
|
798
|
+
void syncSavedFlowToCloud(deps, flow, target);
|
|
775
799
|
// Return the SaveSummary as-is ({ name, stepCount, degraded, empty }) — the outputSchema
|
|
776
800
|
// declares `name`, so the old `flowName` key was silently stripped by schema-strict clients.
|
|
777
801
|
return res.value;
|
|
@@ -76,8 +76,11 @@ export function anchorForStep(args) {
|
|
|
76
76
|
if (by === QueryBy.ROLE && value !== undefined) {
|
|
77
77
|
const name = asString(args['name']);
|
|
78
78
|
if (name !== undefined && name.length > 0) {
|
|
79
|
-
//
|
|
80
|
-
|
|
79
|
+
// Provenance lives on the step, not the anchor; the position among namesakes is identity.
|
|
80
|
+
const nth = args['nth'];
|
|
81
|
+
const of = args['of'];
|
|
82
|
+
const among = 'number' === typeof nth && 'number' === typeof of ? { nth, of } : {};
|
|
83
|
+
return { anchor: { kind: AnchorKind.ROLE, role: value, name, ...among }, degraded: false };
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
if (by === QueryBy.COMPONENT) {
|
|
@@ -12,7 +12,8 @@ import { replayFlow } from './flow-replay.js';
|
|
|
12
12
|
import { applyHealChanges, collectProposals } from './heal.js';
|
|
13
13
|
import { healPrecondition } from './heal-precondition.js';
|
|
14
14
|
import { assertSuccess, dynamicTestids, successLabel } from './flow-success.js';
|
|
15
|
-
import { flowErrorMessage
|
|
15
|
+
import { flowErrorMessage } from './flow-replay-run.js';
|
|
16
|
+
import { sessionTarget } from '../../memory/project/session-root.js';
|
|
16
17
|
import { flowsForSession } from './flow-store-for-session.js';
|
|
17
18
|
const HEAL_MESSAGES = {
|
|
18
19
|
NOTHING: 'nothing to heal — every anchor resolved on replay',
|
|
@@ -48,8 +49,9 @@ function toChange(proposal) {
|
|
|
48
49
|
export async function healFlow(deps, args) {
|
|
49
50
|
const name = asString(args['flowName']) ?? '';
|
|
50
51
|
const apply = true === args['apply'];
|
|
51
|
-
const
|
|
52
|
-
const
|
|
52
|
+
const target = sessionTarget(deps, asString(args['sessionId']));
|
|
53
|
+
const { projectId } = target;
|
|
54
|
+
const loaded = await flowsForSession(deps, target).flows.load(name, projectId);
|
|
53
55
|
if (!loaded.ok) {
|
|
54
56
|
return {
|
|
55
57
|
name,
|
|
@@ -163,7 +165,7 @@ export async function healFlow(deps, args) {
|
|
|
163
165
|
}
|
|
164
166
|
// Healing WRITES, so it must land where the load came from — a heal that read the app's flow and
|
|
165
167
|
// wrote the daemon's copy would silently fork the two.
|
|
166
|
-
const written = await flowsForSession(deps,
|
|
168
|
+
const written = await flowsForSession(deps, target).flows.heal(name, proposals.map(toChange), projectId);
|
|
167
169
|
if (!written.ok) {
|
|
168
170
|
return {
|
|
169
171
|
name,
|
|
@@ -31,17 +31,17 @@ export declare const FlakeFileSchema: z.ZodObject<{
|
|
|
31
31
|
fails: number;
|
|
32
32
|
}>>;
|
|
33
33
|
}, "strip", z.ZodTypeAny, {
|
|
34
|
-
version: 1;
|
|
35
34
|
flows: Record<string, {
|
|
36
35
|
runs: number;
|
|
37
36
|
fails: number;
|
|
38
37
|
}>;
|
|
39
|
-
}, {
|
|
40
38
|
version: 1;
|
|
39
|
+
}, {
|
|
41
40
|
flows: Record<string, {
|
|
42
41
|
runs: number;
|
|
43
42
|
fails: number;
|
|
44
43
|
}>;
|
|
44
|
+
version: 1;
|
|
45
45
|
}>;
|
|
46
46
|
export declare function emptyRecord(): FlakeRecord;
|
|
47
47
|
/** Accrue one unchanged-code replay outcome. */
|
|
@@ -33,9 +33,21 @@ export interface RecordedStep {
|
|
|
33
33
|
endPage?: string;
|
|
34
34
|
/** Why the agent took this step: the `intent` it declared on the action. Names the saved flow. */
|
|
35
35
|
intent?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Why a consequence this step PROVED is not in the flow: the flow file could not express it (a
|
|
38
|
+
* session ref such as `e12` means nothing on a later page). Said at stop, so a weaker flow is
|
|
39
|
+
* never saved silently.
|
|
40
|
+
*/
|
|
41
|
+
unkeptExpect?: string;
|
|
36
42
|
}
|
|
37
43
|
interface ActiveRecording {
|
|
38
44
|
cursor: number;
|
|
45
|
+
/**
|
|
46
|
+
* The session (tab) that started it. Only that session's steps join it: one daemon serves every
|
|
47
|
+
* agent and app on the machine, and capturing into every open recording put a click from an
|
|
48
|
+
* unrelated app into somebody else's saved flow (#988). Absent: takes every session's steps.
|
|
49
|
+
*/
|
|
50
|
+
session?: string;
|
|
39
51
|
steps: RecordedStep[];
|
|
40
52
|
/** The route the journey began on. See CompiledProgram.startPath. */
|
|
41
53
|
startPath?: string;
|
|
@@ -91,7 +103,10 @@ export declare const INVOKE_TOOL: string;
|
|
|
91
103
|
export declare const AMBIENT_RECORDING = "__ambient__";
|
|
92
104
|
export declare class RecordingStore {
|
|
93
105
|
#private;
|
|
94
|
-
|
|
106
|
+
private readonly byHarness;
|
|
107
|
+
/** `byHarness`: whether the step being captured was driven by the Harness, which tapes its own. */
|
|
108
|
+
constructor(byHarness?: () => boolean);
|
|
109
|
+
start(name: string, cursor: number, startPath?: string, session?: string): void;
|
|
95
110
|
isRecording(name: string): boolean;
|
|
96
111
|
/**
|
|
97
112
|
* Number of steps captured so far in the named ACTIVE recording (0 if it
|
|
@@ -112,7 +127,7 @@ export declare class RecordingStore {
|
|
|
112
127
|
* Opened lazily on the first step rather than in the constructor: a store that never records
|
|
113
128
|
* anything should not carry an empty tape, and "did anything happen at all" stays answerable.
|
|
114
129
|
*/
|
|
115
|
-
capture(step: RecordedStep, route?: string): void;
|
|
130
|
+
capture(step: RecordedStep, route?: string, session?: string): void;
|
|
116
131
|
/**
|
|
117
132
|
* Add a consequence to the last captured step of every active recording.
|
|
118
133
|
*
|
|
@@ -120,20 +135,31 @@ export declare class RecordingStore {
|
|
|
120
135
|
* reached the recorder — only the act tools called `capture` — so the flow kept the click and lost
|
|
121
136
|
* the proof. What the step already declared is kept; the new check joins it under `allOf`.
|
|
122
137
|
*/
|
|
123
|
-
attachExpect(expect: Predicate): void;
|
|
138
|
+
attachExpect(expect: Predicate, session?: string): void;
|
|
124
139
|
/**
|
|
125
140
|
* A navigation happened. Navigating records no step, so until the next one is captured, a check
|
|
126
141
|
* proved now is about the page the navigation opened, not about the last step — and kept on that
|
|
127
142
|
* step, replay would ask it right after the click, of a page it was never true on.
|
|
128
143
|
*/
|
|
129
144
|
markNavigated(): void;
|
|
145
|
+
/**
|
|
146
|
+
* Take the expectation off the ambient tape's last step, because the verdict on it came back no.
|
|
147
|
+
*
|
|
148
|
+
* A step is captured at dispatch, before anything is judged, so a claim that turned out false is
|
|
149
|
+
* already on the tape as that step's `expect`. Cut into a journey it becomes a flow that is red for
|
|
150
|
+
* ever, on an expectation nobody but the agent's wrong guess made. The failure is not lost: it is
|
|
151
|
+
* filed as a capsule, the artifact built for "this should hold and does not". The ACTION stays,
|
|
152
|
+
* since the steps after it ran on the page it produced. A recording somebody opened deliberately
|
|
153
|
+
* is left as they drove it.
|
|
154
|
+
*/
|
|
155
|
+
unassertLast(): void;
|
|
130
156
|
/**
|
|
131
157
|
* The page the step just captured ended on, once its action settled.
|
|
132
158
|
*
|
|
133
159
|
* Fills only a step that has none, so calling it after an action that captured nothing leaves the
|
|
134
160
|
* previous step's own answer alone.
|
|
135
161
|
*/
|
|
136
|
-
markEnded(page: string | undefined): void;
|
|
162
|
+
markEnded(page: string | undefined, session?: string): void;
|
|
137
163
|
/**
|
|
138
164
|
* Returns the active recording (cursor + steps) and clears it, or undefined if not recording.
|
|
139
165
|
*
|
|
@@ -29,21 +29,31 @@ export const AMBIENT_RECORDING = '__ambient__';
|
|
|
29
29
|
*/
|
|
30
30
|
const AMBIENT_STEP_CAP = 400;
|
|
31
31
|
export class RecordingStore {
|
|
32
|
+
byHarness;
|
|
32
33
|
#active = new Map();
|
|
33
34
|
#compiled = new Map();
|
|
34
35
|
/** A navigation came after the last captured step — see markNavigated. */
|
|
35
36
|
#navigatedSinceStep = false;
|
|
36
|
-
|
|
37
|
+
/** `byHarness`: whether the step being captured was driven by the Harness, which tapes its own. */
|
|
38
|
+
constructor(byHarness = () => false) {
|
|
39
|
+
this.byHarness = byHarness;
|
|
40
|
+
}
|
|
41
|
+
start(name, cursor, startPath, session) {
|
|
37
42
|
const openedOver = new Map();
|
|
38
|
-
for (const [outer, rec] of this.#
|
|
43
|
+
for (const [outer, rec] of this.#targets(session))
|
|
39
44
|
openedOver.set(outer, rec.steps.length);
|
|
40
45
|
this.#active.set(name, {
|
|
41
46
|
cursor,
|
|
42
47
|
steps: [],
|
|
43
48
|
openedOver,
|
|
44
49
|
...(startPath === undefined ? {} : { startPath }),
|
|
50
|
+
...(session === undefined ? {} : { session }),
|
|
45
51
|
});
|
|
46
52
|
}
|
|
53
|
+
/** The open recordings a step from `session` belongs to. See ActiveRecording.session. */
|
|
54
|
+
#targets(session) {
|
|
55
|
+
return [...this.#active].filter(([, rec]) => rec.session === undefined || session === undefined || rec.session === session);
|
|
56
|
+
}
|
|
47
57
|
isRecording(name) {
|
|
48
58
|
return this.#active.has(name);
|
|
49
59
|
}
|
|
@@ -68,7 +78,7 @@ export class RecordingStore {
|
|
|
68
78
|
* Opened lazily on the first step rather than in the constructor: a store that never records
|
|
69
79
|
* anything should not carry an empty tape, and "did anything happen at all" stays answerable.
|
|
70
80
|
*/
|
|
71
|
-
capture(step, route) {
|
|
81
|
+
capture(step, route, session) {
|
|
72
82
|
this.#navigatedSinceStep = false;
|
|
73
83
|
if (!this.#active.has(AMBIENT_RECORDING)) {
|
|
74
84
|
this.#active.set(AMBIENT_RECORDING, {
|
|
@@ -77,7 +87,7 @@ export class RecordingStore {
|
|
|
77
87
|
openedOver: new Map(),
|
|
78
88
|
});
|
|
79
89
|
}
|
|
80
|
-
for (const [name, rec] of this.#
|
|
90
|
+
for (const [name, rec] of this.#targets(session)) {
|
|
81
91
|
// The ambient tape is opened by the system and closed by nobody, so it is the one recording
|
|
82
92
|
// with no human deciding when it has seen enough. Bounded here rather than left to grow for
|
|
83
93
|
// the length of a daemon's life. Appending STOPS at the cap instead of dropping the oldest: a
|
|
@@ -86,6 +96,10 @@ export class RecordingStore {
|
|
|
86
96
|
// opened on purpose is not capped — they said when it starts and they say when it stops.
|
|
87
97
|
if (AMBIENT_RECORDING === name && rec.steps.length >= AMBIENT_STEP_CAP)
|
|
88
98
|
continue;
|
|
99
|
+
// The Harness saves its own recordings. Taping its steps too saved every drive twice: once as
|
|
100
|
+
// the Harness's flow and again, at session end, as a `drive-*` copy with a guessed name.
|
|
101
|
+
if (AMBIENT_RECORDING === name && this.byHarness())
|
|
102
|
+
continue;
|
|
89
103
|
// A step nobody marked ended where the next one began.
|
|
90
104
|
const previous = rec.steps.at(-1);
|
|
91
105
|
if (previous !== undefined && previous.endPage === undefined && step.page !== undefined) {
|
|
@@ -104,10 +118,10 @@ export class RecordingStore {
|
|
|
104
118
|
* reached the recorder — only the act tools called `capture` — so the flow kept the click and lost
|
|
105
119
|
* the proof. What the step already declared is kept; the new check joins it under `allOf`.
|
|
106
120
|
*/
|
|
107
|
-
attachExpect(expect) {
|
|
121
|
+
attachExpect(expect, session) {
|
|
108
122
|
if (this.#navigatedSinceStep)
|
|
109
123
|
return;
|
|
110
|
-
for (const rec of this.#
|
|
124
|
+
for (const [, rec] of this.#targets(session)) {
|
|
111
125
|
const last = rec.steps.at(-1);
|
|
112
126
|
if (last === undefined)
|
|
113
127
|
continue;
|
|
@@ -128,16 +142,34 @@ export class RecordingStore {
|
|
|
128
142
|
markNavigated() {
|
|
129
143
|
this.#navigatedSinceStep = true;
|
|
130
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Take the expectation off the ambient tape's last step, because the verdict on it came back no.
|
|
147
|
+
*
|
|
148
|
+
* A step is captured at dispatch, before anything is judged, so a claim that turned out false is
|
|
149
|
+
* already on the tape as that step's `expect`. Cut into a journey it becomes a flow that is red for
|
|
150
|
+
* ever, on an expectation nobody but the agent's wrong guess made. The failure is not lost: it is
|
|
151
|
+
* filed as a capsule, the artifact built for "this should hold and does not". The ACTION stays,
|
|
152
|
+
* since the steps after it ran on the page it produced. A recording somebody opened deliberately
|
|
153
|
+
* is left as they drove it.
|
|
154
|
+
*/
|
|
155
|
+
unassertLast() {
|
|
156
|
+
const tape = this.#active.get(AMBIENT_RECORDING);
|
|
157
|
+
const last = tape?.steps.at(-1);
|
|
158
|
+
if (tape === undefined || last === undefined || last.expect === undefined)
|
|
159
|
+
return;
|
|
160
|
+
const { expect: _dropped, ...kept } = last;
|
|
161
|
+
tape.steps[tape.steps.length - 1] = kept;
|
|
162
|
+
}
|
|
131
163
|
/**
|
|
132
164
|
* The page the step just captured ended on, once its action settled.
|
|
133
165
|
*
|
|
134
166
|
* Fills only a step that has none, so calling it after an action that captured nothing leaves the
|
|
135
167
|
* previous step's own answer alone.
|
|
136
168
|
*/
|
|
137
|
-
markEnded(page) {
|
|
169
|
+
markEnded(page, session) {
|
|
138
170
|
if (page === undefined)
|
|
139
171
|
return;
|
|
140
|
-
for (const rec of this.#
|
|
172
|
+
for (const [, rec] of this.#targets(session)) {
|
|
141
173
|
const last = rec.steps.at(-1);
|
|
142
174
|
if (last !== undefined && last.endPage === undefined)
|
|
143
175
|
last.endPage = page;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReticleCommand } from '@reticlehq/core';
|
|
2
|
+
import type { FlowReplayResult } from '@reticlehq/core';
|
|
3
|
+
import type { ToolDeps } from '../../surface/tools/tool-kit.js';
|
|
4
|
+
/** The session a HUD replay reports to. Resolved per message: a replay usually reloads the page. */
|
|
5
|
+
interface ProgressTarget {
|
|
6
|
+
pushNarration(text: string): void;
|
|
7
|
+
pushView(name: typeof ReticleCommand.FLOW_PROGRESS, args: object): void;
|
|
8
|
+
}
|
|
9
|
+
/** A human-facing one-liner for a panel replay verdict — ✓ passed / ⚠ drifted / ✗ errored / ? unverifiable. */
|
|
10
|
+
export declare function replayVerdictLine(result: FlowReplayResult): string;
|
|
11
|
+
/**
|
|
12
|
+
* Replay a saved flow because a person pressed ▶ in the HUD, with no agent involved.
|
|
13
|
+
*
|
|
14
|
+
* The chip they pressed shows the replay as it runs: a progress push after every step, and the
|
|
15
|
+
* verdict at the end, sent to whichever session holds the tab at that moment (a replay reloads the
|
|
16
|
+
* page, and the session object from before the reload no longer has a socket). The verdict also
|
|
17
|
+
* lands in the Agent Log as a line, where the "See the logs" link on the chip leads.
|
|
18
|
+
*/
|
|
19
|
+
export declare function replayFromHud(deps: ToolDeps, resolve: (sessionId: string) => ProgressTarget | undefined, sessionId: string, flowName: string): Promise<void>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { FlowProgressStatus, ReplayStatus, ReticleCommand } from '@reticlehq/core';
|
|
2
|
+
import { replayAndLearn } from './flow-learning.js';
|
|
3
|
+
/** A human-facing one-liner for a panel replay verdict — ✓ passed / ⚠ drifted / ✗ errored / ? unverifiable. */
|
|
4
|
+
export function replayVerdictLine(result) {
|
|
5
|
+
if (result.status === ReplayStatus.OK)
|
|
6
|
+
return `✓ "${result.name}" passed`;
|
|
7
|
+
if (result.status === ReplayStatus.DRIFT)
|
|
8
|
+
return `⚠ "${result.name}" drifted — a step no longer matches`;
|
|
9
|
+
if (result.status === ReplayStatus.UNVERIFIABLE)
|
|
10
|
+
return `? "${result.name}" unverifiable — ${result.unverifiable?.reason ?? 'could not be graded'}`;
|
|
11
|
+
return `✗ "${result.name}" failed — ${result.error?.message ?? 'could not replay'}`;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Replay a saved flow because a person pressed ▶ in the HUD, with no agent involved.
|
|
15
|
+
*
|
|
16
|
+
* The chip they pressed shows the replay as it runs: a progress push after every step, and the
|
|
17
|
+
* verdict at the end, sent to whichever session holds the tab at that moment (a replay reloads the
|
|
18
|
+
* page, and the session object from before the reload no longer has a socket). The verdict also
|
|
19
|
+
* lands in the Agent Log as a line, where the "See the logs" link on the chip leads.
|
|
20
|
+
*/
|
|
21
|
+
export async function replayFromHud(deps, resolve, sessionId, flowName) {
|
|
22
|
+
const progress = (done, total, status) => resolve(sessionId)?.pushView(ReticleCommand.FLOW_PROGRESS, {
|
|
23
|
+
name: flowName,
|
|
24
|
+
done,
|
|
25
|
+
total,
|
|
26
|
+
status,
|
|
27
|
+
});
|
|
28
|
+
resolve(sessionId)?.pushNarration(`▶ Replaying "${flowName}"…`);
|
|
29
|
+
progress(0, 0, FlowProgressStatus.PLAYING);
|
|
30
|
+
let total = 0;
|
|
31
|
+
let done = 0;
|
|
32
|
+
try {
|
|
33
|
+
const result = await replayAndLearn(deps, {
|
|
34
|
+
flowName,
|
|
35
|
+
sessionId,
|
|
36
|
+
onStep: (stepsDone, stepsTotal) => {
|
|
37
|
+
done = stepsDone;
|
|
38
|
+
total = stepsTotal;
|
|
39
|
+
progress(done, total, FlowProgressStatus.PLAYING);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
const passed = ReplayStatus.OK === result.status;
|
|
43
|
+
progress(passed ? total : done, total, passed ? FlowProgressStatus.PASSED : FlowProgressStatus.FAILED);
|
|
44
|
+
resolve(sessionId)?.pushNarration(replayVerdictLine(result));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
progress(done, total, FlowProgressStatus.FAILED);
|
|
49
|
+
resolve(sessionId)?.pushNarration(`✗ Replay "${flowName}" failed — ${message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -35,6 +35,8 @@ export declare function replayActionArgs(value: unknown, confirmDangerous?: bool
|
|
|
35
35
|
* a hash-routed app makes meaningless in the document location — see routeOfEvent.
|
|
36
36
|
*/
|
|
37
37
|
export declare function pathOf(url: string | undefined): string | undefined;
|
|
38
|
+
/** What a saved flow could not keep, and the fix. See RecordedStep.unkeptExpect. */
|
|
39
|
+
export declare const UNKEPT_EXPECT: string;
|
|
38
40
|
/**
|
|
39
41
|
* Capture an act into every in-flight recording, or do nothing when none is running.
|
|
40
42
|
*
|
|
@@ -44,21 +46,23 @@ export declare function pathOf(url: string | undefined): string | undefined;
|
|
|
44
46
|
*/
|
|
45
47
|
export declare function captureAct(recordings: {
|
|
46
48
|
active: () => string[];
|
|
47
|
-
capture: (step: RecordedStep, route?: string) => void;
|
|
49
|
+
capture: (step: RecordedStep, route?: string, session?: string) => void;
|
|
48
50
|
}, args: Record<string, unknown>, res: unknown,
|
|
49
51
|
/** Where this step ran, so the ambient tape can be cut into journeys. See RecordedStep.route. */
|
|
50
|
-
route?: string
|
|
52
|
+
route?: string,
|
|
53
|
+
/** The session that acted: only recordings it started take the step. */
|
|
54
|
+
session?: string): void;
|
|
51
55
|
/**
|
|
52
56
|
* Capture an act_and_wait once its verdict is known, keeping `until` as the step's expectation only
|
|
53
57
|
* on a yes. Recorded before the verdict, a step kept an `until` that came back `no` or `no-fault` —
|
|
54
58
|
* a regression test asserting something never once observed to hold because of the action. The
|
|
55
59
|
* action itself is always recorded: it happened, and replay needs it to reach the next step.
|
|
56
60
|
*/
|
|
57
|
-
export declare function captureVerdictedAct(recordings: Parameters<typeof captureAct>[0], args: Record<string, unknown>, res: unknown, route: string | undefined, verified: string | undefined): void;
|
|
61
|
+
export declare function captureVerdictedAct(recordings: Parameters<typeof captureAct>[0], args: Record<string, unknown>, res: unknown, route: string | undefined, verified: string | undefined, session?: string): void;
|
|
58
62
|
/** Fold a PASSING standalone assertion into the step it proved. See RecordingStore.attachExpect. */
|
|
59
63
|
export declare function captureAssertion(recordings: {
|
|
60
|
-
attachExpect: (expect: Predicate) => void;
|
|
61
|
-
}, raw: unknown): void;
|
|
64
|
+
attachExpect: (expect: Predicate, session?: string) => void;
|
|
65
|
+
}, raw: unknown, session?: string): void;
|
|
62
66
|
/**
|
|
63
67
|
* Compile a single reticle_act invocation into a normalized RecordedStep using the action result. Anchor
|
|
64
68
|
* priority mirrors synthesizeAnchor: a data-testid is the gold standard; failing that, the element's
|