@reticlehq/server 3.5.0 → 3.7.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 +123 -116
- package/dist/chromium-launch-options.d.ts +3 -1
- package/dist/chromium-launch-options.js +2 -1
- package/dist/command/cli/adhoc-suite.d.ts +14 -0
- package/dist/command/cli/adhoc-suite.js +42 -7
- package/dist/command/cli/adhoc-verdict.d.ts +19 -2
- package/dist/command/cli/adhoc-verdict.js +76 -9
- package/dist/command/cli/answers/gate-exit.d.ts +23 -0
- package/dist/command/cli/answers/gate-exit.js +45 -0
- package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
- package/dist/command/cli/answers/gate-hook-message.js +3 -1
- 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/auth/device-flow.d.ts +13 -2
- package/dist/command/cli/auth/device-flow.js +17 -0
- package/dist/command/cli/cli-doctor.js +14 -0
- package/dist/command/cli/cli-flow-commands.d.ts +0 -11
- package/dist/command/cli/cli-flow-commands.js +31 -112
- package/dist/command/cli/cli-kill.js +3 -2
- package/dist/command/cli/cli-parse-grammar.d.ts +24 -0
- package/dist/command/cli/cli-parse-grammar.js +75 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -0
- package/dist/command/cli/cli-parse-verify.js +18 -0
- package/dist/command/cli/cli-parse.d.ts +19 -28
- package/dist/command/cli/cli-parse.js +82 -182
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +61 -9
- package/dist/command/cli/cli-usage.d.ts +33 -0
- package/dist/command/cli/cli-usage.js +287 -0
- package/dist/command/cli/cli-verify.d.ts +35 -5
- package/dist/command/cli/cli-verify.js +114 -35
- package/dist/command/cli/cloud-cli.d.ts +13 -0
- package/dist/command/cli/cloud-cli.js +192 -341
- package/dist/command/cli/cloud-kit.d.ts +12 -0
- package/dist/command/cli/cloud-kit.js +41 -2
- package/dist/command/cli/cloud-link.d.ts +57 -0
- package/dist/command/cli/cloud-link.js +308 -0
- package/dist/command/cli/cloud-login.d.ts +22 -0
- package/dist/command/cli/cloud-login.js +96 -8
- package/dist/command/cli/context-command.d.ts +1 -0
- package/dist/command/cli/context-command.js +33 -0
- package/dist/command/cli/daemon-start-options.d.ts +3 -0
- package/dist/command/cli/daemon-start-options.js +24 -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/doctor/doctor-coverage-line.d.ts +2 -0
- package/dist/command/cli/doctor/doctor-coverage-line.js +47 -0
- package/dist/command/cli/doctor/doctor-project-line.d.ts +0 -12
- package/dist/command/cli/doctor/doctor-project-line.js +5 -4
- package/dist/command/cli/doctor/doctor-rows.d.ts +6 -0
- package/dist/command/cli/doctor/doctor-rows.js +6 -0
- package/dist/command/cli/doctor/doctor-signing-line.d.ts +38 -0
- package/dist/command/cli/doctor/doctor-signing-line.js +114 -0
- 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/known-regression.d.ts +13 -0
- package/dist/command/cli/known-regression.js +34 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +12 -0
- package/dist/command/cli/launch/sdk-version-match.js +25 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +15 -3
- package/dist/command/cli/mcp-command.d.ts +6 -0
- package/dist/command/cli/mcp-command.js +13 -5
- package/dist/command/cli/ports/port-holder.d.ts +10 -0
- package/dist/command/cli/ports/port-holder.js +25 -1
- package/dist/command/cli/project-name.d.ts +29 -1
- package/dist/command/cli/project-name.js +107 -2
- package/dist/command/cli/setup-mcp-cli.d.ts +17 -1
- package/dist/command/cli/setup-mcp-cli.js +35 -12
- package/dist/command/cli/status/status-lines.d.ts +1 -0
- package/dist/command/cli/status/status-lines.js +47 -2
- package/dist/command/cli/status/workspace-links.d.ts +10 -0
- package/dist/command/cli/status/workspace-links.js +29 -0
- package/dist/command/cli/try-command.d.ts +96 -0
- package/dist/command/cli/try-command.js +362 -0
- package/dist/command/cli/tutorial.d.ts +0 -100
- package/dist/command/cli/tutorial.js +0 -181
- package/dist/command/cli/verify-results.d.ts +19 -0
- package/dist/command/cli/verify-results.js +29 -0
- package/dist/command/cli-onboarding.d.ts +0 -11
- package/dist/command/cli-onboarding.js +0 -15
- package/dist/command/cli.d.ts +20 -0
- package/dist/command/cli.js +130 -104
- 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-resilience.js +4 -4
- package/dist/command/daemon/daemon-resolve.d.ts +23 -1
- package/dist/command/daemon/daemon-resolve.js +79 -9
- package/dist/command/setup/agent-writer.d.ts +1 -1
- package/dist/command/setup/agent-writer.js +3 -2
- package/dist/command/setup/bringup/bridge-port.d.ts +3 -1
- package/dist/command/setup/bringup/bridge-port.js +13 -3
- 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-log-cap.js +2 -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/bringup/ensure-daemon.js +9 -2
- package/dist/command/setup/init/agent-restart.d.ts +23 -0
- package/dist/command/setup/init/agent-restart.js +28 -0
- package/dist/command/setup/init/init-port.d.ts +14 -1
- package/dist/command/setup/init/init-port.js +32 -2
- package/dist/command/setup/init/init-runtime.d.ts +22 -2
- package/dist/command/setup/init/init-runtime.js +114 -38
- package/dist/command/setup/node-effects.d.ts +2 -0
- package/dist/command/setup/node-effects.js +38 -9
- package/dist/command/setup/probe/existing-app.d.ts +12 -0
- package/dist/command/setup/probe/existing-app.js +14 -0
- package/dist/command/setup/remaining-steps.js +2 -1
- package/dist/command/setup/run-setup.d.ts +34 -9
- package/dist/command/setup/run-setup.js +65 -51
- package/dist/command/setup/setup-command.d.ts +25 -1
- package/dist/command/setup/setup-command.js +59 -4
- package/dist/command/setup/setup-mcp.js +25 -2
- package/dist/command/setup/tauri-signing.d.ts +19 -0
- package/dist/command/setup/tauri-signing.js +79 -0
- package/dist/command/setup/terminal/listeners.d.ts +10 -1
- package/dist/command/setup/terminal/listeners.js +37 -6
- package/dist/command/update/update-nudge.d.ts +9 -0
- package/dist/command/update/update-nudge.js +1 -1
- package/dist/command/update/updater.js +11 -15
- package/dist/command/version/version-nudge.js +3 -0
- package/dist/command/version/version-skew.d.ts +20 -1
- package/dist/command/version/version-skew.js +49 -4
- package/dist/features/crawl/crawl-tools.js +4 -0
- package/dist/features/crawl/crawl.d.ts +6 -0
- package/dist/features/crawl/crawl.js +23 -2
- package/dist/features/exhaust/ledger.d.ts +21 -14
- 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 +61 -1
- package/dist/features/harness/drive-report.js +394 -26
- package/dist/features/harness/drive-runs.d.ts +120 -0
- package/dist/features/harness/drive-runs.js +289 -0
- package/dist/features/harness/drivers.d.ts +10 -14
- package/dist/features/harness/drivers.js +10 -14
- package/dist/features/harness/explore/console-pages.d.ts +15 -0
- package/dist/features/harness/explore/console-pages.js +146 -0
- package/dist/features/harness/explore/disparity.d.ts +19 -0
- package/dist/features/harness/explore/disparity.js +71 -0
- package/dist/features/harness/explore/fake-app.d.ts +16 -0
- package/dist/features/harness/explore/fake-app.js +49 -0
- package/dist/features/harness/explore/goal-search.d.ts +26 -0
- package/dist/features/harness/explore/goal-search.js +144 -0
- package/dist/features/harness/explore/page-tree.d.ts +49 -0
- package/dist/features/harness/explore/page-tree.js +148 -0
- package/dist/features/harness/explore/site-map.d.ts +34 -0
- package/dist/features/harness/explore/site-map.js +150 -0
- 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 +82 -0
- package/dist/features/harness/goals.js +330 -0
- package/dist/features/harness/harness.d.ts +51 -1
- package/dist/features/harness/harness.js +207 -6
- 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 +127 -0
- package/dist/features/harness/platform/local-apps.js +280 -0
- package/dist/features/harness/platform/persona-credentials.d.ts +20 -0
- package/dist/features/harness/platform/persona-credentials.js +69 -0
- package/dist/features/harness/platform/platform-drives.d.ts +61 -0
- package/dist/features/harness/platform/platform-drives.js +114 -0
- package/dist/features/harness/platform/refusal.d.ts +52 -0
- package/dist/features/harness/platform/refusal.js +71 -0
- package/dist/features/harness/platform/remote-drive.d.ts +186 -0
- package/dist/features/harness/platform/remote-drive.js +378 -0
- package/dist/features/harness/platform/script.d.ts +94 -0
- package/dist/features/harness/platform/script.js +260 -0
- package/dist/features/harness/platform/server-driver.d.ts +54 -0
- package/dist/features/harness/platform/server-driver.js +182 -0
- package/dist/features/harness/platform-config.d.ts +41 -1
- package/dist/features/harness/platform-config.js +106 -9
- package/dist/features/harness/route-guess.d.ts +13 -0
- package/dist/features/harness/route-guess.js +49 -0
- 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 +70 -0
- package/dist/features/harness/script/script-run.js +606 -0
- package/dist/features/license/license.d.ts +2 -2
- package/dist/features/visual/step-shots.d.ts +31 -0
- package/dist/features/visual/step-shots.js +231 -0
- package/dist/features/visual/visual-tools.d.ts +10 -1
- package/dist/features/visual/visual-tools.js +62 -17
- package/dist/hooks/client-directory.d.ts +18 -0
- package/dist/hooks/client-directory.js +57 -0
- package/dist/hooks/coding-agents.d.ts +34 -0
- package/dist/hooks/coding-agents.js +117 -0
- package/dist/hooks/driven-by.d.ts +23 -0
- package/dist/hooks/driven-by.js +56 -0
- package/dist/hooks/hook-bus.d.ts +4 -0
- package/dist/hooks/hook-bus.js +18 -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 +2 -0
- package/dist/index.js +80 -243
- package/dist/judgement/capsule/capsule-store.d.ts +2 -2
- package/dist/judgement/capsule/causal-summary.d.ts +15 -1
- package/dist/judgement/capsule/causal-summary.js +33 -0
- package/dist/judgement/runs/artifact/build-verification-run.d.ts +1 -1
- package/dist/judgement/runs/artifact/build-verification-run.js +25 -3
- package/dist/judgement/runs/artifact/gap-summary.d.ts +0 -18
- package/dist/judgement/runs/artifact/gap-summary.js +12 -3
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +37 -11
- package/dist/judgement/runs/artifact/run-store.d.ts +14 -2
- package/dist/judgement/runs/artifact/run-store.js +195 -3
- package/dist/judgement/runs/context-tools.js +59 -1
- package/dist/judgement/runs/default-run-id.d.ts +3 -0
- package/dist/judgement/runs/default-run-id.js +16 -0
- package/dist/judgement/runs/drive-run.d.ts +71 -1
- package/dist/judgement/runs/drive-run.js +317 -5
- package/dist/judgement/runs/repair-prompt.js +1 -1
- package/dist/judgement/runs/resume.d.ts +44 -0
- package/dist/judgement/runs/resume.js +137 -0
- package/dist/judgement/runs/reticle-runner.js +13 -3
- package/dist/judgement/runs/runner-port.d.ts +5 -1
- package/dist/judgement/runs/runner-port.js +8 -2
- package/dist/judgement/runs/verification-sync.d.ts +5 -2
- package/dist/judgement/runs/verification-sync.js +40 -18
- 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 +36 -1
- package/dist/language/flows/fields/flow-secret-field.d.ts +12 -0
- package/dist/language/flows/fields/flow-secret-field.js +25 -2
- package/dist/language/flows/flow-anchor.d.ts +61 -10
- package/dist/language/flows/flow-anchor.js +98 -6
- 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-intent.d.ts +1 -0
- package/dist/language/flows/flow-journey.d.ts +6 -0
- package/dist/language/flows/flow-journey.js +20 -0
- package/dist/language/flows/flow-learning.d.ts +4 -0
- package/dist/language/flows/flow-learning.js +45 -14
- 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 +115 -110
- package/dist/language/flows/flow-replay-types.d.ts +1 -1
- package/dist/language/flows/flow-replay.d.ts +40 -1
- package/dist/language/flows/flow-replay.js +110 -22
- package/dist/language/flows/flow-result.d.ts +22 -1
- package/dist/language/flows/flow-result.js +47 -2
- package/dist/language/flows/flow-scope.js +5 -1
- package/dist/language/flows/flow-step-runners.d.ts +22 -2
- package/dist/language/flows/flow-step-runners.js +247 -47
- package/dist/language/flows/flow-store-for-session.d.ts +2 -2
- package/dist/language/flows/flow-store-for-session.js +7 -3
- package/dist/language/flows/flow-tools.d.ts +10 -0
- package/dist/language/flows/flow-tools.js +112 -89
- package/dist/language/flows/flows.d.ts +16 -1
- package/dist/language/flows/flows.js +74 -12
- package/dist/language/flows/heal-run.js +6 -4
- package/dist/language/flows/narrow-write.d.ts +7 -0
- package/dist/language/flows/narrow-write.js +36 -1
- package/dist/language/flows/recording/flake.d.ts +2 -2
- package/dist/language/flows/recording/recording-backtrack.js +14 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +58 -4
- package/dist/language/flows/recording/tape/recordings.js +95 -13
- 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 +56 -15
- package/dist/language/flows/single-replay.d.ts +7 -0
- package/dist/language/flows/single-replay.js +26 -0
- 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.d.ts +1 -0
- package/dist/language/flows/verify-change-tools.js +202 -191
- package/dist/launch-chromium.d.ts +27 -5
- package/dist/launch-chromium.js +38 -8
- package/dist/memory/cloud/cloud-config.d.ts +22 -1
- package/dist/memory/cloud/cloud-config.js +34 -5
- package/dist/memory/cloud/cloud-sync.d.ts +17 -14
- package/dist/memory/cloud/cloud-sync.js +15 -4
- package/dist/memory/cloud/harness-config.d.ts +75 -1
- package/dist/memory/cloud/harness-config.js +115 -24
- 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 +161 -0
- package/dist/memory/cloud/memory-scope.js +227 -0
- package/dist/memory/cloud/sync-cycle.d.ts +44 -7
- package/dist/memory/cloud/sync-cycle.js +129 -15
- package/dist/memory/cloud/sync-daemon.d.ts +11 -0
- package/dist/memory/cloud/sync-daemon.js +121 -9
- package/dist/memory/cloud/sync-disk.d.ts +1 -1
- package/dist/memory/cloud/sync-disk.js +31 -10
- package/dist/memory/cloud/sync-screenshots.d.ts +32 -0
- package/dist/memory/cloud/sync-screenshots.js +105 -0
- package/dist/memory/cloud/unsynced-roots.d.ts +47 -0
- package/dist/memory/cloud/unsynced-roots.js +166 -0
- package/dist/memory/impact/account-watch.d.ts +34 -0
- package/dist/memory/impact/account-watch.js +59 -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 +17 -4
- package/dist/memory/impact/impact-store.js +169 -63
- package/dist/memory/intent/inline-intent.d.ts +1 -0
- package/dist/memory/intent/intent-shard-store.js +16 -4
- package/dist/memory/intent/intent-shard.d.ts +14 -2
- package/dist/memory/intent/intent-store.d.ts +1 -0
- package/dist/memory/intent/intent-tools.js +122 -4
- package/dist/memory/intent/open-intents.d.ts +6 -0
- package/dist/memory/intent/open-intents.js +10 -1
- package/dist/memory/intent/settled-words.d.ts +24 -0
- package/dist/memory/intent/settled-words.js +130 -0
- package/dist/memory/journal/drive-flow.d.ts +16 -0
- package/dist/memory/journal/drive-flow.js +34 -2
- package/dist/memory/journal/drive-run-flush.d.ts +2 -0
- package/dist/memory/journal/drive-run-flush.js +20 -5
- package/dist/memory/journal/envelope.d.ts +8 -8
- package/dist/memory/journal/journal-recorder.js +52 -3
- package/dist/memory/journal/on-disk/retention.d.ts +8 -0
- package/dist/memory/journal/on-disk/retention.js +32 -2
- package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
- package/dist/memory/journal/on-disk/workspace-tiers.js +9 -4
- package/dist/memory/journal/session-end.d.ts +15 -1
- package/dist/memory/journal/session-end.js +42 -9
- package/dist/memory/project/artifact-root-resolver.d.ts +26 -1
- package/dist/memory/project/artifact-root-resolver.js +125 -16
- package/dist/memory/project/artifact-root.d.ts +46 -8
- package/dist/memory/project/artifact-root.js +89 -10
- package/dist/memory/project/dir/reticle-dir.d.ts +18 -2
- package/dist/memory/project/dir/reticle-dir.js +26 -3
- package/dist/memory/project/link-directory.d.ts +15 -0
- package/dist/memory/project/link-directory.js +44 -0
- package/dist/memory/project/project-tools.js +22 -3
- package/dist/memory/project/root-claims.d.ts +6 -0
- package/dist/memory/project/root-claims.js +39 -0
- package/dist/memory/project/session-root.d.ts +26 -1
- package/dist/memory/project/session-root.js +49 -5
- package/dist/memory/project/sync-status.d.ts +85 -0
- package/dist/memory/project/sync-status.js +224 -0
- package/dist/memory/recall/memory-tools.js +47 -2
- package/dist/memory/recall/platform-memory.d.ts +34 -0
- package/dist/memory/recall/platform-memory.js +139 -0
- package/dist/memory/recall/project-memory.d.ts +19 -0
- package/dist/memory/recall/project-memory.js +80 -13
- package/dist/portal/bridge/bridge.d.ts +9 -3
- package/dist/portal/bridge/bridge.js +82 -22
- package/dist/portal/bridge/harness-request.d.ts +23 -0
- package/dist/portal/bridge/harness-request.js +27 -0
- package/dist/portal/bridge/pairing-token.d.ts +9 -0
- package/dist/portal/bridge/pairing-token.js +17 -0
- package/dist/portal/bridge/wrong-path.d.ts +17 -0
- package/dist/portal/bridge/wrong-path.js +35 -0
- package/dist/portal/input/network-mock-tools.js +11 -2
- package/dist/portal/input/real-input.d.ts +83 -10
- package/dist/portal/input/real-input.js +218 -30
- package/dist/portal/input/viewport-tools.js +3 -3
- package/dist/portal/pool/browser-pool.d.ts +70 -9
- package/dist/portal/pool/browser-pool.js +190 -37
- package/dist/portal/pool/context-permissions.d.ts +33 -0
- package/dist/portal/pool/context-permissions.js +33 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +23 -0
- package/dist/portal/pool/lease-marker-stamp.js +49 -0
- package/dist/portal/pool/playwright-launcher.d.ts +35 -0
- package/dist/portal/pool/playwright-launcher.js +64 -3
- package/dist/portal/pool/pool-contract.d.ts +28 -5
- package/dist/portal/session/agent-link.d.ts +8 -0
- package/dist/portal/session/agent-link.js +17 -0
- package/dist/portal/session/command-payload.d.ts +8 -0
- package/dist/portal/session/command-payload.js +24 -0
- package/dist/portal/session/daemon-answers.d.ts +3 -3
- package/dist/portal/session/daemon-answers.js +4 -2
- package/dist/portal/session/dev-server/dev-command.d.ts +21 -0
- package/dist/portal/session/dev-server/dev-command.js +87 -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/observed-state.d.ts +16 -18
- package/dist/portal/session/facts/observed-state.js +46 -3
- 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/live-control.d.ts +9 -2
- package/dist/portal/session/human/live-control.js +12 -3
- 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 +107 -13
- package/dist/portal/session/narration-args.d.ts +5 -0
- package/dist/portal/session/narration-args.js +17 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +8 -1
- package/dist/portal/session/no-session-diagnosis.js +46 -9
- package/dist/portal/session/no-session-next-action.d.ts +31 -0
- package/dist/portal/session/no-session-next-action.js +159 -60
- package/dist/portal/session/no-session-watch.d.ts +14 -1
- package/dist/portal/session/no-session-watch.js +227 -17
- package/dist/portal/session/presence/session-lease.js +1 -1
- package/dist/portal/session/presence/session-mirror.js +3 -0
- package/dist/portal/session/recorded-gaps.d.ts +97 -0
- package/dist/portal/session/recorded-gaps.js +251 -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 +18 -0
- package/dist/portal/session/session-health.js +9 -1
- package/dist/portal/session/session-info.d.ts +8 -1
- package/dist/portal/session/session-info.js +8 -1
- package/dist/portal/session/session-manager.d.ts +39 -0
- package/dist/portal/session/session-manager.js +98 -11
- package/dist/portal/session/session-reaper.js +4 -1
- package/dist/portal/session/session-successor.d.ts +13 -0
- package/dist/portal/session/session-successor.js +1 -1
- package/dist/portal/session/session-tools.js +26 -3
- package/dist/portal/session/session.d.ts +22 -30
- package/dist/portal/session/session.js +81 -79
- package/dist/portal/session/timing/command-timeout.js +25 -1
- package/dist/portal/session/timing/wall-clock.d.ts +1 -1
- package/dist/portal/session/timing/wall-clock.js +1 -1
- package/dist/portal/session/zero-install-first-call.d.ts +31 -0
- package/dist/portal/session/zero-install-first-call.js +88 -0
- package/dist/start-options.d.ts +12 -0
- package/dist/start-wiring.d.ts +63 -0
- package/dist/start-wiring.js +169 -0
- package/dist/status-payload.d.ts +23 -1
- package/dist/status-payload.js +36 -1
- package/dist/surface/http-server.js +18 -2
- package/dist/surface/mcp/attach-memory.d.ts +1 -0
- package/dist/surface/mcp/attach-memory.js +12 -1
- package/dist/surface/mcp/mcp-proxy.d.ts +9 -0
- package/dist/surface/mcp/mcp-proxy.js +65 -13
- package/dist/surface/mcp/mcp.d.ts +34 -6
- package/dist/surface/mcp/mcp.js +61 -17
- package/dist/surface/mcp/proxy/drive-calls.d.ts +10 -0
- package/dist/surface/mcp/proxy/drive-calls.js +27 -0
- package/dist/surface/mcp/proxy/mcp-post-transport.js +5 -0
- package/dist/surface/mcp/server-instructions.d.ts +4 -2
- package/dist/surface/mcp/server-instructions.js +32 -14
- package/dist/surface/mcp/shared-params.js +2 -2
- package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
- package/dist/surface/mcp/surface-vocabulary.js +1 -0
- package/dist/surface/tools/account-tool.d.ts +22 -0
- package/dist/surface/tools/account-tool.js +157 -0
- package/dist/surface/tools/act/act-danger.d.ts +11 -0
- package/dist/surface/tools/act/act-danger.js +38 -1
- package/dist/surface/tools/act/act-observation.d.ts +33 -0
- package/dist/surface/tools/act/act-observation.js +57 -0
- package/dist/surface/tools/act/act-preflight.js +25 -2
- package/dist/surface/tools/act/act-target.js +8 -3
- 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/resolve-target.d.ts +1 -1
- package/dist/surface/tools/act/resolve-target.js +18 -3
- package/dist/surface/tools/act/screen-request.d.ts +34 -0
- package/dist/surface/tools/act/screen-request.js +113 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +8 -0
- package/dist/surface/tools/act/sequence-grade.js +13 -2
- package/dist/surface/tools/act/settle-in-flight.js +25 -3
- package/dist/surface/tools/act-sequence-tool.js +138 -29
- package/dist/surface/tools/act-tools.d.ts +0 -9
- package/dist/surface/tools/act-tools.js +71 -41
- package/dist/surface/tools/agent/app-notes-learning.d.ts +7 -0
- package/dist/surface/tools/agent/app-notes-learning.js +80 -0
- package/dist/surface/tools/agent/app-notes.d.ts +4 -0
- package/dist/surface/tools/agent/app-notes.js +48 -0
- package/dist/surface/tools/agent/build-staleness.d.ts +7 -0
- package/dist/surface/tools/agent/build-staleness.js +62 -0
- package/dist/surface/tools/agent/compact-envelope.d.ts +80 -0
- package/dist/surface/tools/agent/compact-envelope.js +374 -0
- package/dist/surface/tools/agent/for-the-agent.d.ts +12 -0
- package/dist/surface/tools/agent/for-the-agent.js +44 -0
- package/dist/surface/tools/agent/notices.d.ts +50 -0
- package/dist/surface/tools/agent/notices.js +152 -0
- package/dist/surface/tools/agent/reteach.d.ts +11 -0
- package/dist/surface/tools/agent/reteach.js +50 -0
- package/dist/surface/tools/args/alias-args.d.ts +5 -0
- package/dist/surface/tools/args/alias-args.js +10 -0
- package/dist/surface/tools/args/confirm-dangerous-arg.d.ts +6 -0
- package/dist/surface/tools/args/confirm-dangerous-arg.js +6 -0
- package/dist/surface/tools/assert/assert-source.d.ts +15 -0
- package/dist/surface/tools/assert/assert-source.js +40 -0
- package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +42 -17
- package/dist/surface/tools/browser-tools.js +51 -2
- package/dist/surface/tools/chat-drives.d.ts +101 -0
- package/dist/surface/tools/chat-drives.js +409 -0
- package/dist/surface/tools/coverage-tools.d.ts +2 -10
- package/dist/surface/tools/coverage-tools.js +2 -53
- package/dist/surface/tools/dynamic-tools.js +29 -1
- package/dist/surface/tools/empty-session-list.d.ts +51 -0
- package/dist/surface/tools/empty-session-list.js +120 -0
- package/dist/surface/tools/error-recovery.d.ts +14 -0
- package/dist/surface/tools/error-recovery.js +105 -8
- package/dist/surface/tools/explore-tools.d.ts +31 -1
- package/dist/surface/tools/explore-tools.js +438 -66
- package/dist/surface/tools/feedback-tools.d.ts +2 -3
- package/dist/surface/tools/feedback-tools.js +18 -3
- package/dist/surface/tools/gap-novelty.d.ts +3 -1
- package/dist/surface/tools/gap-novelty.js +8 -1
- package/dist/surface/tools/harness-explore.d.ts +118 -57
- package/dist/surface/tools/harness-explore.js +344 -292
- package/dist/surface/tools/harness-gate.d.ts +88 -0
- package/dist/surface/tools/harness-gate.js +235 -0
- package/dist/surface/tools/harness-hunt.d.ts +43 -0
- package/dist/surface/tools/harness-hunt.js +215 -0
- package/dist/surface/tools/harness-plan.d.ts +17 -0
- package/dist/surface/tools/harness-plan.js +38 -0
- package/dist/surface/tools/harness-script.d.ts +30 -0
- package/dist/surface/tools/harness-script.js +560 -0
- package/dist/surface/tools/harness-tab-guard.d.ts +5 -0
- package/dist/surface/tools/harness-tab-guard.js +27 -0
- package/dist/surface/tools/harness-toolset.d.ts +22 -1
- package/dist/surface/tools/harness-toolset.js +101 -7
- package/dist/surface/tools/human-notes.d.ts +16 -0
- package/dist/surface/tools/human-notes.js +27 -0
- package/dist/surface/tools/invoke-tool.d.ts +5 -2
- package/dist/surface/tools/invoke-tool.js +263 -85
- package/dist/surface/tools/lease-hint.js +26 -5
- package/dist/surface/tools/lease-permissions.d.ts +33 -0
- package/dist/surface/tools/lease-permissions.js +73 -0
- package/dist/surface/tools/lease-readiness.d.ts +38 -0
- package/dist/surface/tools/lease-readiness.js +57 -0
- package/dist/surface/tools/lease-seed.d.ts +49 -0
- package/dist/surface/tools/lease-seed.js +167 -0
- package/dist/surface/tools/lease-tools.d.ts +13 -24
- package/dist/surface/tools/lease-tools.js +146 -197
- package/dist/surface/tools/live-call-text.js +4 -4
- package/dist/surface/tools/merge-tools.js +42 -2
- package/dist/surface/tools/merged-name-redirect.js +4 -0
- package/dist/surface/tools/next-step.d.ts +29 -0
- package/dist/surface/tools/next-step.js +214 -0
- package/dist/surface/tools/observe-tools.js +45 -9
- package/dist/surface/tools/platform-moment.d.ts +72 -0
- package/dist/surface/tools/platform-moment.js +296 -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 +71 -14
- package/dist/surface/tools/real-input-attempt.js +96 -13
- package/dist/surface/tools/reticle-result.d.ts +120 -0
- package/dist/surface/tools/reticle-result.js +364 -0
- package/dist/surface/tools/tool-kit.d.ts +83 -3
- package/dist/surface/tools/tool-kit.js +77 -7
- package/dist/surface/tools/tools.js +43 -59
- package/dist/telemetry/account-envelope.d.ts +7 -0
- package/dist/telemetry/account-envelope.js +61 -0
- package/dist/telemetry/agent-client.d.ts +15 -0
- package/dist/telemetry/agent-client.js +57 -0
- package/dist/telemetry/app-instrumented.js +9 -2
- package/dist/telemetry/argument-shape.d.ts +7 -0
- package/dist/telemetry/argument-shape.js +86 -1
- package/dist/telemetry/automation-hint.d.ts +2 -3
- package/dist/telemetry/automation-hint.js +33 -8
- package/dist/telemetry/cli-telemetry.js +7 -2
- package/dist/telemetry/daemon-telemetry.js +3 -0
- package/dist/telemetry/error-fingerprint.d.ts +10 -1
- package/dist/telemetry/error-fingerprint.js +61 -5
- package/dist/telemetry/feedback-cli.d.ts +0 -13
- package/dist/telemetry/feedback-cli.js +0 -77
- package/dist/telemetry/feedback.d.ts +13 -0
- package/dist/telemetry/feedback.js +183 -17
- package/dist/telemetry/harness-run.d.ts +18 -0
- package/dist/telemetry/harness-run.js +80 -0
- package/dist/telemetry/hud-events.d.ts +16 -0
- package/dist/telemetry/hud-events.js +27 -0
- package/dist/telemetry/hud-metrics.d.ts +29 -6
- package/dist/telemetry/hud-metrics.js +116 -25
- package/dist/telemetry/init-telemetry.js +2 -2
- package/dist/telemetry/install-source.d.ts +13 -25
- package/dist/telemetry/install-source.js +44 -3
- package/dist/telemetry/mcp-connection.d.ts +0 -14
- package/dist/telemetry/mcp-connection.js +5 -1
- package/dist/telemetry/onboarding-firsts.d.ts +32 -0
- package/dist/telemetry/onboarding-firsts.js +81 -6
- package/dist/telemetry/platform-linked.d.ts +5 -0
- package/dist/telemetry/platform-linked.js +32 -0
- package/dist/telemetry/project-profile.js +5 -3
- package/dist/telemetry/proxy-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.d.ts +15 -4
- package/dist/telemetry/session-metrics.js +88 -52
- package/dist/telemetry/telemetry.d.ts +38 -2
- package/dist/telemetry/telemetry.js +144 -34
- package/dist/telemetry/verification-of.d.ts +3 -1
- package/dist/telemetry/verification-of.js +13 -4
- package/dist/wire-journal.d.ts +2 -2
- package/dist/wire-journal.js +20 -6
- package/docs/README.md +3 -3
- package/docs/actions.mdx +6 -3
- package/docs/adapters.md +1 -1
- package/docs/agent-cheatsheet.md +25 -28
- package/docs/architecture.md +5 -5
- package/docs/autodrive.md +33 -28
- package/docs/benchmarks.md +5 -5
- package/docs/best-practices.mdx +27 -9
- package/docs/capabilities.mdx +14 -16
- package/docs/cli/affected.mdx +1 -1
- package/docs/cli/cloud.mdx +54 -75
- package/docs/cli/doctor.mdx +14 -3
- package/docs/cli/drive.mdx +20 -14
- package/docs/cli/feedback.mdx +3 -3
- package/docs/cli/gate.mdx +7 -2
- package/docs/cli/help.mdx +50 -55
- package/docs/cli/init.mdx +43 -28
- package/docs/cli/license.mdx +12 -8
- package/docs/cli/mcp.mdx +4 -4
- package/docs/cli/open.mdx +3 -3
- package/docs/cli/report.mdx +4 -2
- package/docs/cli/restart.mdx +2 -2
- package/docs/cli/rollback.mdx +14 -8
- package/docs/cli/serve.mdx +4 -4
- package/docs/cli/setup.mdx +23 -0
- package/docs/cli/status.mdx +18 -5
- package/docs/cli/stop.mdx +67 -8
- package/docs/cli/try.mdx +62 -0
- package/docs/cli/update.mdx +4 -3
- package/docs/cli/verify.mdx +17 -6
- package/docs/cli/version.mdx +5 -4
- package/docs/cli.mdx +111 -42
- package/docs/debugging.md +3 -3
- package/docs/deploy-checks.md +1 -1
- package/docs/desktop.mdx +9 -3
- package/docs/enterprise.md +11 -10
- package/docs/environment.mdx +7 -8
- package/docs/faq.mdx +3 -3
- package/docs/first-drive.md +5 -4
- package/docs/flows.md +35 -27
- package/docs/for-agents.md +4 -5
- package/docs/frameworks.mdx +32 -21
- package/docs/getting-started.md +65 -61
- package/docs/human-control.md +16 -16
- package/docs/index.mdx +15 -42
- package/docs/install-agentic.mdx +22 -37
- package/docs/install-manual.mdx +23 -13
- package/docs/instrumentation.mdx +5 -3
- package/docs/integration-patterns.md +7 -7
- package/docs/license-activation.mdx +1 -1
- package/docs/multi-agent-testing.md +24 -6
- package/docs/packages/babel-plugin.mdx +4 -2
- package/docs/packages/browser.mdx +16 -10
- package/docs/packages/core.mdx +12 -5
- package/docs/packages/electron.mdx +3 -2
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/eslint-plugin.mdx +18 -12
- package/docs/packages/next.mdx +8 -7
- package/docs/packages/open-verification.mdx +1 -1
- package/docs/packages/server.mdx +5 -5
- package/docs/packages/tauri.mdx +9 -9
- package/docs/packages/test.mdx +4 -10
- package/docs/packages/vite-plugin.mdx +32 -4
- package/docs/packages.mdx +11 -15
- package/docs/platform-integration.md +6 -5
- package/docs/predicates.mdx +40 -24
- package/docs/quickstart.mdx +69 -30
- package/docs/required-checks.md +8 -7
- package/docs/reticle-manual.md +15 -5
- package/docs/skill-file.mdx +17 -79
- package/docs/skill-setup.md +9 -9
- package/docs/skill.md +200 -0
- package/docs/system-map.md +7 -7
- package/docs/telemetry.md +23 -25
- package/docs/testing.md +4 -4
- package/docs/token-efficiency.md +12 -12
- package/docs/tools/act-and-wait.mdx +9 -2
- package/docs/tools/act-sequence.mdx +6 -7
- package/docs/tools/act.mdx +2 -2
- package/docs/tools/assert.mdx +18 -17
- package/docs/tools/console.mdx +9 -9
- package/docs/tools/inspect.mdx +16 -7
- package/docs/tools/look.mdx +24 -0
- package/docs/tools/navigate.mdx +5 -3
- package/docs/tools/network.mdx +2 -2
- package/docs/tools/observe.mdx +6 -6
- package/docs/tools/overview.mdx +34 -33
- package/docs/tools/query.mdx +5 -5
- package/docs/tools/session-and-feedback.mdx +22 -13
- package/docs/tools/sessions.mdx +8 -8
- package/docs/tools/snapshot.mdx +6 -5
- package/docs/tools/state.mdx +9 -9
- package/docs/tools/tools-and-run.mdx +26 -24
- package/docs/tools/verify.mdx +28 -0
- package/docs/tools/wait-for.mdx +8 -4
- package/docs/troubleshooting.mdx +21 -6
- package/docs/usage.md +55 -46
- package/docs/use-cases.mdx +3 -3
- package/docs/vs-chrome-devtools-mcp.mdx +9 -6
- package/docs/vs-playwright-mcp.mdx +3 -1
- package/docs/what-is-recorded.md +17 -5
- package/docs/what-is-reticle.mdx +5 -3
- package/docs/why-reticle.mdx +1 -1
- package/package.json +9 -12
- package/dist/command/cli/demo/demo-page.d.ts +0 -78
- package/dist/command/cli/demo/demo-page.js +0 -241
- package/dist/command/cli/demo/demo-run.d.ts +0 -38
- package/dist/command/cli/demo/demo-run.js +0 -138
- 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/judgement/hunt/hunt-report.d.ts +0 -49
- package/dist/judgement/hunt/hunt-report.js +0 -84
- package/dist/language/flows/buddy-status.d.ts +0 -25
- package/dist/language/flows/buddy-status.js +0 -32
- package/dist/language/flows/change/watch-batcher.d.ts +0 -18
- package/dist/language/flows/change/watch-batcher.js +0 -19
- package/dist/memory/cloud/harness-offer.d.ts +0 -44
- package/dist/memory/cloud/harness-offer.js +0 -145
- package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
- package/dist/memory/project/dir/fill-value-store.js +0 -88
- package/dist/surface/tools/feature-capture.d.ts +0 -151
- package/dist/surface/tools/feature-capture.js +0 -235
- package/dist/surface/tools/tool-hit-rate.d.ts +0 -87
- package/dist/surface/tools/tool-hit-rate.js +0 -90
- package/dist/telemetry/harness-drive.d.ts +0 -4
- package/dist/telemetry/harness-drive.js +0 -35
- package/dist/telemetry/identify.d.ts +0 -22
- package/dist/telemetry/identify.js +0 -73
- package/docs/cli/capsules.mdx +0 -70
- package/docs/cli/hunt.mdx +0 -73
- package/docs/cli/identify.mdx +0 -76
- package/docs/cli/kill.mdx +0 -91
- package/docs/cli/watch.mdx +0 -62
- package/docs/local-registry.md +0 -143
|
@@ -62,7 +62,8 @@ interface SessionEndDeps {
|
|
|
62
62
|
* directories into `language/flows/recording/tape/` for a constant and two types was a dependency
|
|
63
63
|
* nobody needed. `directory-reach.test.ts` refused it, and its advice was right.
|
|
64
64
|
*/
|
|
65
|
-
|
|
65
|
+
/** The tape of THIS tab's steps: another app's drive is never saved into this project. */
|
|
66
|
+
takeAmbientTape?: (sessionId: string) => {
|
|
66
67
|
steps: readonly TapeStep[];
|
|
67
68
|
startPath?: string;
|
|
68
69
|
} | undefined;
|
|
@@ -120,4 +121,17 @@ export declare function makeSessionEnd(deps: SessionEndDeps): (session: SessionE
|
|
|
120
121
|
* dashboard.
|
|
121
122
|
*/
|
|
122
123
|
export declare function recordDriveRun(deps: SessionEndDeps, session: SessionEndTarget): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Tear down every session still open, and wait for it.
|
|
126
|
+
*
|
|
127
|
+
* Teardown is what saves a tab's driven flow and its last run, and it normally fires from the
|
|
128
|
+
* socket's close handler, unawaited. A daemon closing with tabs open terminated those sockets after
|
|
129
|
+
* its final sync, so the flow was written too late to be sent, or not at all once the process
|
|
130
|
+
* exited. Called before that final sync instead: each session leaves the registry first, so the
|
|
131
|
+
* socket closing later finds nothing to tear down a second time.
|
|
132
|
+
*/
|
|
133
|
+
export declare function endLiveSessions<S>(sessions: {
|
|
134
|
+
all: () => S[];
|
|
135
|
+
remove: (session: S) => boolean;
|
|
136
|
+
}, teardown: (session: S) => Promise<void>): Promise<void>;
|
|
123
137
|
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { DRIVE_FLOW_PREFIX, driveFlowsFrom, } from './drive-flow.js';
|
|
1
|
+
import { DRIVE_FLOW_PREFIX, DriveFlowSkipReason, driveFlowsFrom, } from './drive-flow.js';
|
|
2
|
+
import { basename, dirname } from 'node:path';
|
|
2
3
|
import { log } from '../../log.js';
|
|
3
4
|
/** What a session's teardown writes to the daemon log when a by-product fails to save. */
|
|
4
5
|
const SESSION_END_LOG = {
|
|
@@ -7,13 +8,13 @@ const SESSION_END_LOG = {
|
|
|
7
8
|
};
|
|
8
9
|
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
9
10
|
import { journalActionsPath, sessionDirPath } from '../project/dir/reticle-dir.js';
|
|
10
|
-
import { asSessionId } from '@reticlehq/core';
|
|
11
|
+
import { asRunId, asSessionId } from '@reticlehq/core';
|
|
11
12
|
import { OnboardingPhase, OnboardingStepStatus, } from '@reticlehq/core/telemetry';
|
|
12
13
|
import { AmbientStore } from './ambient-store.js';
|
|
13
14
|
import { subjectOf } from '@reticlehq/core';
|
|
14
15
|
import { pruneWorkspace } from './on-disk/startup-maintenance.js';
|
|
15
16
|
import { buildVerificationRun } from '../../judgement/runs/artifact/build-verification-run.js';
|
|
16
|
-
import {
|
|
17
|
+
import { agentEndClaims, agentEndCounts, driveRunId, driveRunsFrom, } from '../../judgement/runs/drive-run.js';
|
|
17
18
|
import { RunStore } from '../../judgement/runs/artifact/run-store.js';
|
|
18
19
|
/**
|
|
19
20
|
* Build the teardown handler the bridge fires when a session is removed. Flushes the journal first (so no
|
|
@@ -173,16 +174,21 @@ export async function recordDriveRun(deps, session) {
|
|
|
173
174
|
if (session.readJournalActions === undefined)
|
|
174
175
|
return;
|
|
175
176
|
const actions = await session.readJournalActions();
|
|
176
|
-
const
|
|
177
|
+
const root = session.artifactRoot ?? deps.reticleRoot;
|
|
178
|
+
const inputs = driveRunsFrom(actions, {
|
|
177
179
|
// Derived from the session, NOT random. Teardown fires on every socket close, and a reconnecting
|
|
178
180
|
// tab keeps its session id and appends to the same ledger — so a drive across two page reloads
|
|
179
181
|
// would fold the whole ledger twice and publish two overlapping rows, the second a superset of
|
|
180
182
|
// the first. A stable id makes the artifact idempotent: the same session rewrites its own run,
|
|
181
183
|
// and the cloud diffs by runId, so a re-push supersedes rather than duplicates.
|
|
182
184
|
runId: driveRunId(session.id),
|
|
185
|
+
// Each agent session in the tab, as `reticle_session {end}` cut them, is its own run.
|
|
186
|
+
agentEnds: agentEndCounts(session.id),
|
|
187
|
+
agentClaims: agentEndClaims(session.id),
|
|
183
188
|
...(session.projectId === undefined ? {} : { projectId: session.projectId }),
|
|
189
|
+
projectName: basename(dirname(root)),
|
|
184
190
|
});
|
|
185
|
-
if (
|
|
191
|
+
if (0 === inputs.length)
|
|
186
192
|
return;
|
|
187
193
|
// The protocol reaching the artifact a person actually reads. Only when the session could say
|
|
188
194
|
// where it was: a subject with no locator is not a weaker subject, it is a guess, and the
|
|
@@ -196,12 +202,19 @@ export async function recordDriveRun(deps, session) {
|
|
|
196
202
|
currentDocumentId: session.currentDocumentId,
|
|
197
203
|
currentEditEpoch: session.currentEditEpoch,
|
|
198
204
|
});
|
|
199
|
-
const store = new RunStore(deps.fs,
|
|
205
|
+
const store = new RunStore(deps.fs, root,
|
|
200
206
|
// Tell the sync daemon a run landed, rather than letting it find out on its next tick. Teardown
|
|
201
207
|
// used to write this artifact with no callback at all, so the evidence from a whole session sat
|
|
202
208
|
// on disk until a timer noticed — which, for the last session of the day, could be never.
|
|
203
209
|
deps.onRunPersisted === undefined ? undefined : { onWrote: deps.onRunPersisted });
|
|
204
|
-
|
|
210
|
+
const clock = deps.now ?? (() => Date.now());
|
|
211
|
+
for (const input of inputs) {
|
|
212
|
+
// A reload rewrites this session's run under the same id; the first stamp is when it ran, and
|
|
213
|
+
// restamping on every rewrite showed the tab's close as the drive's time.
|
|
214
|
+
const held = await store.read(asRunId(input.runId));
|
|
215
|
+
const firstAt = held.ok ? held.run.createdAt : undefined;
|
|
216
|
+
await store.write(buildVerificationRun({ ...input, ...(subject === undefined ? {} : { subject }) }, firstAt === undefined ? clock : () => firstAt));
|
|
217
|
+
}
|
|
205
218
|
}
|
|
206
219
|
/** Persist what this session drove as a replayable flow, when it declared anything provable. */
|
|
207
220
|
async function saveDrivenFlow(deps, session) {
|
|
@@ -210,7 +223,7 @@ async function saveDrivenFlow(deps, session) {
|
|
|
210
223
|
if (takeTape === undefined || flows === undefined)
|
|
211
224
|
return;
|
|
212
225
|
// One flow per journey the session contained, not one flow per session — see drive-flow.ts.
|
|
213
|
-
const { programs, outcome } = driveFlowsFrom(takeTape());
|
|
226
|
+
const { programs, outcome } = driveFlowsFrom(takeTape(session.id));
|
|
214
227
|
const author = deps.author?.();
|
|
215
228
|
for (const program of programs) {
|
|
216
229
|
// Folded into any saved copy of the same journey, so each session does not add a near-copy.
|
|
@@ -226,7 +239,13 @@ async function saveDrivenFlow(deps, session) {
|
|
|
226
239
|
phase: OnboardingPhase.FIRST_RUN,
|
|
227
240
|
step: 'flow_recorded',
|
|
228
241
|
status: 0 === programs.length ? OnboardingStepStatus.SKIPPED : OnboardingStepStatus.COMPLETED,
|
|
229
|
-
...(outcome.unprovenSteps === undefined
|
|
242
|
+
...(outcome.unprovenSteps === undefined
|
|
243
|
+
? {}
|
|
244
|
+
: { reason: DriveFlowSkipReason.NO_DECLARED_CONSEQUENCE }),
|
|
245
|
+
// Not saved, with the reason: proved against mocked responses, which replay cannot see (#1459).
|
|
246
|
+
...(0 < programs.length || outcome.mockedSteps === undefined
|
|
247
|
+
? {}
|
|
248
|
+
: { reason: DriveFlowSkipReason.DRIVEN_UNDER_NETWORK_MOCKS }),
|
|
230
249
|
});
|
|
231
250
|
}
|
|
232
251
|
/**
|
|
@@ -247,3 +266,17 @@ async function dropUndrivenJournal(fs, root, sessionId) {
|
|
|
247
266
|
// tidying is never the reason a teardown fails
|
|
248
267
|
}
|
|
249
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Tear down every session still open, and wait for it.
|
|
271
|
+
*
|
|
272
|
+
* Teardown is what saves a tab's driven flow and its last run, and it normally fires from the
|
|
273
|
+
* socket's close handler, unawaited. A daemon closing with tabs open terminated those sockets after
|
|
274
|
+
* its final sync, so the flow was written too late to be sent, or not at all once the process
|
|
275
|
+
* exited. Called before that final sync instead: each session leaves the registry first, so the
|
|
276
|
+
* socket closing later finds nothing to tear down a second time.
|
|
277
|
+
*/
|
|
278
|
+
export async function endLiveSessions(sessions, teardown) {
|
|
279
|
+
const ending = sessions.all().filter((session) => sessions.remove(session));
|
|
280
|
+
// Teardown is best-effort everywhere it runs: one that fails must not stop the others.
|
|
281
|
+
await Promise.all(ending.map((session) => teardown(session).catch(() => undefined)));
|
|
282
|
+
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
import { type ProjectCandidate } from '@reticlehq/core/artifacts';
|
|
1
2
|
import { type ProjectId } from '@reticlehq/core';
|
|
2
3
|
import { type ArtifactRoot } from './artifact-root.js';
|
|
4
|
+
/**
|
|
5
|
+
* Every project this machine knows the directory of: discovered `.reticle.json` files first, then
|
|
6
|
+
* the user-level registry, then the dev servers the build plugin announced. Read on each call, since
|
|
7
|
+
* `init` can run in another terminal while the daemon is up.
|
|
8
|
+
*
|
|
9
|
+
* Exported because sync must cover exactly the projects runs are written into. It kept its own list
|
|
10
|
+
* without the announced dev servers, so a project known only through its running dev server had its
|
|
11
|
+
* runs written to its `.reticle/runs/` and never sent, with nothing reporting a problem.
|
|
12
|
+
*/
|
|
13
|
+
export declare function knownProjectCandidates(): ProjectCandidate[];
|
|
3
14
|
/**
|
|
4
15
|
* The directory of the project a page announced, or undefined when this machine does not know it.
|
|
5
16
|
*
|
|
@@ -10,4 +21,18 @@ import { type ArtifactRoot } from './artifact-root.js';
|
|
|
10
21
|
* own project id.
|
|
11
22
|
*/
|
|
12
23
|
export declare function projectDirectoryFor(projectId: string | undefined): string | undefined;
|
|
13
|
-
|
|
24
|
+
/** Where this resolver gets its IO answers. The defaults are the production ones. */
|
|
25
|
+
export interface ArtifactRootResolverDeps {
|
|
26
|
+
/** Every project this machine knows about. Defaults to discovery + the user registry. */
|
|
27
|
+
candidates?: () => ProjectCandidate[];
|
|
28
|
+
/** Whether the daemon's own directory is a Reticle project. Defaults to looking at disk. */
|
|
29
|
+
daemonIsProject?: (daemonRoot: string) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* The directory of the dev server that served a page origin, or undefined when unobservable.
|
|
32
|
+
* Required, not defaulted: the production answer reads daemon state and shells out, which is the
|
|
33
|
+
* wiring layer's business (`portal/session/serving-directory.ts`), and a resolver built without it
|
|
34
|
+
* would silently keep sending every worktree's artifacts to the primary checkout.
|
|
35
|
+
*/
|
|
36
|
+
servingDirectory: (origin: string) => string | undefined;
|
|
37
|
+
}
|
|
38
|
+
export declare function artifactRootResolver(daemonRoot: string, deps: ArtifactRootResolverDeps): (projectId: ProjectId | undefined, origin?: string) => ArtifactRoot;
|
|
@@ -7,12 +7,21 @@
|
|
|
7
7
|
*
|
|
8
8
|
* It lives here rather than in the daemon bootstrap because it is a subject, not wiring — and
|
|
9
9
|
* because the bootstrap is at its line cap, which is the cohesion signal that rule doing its job.
|
|
10
|
+
*
|
|
11
|
+
* ## Why `artifactRootResolver` takes a `deps`
|
|
12
|
+
*
|
|
13
|
+
* Both of its IO answers come from the REAL disk and the REAL `process.cwd()`: which projects this
|
|
14
|
+
* machine knows, and whether the daemon's own directory declares one. That left the file untestable
|
|
15
|
+
* at the level where its defect actually lives — the `unmatchedRoot` specs next door hand a
|
|
16
|
+
* `daemonProjectId` straight in, so a mistake in READING it would leave every one of them green
|
|
17
|
+
* while the worktree bug reproduced unchanged. The defaults are the production answers; the seam is
|
|
18
|
+
* what lets a test say "these are the projects on this machine" without one.
|
|
10
19
|
*/
|
|
11
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
12
|
-
import { dirname, join } from 'node:path';
|
|
20
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
21
|
+
import { dirname, isAbsolute, join, relative } from 'node:path';
|
|
13
22
|
import { homedir } from 'node:os';
|
|
14
23
|
import { PROJECT_REGISTRY_FILE, emptyProjectRegistry, parseProjectRegistry, } from '@reticlehq/core/artifacts';
|
|
15
|
-
import { ReticleDir } from '@reticlehq/core';
|
|
24
|
+
import { DevServerEntrySchema, ReticleDir, ReticleEnv, devServerRegistryPort, } from '@reticlehq/core';
|
|
16
25
|
import { discoverProjectConfigs, hasProjectConfig, } from '../../command/cli/config/config-discovery.js';
|
|
17
26
|
import { log } from '../../log.js';
|
|
18
27
|
import { ArtifactRootReason, projectCandidatesFrom, resolveArtifactRoot, unmatchedRoot, } from './artifact-root.js';
|
|
@@ -34,12 +43,47 @@ function daemonSitsInAProject(daemonRoot) {
|
|
|
34
43
|
return false;
|
|
35
44
|
}
|
|
36
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* The project a candidate list says lives at the daemon's own directory.
|
|
48
|
+
*
|
|
49
|
+
* Not the same question as `daemonSitsInAProject`: that one asks whether the tree invited Reticle in
|
|
50
|
+
* at all (either mark counts, including a bare `.reticle/` from a version that predates the config
|
|
51
|
+
* file), and this one asks WHICH project it is. The difference is the whole of #1244 — a daemon in
|
|
52
|
+
* repo A serving an app from a git worktree B belongs to A and is being asked about B.
|
|
53
|
+
*
|
|
54
|
+
* Takes the candidates rather than gathering them, so the answer is a function of one list and a test
|
|
55
|
+
* can supply it. Read from that same list the resolver already builds, so there is one answer to
|
|
56
|
+
* "where does this project live" rather than a second `.reticle.json` parser here. A daemon whose
|
|
57
|
+
* root was passed explicitly (`--root`, or an embedder's own `reticleRoot`) is not in that list and
|
|
58
|
+
* answers undefined, which is the safe direction: it cannot prove the named project is its own, so it
|
|
59
|
+
* declines the daemon root and the session goes to the unmatched bucket instead.
|
|
60
|
+
*/
|
|
61
|
+
function daemonOwnProjectId(daemonRoot, candidates) {
|
|
62
|
+
const directory = dirname(daemonRoot);
|
|
63
|
+
const own = candidates.find((candidate) => candidate.directory === directory)?.projectId;
|
|
64
|
+
// A monorepo root carries the id of the app `init --app` wired, as a pointer for an agent started
|
|
65
|
+
// there. When an app inside the root declares the same id, the root is pointing at it, not being
|
|
66
|
+
// it: answering with the id handed a second checkout's flows to this root with no warning.
|
|
67
|
+
const pointer = candidates.some((candidate) => candidate.projectId === own &&
|
|
68
|
+
candidate.directory !== directory &&
|
|
69
|
+
isInside(directory, candidate.directory));
|
|
70
|
+
return pointer ? undefined : own;
|
|
71
|
+
}
|
|
72
|
+
/** Is `dir` strictly inside `root`? */
|
|
73
|
+
function isInside(root, dir) {
|
|
74
|
+
const rel = relative(root, dir);
|
|
75
|
+
return 0 < rel.length && !rel.startsWith('..') && !isAbsolute(rel);
|
|
76
|
+
}
|
|
37
77
|
/**
|
|
38
78
|
* Every project this machine knows the directory of: discovered `.reticle.json` files first, then
|
|
39
|
-
* the user-level registry
|
|
40
|
-
* daemon is up.
|
|
79
|
+
* the user-level registry, then the dev servers the build plugin announced. Read on each call, since
|
|
80
|
+
* `init` can run in another terminal while the daemon is up.
|
|
81
|
+
*
|
|
82
|
+
* Exported because sync must cover exactly the projects runs are written into. It kept its own list
|
|
83
|
+
* without the announced dev servers, so a project known only through its running dev server had its
|
|
84
|
+
* runs written to its `.reticle/runs/` and never sent, with nothing reporting a problem.
|
|
41
85
|
*/
|
|
42
|
-
function knownProjectCandidates() {
|
|
86
|
+
export function knownProjectCandidates() {
|
|
43
87
|
let registry = emptyProjectRegistry();
|
|
44
88
|
try {
|
|
45
89
|
const path = join(homedir(), ReticleDir.ROOT, PROJECT_REGISTRY_FILE);
|
|
@@ -58,7 +102,35 @@ function knownProjectCandidates() {
|
|
|
58
102
|
catch {
|
|
59
103
|
// Same reasoning: a diagnostic search that throws must not take a tool call with it.
|
|
60
104
|
}
|
|
61
|
-
return projectCandidatesFrom(discovery, registry);
|
|
105
|
+
return projectCandidatesFrom(discovery, registry, announcedDevServers());
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The dev servers the build plugin announced, with the project id and root each serves. Read from
|
|
109
|
+
* the same state directory the plugin writes to. Unreadable or malformed entries are skipped: a stale
|
|
110
|
+
* announcement only ever names a directory that really was that project.
|
|
111
|
+
*/
|
|
112
|
+
function announcedDevServers() {
|
|
113
|
+
const override = process.env[ReticleEnv.STATE_DIR];
|
|
114
|
+
const dir = override !== undefined && 0 < override.length ? override : join(homedir(), ReticleDir.ROOT);
|
|
115
|
+
const servers = [];
|
|
116
|
+
try {
|
|
117
|
+
for (const name of readdirSync(dir)) {
|
|
118
|
+
if (null === devServerRegistryPort(name))
|
|
119
|
+
continue;
|
|
120
|
+
try {
|
|
121
|
+
const parsed = DevServerEntrySchema.safeParse(JSON.parse(readFileSync(join(dir, name), 'utf8')));
|
|
122
|
+
if (parsed.success)
|
|
123
|
+
servers.push({ projectId: parsed.data.projectId, root: parsed.data.root });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* one bad announcement does not hide the others */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
/* no state directory yet: nothing has been announced */
|
|
132
|
+
}
|
|
133
|
+
return servers;
|
|
62
134
|
}
|
|
63
135
|
/**
|
|
64
136
|
* The directory of the project a page announced, or undefined when this machine does not know it.
|
|
@@ -79,14 +151,41 @@ export function projectDirectoryFor(projectId) {
|
|
|
79
151
|
// them would read the wrong app's package.json and prescribe its packages to the other.
|
|
80
152
|
return 1 === directories.size ? [...directories][0] : undefined;
|
|
81
153
|
}
|
|
82
|
-
export function artifactRootResolver(daemonRoot) {
|
|
83
|
-
const
|
|
154
|
+
export function artifactRootResolver(daemonRoot, deps) {
|
|
155
|
+
const candidates = deps.candidates ?? knownProjectCandidates;
|
|
156
|
+
const sitsInAProject = deps.daemonIsProject ?? daemonSitsInAProject;
|
|
157
|
+
const { servingDirectory } = deps;
|
|
158
|
+
const daemonIsProject = sitsInAProject(daemonRoot);
|
|
159
|
+
// Resolved beside it and ONCE, for the same reason: a directory's `.reticle.json` does not change
|
|
160
|
+
// between two tabs connecting. Only read when the daemon really is in a project, since it is the
|
|
161
|
+
// sole thing that can license a write into the daemon's own tree.
|
|
162
|
+
const daemonProjectId = daemonIsProject
|
|
163
|
+
? daemonOwnProjectId(daemonRoot, candidates())
|
|
164
|
+
: undefined;
|
|
165
|
+
/** Said once per project, reason and folder: one session's calls repeated it 16 times in 3s. */
|
|
166
|
+
const told = new Set();
|
|
167
|
+
// ponytail: asked once per origin (it shells out); a dev server moved to another folder on the
|
|
168
|
+
// same port is named stale until the daemon restarts.
|
|
169
|
+
const servedFrom = new Map();
|
|
170
|
+
const servingOf = (origin) => {
|
|
171
|
+
if (!servedFrom.has(origin))
|
|
172
|
+
servedFrom.set(origin, servingDirectory(origin));
|
|
173
|
+
return servedFrom.get(origin);
|
|
174
|
+
};
|
|
84
175
|
return (projectId, origin) => {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
176
|
+
const known = candidates();
|
|
177
|
+
let resolved = resolveArtifactRoot({ projectId, candidates: known, daemonRoot });
|
|
178
|
+
// Several checkouts declare this project — every git worktree carries the same committed
|
|
179
|
+
// `.reticle.json`. Only now is the serving process looked up: it shells out, and the tie is the
|
|
180
|
+
// one case where where the page was SERVED from is the deciding fact.
|
|
181
|
+
if (resolved.reason === ArtifactRootReason.AMBIGUOUS && origin !== undefined) {
|
|
182
|
+
resolved = resolveArtifactRoot({
|
|
183
|
+
projectId,
|
|
184
|
+
candidates: known,
|
|
185
|
+
daemonRoot,
|
|
186
|
+
servingDirectory: servingOf(origin),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
90
189
|
if (resolved.reason === ArtifactRootReason.MATCHED_PROJECT)
|
|
91
190
|
return resolved;
|
|
92
191
|
// Could not name the project. The old code wrote into the daemon's directory anyway and said
|
|
@@ -98,19 +197,29 @@ export function artifactRootResolver(daemonRoot) {
|
|
|
98
197
|
daemonRoot,
|
|
99
198
|
daemonIsProject,
|
|
100
199
|
home: homedir(),
|
|
200
|
+
...(daemonProjectId === undefined ? {} : { daemonProjectId }),
|
|
101
201
|
...(projectId === undefined ? {} : { projectId }),
|
|
102
202
|
// Only reached when no project id survives the guard. Without it every app that never
|
|
103
203
|
// stamped one shares a single directory -- and that directory holds the durable half, so one
|
|
104
204
|
// app's learned expectations become another's.
|
|
105
205
|
...(origin === undefined ? {} : { origin }),
|
|
106
206
|
});
|
|
107
|
-
|
|
207
|
+
const key = `${projectId ?? ''}\u0000${resolved.reason}\u0000${root}`;
|
|
208
|
+
if (root !== daemonRoot && !told.has(key)) {
|
|
209
|
+
told.add(key);
|
|
108
210
|
log('artifact_root_unmatched', {
|
|
109
211
|
projectId: projectId ?? null,
|
|
212
|
+
// The other half of the comparison, so a reader can tell "the daemon was a guest" from
|
|
213
|
+
// "the daemon is in a DIFFERENT project" without reading the config files by hand.
|
|
214
|
+
daemonProjectId: daemonProjectId ?? null,
|
|
110
215
|
reason: resolved.reason,
|
|
111
216
|
root,
|
|
217
|
+
...(resolved.candidates === undefined ? {} : { candidates: resolved.candidates }),
|
|
112
218
|
});
|
|
113
219
|
}
|
|
114
|
-
|
|
220
|
+
// Named so the agent is told WHICH checkout's work is stranded: a clone serving the app was
|
|
221
|
+
// never named, only the primary checkout's folders.
|
|
222
|
+
const serving = origin === undefined ? undefined : servingOf(origin);
|
|
223
|
+
return { ...resolved, root, ...(serving === undefined ? {} : { serving }) };
|
|
115
224
|
};
|
|
116
225
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UNMATCHED_SUBDIR } from './dir/reticle-dir.js';
|
|
1
2
|
import { type ProjectCandidate, type ProjectId, type ProjectRegistry } from '@reticlehq/core';
|
|
2
3
|
import type { ConfigDiscovery } from '../../command/cli/config/config-discovery.js';
|
|
3
4
|
/**
|
|
@@ -43,6 +44,13 @@ interface ArtifactRootQuery {
|
|
|
43
44
|
candidates: readonly ProjectCandidate[];
|
|
44
45
|
/** Where artifacts go when the project cannot be identified. Already a `.reticle` path. */
|
|
45
46
|
daemonRoot: string;
|
|
47
|
+
/**
|
|
48
|
+
* The directory the dev server that served this page runs in, when the caller could observe it.
|
|
49
|
+
* Only consulted to break an AMBIGUOUS tie: every git worktree carries the same committed
|
|
50
|
+
* `.reticle.json`, so the projectId cannot tell them apart, but the serving process runs inside
|
|
51
|
+
* exactly one of them.
|
|
52
|
+
*/
|
|
53
|
+
servingDirectory?: string | undefined;
|
|
46
54
|
}
|
|
47
55
|
export interface ArtifactRoot {
|
|
48
56
|
/** Absolute path to the `.reticle` directory. Always present, on every branch. */
|
|
@@ -53,6 +61,8 @@ export interface ArtifactRoot {
|
|
|
53
61
|
* needs to name them; a caller that does not can ignore the field.
|
|
54
62
|
*/
|
|
55
63
|
candidates?: string[];
|
|
64
|
+
/** The folder whose dev server served the page, when the evidence went unmatched and it is known. */
|
|
65
|
+
serving?: string;
|
|
56
66
|
}
|
|
57
67
|
/**
|
|
58
68
|
* Resolve the artifact root for one session.
|
|
@@ -68,9 +78,11 @@ export declare function resolveArtifactRoot(query: ArtifactRootQuery): ArtifactR
|
|
|
68
78
|
* is a cache of something `init` saw once. Order only decides which duplicate survives dedupe, and
|
|
69
79
|
* the one confirmed a moment ago is the better survivor.
|
|
70
80
|
*/
|
|
71
|
-
export declare function projectCandidatesFrom(discovery: ConfigDiscovery, registry: ProjectRegistry
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
export declare function projectCandidatesFrom(discovery: ConfigDiscovery, registry: ProjectRegistry, devServers?: readonly {
|
|
82
|
+
projectId?: string | undefined;
|
|
83
|
+
root: string;
|
|
84
|
+
}[]): ProjectCandidate[];
|
|
85
|
+
export { UNMATCHED_SUBDIR };
|
|
74
86
|
/**
|
|
75
87
|
* The fallback root for a session whose project could not be resolved.
|
|
76
88
|
*
|
|
@@ -83,20 +95,47 @@ export declare const UNMATCHED_SUBDIR = "unmatched";
|
|
|
83
95
|
* Two cases, and only one of them was ever the intended behaviour:
|
|
84
96
|
*
|
|
85
97
|
* - the daemon is sitting IN a Reticle project (a developer who ran `reticle serve` in their own
|
|
86
|
-
* app). Its root is the right answer, and it is
|
|
87
|
-
*
|
|
88
|
-
*
|
|
98
|
+
* app), and the session is asking about THAT project. Its root is the right answer, and it is
|
|
99
|
+
* the case the old fallback was written for.
|
|
100
|
+
* - otherwise — a guest in the tree, or a daemon in its own checkout serving a DIFFERENT project.
|
|
101
|
+
* Then the evidence goes to the user's own `~/.reticle/unmatched/<projectId>`, which is
|
|
102
|
+
* Reticle's to write.
|
|
89
103
|
*
|
|
90
104
|
* It is never dropped. A verdict with nowhere to live is a worse failure than one in an unexpected
|
|
91
105
|
* place, and the caller says out loud where it went.
|
|
92
106
|
*
|
|
107
|
+
* ## The second case in that list used to be missing, and it is not an edge case
|
|
108
|
+
*
|
|
109
|
+
* `daemonIsProject` answers "is the daemon sitting in SOME Reticle project". The guard below read it
|
|
110
|
+
* as "this session's project", and those are different questions whenever the two ids differ. Run
|
|
111
|
+
* the daemon in repo A, then connect an app from a git worktree B of that same repo: B has its own
|
|
112
|
+
* `projectId` in its own `.reticle.json`, and discovery cannot see it — it walks out from A's
|
|
113
|
+
* `process.cwd()` and never crosses into a sibling checkout — so this function is reached with a
|
|
114
|
+
* NAMED project and the daemon's root came back anyway. B's intents, flows and runs were written
|
|
115
|
+
* into A's checkout, and the returned path said A, so nothing looked wrong.
|
|
116
|
+
*
|
|
117
|
+
* A real identity always wins. The daemon's root is only the answer when it is that identity's own
|
|
118
|
+
* root, or when no identity was named at all.
|
|
119
|
+
*
|
|
93
120
|
* `projectId` arrives in HELLO from the page, so it is untrusted input on a path join and is held
|
|
94
|
-
* to one safe segment — the same guard session ids and flow names already pass.
|
|
121
|
+
* to one safe segment — the same guard session ids and flow names already pass. `daemonProjectId`
|
|
122
|
+
* is read from the daemon's own `.reticle.json` and gets the same treatment, because equality
|
|
123
|
+
* against a guarded value is only as strong as the weaker side.
|
|
95
124
|
*/
|
|
96
125
|
export declare function unmatchedRoot(query: {
|
|
97
126
|
daemonRoot: string;
|
|
98
127
|
/** Whether the daemon's own directory is a Reticle project — an IO question, answered by the caller. */
|
|
99
128
|
daemonIsProject: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* The daemon's OWN projectId, when its directory declares one. Absent means the daemon cannot
|
|
131
|
+
* prove which project it belongs to, so a named project that is not demonstrably its own declines.
|
|
132
|
+
*
|
|
133
|
+
* A plain `string` rather than a `ProjectId`, because it arrives from a `ProjectCandidate` — the
|
|
134
|
+
* registry's view of a `.reticle.json` — and the brand is only minted where the id is read out of
|
|
135
|
+
* that file for this daemon. Comparing it against a branded session id is unaffected: the brand is
|
|
136
|
+
* a `string`, and the guard below is what actually holds it to one safe path segment.
|
|
137
|
+
*/
|
|
138
|
+
daemonProjectId?: string | undefined;
|
|
100
139
|
/** The user's home directory. Passed in rather than read, so this stays pure. */
|
|
101
140
|
home: string;
|
|
102
141
|
projectId?: ProjectId | undefined;
|
|
@@ -106,4 +145,3 @@ export declare function unmatchedRoot(query: {
|
|
|
106
145
|
*/
|
|
107
146
|
origin?: string | undefined;
|
|
108
147
|
}): string;
|
|
109
|
-
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
1
|
+
import { isAbsolute, join, relative, sep } from 'node:path';
|
|
2
|
+
import { UNMATCHED_SUBDIR } from './dir/reticle-dir.js';
|
|
2
3
|
import { SAFE_SEGMENT_PATTERN, ReticleDir, fnv1a, projectCandidates, } from '@reticlehq/core';
|
|
3
4
|
/**
|
|
4
5
|
* Which `.reticle/` a session's artifacts belong in.
|
|
@@ -51,6 +52,10 @@ export function resolveArtifactRoot(query) {
|
|
|
51
52
|
return { root: daemonRoot, reason: ArtifactRootReason.NO_MATCH };
|
|
52
53
|
}
|
|
53
54
|
if (matches.length > 1) {
|
|
55
|
+
const served = checkoutContaining(matches, query.servingDirectory);
|
|
56
|
+
if (served !== undefined) {
|
|
57
|
+
return { root: join(served, ReticleDir.ROOT), reason: ArtifactRootReason.MATCHED_PROJECT };
|
|
58
|
+
}
|
|
54
59
|
return {
|
|
55
60
|
root: daemonRoot,
|
|
56
61
|
reason: ArtifactRootReason.AMBIGUOUS,
|
|
@@ -66,6 +71,24 @@ export function resolveArtifactRoot(query) {
|
|
|
66
71
|
}
|
|
67
72
|
return { root: join(directory, ReticleDir.ROOT), reason: ArtifactRootReason.MATCHED_PROJECT };
|
|
68
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The checkout the serving directory sits in, or undefined when it is in none of them.
|
|
76
|
+
*
|
|
77
|
+
* Deepest wins, because a worktree can live INSIDE the primary checkout (`.claude/worktrees/x`) and
|
|
78
|
+
* then sits under both; the deeper one is the one the server actually runs in. `relative` rather
|
|
79
|
+
* than a string prefix, so `/wt/pr-77` is not read as inside `/wt/pr-7`, and on every platform.
|
|
80
|
+
*/
|
|
81
|
+
function checkoutContaining(matches, servingDirectory) {
|
|
82
|
+
if (servingDirectory === undefined || 0 === servingDirectory.length)
|
|
83
|
+
return undefined;
|
|
84
|
+
const inside = matches
|
|
85
|
+
.map((candidate) => candidate.directory)
|
|
86
|
+
.filter((directory) => {
|
|
87
|
+
const rel = relative(directory, servingDirectory);
|
|
88
|
+
return '' === rel || ('..' !== rel && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
89
|
+
});
|
|
90
|
+
return inside.sort((a, b) => b.length - a.length)[0];
|
|
91
|
+
}
|
|
69
92
|
/**
|
|
70
93
|
* One entry per directory.
|
|
71
94
|
*
|
|
@@ -92,14 +115,23 @@ function dedupeByDirectory(matches) {
|
|
|
92
115
|
* is a cache of something `init` saw once. Order only decides which duplicate survives dedupe, and
|
|
93
116
|
* the one confirmed a moment ago is the better survivor.
|
|
94
117
|
*/
|
|
95
|
-
export function projectCandidatesFrom(discovery, registry) {
|
|
118
|
+
export function projectCandidatesFrom(discovery, registry, devServers = []) {
|
|
96
119
|
const discovered = discovery.found.flatMap((config) => config.projectId === undefined || 0 === config.projectId.length
|
|
97
120
|
? []
|
|
98
121
|
: [{ projectId: config.projectId, directory: config.directory }]);
|
|
99
|
-
|
|
122
|
+
const known = [...discovered, ...projectCandidates(registry)];
|
|
123
|
+
// A dev server's own announcement of its project id and root, ONLY for an id nothing above names.
|
|
124
|
+
// It is the one source that knows an app wired by the build plugin alone (no `.reticle.json`, never
|
|
125
|
+
// `init`-ed), whose runs otherwise went to `unmatched/`, which nothing syncs. It fills gaps and never
|
|
126
|
+
// competes: `init` at a monorepo root names the root while the dev server names the app's subfolder,
|
|
127
|
+
// and counting both refused a project that resolved fine before.
|
|
128
|
+
const named = new Set(known.map((candidate) => candidate.projectId));
|
|
129
|
+
const announced = devServers.flatMap((server) => server.projectId === undefined || 0 === server.projectId.length || named.has(server.projectId)
|
|
130
|
+
? []
|
|
131
|
+
: [{ projectId: server.projectId, directory: server.root }]);
|
|
132
|
+
return [...known, ...announced];
|
|
100
133
|
}
|
|
101
|
-
|
|
102
|
-
export const UNMATCHED_SUBDIR = 'unmatched';
|
|
134
|
+
export { UNMATCHED_SUBDIR };
|
|
103
135
|
/** What a project with NO identity at all is called on disk. Never blank, so the path is always real. */
|
|
104
136
|
const UNNAMED_PROJECT = 'unnamed';
|
|
105
137
|
/**
|
|
@@ -132,23 +164,70 @@ const ORIGIN_BUCKET_PREFIX = 'origin-';
|
|
|
132
164
|
* Two cases, and only one of them was ever the intended behaviour:
|
|
133
165
|
*
|
|
134
166
|
* - the daemon is sitting IN a Reticle project (a developer who ran `reticle serve` in their own
|
|
135
|
-
* app). Its root is the right answer, and it is
|
|
136
|
-
*
|
|
137
|
-
*
|
|
167
|
+
* app), and the session is asking about THAT project. Its root is the right answer, and it is
|
|
168
|
+
* the case the old fallback was written for.
|
|
169
|
+
* - otherwise — a guest in the tree, or a daemon in its own checkout serving a DIFFERENT project.
|
|
170
|
+
* Then the evidence goes to the user's own `~/.reticle/unmatched/<projectId>`, which is
|
|
171
|
+
* Reticle's to write.
|
|
138
172
|
*
|
|
139
173
|
* It is never dropped. A verdict with nowhere to live is a worse failure than one in an unexpected
|
|
140
174
|
* place, and the caller says out loud where it went.
|
|
141
175
|
*
|
|
176
|
+
* ## The second case in that list used to be missing, and it is not an edge case
|
|
177
|
+
*
|
|
178
|
+
* `daemonIsProject` answers "is the daemon sitting in SOME Reticle project". The guard below read it
|
|
179
|
+
* as "this session's project", and those are different questions whenever the two ids differ. Run
|
|
180
|
+
* the daemon in repo A, then connect an app from a git worktree B of that same repo: B has its own
|
|
181
|
+
* `projectId` in its own `.reticle.json`, and discovery cannot see it — it walks out from A's
|
|
182
|
+
* `process.cwd()` and never crosses into a sibling checkout — so this function is reached with a
|
|
183
|
+
* NAMED project and the daemon's root came back anyway. B's intents, flows and runs were written
|
|
184
|
+
* into A's checkout, and the returned path said A, so nothing looked wrong.
|
|
185
|
+
*
|
|
186
|
+
* A real identity always wins. The daemon's root is only the answer when it is that identity's own
|
|
187
|
+
* root, or when no identity was named at all.
|
|
188
|
+
*
|
|
142
189
|
* `projectId` arrives in HELLO from the page, so it is untrusted input on a path join and is held
|
|
143
|
-
* to one safe segment — the same guard session ids and flow names already pass.
|
|
190
|
+
* to one safe segment — the same guard session ids and flow names already pass. `daemonProjectId`
|
|
191
|
+
* is read from the daemon's own `.reticle.json` and gets the same treatment, because equality
|
|
192
|
+
* against a guarded value is only as strong as the weaker side.
|
|
144
193
|
*/
|
|
145
194
|
export function unmatchedRoot(query) {
|
|
146
|
-
if (query.daemonIsProject)
|
|
195
|
+
if (query.daemonIsProject && isTheDaemonsOwnProject(query.daemonProjectId, query.projectId))
|
|
147
196
|
return query.daemonRoot;
|
|
148
197
|
const id = query.projectId ?? '';
|
|
149
198
|
const named = SAFE_SEGMENT_PATTERN.test(id) && !id.includes('..');
|
|
150
199
|
return join(query.home, ReticleDir.ROOT, UNMATCHED_SUBDIR, named ? id : unnamedSegment(query.origin));
|
|
151
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* May the daemon write this session's artifacts into its own `.reticle/`?
|
|
203
|
+
*
|
|
204
|
+
* Two ways to qualify, and the difference between them is the whole bug: belonging to the directory
|
|
205
|
+
* is not the same as owning it.
|
|
206
|
+
*
|
|
207
|
+
* - Nobody named a project (a pre-2.0 SDK sends no projectId). There is no identity to disagree
|
|
208
|
+
* with, and the daemon's own root is the only thing on offer.
|
|
209
|
+
* - The named project IS the daemon's own, and the daemon can prove it — `.reticle.json` sits in
|
|
210
|
+
* its directory and declares the same id. Both sides go through the same segment guard, because
|
|
211
|
+
* an equality between a guarded value and an unguarded one is only as strong as the loose side.
|
|
212
|
+
*
|
|
213
|
+
* A NAMED project the daemon cannot match declines, including when it has a `.reticle/` but no
|
|
214
|
+
* readable id. Declining costs the session its home directory and buys the guarantee that one
|
|
215
|
+
* checkout never receives another's ledger; the alternative is a wrong answer that reports success.
|
|
216
|
+
*
|
|
217
|
+
* The two ids are REQUIRED parameters rather than one object, and that is load-bearing. Taking a
|
|
218
|
+
* partial object let a caller omit a field and still compile — and an omitted `projectId` reads as
|
|
219
|
+
* "nobody named a project", which answers `true` and puts the session straight back into the daemon's
|
|
220
|
+
* checkout. That is the original defect, reachable by a typo rather than by a decision.
|
|
221
|
+
*/
|
|
222
|
+
function isTheDaemonsOwnProject(daemonProjectId, projectId) {
|
|
223
|
+
if (projectId === undefined || 0 === projectId.length)
|
|
224
|
+
return true;
|
|
225
|
+
if (daemonProjectId === undefined || 0 === daemonProjectId.length)
|
|
226
|
+
return false;
|
|
227
|
+
return (daemonProjectId === projectId &&
|
|
228
|
+
SAFE_SEGMENT_PATTERN.test(daemonProjectId) &&
|
|
229
|
+
!daemonProjectId.includes('..'));
|
|
230
|
+
}
|
|
152
231
|
/** Hashed, not spelled: an origin carries a host and a port, and neither belongs in a path segment. */
|
|
153
232
|
function unnamedSegment(origin) {
|
|
154
233
|
if (origin === undefined || 0 === origin.trim().length)
|