@reticlehq/server 3.6.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/SKILL.md +122 -123
- 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 +18 -2
- package/dist/command/cli/adhoc-verdict.js +70 -6
- 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/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 +28 -105
- 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 +34 -4
- package/dist/command/cli/cli-verify.js +81 -12
- package/dist/command/cli/cloud-cli.d.ts +13 -0
- package/dist/command/cli/cloud-cli.js +145 -333
- 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 +15 -1
- package/dist/command/cli/cloud-login.js +67 -13
- 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/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/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 +11 -2
- 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.js +1 -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 +29 -8
- 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 -178
- 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 +14 -0
- package/dist/command/cli.js +103 -83
- package/dist/command/daemon/daemon-resilience.js +4 -4
- package/dist/command/daemon/daemon-resolve.d.ts +9 -1
- package/dist/command/daemon/daemon-resolve.js +49 -10
- 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/dev-server-log-cap.js +2 -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 +10 -1
- package/dist/command/setup/init/init-port.js +22 -2
- package/dist/command/setup/init/init-runtime.d.ts +12 -2
- package/dist/command/setup/init/init-runtime.js +96 -37
- package/dist/command/setup/node-effects.d.ts +2 -0
- package/dist/command/setup/node-effects.js +36 -8
- 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 +1 -1
- package/dist/command/setup/run-setup.d.ts +29 -9
- package/dist/command/setup/run-setup.js +34 -28
- package/dist/command/setup/setup-command.d.ts +23 -0
- package/dist/command/setup/setup-command.js +57 -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 +38 -2
- package/dist/features/crawl/crawl.js +14 -1
- package/dist/features/exhaust/ledger.d.ts +14 -14
- package/dist/features/harness/drive-report.d.ts +39 -5
- package/dist/features/harness/drive-report.js +271 -54
- 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 +1 -1
- package/dist/features/harness/drivers.js +1 -1
- 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/goals.d.ts +68 -7
- package/dist/features/harness/goals.js +289 -36
- package/dist/features/harness/harness.d.ts +30 -1
- package/dist/features/harness/harness.js +141 -7
- package/dist/features/harness/platform/local-apps.d.ts +36 -3
- package/dist/features/harness/platform/local-apps.js +81 -5
- 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 +8 -2
- package/dist/features/harness/platform/remote-drive.js +22 -9
- package/dist/features/harness/platform/script.d.ts +57 -11
- package/dist/features/harness/platform/script.js +144 -12
- package/dist/features/harness/platform/server-driver.d.ts +17 -4
- package/dist/features/harness/platform/server-driver.js +29 -26
- package/dist/features/harness/platform-config.d.ts +38 -5
- package/dist/features/harness/platform-config.js +93 -8
- package/dist/features/harness/route-guess.d.ts +13 -0
- package/dist/features/harness/route-guess.js +49 -0
- package/dist/features/harness/script/script-run.d.ts +30 -1
- package/dist/features/harness/script/script-run.js +355 -48
- 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.js +22 -14
- 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 +21 -2
- package/dist/hooks/driven-by.js +42 -2
- package/dist/hooks/hook-bus.d.ts +4 -0
- package/dist/hooks/hook-bus.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +26 -183
- 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 +20 -4
- 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.js +25 -9
- package/dist/judgement/runs/artifact/run-store.d.ts +13 -1
- package/dist/judgement/runs/artifact/run-store.js +175 -8
- 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 +53 -5
- package/dist/judgement/runs/drive-run.js +234 -20
- package/dist/judgement/runs/resume.d.ts +44 -0
- package/dist/judgement/runs/resume.js +137 -0
- package/dist/judgement/runs/reticle-runner.js +5 -2
- 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 +2 -0
- package/dist/judgement/runs/verification-sync.js +36 -15
- package/dist/language/flows/decision.js +2 -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 +7 -2
- package/dist/language/flows/flow-anchor.js +10 -3
- 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 +37 -3
- package/dist/language/flows/flow-replay-run.js +16 -18
- package/dist/language/flows/flow-replay-types.d.ts +1 -1
- package/dist/language/flows/flow-replay.d.ts +18 -1
- package/dist/language/flows/flow-replay.js +22 -7
- package/dist/language/flows/flow-result.d.ts +22 -1
- package/dist/language/flows/flow-result.js +47 -2
- package/dist/language/flows/flow-step-runners.d.ts +2 -0
- package/dist/language/flows/flow-step-runners.js +12 -6
- package/dist/language/flows/flow-store-for-session.js +5 -1
- package/dist/language/flows/flow-tools.d.ts +10 -0
- package/dist/language/flows/flow-tools.js +61 -62
- package/dist/language/flows/flows.d.ts +16 -1
- package/dist/language/flows/flows.js +69 -10
- package/dist/language/flows/narrow-write.d.ts +7 -0
- package/dist/language/flows/narrow-write.js +36 -1
- package/dist/language/flows/recording/recording-backtrack.js +14 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +29 -1
- package/dist/language/flows/recording/tape/recordings.js +60 -10
- package/dist/language/flows/replay.js +29 -6
- package/dist/language/flows/single-replay.d.ts +7 -0
- package/dist/language/flows/single-replay.js +26 -0
- package/dist/language/flows/verify-change-tools.d.ts +1 -0
- package/dist/language/flows/verify-change-tools.js +200 -193
- package/dist/memory/cloud/cloud-config.d.ts +10 -1
- package/dist/memory/cloud/cloud-config.js +19 -5
- package/dist/memory/cloud/cloud-sync.js +3 -3
- package/dist/memory/cloud/harness-config.d.ts +49 -0
- package/dist/memory/cloud/harness-config.js +16 -20
- package/dist/memory/cloud/memory-scope.d.ts +17 -0
- package/dist/memory/cloud/memory-scope.js +22 -0
- package/dist/memory/cloud/sync-cycle.d.ts +32 -4
- package/dist/memory/cloud/sync-cycle.js +113 -12
- package/dist/memory/cloud/sync-daemon.d.ts +11 -0
- package/dist/memory/cloud/sync-daemon.js +114 -4
- package/dist/memory/cloud/sync-disk.js +26 -2
- 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 +16 -1
- package/dist/memory/impact/account-watch.js +26 -1
- package/dist/memory/impact/impact-store.d.ts +1 -4
- package/dist/memory/impact/impact-store.js +32 -42
- 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 +12 -0
- package/dist/memory/intent/intent-store.d.ts +1 -0
- package/dist/memory/intent/intent-tools.js +42 -2
- 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/journal-recorder.js +46 -3
- package/dist/memory/journal/on-disk/retention.d.ts +5 -0
- package/dist/memory/journal/on-disk/retention.js +27 -2
- package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
- package/dist/memory/journal/session-end.d.ts +15 -1
- package/dist/memory/journal/session-end.js +38 -7
- package/dist/memory/project/artifact-root-resolver.js +33 -5
- package/dist/memory/project/artifact-root.d.ts +4 -3
- package/dist/memory/project/artifact-root.js +2 -2
- package/dist/memory/project/dir/reticle-dir.d.ts +14 -0
- package/dist/memory/project/dir/reticle-dir.js +23 -2
- package/dist/memory/project/link-directory.d.ts +15 -0
- package/dist/memory/project/link-directory.js +44 -0
- 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 +2 -0
- package/dist/memory/project/session-root.js +9 -1
- package/dist/memory/project/sync-status.d.ts +46 -0
- package/dist/memory/project/sync-status.js +135 -1
- package/dist/memory/recall/memory-tools.js +46 -3
- 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.js +36 -8
- package/dist/portal/bridge/bridge.d.ts +9 -5
- package/dist/portal/bridge/bridge.js +66 -23
- package/dist/portal/bridge/harness-request.d.ts +23 -0
- package/dist/portal/bridge/harness-request.js +27 -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 +2 -0
- package/dist/portal/input/real-input.js +13 -10
- package/dist/portal/pool/browser-pool.d.ts +48 -0
- package/dist/portal/pool/browser-pool.js +120 -2
- 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 +27 -5
- package/dist/portal/pool/playwright-launcher.js +59 -14
- package/dist/portal/pool/pool-contract.d.ts +21 -1
- 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.js +6 -1
- 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 +9 -0
- package/dist/portal/session/dev-server/dev-command.js +27 -1
- 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/human/live-control.d.ts +9 -2
- package/dist/portal/session/human/live-control.js +12 -3
- package/dist/portal/session/live-control-tools.js +82 -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 +1 -1
- package/dist/portal/session/no-session-diagnosis.js +31 -9
- package/dist/portal/session/no-session-next-action.d.ts +20 -0
- package/dist/portal/session/no-session-next-action.js +134 -61
- package/dist/portal/session/no-session-watch.js +179 -22
- package/dist/portal/session/presence/session-lease.js +1 -1
- package/dist/portal/session/recorded-gaps.d.ts +97 -0
- package/dist/portal/session/recorded-gaps.js +251 -0
- package/dist/portal/session/session-health.d.ts +11 -0
- package/dist/portal/session/session-health.js +3 -1
- package/dist/portal/session/session-info.js +2 -1
- package/dist/portal/session/session-manager.d.ts +18 -0
- package/dist/portal/session/session-manager.js +54 -3
- package/dist/portal/session/session-successor.d.ts +8 -0
- package/dist/portal/session/session-successor.js +1 -1
- package/dist/portal/session/session.d.ts +15 -28
- package/dist/portal/session/session.js +60 -61
- 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 +7 -1
- package/dist/start-wiring.d.ts +63 -0
- package/dist/start-wiring.js +169 -0
- package/dist/status-payload.d.ts +13 -1
- package/dist/status-payload.js +13 -1
- package/dist/surface/http-server.js +7 -1
- 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 +4 -0
- package/dist/surface/mcp/mcp-proxy.js +29 -8
- package/dist/surface/mcp/mcp.d.ts +23 -0
- package/dist/surface/mcp/mcp.js +34 -4
- 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.js +22 -14
- package/dist/surface/mcp/shared-params.js +1 -1
- 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/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-sequence-tool.js +136 -27
- package/dist/surface/tools/act-tools.d.ts +0 -9
- package/dist/surface/tools/act-tools.js +48 -34
- 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.js +25 -15
- package/dist/surface/tools/browser-tools.js +43 -1
- package/dist/surface/tools/chat-drives.d.ts +75 -0
- package/dist/surface/tools/chat-drives.js +265 -21
- 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 +10 -0
- package/dist/surface/tools/empty-session-list.js +45 -5
- package/dist/surface/tools/error-recovery.d.ts +8 -0
- package/dist/surface/tools/error-recovery.js +69 -5
- package/dist/surface/tools/explore-tools.d.ts +23 -2
- package/dist/surface/tools/explore-tools.js +377 -74
- package/dist/surface/tools/feedback-tools.d.ts +2 -3
- package/dist/surface/tools/feedback-tools.js +4 -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 +52 -73
- package/dist/surface/tools/harness-explore.js +177 -146
- 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 +5 -0
- package/dist/surface/tools/harness-plan.js +7 -0
- package/dist/surface/tools/harness-script.d.ts +22 -0
- package/dist/surface/tools/harness-script.js +256 -38
- 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 +8 -0
- package/dist/surface/tools/harness-toolset.js +27 -4
- 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 +220 -93
- 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 +20 -0
- package/dist/surface/tools/lease-seed.js +45 -1
- package/dist/surface/tools/lease-tools.d.ts +10 -1
- package/dist/surface/tools/lease-tools.js +119 -69
- package/dist/surface/tools/live-call-text.js +2 -2
- 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 +11 -1
- package/dist/surface/tools/next-step.js +73 -18
- package/dist/surface/tools/observe-tools.js +43 -5
- package/dist/surface/tools/platform-moment.d.ts +60 -0
- package/dist/surface/tools/platform-moment.js +132 -4
- package/dist/surface/tools/read-tools.js +65 -14
- 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 +68 -2
- package/dist/surface/tools/tool-kit.js +73 -1
- package/dist/surface/tools/tools.js +25 -13
- 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 +27 -6
- package/dist/telemetry/hud-metrics.js +99 -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 +26 -2
- package/dist/telemetry/telemetry.js +102 -21
- package/dist/telemetry/verification-of.d.ts +3 -1
- package/dist/telemetry/verification-of.js +12 -4
- package/dist/wire-journal.d.ts +2 -2
- package/dist/wire-journal.js +16 -5
- package/docs/README.md +2 -2
- package/docs/actions.mdx +6 -3
- package/docs/adapters.md +1 -1
- package/docs/agent-cheatsheet.md +24 -27
- package/docs/architecture.md +5 -5
- package/docs/autodrive.md +13 -12
- 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 +8 -3
- package/docs/cli/drive.mdx +12 -12
- 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 +32 -15
- 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 +13 -4
- 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 +3 -2
- package/docs/cli.mdx +110 -41
- package/docs/debugging.md +3 -3
- package/docs/deploy-checks.md +1 -1
- package/docs/desktop.mdx +5 -3
- package/docs/enterprise.md +11 -10
- package/docs/environment.mdx +5 -2
- 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 +66 -64
- package/docs/human-control.md +15 -14
- package/docs/index.mdx +15 -43
- package/docs/install-agentic.mdx +22 -37
- package/docs/install-manual.mdx +22 -12
- 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 +8 -8
- package/docs/packages/test.mdx +4 -10
- package/docs/packages/vite-plugin.mdx +11 -4
- package/docs/packages.mdx +11 -15
- package/docs/platform-integration.md +6 -5
- package/docs/predicates.mdx +40 -24
- package/docs/quickstart.mdx +58 -29
- 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 +22 -24
- 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 +13 -4
- 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 +51 -45
- 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 +7 -5
- package/docs/what-is-reticle.mdx +2 -2
- package/docs/why-reticle.mdx +1 -1
- package/package.json +6 -5
- package/dist/command/cli/demo/demo-page.d.ts +0 -78
- package/dist/command/cli/demo/demo-page.js +0 -249
- package/dist/command/cli/demo/demo-run.d.ts +0 -38
- package/dist/command/cli/demo/demo-run.js +0 -138
- 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/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
|
@@ -21,12 +21,21 @@
|
|
|
21
21
|
* laptop on a train would otherwise fill the log with the same line four hundred times and teach
|
|
22
22
|
* everyone to ignore it.
|
|
23
23
|
*/
|
|
24
|
+
import { reticleDirPaths } from '../project/dir/reticle-dir.js';
|
|
24
25
|
import { syncRequest } from './cloud-sync.js';
|
|
25
26
|
import { log } from '../../log.js';
|
|
27
|
+
import { homedir } from 'node:os';
|
|
26
28
|
import { emitSyncHook } from '../../hooks/hook-emit.js';
|
|
29
|
+
import { MemoryReadHeader, MemoryReadPurpose, memoryReadUrl, scopedMemoryEntries, } from './memory-scope.js';
|
|
30
|
+
import { IntentShardStore } from '../intent/intent-shard-store.js';
|
|
31
|
+
import { applySettledWords, keptWordsInRequest, settledWordsIn, } from '../intent/settled-words.js';
|
|
32
|
+
import { createNodeFileSystem } from '../project/fs/fs-port.js';
|
|
33
|
+
import { flushPendingWrites } from '../../hooks/hook-bus.js';
|
|
27
34
|
import { describeSync, runSyncCycle } from './sync-cycle.js';
|
|
28
35
|
import { diskSink, diskSource, readCloudState } from './sync-disk.js';
|
|
29
|
-
import { resolveProjectCloud } from './cloud-config.js';
|
|
36
|
+
import { CLOUD_LINK_FILE, resolveProjectCloud } from './cloud-config.js';
|
|
37
|
+
import { watch } from 'node:fs';
|
|
38
|
+
import { unmatchedRoots, unsyncedRoots } from './unsynced-roots.js';
|
|
30
39
|
/** How often the daemon cycles. See the note above on why this is a constant and not a curve. */
|
|
31
40
|
const DAEMON_SYNC_INTERVAL_MS = 60_000;
|
|
32
41
|
/**
|
|
@@ -100,18 +109,62 @@ export function startSyncDaemon(deps) {
|
|
|
100
109
|
* so too. Once per transition, never per tick — a line every minute is a line people stop reading.
|
|
101
110
|
*/
|
|
102
111
|
let wasLinked;
|
|
112
|
+
/** The project the last cycle synced to: a re-link to another one is announced too. */
|
|
113
|
+
let wasProject;
|
|
114
|
+
/** The last count logged per root: a root is announced when its count changes, never per tick. */
|
|
115
|
+
const announced = new Map();
|
|
116
|
+
let unsynced = [];
|
|
117
|
+
/** Which roots pushed this cycle, and whether each is linked by its own link file. */
|
|
118
|
+
let rootsLinked = new Map();
|
|
119
|
+
/** The linked roots that send flows: a link with `sync.flows` off keeps every flow local. */
|
|
120
|
+
let rootsSendFlows = new Set();
|
|
121
|
+
/**
|
|
122
|
+
* Count what every root still owes, and say once per change where runs sit that nothing sends.
|
|
123
|
+
* Linked roots are listed too (the platform report and `doctor` read them), but only an unlinked
|
|
124
|
+
* one earns a log line: a linked root's pending runs are the next cycle's job.
|
|
125
|
+
*/
|
|
126
|
+
const refreshUnsynced = async () => {
|
|
127
|
+
const linked = rootsLinked;
|
|
128
|
+
const sendsFlows = rootsSendFlows;
|
|
129
|
+
unsynced = await unsyncedRoots(
|
|
130
|
+
// With what no project claimed, which no cycle pushes: the dashboard's unsent list left it out.
|
|
131
|
+
[...linked.keys(), ...unmatchedRoots(homedir())], (r) => Promise.resolve(true === linked.get(r)), (r) => Promise.resolve(sendsFlows.has(r)));
|
|
132
|
+
for (const entry of unsynced) {
|
|
133
|
+
const held = entry.runs + entry.flows;
|
|
134
|
+
if (entry.linked || announced.get(entry.root) === held)
|
|
135
|
+
continue;
|
|
136
|
+
announced.set(entry.root, held);
|
|
137
|
+
log('reticle_cloud_unsynced_root', {
|
|
138
|
+
root: entry.root,
|
|
139
|
+
runs: entry.runs,
|
|
140
|
+
flows: entry.flows,
|
|
141
|
+
linked: entry.linked,
|
|
142
|
+
fix: "run `reticle connect` in that folder's project: this work is on this machine only",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
103
146
|
/** One root's bundle. The state, source and sink are already per-root; only the caller was not. */
|
|
104
147
|
const pushRoot = async (root, cloud) => {
|
|
105
148
|
if (null === cloud.config)
|
|
106
149
|
return undefined;
|
|
107
150
|
const full = diskSource(root);
|
|
151
|
+
// The team's decisions come down too: an author's record kept the words a maintainer set aside,
|
|
152
|
+
// and re-sent them every cycle as a new disagreement.
|
|
153
|
+
if (cloud.policy.memory)
|
|
154
|
+
await pullSettledWords(root, cloud.config, cloud.projectId).catch(() => 0);
|
|
108
155
|
return runSyncCycle({
|
|
109
156
|
config: cloud.config,
|
|
157
|
+
...(cloud.link === undefined ? {} : { link: cloud.link }),
|
|
158
|
+
// Flows deleted here are deleted there, for the project they were sent to.
|
|
159
|
+
...(cloud.policy.flows && null !== cloud.projectId ? { projectId: cloud.projectId } : {}),
|
|
110
160
|
source: {
|
|
111
161
|
runs: () => (cloud.policy.runs ? full.runs() : []),
|
|
112
162
|
flows: () => (cloud.policy.flows ? full.flows() : []),
|
|
113
163
|
capsules: () => (cloud.policy.capsules ? full.capsules() : []),
|
|
114
164
|
derived: (kind) => (cloud.policy.memory ? full.derived(kind) : undefined),
|
|
165
|
+
...(cloud.policy.screenshots && full.screenshot !== undefined
|
|
166
|
+
? { screenshot: full.screenshot }
|
|
167
|
+
: {}),
|
|
115
168
|
},
|
|
116
169
|
sink: diskSink(root),
|
|
117
170
|
state: readCloudState(root),
|
|
@@ -119,6 +172,23 @@ export function startSyncDaemon(deps) {
|
|
|
119
172
|
request,
|
|
120
173
|
});
|
|
121
174
|
};
|
|
175
|
+
/** Bring the dashboard's memory decisions down to this root's records. Returns how many changed. */
|
|
176
|
+
const pullSettledWords = async (root, config, projectId) => {
|
|
177
|
+
const res = await request(memoryReadUrl(config.url, { projectId }), {
|
|
178
|
+
method: 'GET',
|
|
179
|
+
// The sync, not an agent: without this every cycle counted as an agent consulting memory.
|
|
180
|
+
headers: {
|
|
181
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
182
|
+
[MemoryReadHeader.PURPOSE]: MemoryReadPurpose.SYNC,
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
if (200 !== res.status)
|
|
186
|
+
return 0;
|
|
187
|
+
const entries = scopedMemoryEntries(JSON.parse(res.text), projectId) ?? [];
|
|
188
|
+
const settled = settledWordsIn(entries);
|
|
189
|
+
await keptWordsInRequest(reticleDirPaths(root).request, settled);
|
|
190
|
+
return applySettledWords(new IntentShardStore(createNodeFileSystem(), root, { now }), settled);
|
|
191
|
+
};
|
|
122
192
|
/**
|
|
123
193
|
* Push every other linked root this machine knows about.
|
|
124
194
|
*
|
|
@@ -143,14 +213,19 @@ export function startSyncDaemon(deps) {
|
|
|
143
213
|
for (const root of list) {
|
|
144
214
|
if (root === deps.reticleRoot)
|
|
145
215
|
continue;
|
|
216
|
+
rootsLinked.set(root, false);
|
|
146
217
|
try {
|
|
147
218
|
const cloud = await cloudFor(root);
|
|
148
219
|
// Linked HERE, by its own link file. An unlinked root resolves to the environment's key,
|
|
149
220
|
// which is the right answer for the root this daemon stands in and somebody else's project
|
|
150
221
|
// for every other one: a daemon started with RETICLE_API_KEY pushed every project this
|
|
151
222
|
// machine had seen into that one, a status and a pull per root on every cycle.
|
|
223
|
+
// Never silently: `refreshUnsynced` names this root if it holds runs.
|
|
152
224
|
if (null === cloud.config || null === cloud.projectId)
|
|
153
225
|
continue;
|
|
226
|
+
rootsLinked.set(root, true);
|
|
227
|
+
if (cloud.policy.flows)
|
|
228
|
+
rootsSendFlows.add(root);
|
|
154
229
|
const report = await pushRoot(root, cloud);
|
|
155
230
|
if (report === undefined)
|
|
156
231
|
continue;
|
|
@@ -180,11 +255,18 @@ export function startSyncDaemon(deps) {
|
|
|
180
255
|
if (running)
|
|
181
256
|
return undefined;
|
|
182
257
|
running = true;
|
|
258
|
+
rootsLinked = new Map();
|
|
259
|
+
rootsSendFlows = new Set();
|
|
183
260
|
try {
|
|
184
261
|
const cloud = await deps.cloud();
|
|
185
262
|
const linked = null !== cloud.config;
|
|
186
|
-
|
|
263
|
+
const own = deps.reticleRoot;
|
|
264
|
+
rootsLinked.set(own, linked);
|
|
265
|
+
if (linked && cloud.policy.flows)
|
|
266
|
+
rootsSendFlows.add(own);
|
|
267
|
+
if (linked !== wasLinked || (linked && cloud.projectId !== wasProject)) {
|
|
187
268
|
wasLinked = linked;
|
|
269
|
+
wasProject = cloud.projectId;
|
|
188
270
|
if (linked)
|
|
189
271
|
log('reticle_cloud_linked', {
|
|
190
272
|
projectId: cloud.projectId,
|
|
@@ -195,7 +277,7 @@ export function startSyncDaemon(deps) {
|
|
|
195
277
|
else
|
|
196
278
|
log('reticle_cloud_unlinked', {
|
|
197
279
|
root: deps.reticleRoot,
|
|
198
|
-
fix: 'run `reticle
|
|
280
|
+
fix: 'run `reticle connect` in this directory, or start the daemon in the linked one — nothing is being synced from here',
|
|
199
281
|
});
|
|
200
282
|
}
|
|
201
283
|
// Every OTHER linked root, first. Their failures are logged and never abort this daemon's own
|
|
@@ -253,6 +335,7 @@ export function startSyncDaemon(deps) {
|
|
|
253
335
|
return undefined;
|
|
254
336
|
}
|
|
255
337
|
finally {
|
|
338
|
+
await refreshUnsynced().catch(() => undefined);
|
|
256
339
|
running = false;
|
|
257
340
|
}
|
|
258
341
|
};
|
|
@@ -272,20 +355,30 @@ export function startSyncDaemon(deps) {
|
|
|
272
355
|
timer.unref?.();
|
|
273
356
|
};
|
|
274
357
|
schedule(FIRST_CYCLE_DELAY_MS);
|
|
358
|
+
// A nudge, not an immediate cycle: the link file and its key are written one after the other.
|
|
359
|
+
const unwatch = deps.watchLink?.(() => schedule(NUDGE_DELAY_MS));
|
|
275
360
|
return {
|
|
276
|
-
|
|
361
|
+
// A sync somebody asked for writes what is held on a timer first: a drive's run waits a few
|
|
362
|
+
// seconds after its last verdict, and a push straight after `session end` sent "0 of 0".
|
|
363
|
+
syncNow: async () => {
|
|
364
|
+
await flushPendingWrites();
|
|
365
|
+
return cycle();
|
|
366
|
+
},
|
|
367
|
+
unsyncedRoots: () => unsynced,
|
|
277
368
|
nudge: () => {
|
|
278
369
|
// A cycle already in flight will not pick this up, so the nudge is still scheduled behind it
|
|
279
370
|
// rather than dropped — otherwise the run that arrived during a slow cycle waits a full tick.
|
|
280
371
|
schedule(NUDGE_DELAY_MS);
|
|
281
372
|
},
|
|
282
373
|
stop: () => {
|
|
374
|
+
unwatch?.();
|
|
283
375
|
stopped = true;
|
|
284
376
|
if (timer !== undefined)
|
|
285
377
|
clearTimeout(timer);
|
|
286
378
|
timer = undefined;
|
|
287
379
|
},
|
|
288
380
|
flush: async () => {
|
|
381
|
+
unwatch?.();
|
|
289
382
|
stopped = true;
|
|
290
383
|
if (timer !== undefined)
|
|
291
384
|
clearTimeout(timer);
|
|
@@ -320,5 +413,22 @@ export function attachCloudSync(deps) {
|
|
|
320
413
|
// reporting nothing — indistinguishable, on the dashboard, from nobody having verified anything.
|
|
321
414
|
otherRoots: () => Promise.resolve(deps.otherRoots()),
|
|
322
415
|
cloudFor: (root) => resolveProjectCloud(deps.fs, root, deps.homeDir, deps.env),
|
|
416
|
+
// ponytail: this root only; a sibling root's re-link is still read on the next tick.
|
|
417
|
+
watchLink: (onChange) => watchLinkFile(deps.reticleRoot, onChange),
|
|
323
418
|
});
|
|
324
419
|
}
|
|
420
|
+
/** Watch a root for its link file changing. A root with no `.reticle` yet is picked up per tick. */
|
|
421
|
+
function watchLinkFile(reticleRoot, onChange) {
|
|
422
|
+
try {
|
|
423
|
+
const watcher = watch(reticleRoot, (_event, name) => {
|
|
424
|
+
if (CLOUD_LINK_FILE === String(name))
|
|
425
|
+
onChange();
|
|
426
|
+
});
|
|
427
|
+
watcher.on('error', () => watcher.close());
|
|
428
|
+
watcher.unref();
|
|
429
|
+
return () => watcher.close();
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
return () => undefined;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { shareableRun } from '@reticlehq/core/artifacts';
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from 'node:fs';
|
|
15
|
-
import { dirname, join } from 'node:path';
|
|
15
|
+
import { basename, dirname, join } from 'node:path';
|
|
16
|
+
import { stepShotDir } from '../project/dir/reticle-dir.js';
|
|
16
17
|
import { IntentDir, ReticleDir } from '@reticlehq/core';
|
|
17
18
|
import { DERIVED_RECORDS, } from './sync-cycle.js';
|
|
18
19
|
import { subjectFor } from '../intent/intent-subject.js';
|
|
@@ -102,6 +103,8 @@ const INTENT_INDEX = IntentDir.INDEX_FILE;
|
|
|
102
103
|
* Returns undefined when there is no memory at all, so a project with nothing to say sends nothing
|
|
103
104
|
* rather than an empty envelope the server has to store.
|
|
104
105
|
*/
|
|
106
|
+
/** The longest reason the platform keeps for a statement. */
|
|
107
|
+
const MAX_REASON = 2000;
|
|
105
108
|
function readIntent(reticleRoot) {
|
|
106
109
|
const legacy = readJson(join(reticleRoot, ReticleDir.INTENT_FILE));
|
|
107
110
|
const merged = {
|
|
@@ -139,7 +142,16 @@ function readIntent(reticleRoot) {
|
|
|
139
142
|
* answers start disagreeing — and the one with the evidence should be the one that decides.
|
|
140
143
|
* Records that already name a subject keep it: an agent's explicit choice outranks inference.
|
|
141
144
|
*/
|
|
142
|
-
const placed = Object.fromEntries(Object.entries(merged).map(([id,
|
|
145
|
+
const placed = Object.fromEntries(Object.entries(merged).map(([id, given]) => {
|
|
146
|
+
// The record's `why` is what the platform keeps as `reason`.
|
|
147
|
+
const why = given.why;
|
|
148
|
+
// The run that proved it, under the name the platform reads (it does not read `provenBy`).
|
|
149
|
+
const run = given.provenBy?.runId;
|
|
150
|
+
const record = {
|
|
151
|
+
...given,
|
|
152
|
+
...('string' === typeof why && 0 < why.length ? { reason: why.slice(0, MAX_REASON) } : {}),
|
|
153
|
+
...('string' === typeof run && 0 < run.length ? { provedBy: run } : {}),
|
|
154
|
+
};
|
|
143
155
|
const held = record;
|
|
144
156
|
if ('string' === typeof held.subject && held.subject.length > 0)
|
|
145
157
|
return [id, record];
|
|
@@ -178,6 +190,18 @@ export function diskSource(reticleRoot) {
|
|
|
178
190
|
capsules: () => readJsonDir(join(reticleRoot, ReticleDir.CAPSULES_SUBDIR)),
|
|
179
191
|
// Intent is the only derived record that is no longer one file. See readIntent.
|
|
180
192
|
derived: (kind) => 'intent' === kind ? readIntent(reticleRoot) : readJson(join(reticleRoot, DERIVED_FILE[kind])),
|
|
193
|
+
// The file name comes from a run artifact on disk, so it is held to one segment of the run's
|
|
194
|
+
// own directory before it is read.
|
|
195
|
+
screenshot: (runId, file) => {
|
|
196
|
+
try {
|
|
197
|
+
if (basename(file) !== file)
|
|
198
|
+
return undefined;
|
|
199
|
+
return new Uint8Array(readFileSync(join(stepShotDir(reticleRoot, runId), file)));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
},
|
|
181
205
|
};
|
|
182
206
|
}
|
|
183
207
|
/** What the cycle writes back into `.reticle/`. */
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** This machine's record of what went up, kept in `.reticle/cloud-state.json`. */
|
|
2
|
+
export interface ScreenshotSyncState {
|
|
3
|
+
/** Per run, `seq:sha256` of every screenshot the platform holds or refused for good. */
|
|
4
|
+
sentScreenshots?: Record<string, string[]>;
|
|
5
|
+
/** Runs whose screenshots an older platform could not take. Kept local, never offered again. */
|
|
6
|
+
screenshotsHeldLocal?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface ScreenshotSyncDeps {
|
|
9
|
+
/** Every run on disk, with the payload the sync just sent. */
|
|
10
|
+
runs: ReadonlyArray<{
|
|
11
|
+
runId: string;
|
|
12
|
+
payload: unknown;
|
|
13
|
+
}>;
|
|
14
|
+
/** Whether the platform holds this run now. */
|
|
15
|
+
accepted: (runId: string) => boolean;
|
|
16
|
+
/** One image from the run's directory, or undefined when it is gone. */
|
|
17
|
+
readImage: (runId: string, file: string) => Uint8Array | undefined;
|
|
18
|
+
state: ScreenshotSyncState;
|
|
19
|
+
/** POST a JSON body to a path on the platform, with the sync's own credentials. */
|
|
20
|
+
post: (path: string, body: string) => Promise<{
|
|
21
|
+
status: number;
|
|
22
|
+
text: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export interface ScreenshotSyncResult {
|
|
26
|
+
sent: number;
|
|
27
|
+
state: ScreenshotSyncState;
|
|
28
|
+
/** Why this cycle stopped early, when it did. Kept local; the next cycle tries again. */
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Send what is owed. Never throws; the record is bounded by the runs still on disk. */
|
|
32
|
+
export declare function syncScreenshots(deps: ScreenshotSyncDeps): Promise<ScreenshotSyncResult>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The step screenshots of each run the platform has accepted, sent once each.
|
|
3
|
+
*
|
|
4
|
+
* A run goes up first, through the sync; its screenshots follow, one request per image, to
|
|
5
|
+
* `POST /v1/runs/:runId/screenshots` — only once the platform holds the run, because the endpoint
|
|
6
|
+
* answers 404 for a run it does not. What was sent is remembered by `seq:sha256`, like runs by hash,
|
|
7
|
+
* so a quiet cycle sends nothing.
|
|
8
|
+
*
|
|
9
|
+
* An OLDER platform has no such endpoint and answers 404 or 405. That run's screenshots stay on
|
|
10
|
+
* this machine and are not offered again: the alternative is one refused request per image on every
|
|
11
|
+
* cycle, forever. A newer platform picks up the next run's. Anything else that failed (the network,
|
|
12
|
+
* a 5xx) is simply retried next cycle.
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
import { RunScreenshotSchema, STEP_SCREENSHOT } from '@reticlehq/core';
|
|
16
|
+
const SCREENSHOTS_PATH = (runId) => `/v1/runs/${encodeURIComponent(runId)}/screenshots`;
|
|
17
|
+
const HTTP_CREATED = 201;
|
|
18
|
+
const HTTP_OK = 200;
|
|
19
|
+
/** An older platform: no route (404), or a route that takes no POST (405). */
|
|
20
|
+
const NOT_THERE = new Set([404, 405]);
|
|
21
|
+
/** Refused for good: too large (413), too many for one run (409). Retrying cannot change it. */
|
|
22
|
+
const REFUSED_FOR_GOOD = new Set([409, 413]);
|
|
23
|
+
/** The screenshots a run artifact lists, read defensively: a malformed entry is skipped. */
|
|
24
|
+
function listed(payload) {
|
|
25
|
+
const raw = 'object' === typeof payload && null !== payload
|
|
26
|
+
? payload['screenshots']
|
|
27
|
+
: undefined;
|
|
28
|
+
if (!Array.isArray(raw))
|
|
29
|
+
return [];
|
|
30
|
+
return raw.flatMap((entry) => {
|
|
31
|
+
const parsed = RunScreenshotSchema.safeParse(entry);
|
|
32
|
+
return parsed.success ? [parsed.data] : [];
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const keyOf = (shot) => `${String(shot.seq)}:${shot.sha256}`;
|
|
36
|
+
/** The upload body for one screenshot — the shape `ScreenshotUploadSchema` reads. */
|
|
37
|
+
function bodyOf(shot, image) {
|
|
38
|
+
return {
|
|
39
|
+
seq: shot.seq,
|
|
40
|
+
file: shot.file,
|
|
41
|
+
...(shot.step === undefined ? {} : { step: shot.step }),
|
|
42
|
+
tool: shot.tool,
|
|
43
|
+
...(shot.checkId === undefined ? {} : { checkId: shot.checkId }),
|
|
44
|
+
source: shot.source,
|
|
45
|
+
width: shot.width,
|
|
46
|
+
height: shot.height,
|
|
47
|
+
sha256: shot.sha256,
|
|
48
|
+
jpeg: Buffer.from(image).toString('base64'),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Send what is owed. Never throws; the record is bounded by the runs still on disk. */
|
|
52
|
+
export async function syncScreenshots(deps) {
|
|
53
|
+
const onDisk = new Set(deps.runs.map((run) => run.runId));
|
|
54
|
+
const sent = {};
|
|
55
|
+
for (const [runId, keys] of Object.entries(deps.state.sentScreenshots ?? {}))
|
|
56
|
+
if (onDisk.has(runId) && Array.isArray(keys))
|
|
57
|
+
sent[runId] = keys.filter((k) => 'string' === typeof k);
|
|
58
|
+
const heldLocal = new Set((deps.state.screenshotsHeldLocal ?? []).filter((id) => onDisk.has(id)));
|
|
59
|
+
const finish = (count, error) => ({
|
|
60
|
+
sent: count,
|
|
61
|
+
state: { sentScreenshots: sent, screenshotsHeldLocal: [...heldLocal] },
|
|
62
|
+
...(error === undefined ? {} : { error }),
|
|
63
|
+
});
|
|
64
|
+
let count = 0;
|
|
65
|
+
try {
|
|
66
|
+
for (const run of deps.runs) {
|
|
67
|
+
if (heldLocal.has(run.runId) || !deps.accepted(run.runId))
|
|
68
|
+
continue;
|
|
69
|
+
const done = new Set(sent[run.runId] ?? []);
|
|
70
|
+
for (const shot of listed(run.payload)) {
|
|
71
|
+
if (done.has(keyOf(shot)))
|
|
72
|
+
continue;
|
|
73
|
+
const image = deps.readImage(run.runId, shot.file);
|
|
74
|
+
// Gone (retention took it) or changed under its name: nothing true is left to send.
|
|
75
|
+
if (image === undefined || createHash('sha256').update(image).digest('hex') !== shot.sha256)
|
|
76
|
+
continue;
|
|
77
|
+
const markDone = () => {
|
|
78
|
+
done.add(keyOf(shot));
|
|
79
|
+
sent[run.runId] = [...done];
|
|
80
|
+
};
|
|
81
|
+
if (image.byteLength > STEP_SCREENSHOT.MAX_BYTES) {
|
|
82
|
+
markDone();
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const res = await deps.post(SCREENSHOTS_PATH(run.runId), JSON.stringify(bodyOf(shot, image)));
|
|
86
|
+
if (HTTP_CREATED === res.status || HTTP_OK === res.status) {
|
|
87
|
+
markDone();
|
|
88
|
+
count += 1;
|
|
89
|
+
}
|
|
90
|
+
else if (NOT_THERE.has(res.status)) {
|
|
91
|
+
heldLocal.add(run.runId);
|
|
92
|
+
return finish(count, `screenshots ${String(res.status)}: this platform does not take screenshots yet, so run ${run.runId}'s stay on this machine`);
|
|
93
|
+
}
|
|
94
|
+
else if (REFUSED_FOR_GOOD.has(res.status))
|
|
95
|
+
markDone();
|
|
96
|
+
else
|
|
97
|
+
return finish(count, `screenshots ${String(res.status)}: ${res.text.slice(0, 200)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return finish(count, error instanceof Error ? error.message : String(error));
|
|
103
|
+
}
|
|
104
|
+
return finish(count);
|
|
105
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface UnsyncedRoot {
|
|
2
|
+
root: string;
|
|
3
|
+
/** Runs on disk the platform has not accepted. */
|
|
4
|
+
runs: number;
|
|
5
|
+
/** Flows on disk nothing will send: see `unsentFlowCount`. */
|
|
6
|
+
flows: number;
|
|
7
|
+
/** Whether this folder holds a link. False is the case nothing will ever send. */
|
|
8
|
+
linked: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The project it belongs to: the linked one, else the id its `.reticle.json` names. A machine
|
|
11
|
+
* reports every folder to each workspace it is linked to; this is how a board keeps only its own.
|
|
12
|
+
*/
|
|
13
|
+
projectId?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Run files in `root` the platform never accepted. Ids only — a run file is named by its id — so
|
|
17
|
+
* this costs a directory listing, never a payload read. Never throws.
|
|
18
|
+
*/
|
|
19
|
+
export declare function unsentRunCount(root: string): number;
|
|
20
|
+
/**
|
|
21
|
+
* Flows in `root` that will not reach the platform: all of them when nothing sends them (the root is
|
|
22
|
+
* not linked, has no key for its host, or has flow sync switched off), else the ones the platform
|
|
23
|
+
* refused. A flow waiting on a root that does send them is not counted: a save wakes the next cycle.
|
|
24
|
+
* Never throws.
|
|
25
|
+
*/
|
|
26
|
+
export declare function unsentFlowCount(root: string, sendsFlows: boolean): number;
|
|
27
|
+
/**
|
|
28
|
+
* Every root here with runs or flows the platform does not have. Roots are deduped; order is kept.
|
|
29
|
+
* `sendsFlows` defaults to `isLinked`: a linked root sends its flows unless flow sync is off.
|
|
30
|
+
*/
|
|
31
|
+
export declare function unsyncedRoots(roots: readonly string[], isLinked: (root: string) => Promise<boolean>, sendsFlows?: (root: string) => Promise<boolean>): Promise<UnsyncedRoot[]>;
|
|
32
|
+
/**
|
|
33
|
+
* The buckets under `~/.reticle/unmatched/` — evidence no project claimed, which nothing sends. After
|
|
34
|
+
* the first warning nothing named them again: not `status`, not `connect`, not the dashboard's
|
|
35
|
+
* unsent list. Never throws.
|
|
36
|
+
*/
|
|
37
|
+
export declare function unmatchedRoots(home: string): string[];
|
|
38
|
+
/** The project a `.reticle` folder belongs to: where a person runs the command that sends it. */
|
|
39
|
+
export declare function projectFolderOf(root: string): string;
|
|
40
|
+
/** One line a person can act on: where the runs are, and the command that sends them. */
|
|
41
|
+
export declare function describeUnsynced(entry: UnsyncedRoot, from: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* The unsent folders as a command's few lines: every unclaimed bucket in one (they all need the same
|
|
44
|
+
* thing, and `reticle connect` printed ninety near-identical lines of them before "Connected"), the
|
|
45
|
+
* rest one by one up to a handful, then a count and where the full list is.
|
|
46
|
+
*/
|
|
47
|
+
export declare function describeUnsyncedBriefly(entries: readonly UnsyncedRoot[], from: string): string[];
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.reticle` folders whose runs never reached the platform, and why.
|
|
3
|
+
*
|
|
4
|
+
* The daemon skipped every root without a link and said nothing: one repo held 31 runs nobody sent
|
|
5
|
+
* while the log repeated `reticle_cloud_unlinked` about a different folder. Counted here, from run
|
|
6
|
+
* ids on disk against the ids the platform accepted, so `reticle sync`, `doctor`, the HUD and the
|
|
7
|
+
* platform report can each name the folder and the one command that fixes it.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
10
|
+
import { basename, dirname, join, relative, sep } from 'node:path';
|
|
11
|
+
import { ReticleDir } from '@reticlehq/core';
|
|
12
|
+
import { DRIVE_RECORD_SUFFIX, UNMATCHED_SUBDIR, isUnmatchedRoot, } from '../project/dir/reticle-dir.js';
|
|
13
|
+
import { diskSource, readCloudState } from './sync-disk.js';
|
|
14
|
+
/** The project's own config, beside its `.reticle` folder. */
|
|
15
|
+
const PROJECT_CONFIG = '.reticle.json';
|
|
16
|
+
/** `projectId` out of a JSON file, or undefined. Never throws. */
|
|
17
|
+
function projectIdIn(path) {
|
|
18
|
+
try {
|
|
19
|
+
const id = JSON.parse(readFileSync(path, 'utf8'))?.['projectId'];
|
|
20
|
+
return 'string' === typeof id && 0 < id.length ? id : undefined;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** The project a `.reticle` folder belongs to, as the platform keys it. */
|
|
27
|
+
function projectOfRoot(root) {
|
|
28
|
+
return (projectIdIn(join(root, ReticleDir.CLOUD_LINK_FILE)) ??
|
|
29
|
+
projectIdIn(join(projectFolderOf(root), PROJECT_CONFIG)));
|
|
30
|
+
}
|
|
31
|
+
const RUN_SUFFIX = '.json';
|
|
32
|
+
/** The folder the command is run from, named when it is the one the person is already in. */
|
|
33
|
+
const HERE = 'this folder';
|
|
34
|
+
/**
|
|
35
|
+
* Run files in `root` the platform never accepted. Ids only — a run file is named by its id — so
|
|
36
|
+
* this costs a directory listing, never a payload read. Never throws.
|
|
37
|
+
*/
|
|
38
|
+
export function unsentRunCount(root) {
|
|
39
|
+
try {
|
|
40
|
+
const dir = join(root, ReticleDir.RUNS_SUBDIR);
|
|
41
|
+
if (!existsSync(dir))
|
|
42
|
+
return 0;
|
|
43
|
+
const state = readCloudState(root);
|
|
44
|
+
const sent = new Set([...Object.keys(state.sentRunHashes ?? {}), ...(state.sentRunIds ?? [])]);
|
|
45
|
+
// Made before the folder was linked here: held for `--move-history`, so not waiting for a sync.
|
|
46
|
+
const linkedAt = state.linkedAt;
|
|
47
|
+
const heldBack = (file) => {
|
|
48
|
+
if (linkedAt === undefined)
|
|
49
|
+
return false;
|
|
50
|
+
try {
|
|
51
|
+
const at = JSON.parse(readFileSync(join(dir, file), 'utf8'))
|
|
52
|
+
.createdAt;
|
|
53
|
+
return !('number' === typeof at && at >= linkedAt);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
return readdirSync(dir)
|
|
60
|
+
.filter((file) => file.endsWith(RUN_SUFFIX) && !file.endsWith(DRIVE_RECORD_SUFFIX))
|
|
61
|
+
.filter((file) => !sent.has(file.slice(0, -RUN_SUFFIX.length)) && !heldBack(file)).length;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Flows in `root` that will not reach the platform: all of them when nothing sends them (the root is
|
|
69
|
+
* not linked, has no key for its host, or has flow sync switched off), else the ones the platform
|
|
70
|
+
* refused. A flow waiting on a root that does send them is not counted: a save wakes the next cycle.
|
|
71
|
+
* Never throws.
|
|
72
|
+
*/
|
|
73
|
+
export function unsentFlowCount(root, sendsFlows) {
|
|
74
|
+
try {
|
|
75
|
+
if (!sendsFlows)
|
|
76
|
+
return diskSource(root).flows().length;
|
|
77
|
+
return readCloudState(root).refusedSets?.flow?.count ?? 0;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Every root here with runs or flows the platform does not have. Roots are deduped; order is kept.
|
|
85
|
+
* `sendsFlows` defaults to `isLinked`: a linked root sends its flows unless flow sync is off.
|
|
86
|
+
*/
|
|
87
|
+
export async function unsyncedRoots(roots, isLinked, sendsFlows = isLinked) {
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const root of new Set(roots)) {
|
|
90
|
+
const runs = unsentRunCount(root);
|
|
91
|
+
const linked = await isLinked(root).catch(() => false);
|
|
92
|
+
const flows = unsentFlowCount(root, linked && (await sendsFlows(root).catch(() => false)));
|
|
93
|
+
if (0 === runs && 0 === flows)
|
|
94
|
+
continue;
|
|
95
|
+
const projectId = projectOfRoot(root);
|
|
96
|
+
out.push({ root, runs, flows, linked, ...(projectId === undefined ? {} : { projectId }) });
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The buckets under `~/.reticle/unmatched/` — evidence no project claimed, which nothing sends. After
|
|
102
|
+
* the first warning nothing named them again: not `status`, not `connect`, not the dashboard's
|
|
103
|
+
* unsent list. Never throws.
|
|
104
|
+
*/
|
|
105
|
+
export function unmatchedRoots(home) {
|
|
106
|
+
const dir = join(home, ReticleDir.ROOT, UNMATCHED_SUBDIR);
|
|
107
|
+
try {
|
|
108
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
109
|
+
.filter((entry) => entry.isDirectory())
|
|
110
|
+
.map((entry) => join(dir, entry.name));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** The project a `.reticle` folder belongs to: where a person runs the command that sends it. */
|
|
117
|
+
export function projectFolderOf(root) {
|
|
118
|
+
return ReticleDir.ROOT === basename(root) ? dirname(root) : root;
|
|
119
|
+
}
|
|
120
|
+
/** One line a person can act on: where the runs are, and the command that sends them. */
|
|
121
|
+
export function describeUnsynced(entry, from) {
|
|
122
|
+
// Forward slashes on every platform, as the platform report writes `~/...`: one spelling of a
|
|
123
|
+
// folder whether it is read in a Windows terminal, the HUD or the dashboard.
|
|
124
|
+
const folder = projectFolderOf(entry.root);
|
|
125
|
+
const where = 0 === relative(from, folder).length ? HERE : relative(from, folder).split(sep).join('/');
|
|
126
|
+
const what = [
|
|
127
|
+
...(0 < entry.runs ? [`${String(entry.runs)} run(s)`] : []),
|
|
128
|
+
...(0 < entry.flows ? [`${String(entry.flows)} flow(s)`] : []),
|
|
129
|
+
].join(' and ');
|
|
130
|
+
if (isUnmatchedRoot(entry.root)) {
|
|
131
|
+
return (`${what} in ${entry.root.split(sep).join('/')} are not on your dashboard: no project claimed ` +
|
|
132
|
+
'them — the checkout that made them shares its project id with another, or has none. They stay ' +
|
|
133
|
+
'on this machine; serve the app from the checkout that owns the id, or give this one its own, so new work reaches it');
|
|
134
|
+
}
|
|
135
|
+
// `connect`, not `link`: it signs in first when it has to, so it is the one command that works.
|
|
136
|
+
if (!entry.linked)
|
|
137
|
+
return `${what} in ${where} are not on your dashboard: run \`reticle connect\` there`;
|
|
138
|
+
// A linked root counts flows only when flow sync is off or the platform refused them.
|
|
139
|
+
return 0 < entry.flows
|
|
140
|
+
? `${what} in ${where} are not on your dashboard: \`reticle config --flows on\` there if flow ` +
|
|
141
|
+
'sync is off; `reticle sync` names any the platform refused'
|
|
142
|
+
: `${what} in ${where} are waiting to be sent: run \`reticle sync\` there`;
|
|
143
|
+
}
|
|
144
|
+
/** Folders named one by one before the rest are counted. */
|
|
145
|
+
const FOLDERS_SHOWN = 5;
|
|
146
|
+
/**
|
|
147
|
+
* The unsent folders as a command's few lines: every unclaimed bucket in one (they all need the same
|
|
148
|
+
* thing, and `reticle connect` printed ninety near-identical lines of them before "Connected"), the
|
|
149
|
+
* rest one by one up to a handful, then a count and where the full list is.
|
|
150
|
+
*/
|
|
151
|
+
export function describeUnsyncedBriefly(entries, from) {
|
|
152
|
+
const unclaimed = entries.filter((entry) => isUnmatchedRoot(entry.root));
|
|
153
|
+
const claimed = entries.filter((entry) => !isUnmatchedRoot(entry.root));
|
|
154
|
+
if (entries.length <= FOLDERS_SHOWN)
|
|
155
|
+
return entries.map((entry) => describeUnsynced(entry, from));
|
|
156
|
+
const runs = unclaimed.reduce((sum, entry) => sum + entry.runs, 0);
|
|
157
|
+
const lines = claimed.slice(0, FOLDERS_SHOWN).map((entry) => describeUnsynced(entry, from));
|
|
158
|
+
if (0 < unclaimed.length)
|
|
159
|
+
lines.unshift(`${String(runs)} run(s) in ${String(unclaimed.length)} folders under ~/${ReticleDir.ROOT}/${UNMATCHED_SUBDIR} ` +
|
|
160
|
+
'are not on your dashboard: no project claimed them, so they stay on this machine');
|
|
161
|
+
const more = claimed.length - FOLDERS_SHOWN;
|
|
162
|
+
if (0 < more || 0 < unclaimed.length)
|
|
163
|
+
lines.push(`${0 < more ? `${String(more)} more folder(s) hold work not on your dashboard. ` : ''}` +
|
|
164
|
+
'`reticle doctor` lists every one');
|
|
165
|
+
return lines;
|
|
166
|
+
}
|
|
@@ -9,11 +9,26 @@
|
|
|
9
9
|
* ponytail: stat polling (`fs.watchFile`), not `fs.watch`. It works on a file that does not exist
|
|
10
10
|
* yet — the usual state of `cloud.json` before a link — and costs one stat per file per interval.
|
|
11
11
|
*/
|
|
12
|
+
import type { ImpactSnapshot } from '@reticlehq/core';
|
|
12
13
|
/** How often each watched file is stat'ed. A second reads as live to somebody switching windows. */
|
|
13
14
|
export declare const ACCOUNT_WATCH_INTERVAL_MS = 1000;
|
|
15
|
+
/** How often `onTick` re-reads what has no file to watch; the platform config is polled as often. */
|
|
16
|
+
export declare const HUD_REFRESH_MS = 30000;
|
|
14
17
|
export interface AccountFilesWatch {
|
|
15
18
|
/** Start watching one file. Watching the same path twice is a no-op. */
|
|
16
19
|
watch(path: string): void;
|
|
17
20
|
close(): void;
|
|
18
21
|
}
|
|
19
|
-
export declare function watchAccountFiles(onChange: () => void, intervalMs?: number
|
|
22
|
+
export declare function watchAccountFiles(onChange: () => void, intervalMs?: number,
|
|
23
|
+
/** The rest of the platform's side of the HUD, which has no file to watch: run every 30s. */
|
|
24
|
+
onTick?: () => void): AccountFilesWatch;
|
|
25
|
+
/**
|
|
26
|
+
* Push a tab its impact snapshot, or, with `ifChanged`, only when it moved since this last pushed it.
|
|
27
|
+
*
|
|
28
|
+
* The daemon re-reads every tab on a timer so the platform's side of the HUD (sync status, runs on
|
|
29
|
+
* the dashboard, days left) reaches an idle tab without a tool call; resending an unchanged snapshot
|
|
30
|
+
* on every tick would repaint every HUD for nothing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function pushIfMoved(tab: {
|
|
33
|
+
pushImpact(read: () => ImpactSnapshot | undefined, immediate: boolean): void;
|
|
34
|
+
}, read: () => ImpactSnapshot | undefined, ifChanged: boolean): void;
|