@reticlehq/server 3.4.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +16 -8
- package/dist/chromium-launch-options.d.ts +3 -1
- package/dist/chromium-launch-options.js +2 -1
- package/dist/command/cli/adhoc-verdict.d.ts +1 -0
- package/dist/command/cli/adhoc-verdict.js +6 -3
- package/dist/command/cli/answers/open-note.d.ts +2 -0
- package/dist/command/cli/answers/open-note.js +22 -0
- package/dist/command/cli/auth/cloud-session.d.ts +4 -4
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-flow-commands.js +4 -8
- package/dist/command/cli/cli-parse.d.ts +1 -1
- package/dist/command/cli/cli-parse.js +13 -11
- package/dist/command/cli/cli-verify.d.ts +1 -1
- package/dist/command/cli/cli-verify.js +35 -25
- package/dist/command/cli/cloud-cli.js +110 -12
- package/dist/command/cli/cloud-login.d.ts +9 -1
- package/dist/command/cli/cloud-login.js +43 -8
- package/dist/command/cli/demo/demo-page.js +11 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
- package/dist/command/cli/drive/drive-attach.d.ts +27 -2
- package/dist/command/cli/drive/drive-attach.js +71 -4
- package/dist/command/cli/drive/drive-command.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +13 -7
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
- package/dist/command/cli/ports/port-holder.d.ts +10 -0
- package/dist/command/cli/ports/port-holder.js +24 -0
- package/dist/command/cli/setup-mcp-cli.js +15 -5
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +6 -9
- package/dist/command/cli.d.ts +6 -0
- package/dist/command/cli.js +45 -23
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
- package/dist/command/daemon/daemon-resolve.d.ts +14 -0
- package/dist/command/daemon/daemon-resolve.js +33 -2
- package/dist/command/setup/agent-writer.d.ts +1 -1
- package/dist/command/setup/agent-writer.js +3 -2
- package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
- package/dist/command/setup/bringup/connect-lease.js +1 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +10 -0
- package/dist/command/setup/bringup/dev-server-wait.js +20 -1
- package/dist/command/setup/init/init-port.d.ts +4 -0
- package/dist/command/setup/init/init-port.js +10 -0
- package/dist/command/setup/init/init-runtime.d.ts +10 -0
- package/dist/command/setup/init/init-runtime.js +71 -9
- package/dist/command/setup/node-effects.js +8 -2
- package/dist/command/setup/remaining-steps.js +2 -1
- package/dist/command/setup/run-setup.d.ts +7 -0
- package/dist/command/setup/run-setup.js +38 -25
- package/dist/command/setup/setup-command.d.ts +2 -1
- package/dist/command/setup/setup-command.js +2 -0
- package/dist/command/setup/setup-mcp.d.ts +2 -0
- package/dist/command/setup/setup-mcp.js +14 -5
- package/dist/command/version/peer-announce.d.ts +6 -2
- package/dist/command/version/peer-announce.js +8 -6
- package/dist/command/version/version-nudge.d.ts +17 -3
- package/dist/command/version/version-nudge.js +25 -4
- package/dist/command/version/version-skew.js +11 -2
- package/dist/features/crawl/crawl-tools.js +4 -0
- package/dist/features/crawl/crawl.d.ts +6 -0
- package/dist/features/crawl/crawl.js +9 -1
- package/dist/features/exhaust/ledger.d.ts +7 -0
- package/dist/features/exhaust/ledger.js +35 -9
- package/dist/features/harness/app-findings.d.ts +2 -0
- package/dist/features/harness/app-findings.js +52 -0
- package/dist/features/harness/drive-report.d.ts +27 -1
- package/dist/features/harness/drive-report.js +162 -11
- package/dist/features/harness/drivers.d.ts +10 -14
- package/dist/features/harness/drivers.js +10 -14
- package/dist/features/harness/fill-values.d.ts +3 -69
- package/dist/features/harness/fill-values.js +3 -133
- package/dist/features/harness/goals.d.ts +21 -0
- package/dist/features/harness/goals.js +77 -0
- package/dist/features/harness/harness.d.ts +21 -0
- package/dist/features/harness/harness.js +69 -2
- package/dist/features/harness/platform/drive-target.d.ts +30 -0
- package/dist/features/harness/platform/drive-target.js +81 -0
- package/dist/features/harness/platform/local-apps.d.ts +94 -0
- package/dist/features/harness/platform/local-apps.js +204 -0
- package/dist/features/harness/platform/remote-drive.d.ts +180 -0
- package/dist/features/harness/platform/remote-drive.js +365 -0
- package/dist/features/harness/platform/script.d.ts +48 -0
- package/dist/features/harness/platform/script.js +128 -0
- package/dist/features/harness/platform/server-driver.d.ts +41 -0
- package/dist/features/harness/platform/server-driver.js +179 -0
- package/dist/features/harness/platform-config.d.ts +7 -0
- package/dist/features/harness/platform-config.js +14 -2
- package/dist/features/harness/script/app-controls.d.ts +5 -0
- package/dist/features/harness/script/app-controls.js +40 -0
- package/dist/features/harness/script/lane-ids.d.ts +14 -0
- package/dist/features/harness/script/lane-ids.js +16 -0
- package/dist/features/harness/script/script-run.d.ts +41 -0
- package/dist/features/harness/script/script-run.js +299 -0
- package/dist/features/license/license.d.ts +2 -2
- package/dist/features/visual/visual-tools.d.ts +10 -1
- package/dist/features/visual/visual-tools.js +40 -3
- package/dist/hooks/driven-by.d.ts +4 -0
- package/dist/hooks/driven-by.js +16 -0
- package/dist/hooks/hook-commands.js +15 -4
- package/dist/hooks/tool-log.d.ts +9 -0
- package/dist/hooks/tool-log.js +29 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +91 -89
- package/dist/judgement/capsule/capsule-store.d.ts +4 -4
- package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
- package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
- package/dist/judgement/runs/artifact/run-store.js +26 -1
- package/dist/judgement/runs/drive-run.d.ts +22 -0
- package/dist/judgement/runs/drive-run.js +99 -1
- package/dist/judgement/runs/repair-prompt.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +8 -1
- package/dist/judgement/runs/verification-sync.d.ts +3 -2
- package/dist/judgement/runs/verification-sync.js +4 -3
- package/dist/language/flows/change/gate.d.ts +10 -0
- package/dist/language/flows/change/gate.js +22 -0
- package/dist/language/flows/decision.js +34 -0
- package/dist/language/flows/flow-anchor.d.ts +54 -8
- package/dist/language/flows/flow-anchor.js +88 -3
- package/dist/language/flows/flow-cross-step.d.ts +15 -0
- package/dist/language/flows/flow-cross-step.js +21 -0
- package/dist/language/flows/flow-learning.js +8 -11
- package/dist/language/flows/flow-mutate-tools.js +5 -3
- package/dist/language/flows/flow-replay-run.d.ts +23 -9
- package/dist/language/flows/flow-replay-run.js +101 -94
- package/dist/language/flows/flow-replay.d.ts +23 -1
- package/dist/language/flows/flow-replay.js +92 -19
- package/dist/language/flows/flow-scope.js +5 -1
- package/dist/language/flows/flow-step-runners.d.ts +20 -2
- package/dist/language/flows/flow-step-runners.js +236 -42
- package/dist/language/flows/flow-store-for-session.d.ts +2 -2
- package/dist/language/flows/flow-store-for-session.js +2 -2
- package/dist/language/flows/flow-tools.js +58 -34
- package/dist/language/flows/flows.js +5 -2
- package/dist/language/flows/heal-run.js +6 -4
- package/dist/language/flows/recording/flake.d.ts +2 -2
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
- package/dist/language/flows/recording/tape/recordings.js +40 -8
- package/dist/language/flows/replay-from-hud.d.ts +20 -0
- package/dist/language/flows/replay-from-hud.js +51 -0
- package/dist/language/flows/replay.d.ts +9 -5
- package/dist/language/flows/replay.js +27 -9
- package/dist/language/flows/suite/suite-flakes.js +2 -0
- package/dist/language/flows/suite-selection.d.ts +2 -15
- package/dist/language/flows/suite-selection.js +4 -3
- package/dist/language/flows/verify-change-tools.js +8 -4
- package/dist/launch-chromium.d.ts +27 -5
- package/dist/launch-chromium.js +38 -8
- package/dist/memory/cloud/cloud-config.d.ts +12 -0
- package/dist/memory/cloud/cloud-config.js +15 -0
- package/dist/memory/cloud/cloud-sync.d.ts +17 -14
- package/dist/memory/cloud/cloud-sync.js +12 -1
- package/dist/memory/cloud/harness-config.d.ts +26 -1
- package/dist/memory/cloud/harness-config.js +99 -4
- package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
- package/dist/memory/cloud/hud-notices-source.js +128 -0
- package/dist/memory/cloud/memory-scope.d.ts +144 -0
- package/dist/memory/cloud/memory-scope.js +205 -0
- package/dist/memory/cloud/sync-cycle.d.ts +21 -3
- package/dist/memory/cloud/sync-cycle.js +45 -5
- package/dist/memory/cloud/sync-daemon.js +7 -5
- package/dist/memory/cloud/sync-disk.d.ts +1 -1
- package/dist/memory/cloud/sync-disk.js +5 -8
- package/dist/memory/impact/account-watch.d.ts +19 -0
- package/dist/memory/impact/account-watch.js +34 -0
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +12 -2
- package/dist/memory/impact/impact-store.d.ts +16 -0
- package/dist/memory/impact/impact-store.js +142 -26
- package/dist/memory/intent/intent-shard.d.ts +2 -2
- package/dist/memory/intent/intent-tools.js +88 -5
- package/dist/memory/journal/envelope.d.ts +16 -16
- package/dist/memory/journal/journal-query.d.ts +14 -0
- package/dist/memory/journal/journal-query.js +15 -0
- package/dist/memory/journal/journal-recorder.js +7 -1
- package/dist/memory/journal/on-disk/retention.d.ts +3 -0
- package/dist/memory/journal/on-disk/retention.js +5 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
- package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
- package/dist/memory/journal/session-end.js +6 -4
- package/dist/memory/project/artifact-root-resolver.d.ts +36 -1
- package/dist/memory/project/artifact-root-resolver.js +134 -26
- package/dist/memory/project/artifact-root.d.ts +42 -5
- package/dist/memory/project/artifact-root.js +87 -8
- package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
- package/dist/memory/project/dir/reticle-dir.js +3 -1
- package/dist/memory/project/project-tools.js +22 -3
- package/dist/memory/project/session-root.d.ts +24 -1
- package/dist/memory/project/session-root.js +41 -5
- package/dist/memory/project/sync-status.d.ts +39 -0
- package/dist/memory/project/sync-status.js +90 -0
- package/dist/memory/recall/memory-tools.js +2 -0
- package/dist/memory/recall/project-memory.d.ts +19 -0
- package/dist/memory/recall/project-memory.js +45 -6
- package/dist/portal/bridge/bridge.d.ts +7 -4
- package/dist/portal/bridge/bridge.js +25 -3
- package/dist/portal/bridge/pairing-token.d.ts +9 -0
- package/dist/portal/bridge/pairing-token.js +17 -0
- package/dist/portal/input/real-input.d.ts +81 -10
- package/dist/portal/input/real-input.js +207 -22
- package/dist/portal/input/viewport-tools.js +19 -9
- package/dist/portal/pool/browser-pool.d.ts +22 -9
- package/dist/portal/pool/browser-pool.js +70 -35
- package/dist/portal/pool/playwright-launcher.d.ts +13 -0
- package/dist/portal/pool/playwright-launcher.js +17 -1
- package/dist/portal/pool/pool-contract.d.ts +7 -4
- package/dist/portal/session/command-payload.d.ts +8 -0
- package/dist/portal/session/command-payload.js +19 -0
- package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
- package/dist/portal/session/dev-server/dev-command.js +61 -12
- package/dist/portal/session/facts/departure-note.d.ts +22 -0
- package/dist/portal/session/facts/departure-note.js +47 -0
- package/dist/portal/session/facts/session-replaced.d.ts +5 -0
- package/dist/portal/session/facts/session-replaced.js +5 -0
- package/dist/portal/session/first-run-wiring.d.ts +42 -0
- package/dist/portal/session/first-run-wiring.js +132 -0
- package/dist/portal/session/human/notes-ledger.d.ts +20 -0
- package/dist/portal/session/human/notes-ledger.js +55 -0
- package/dist/portal/session/human/review-store.d.ts +12 -0
- package/dist/portal/session/human/review-store.js +18 -10
- package/dist/portal/session/live-control-tools.d.ts +0 -13
- package/dist/portal/session/live-control-tools.js +28 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
- package/dist/portal/session/no-session-diagnosis.js +15 -0
- package/dist/portal/session/no-session-next-action.d.ts +11 -0
- package/dist/portal/session/no-session-next-action.js +31 -5
- package/dist/portal/session/no-session-watch.d.ts +14 -1
- package/dist/portal/session/no-session-watch.js +63 -10
- package/dist/portal/session/presence/session-mirror.js +3 -0
- package/dist/portal/session/serving-directory.d.ts +27 -0
- package/dist/portal/session/serving-directory.js +45 -0
- package/dist/portal/session/session-health.d.ts +7 -0
- package/dist/portal/session/session-health.js +6 -0
- package/dist/portal/session/session-info.d.ts +8 -1
- package/dist/portal/session/session-info.js +6 -0
- package/dist/portal/session/session-manager.d.ts +21 -0
- package/dist/portal/session/session-manager.js +44 -8
- package/dist/portal/session/session-reaper.js +4 -1
- package/dist/portal/session/session-successor.d.ts +5 -0
- package/dist/portal/session/session-tools.js +26 -3
- package/dist/portal/session/session.d.ts +12 -10
- package/dist/portal/session/session.js +30 -31
- package/dist/start-options.d.ts +6 -0
- package/dist/status-payload.d.ts +10 -0
- package/dist/status-payload.js +23 -0
- package/dist/surface/http-server.d.ts +1 -1
- package/dist/surface/http-server.js +15 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
- package/dist/surface/mcp/mcp-proxy.js +36 -5
- package/dist/surface/mcp/mcp.d.ts +11 -6
- package/dist/surface/mcp/mcp.js +28 -14
- package/dist/surface/mcp/proxy/proxy-log.js +3 -5
- package/dist/surface/mcp/server-instructions.d.ts +4 -2
- package/dist/surface/mcp/server-instructions.js +14 -4
- package/dist/surface/mcp/shared-params.js +2 -2
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
- package/dist/surface/tools/act/settle-in-flight.js +25 -3
- package/dist/surface/tools/act-sequence-tool.js +2 -2
- package/dist/surface/tools/act-tools.js +25 -9
- package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +17 -2
- package/dist/surface/tools/browser-tools.js +8 -1
- package/dist/surface/tools/chat-drives.d.ts +26 -0
- package/dist/surface/tools/chat-drives.js +165 -0
- package/dist/surface/tools/dynamic-tools.js +29 -2
- package/dist/surface/tools/empty-session-list.d.ts +41 -0
- package/dist/surface/tools/empty-session-list.js +80 -0
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +38 -6
- package/dist/surface/tools/explore-tools.d.ts +10 -1
- package/dist/surface/tools/explore-tools.js +82 -13
- package/dist/surface/tools/feedback-tools.js +14 -0
- package/dist/surface/tools/harness-explore.d.ts +96 -14
- package/dist/surface/tools/harness-explore.js +227 -202
- package/dist/surface/tools/harness-plan.d.ts +12 -0
- package/dist/surface/tools/harness-plan.js +31 -0
- package/dist/surface/tools/harness-script.d.ts +8 -0
- package/dist/surface/tools/harness-script.js +342 -0
- package/dist/surface/tools/harness-toolset.d.ts +14 -1
- package/dist/surface/tools/harness-toolset.js +77 -6
- package/dist/surface/tools/invoke-tool.js +64 -9
- package/dist/surface/tools/lease-seed.d.ts +29 -0
- package/dist/surface/tools/lease-seed.js +123 -0
- package/dist/surface/tools/lease-tools.d.ts +3 -23
- package/dist/surface/tools/lease-tools.js +30 -131
- package/dist/surface/tools/live-call-text.js +2 -2
- package/dist/surface/tools/next-step.d.ts +19 -0
- package/dist/surface/tools/next-step.js +159 -0
- package/dist/surface/tools/observe-tools.js +2 -4
- package/dist/surface/tools/platform-moment.d.ts +12 -0
- package/dist/surface/tools/platform-moment.js +168 -0
- package/dist/surface/tools/query-hint-schema.d.ts +7 -0
- package/dist/surface/tools/query-hint-schema.js +10 -0
- package/dist/surface/tools/read-tools.js +8 -2
- package/dist/surface/tools/real-input-attempt.js +96 -13
- package/dist/surface/tools/tool-kit.d.ts +25 -1
- package/dist/surface/tools/tool-kit.js +4 -6
- package/dist/surface/tools/tools.js +20 -48
- package/dist/telemetry/argument-shape.js +1 -1
- package/dist/telemetry/hud-metrics.d.ts +2 -0
- package/dist/telemetry/hud-metrics.js +17 -0
- package/dist/telemetry/onboarding-firsts.d.ts +2 -2
- package/dist/telemetry/onboarding-firsts.js +2 -2
- package/dist/telemetry/telemetry.d.ts +12 -0
- package/dist/telemetry/telemetry.js +43 -14
- package/dist/telemetry/verification-of.js +1 -0
- package/dist/wire-journal.js +4 -1
- package/docs/README.md +1 -1
- package/docs/actions.mdx +1 -2
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/autodrive.md +29 -25
- package/docs/cli/cloud.mdx +6 -4
- package/docs/cli/doctor.mdx +6 -0
- package/docs/cli/drive.mdx +8 -2
- package/docs/cli/init.mdx +11 -13
- package/docs/cli/status.mdx +5 -1
- package/docs/cli/version.mdx +3 -3
- package/docs/cli.mdx +14 -8
- package/docs/desktop.mdx +5 -1
- package/docs/environment.mdx +3 -7
- package/docs/getting-started.md +14 -2
- package/docs/human-control.md +1 -2
- package/docs/index.mdx +4 -3
- package/docs/install-agentic.mdx +1 -1
- package/docs/install-manual.mdx +2 -2
- package/docs/packages/next.mdx +3 -0
- package/docs/packages/tauri.mdx +2 -2
- package/docs/packages/vite-plugin.mdx +24 -1
- package/docs/packages.mdx +1 -1
- package/docs/quickstart.mdx +23 -11
- package/docs/telemetry.md +1 -1
- package/docs/tools/act.mdx +1 -1
- package/docs/tools/session-and-feedback.mdx +9 -9
- package/docs/usage.md +5 -3
- package/docs/what-is-recorded.md +13 -3
- package/docs/what-is-reticle.mdx +3 -1
- package/package.json +8 -12
- package/dist/features/harness/driver.d.ts +0 -50
- package/dist/features/harness/driver.js +0 -236
- package/dist/features/harness/jev-driver.d.ts +0 -188
- package/dist/features/harness/jev-driver.js +0 -678
- package/dist/features/harness/openai-driver.d.ts +0 -46
- package/dist/features/harness/openai-driver.js +0 -194
- package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
- package/dist/memory/project/dir/fill-value-store.js +0 -88
- package/docs/local-registry.md +0 -143
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import type { ToolDef } from '../../surface/tools/tool-kit.js';
|
|
1
|
+
import type { ToolDef, ToolDeps } from '../../surface/tools/tool-kit.js';
|
|
2
|
+
/**
|
|
3
|
+
* One picture of the tab a chat-requested drive is driving, or undefined when there is none to take.
|
|
4
|
+
*
|
|
5
|
+
* The leased browser first, when this daemon launched the tab; then the desktop window's own capture,
|
|
6
|
+
* the same route `reticle_screenshot` takes. Without the second, a drive of a desktop app sent its
|
|
7
|
+
* steps to the chat and never a picture. Never throws: a frame is a courtesy, and a tab that has
|
|
8
|
+
* gone mid-drive must not break the drive.
|
|
9
|
+
*/
|
|
10
|
+
export declare function driveFrame(deps: ToolDeps, sessionId: string, jpegQuality: number): Promise<Uint8Array | undefined>;
|
|
2
11
|
/**
|
|
3
12
|
* Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
|
|
4
13
|
* become a file-read oracle for whatever a compromised renderer names.
|
|
@@ -111,6 +111,32 @@ async function desktopCapture(deps, sessionId, fullPage) {
|
|
|
111
111
|
return {};
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* One picture of the tab a chat-requested drive is driving, or undefined when there is none to take.
|
|
116
|
+
*
|
|
117
|
+
* The leased browser first, when this daemon launched the tab; then the desktop window's own capture,
|
|
118
|
+
* the same route `reticle_screenshot` takes. Without the second, a drive of a desktop app sent its
|
|
119
|
+
* steps to the chat and never a picture. Never throws: a frame is a courtesy, and a tab that has
|
|
120
|
+
* gone mid-drive must not break the drive.
|
|
121
|
+
*/
|
|
122
|
+
export async function driveFrame(deps, sessionId, jpegQuality) {
|
|
123
|
+
try {
|
|
124
|
+
const leased = await deps.pool?.screenshotLease(sessionId, { jpegQuality });
|
|
125
|
+
if (leased !== undefined)
|
|
126
|
+
return leased;
|
|
127
|
+
// The window `reticle drive` opened: neither a lease nor a desktop shell, but its page is ours.
|
|
128
|
+
const url = deps.sessions.resolve(sessionId).url;
|
|
129
|
+
if (true === (await deps.realInput?.isAvailableFor(url))) {
|
|
130
|
+
const shot = await deps.realInput?.screenshot?.(url, { jpegQuality });
|
|
131
|
+
if (shot !== undefined)
|
|
132
|
+
return shot;
|
|
133
|
+
}
|
|
134
|
+
return (await desktopCapture(deps, sessionId, false)).png;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
114
140
|
/**
|
|
115
141
|
* Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
|
|
116
142
|
* become a file-read oracle for whatever a compromised renderer names.
|
|
@@ -179,9 +205,20 @@ function runtimeOf(deps, sessionId) {
|
|
|
179
205
|
* baseline that says nothing about the content below the fold, and a later diff would call it green.
|
|
180
206
|
*/
|
|
181
207
|
async function capture(deps, sessionId, args) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
/*
|
|
209
|
+
* Only a provider driving THIS session's page. A launched provider photographs the one page it
|
|
210
|
+
* owns whatever url it is handed, so asking it about another session saved the driven tab's pixels
|
|
211
|
+
* under that session's name and answered `saved: true` (#1407). Real input asks the same question
|
|
212
|
+
* before every gesture.
|
|
213
|
+
*/
|
|
214
|
+
const candidate = screenshotProvider(deps);
|
|
215
|
+
const session = candidate === undefined ? undefined : deps.sessions.resolve(sessionId);
|
|
216
|
+
const provider = candidate !== undefined &&
|
|
217
|
+
session !== undefined &&
|
|
218
|
+
(await candidate.isAvailableFor(session.url))
|
|
219
|
+
? candidate
|
|
220
|
+
: undefined;
|
|
221
|
+
if (provider !== undefined && session !== undefined) {
|
|
185
222
|
const png = await provider.screenshot(session.url, await buildOpts(deps, sessionId, args));
|
|
186
223
|
// A driven browser renders the session's URL in a BROWSER — so the pixels are web even when the
|
|
187
224
|
// session named is a desktop window. Scoping those under the desktop runtime would corrupt that
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DrivenBy } from '@reticlehq/core';
|
|
2
|
+
export declare function runDrivenBy<T>(by: DrivenBy, fn: () => Promise<T>): Promise<T>;
|
|
3
|
+
/** The Harness drive this call is part of, or undefined when the connected agent made it. */
|
|
4
|
+
export declare function currentDrivenBy(): DrivenBy | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
/**
|
|
3
|
+
* Which Harness drive the current tool call belongs to, carried across every await under it.
|
|
4
|
+
*
|
|
5
|
+
* The Harness dispatches through the same tools an agent calls, so nothing in a tool's arguments can
|
|
6
|
+
* say who called it, and adding a field to every tool's schema would advertise it to every agent.
|
|
7
|
+
* The context is set once around each Harness call and read where the journal writes the action.
|
|
8
|
+
*/
|
|
9
|
+
const current = new AsyncLocalStorage();
|
|
10
|
+
export function runDrivenBy(by, fn) {
|
|
11
|
+
return current.run(by, fn);
|
|
12
|
+
}
|
|
13
|
+
/** The Harness drive this call is part of, or undefined when the connected agent made it. */
|
|
14
|
+
export function currentDrivenBy() {
|
|
15
|
+
return current.getStore();
|
|
16
|
+
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* broken gets a log line; their verdicts keep working.
|
|
22
22
|
*/
|
|
23
23
|
import { spawn } from 'node:child_process';
|
|
24
|
-
import {
|
|
24
|
+
import { closeSync, fstatSync, openSync, readFileSync } from 'node:fs';
|
|
25
25
|
import { join } from 'node:path';
|
|
26
26
|
import { HOOK_EVENT_NAMES, HookConfigSchema } from '@reticlehq/core/hooks';
|
|
27
27
|
import { ReticleDir } from '@reticlehq/core';
|
|
@@ -65,18 +65,29 @@ let cached;
|
|
|
65
65
|
*/
|
|
66
66
|
export function readHookConfig(reticleRoot) {
|
|
67
67
|
const path = join(reticleRoot, ReticleDir.HOOKS_FILE);
|
|
68
|
-
|
|
68
|
+
// Open once, then stat and read the SAME descriptor: the mtime the cache keys on is the mtime of
|
|
69
|
+
// the bytes read, not of whatever the path pointed at a moment earlier.
|
|
70
|
+
let fd;
|
|
69
71
|
try {
|
|
70
|
-
|
|
72
|
+
fd = openSync(path, 'r');
|
|
71
73
|
}
|
|
72
74
|
catch {
|
|
73
75
|
cached = undefined;
|
|
74
76
|
return {};
|
|
75
77
|
}
|
|
78
|
+
try {
|
|
79
|
+
return readOpenHookConfig(fd);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
closeSync(fd);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function readOpenHookConfig(fd) {
|
|
86
|
+
const mtime = fstatSync(fd).mtimeMs;
|
|
76
87
|
if (cached !== undefined && cached.at === mtime)
|
|
77
88
|
return cached.config;
|
|
78
89
|
try {
|
|
79
|
-
const parsed = HookConfigSchema.safeParse(JSON.parse(readFileSync(
|
|
90
|
+
const parsed = HookConfigSchema.safeParse(JSON.parse(readFileSync(fd, 'utf8')));
|
|
80
91
|
if (!parsed.success) {
|
|
81
92
|
reportOnce(`config:${String(mtime)}`, `${ReticleDir.HOOKS_FILE} is not a map of event→command`);
|
|
82
93
|
cached = { at: mtime, config: {} };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { appendFileSync } from 'node:fs';
|
|
2
|
+
import { currentDrivenBy } from './driven-by.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every tool call, its arguments and what it answered, appended to a file a person names.
|
|
5
|
+
*
|
|
6
|
+
* Off unless `RETICLE_TOOL_LOG` is set. It exists to put two drives side by side (an agent's and
|
|
7
|
+
* the Harness's) and read what each one asked and was told; `drivenBy` says which was which. Local
|
|
8
|
+
* only: nothing here leaves the machine.
|
|
9
|
+
*/
|
|
10
|
+
const TOOL_LOG_ENV = 'RETICLE_TOOL_LOG';
|
|
11
|
+
/** A page snapshot can be large; past this a record keeps its head and says it was cut. */
|
|
12
|
+
const MAX_RESULT_CHARS = 200_000;
|
|
13
|
+
export function toolLogPath() {
|
|
14
|
+
const path = process.env[TOOL_LOG_ENV];
|
|
15
|
+
return path === undefined || 0 === path.trim().length ? undefined : path;
|
|
16
|
+
}
|
|
17
|
+
export function logToolCall(path, entry) {
|
|
18
|
+
try {
|
|
19
|
+
const text = JSON.stringify(entry.result ?? null);
|
|
20
|
+
const result = text.length > MAX_RESULT_CHARS
|
|
21
|
+
? { truncated: text.slice(0, MAX_RESULT_CHARS) }
|
|
22
|
+
: entry.result;
|
|
23
|
+
const drivenBy = currentDrivenBy();
|
|
24
|
+
appendFileSync(path, `${JSON.stringify({ ...entry, result, ...(drivenBy === undefined ? {} : { drivenBy }) })}\n`);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
/* a log that cannot be written never fails the call it describes */
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { StartOptions } from './start-options.js';
|
|
|
2
2
|
export type { StartOptions } from './start-options.js';
|
|
3
3
|
import { Bridge } from './portal/bridge/bridge.js';
|
|
4
4
|
import type { RealInputProvider } from './portal/input/real-input.js';
|
|
5
|
+
export { replayVerdictLine } from './language/flows/replay-from-hud.js';
|
|
5
6
|
export { ReticleTool } from '@reticlehq/core';
|
|
6
7
|
export { RingBuffer } from '@reticlehq/engine/window/ring-buffer.js';
|
|
7
8
|
export { Bridge } from './portal/bridge/bridge.js';
|
package/dist/index.js
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { applyHarnessSwitch, harnessConfigsByRoot } from './memory/cloud/harness-config.js';
|
|
2
|
+
import { coveragePercents } from './features/exhaust/ledger.js';
|
|
3
|
+
import { firstRunWiring } from './portal/session/first-run-wiring.js';
|
|
3
4
|
import { fetchPlatformConfig } from './features/harness/platform-config.js';
|
|
5
|
+
import { watchAccountFiles } from './memory/impact/account-watch.js';
|
|
6
|
+
import { SESSION_FILE } from './command/cli/cloud-kit.js';
|
|
4
7
|
import { join } from 'node:path';
|
|
5
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
6
8
|
import { homedir } from 'node:os';
|
|
7
|
-
import { linkedCloudPort, platformEnvPort } from './memory/cloud/cloud-config.js';
|
|
9
|
+
import { linkedCloudPort, platformEnvPort, sessionCloudPort } from './memory/cloud/cloud-config.js';
|
|
10
|
+
import { sessionRoot } from './memory/project/session-root.js';
|
|
8
11
|
import { attachCloudSync } from './memory/cloud/sync-daemon.js';
|
|
9
12
|
import { wireHooks } from './hooks/hook-commands.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
+
import { currentDrivenBy } from './hooks/driven-by.js';
|
|
14
|
+
import { artifactRootResolver, knownProjectCandidates, projectDirectoryFor, } from './memory/project/artifact-root-resolver.js';
|
|
15
|
+
import { servingDirectoryOf } from './portal/session/serving-directory.js';
|
|
13
16
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
-
import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, bridgeWsUrl, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST,
|
|
17
|
+
import { AGENT_STOPPED_NOTICE, RETICLE_DEFAULT_PORT, bridgeWsUrl, ReticleCommand, ReticleDir, ReticleEnv, LOOPBACK_HOST, EventType, } from '@reticlehq/core';
|
|
15
18
|
import { originOf } from './portal/session/session-manager.js';
|
|
16
19
|
import { setBrowserMode, BrowserMode } from './telemetry/browser-mode.js';
|
|
17
|
-
import {
|
|
20
|
+
import { replayFromHud } from './language/flows/replay-from-hud.js';
|
|
18
21
|
import { createSharedServer } from './surface/http-server.js';
|
|
19
22
|
import { openLoopbackAlias } from './command/daemon/binding/loopback-alias.js';
|
|
20
23
|
import { reportAppInstrumented } from './telemetry/app-instrumented.js';
|
|
21
24
|
import { resolveBridgeSecurityWithAutoToken } from './portal/bridge/bridge-security.js';
|
|
22
25
|
import { Bridge } from './portal/bridge/bridge.js';
|
|
23
26
|
import { sdkFixForDirectory } from './command/version/sdk-fix.js';
|
|
27
|
+
import { connectionSkew } from './command/version/version-nudge.js';
|
|
24
28
|
import { SERVER_VERSION } from './command/version/identity/server-version.js';
|
|
25
29
|
import { BaselineStore } from './memory/project/baselines.js';
|
|
26
30
|
import { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
27
|
-
import { initImpact } from './memory/impact/impact-recorder.js';
|
|
31
|
+
import { initImpact, impactSnapshot } from './memory/impact/impact-recorder.js';
|
|
28
32
|
import { flowAuthor } from './language/flows/flow-author.js';
|
|
29
33
|
import { FlowStore } from './language/flows/flows.js';
|
|
30
34
|
import { buildFlowChips } from './language/flows/flow-scope.js';
|
|
@@ -39,7 +43,9 @@ import { createRunnerPort } from './judgement/runs/runner-port.js';
|
|
|
39
43
|
import { RunStore } from './judgement/runs/artifact/run-store.js';
|
|
40
44
|
import { startVerifyServer } from './judgement/runs/verify-server.js';
|
|
41
45
|
import { createMcpServer } from './surface/mcp/mcp.js';
|
|
46
|
+
import { instructionStateAt } from './surface/mcp/mcp-proxy.js';
|
|
42
47
|
import { LEASE_ACQUIRE_TOOL } from './surface/tools/lease-tools.js';
|
|
48
|
+
import { startChatDrives } from './surface/tools/chat-drives.js';
|
|
43
49
|
import { runTool } from './surface/tools/invoke-tool.js';
|
|
44
50
|
import { SessionReaper, endAllSessions, MCP_DISCONNECT_SUMMARY, } from './portal/session/session-reaper.js';
|
|
45
51
|
import { wireSessionScope } from './portal/session/no-session-watch.js';
|
|
@@ -52,9 +58,7 @@ import { BrowserPool } from './portal/pool/browser-pool.js';
|
|
|
52
58
|
import { AGENT_ALREADY_DRIVING_ELSEWHERE, shouldGreetWithLeaseNotice, } from './portal/session/lease-visibility.js';
|
|
53
59
|
import { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
|
|
54
60
|
import { LeaseReaper } from './portal/pool/lease-reaper.js';
|
|
55
|
-
import { findProjectConfig,
|
|
56
|
-
import { hasAnyProjectConnectedBefore } from './memory/recall/prior/connection-memory.js';
|
|
57
|
-
import { reticleStateHome } from './command/daemon/daemon.js';
|
|
61
|
+
import { findProjectConfig, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
|
|
58
62
|
import { probeLaunchableChromium } from './launch-chromium.js';
|
|
59
63
|
import { attachJournal } from './wire-journal.js';
|
|
60
64
|
import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
|
|
@@ -62,14 +66,7 @@ import { AMBIENT_RECORDING } from './language/flows/recording/tape/recordings.js
|
|
|
62
66
|
import { readRetainPolicy } from './memory/journal/on-disk/retain-policy.js';
|
|
63
67
|
import { log } from './log.js';
|
|
64
68
|
import { readReaderBundle, zeroInstallScript as zeroInstallScriptFor, } from './portal/pool/zero-install.js';
|
|
65
|
-
|
|
66
|
-
function replayVerdictLine(result) {
|
|
67
|
-
if (result.status === ReplayStatus.OK)
|
|
68
|
-
return `✓ "${result.name}" passed`;
|
|
69
|
-
if (result.status === ReplayStatus.DRIFT)
|
|
70
|
-
return `⚠ "${result.name}" drifted — a step no longer matches`;
|
|
71
|
-
return `✗ "${result.name}" failed — ${result.error?.message ?? 'could not replay'}`;
|
|
72
|
-
}
|
|
69
|
+
export { replayVerdictLine } from './language/flows/replay-from-hud.js';
|
|
73
70
|
// Re-exported from the contract, where the names now live: a consumer importing the server should
|
|
74
71
|
// not have to know that the vocabulary moved.
|
|
75
72
|
export { ReticleTool } from '@reticlehq/core';
|
|
@@ -137,6 +134,7 @@ function createBrowserPool(headless, reader) {
|
|
|
137
134
|
maxContexts,
|
|
138
135
|
genSessionId,
|
|
139
136
|
zeroInstallScript,
|
|
137
|
+
launchHeaded: playwrightLauncher({ headless: false }),
|
|
140
138
|
});
|
|
141
139
|
}
|
|
142
140
|
/**
|
|
@@ -225,12 +223,12 @@ async function resolveRealInput(options, onNavigateError, onNetworkDetail) {
|
|
|
225
223
|
}
|
|
226
224
|
/** Start the Reticle bridge (browser WS endpoint) and, by default, the MCP stdio server. */
|
|
227
225
|
/**
|
|
228
|
-
* The SDK-upgrade sentence for
|
|
229
|
-
* just-edited manifest is what we name.
|
|
230
|
-
* not an app.
|
|
226
|
+
* The SDK-upgrade sentence for the package.json of the project the page announced, evaluated at
|
|
227
|
+
* each HELLO so a just-edited manifest is what we name. The daemon's cwd only when that project's
|
|
228
|
+
* directory is unknown, and the framework-neutral sensor when that is not an app either (#1135).
|
|
231
229
|
*/
|
|
232
|
-
function
|
|
233
|
-
return sdkFixForDirectory(SERVER_VERSION, process.cwd());
|
|
230
|
+
function sdkFixForProject(projectId) {
|
|
231
|
+
return sdkFixForDirectory(SERVER_VERSION, projectDirectoryFor(projectId) ?? process.cwd());
|
|
234
232
|
}
|
|
235
233
|
/**
|
|
236
234
|
* Resolve a session's artifact root, from everything this machine knows about where projects live.
|
|
@@ -252,44 +250,29 @@ function sdkFixForCwd() {
|
|
|
252
250
|
* not. Deciding that here would mean reading every cloud.json on every tick.
|
|
253
251
|
*/
|
|
254
252
|
function knownProjectRoots() {
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
const path = join(homedir(), ReticleDir.ROOT, PROJECT_REGISTRY_FILE);
|
|
258
|
-
if (existsSync(path)) {
|
|
259
|
-
const registry = parseProjectRegistry(JSON.parse(readFileSync(path, 'utf8')));
|
|
260
|
-
for (const candidate of projectCandidates(registry))
|
|
261
|
-
roots.add(join(candidate.directory, ReticleDir.ROOT));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
catch {
|
|
265
|
-
// A registry that cannot be read is an empty one — never a reason to stop syncing.
|
|
266
|
-
}
|
|
267
|
-
try {
|
|
268
|
-
for (const config of discoverProjectConfigs(process.cwd()).found)
|
|
269
|
-
roots.add(join(config.directory, ReticleDir.ROOT));
|
|
270
|
-
}
|
|
271
|
-
catch {
|
|
272
|
-
// Same: a diagnostic walk that throws must not take the sync loop with it.
|
|
273
|
-
}
|
|
274
|
-
return [...roots];
|
|
253
|
+
return [...new Set(knownProjectCandidates().map((c) => join(c.directory, ReticleDir.ROOT)))];
|
|
275
254
|
}
|
|
276
255
|
/** This project's platform credential (stored, else the env key), as the env the platform readers take. */
|
|
277
256
|
const platformEnvFor = (root) => platformEnvPort(createNodeFileSystem(), root ?? join(process.cwd(), ReticleDir.ROOT), homedir(), process.env);
|
|
257
|
+
const loadHarnessConfig = async (root) => fetchPlatformConfig(await platformEnvFor(root)());
|
|
278
258
|
export async function start(options = {}) {
|
|
279
259
|
const port = options.port ?? RETICLE_DEFAULT_PORT;
|
|
260
|
+
const configForRoot = harnessConfigsByRoot(loadHarnessConfig);
|
|
280
261
|
// Open the user's impact record before anything can connect. Not inside the MCP branch: a daemon
|
|
281
262
|
// serving a browser with no agent attached still has a HUD to answer, and a report that reads
|
|
282
263
|
// "nothing recorded yet" over a month of history on disk is the worst version of this feature.
|
|
283
264
|
initImpact({
|
|
265
|
+
sdkVersion: SERVER_VERSION,
|
|
284
266
|
reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT),
|
|
285
267
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
286
268
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
287
269
|
// to reach for the other.
|
|
288
|
-
|
|
270
|
+
configForRoot,
|
|
271
|
+
coverageForRoot: (root) => () => coveragePercents(root),
|
|
289
272
|
});
|
|
290
273
|
const uninstallHooks = wireHooks(options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT), readProjectId(process.cwd()));
|
|
291
274
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
292
|
-
const bridge = new Bridge({ port, sdkFix:
|
|
275
|
+
const bridge = new Bridge({ port, sdkFix: sdkFixForProject, ...security });
|
|
293
276
|
// Server-authoritative liveness: a Node-side reaper (immune to browser throttling) ends sessions
|
|
294
277
|
// whose agent has gone idle, so a forgotten/crashed agent never leaves the HUD "running" forever.
|
|
295
278
|
const reaper = new SessionReaper(bridge.sessions);
|
|
@@ -301,7 +284,7 @@ export async function start(options = {}) {
|
|
|
301
284
|
bridge.sessions.setDefaultScope({ projectId: activeProjectId });
|
|
302
285
|
}
|
|
303
286
|
const baselines = new BaselineStore();
|
|
304
|
-
const recordings = new RecordingStore();
|
|
287
|
+
const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
|
|
305
288
|
// drive precedence: driveUrl (launch+own a browser) → CDP (attach) → none.
|
|
306
289
|
let pool;
|
|
307
290
|
let leaseReaper;
|
|
@@ -309,6 +292,7 @@ export async function start(options = {}) {
|
|
|
309
292
|
// page and the SDK session share an origin, so a NET_DETAIL is pushed to the matching connected session.
|
|
310
293
|
const routeNetworkDetail = makeNetworkDetailRouter(bridge, options.driveUrl);
|
|
311
294
|
const { realInput, owned } = await resolveRealInput(options, () => bridge.close(), routeNetworkDetail);
|
|
295
|
+
let chatDrives;
|
|
312
296
|
if (options.mcp !== false) {
|
|
313
297
|
// cwd/Date.now are confined to start — never inside reticle-dir.ts's pure logic (rule 7).
|
|
314
298
|
const fs = createNodeFileSystem();
|
|
@@ -366,7 +350,7 @@ export async function start(options = {}) {
|
|
|
366
350
|
fs,
|
|
367
351
|
reticleRoot,
|
|
368
352
|
linkedCloud: linkedCloudPort(fs, reticleRoot, homedir(), process.env),
|
|
369
|
-
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
353
|
+
artifactRootFor: artifactRootResolver(reticleRoot, { servingDirectory: servingDirectoryOf }),
|
|
370
354
|
now,
|
|
371
355
|
bridgePort: port,
|
|
372
356
|
browserProbe: probeLaunchableChromium,
|
|
@@ -375,13 +359,13 @@ export async function start(options = {}) {
|
|
|
375
359
|
...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
|
|
376
360
|
};
|
|
377
361
|
const profile = resolveToolSurface(options.toolProfile);
|
|
378
|
-
const
|
|
379
|
-
//
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
362
|
+
const toolDeps = realInput !== undefined ? { ...deps, realInput } : deps;
|
|
363
|
+
// The platform chat sees this app too: `reticle drive` in a window runs here, not in a daemon.
|
|
364
|
+
chatDrives = startChatDrives(toolDeps, bridge.sessions, fs, {
|
|
365
|
+
sessionCloud: sessionCloudPort(fs, homedir(), process.env, (id) => sessionRoot(toolDeps, id)),
|
|
366
|
+
version: SERVER_VERSION,
|
|
367
|
+
}, log);
|
|
368
|
+
const server = createMcpServer(toolDeps, profile, instructionStateAt(port, bridge.sessions.count()));
|
|
385
369
|
// When the agent (the MCP client) disconnects cleanly, end every active session at once so the
|
|
386
370
|
// HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
|
|
387
371
|
// self-ends via SESSION_LIFECYCLE.BRIDGE_LOST_MS — see transport.ts.)
|
|
@@ -400,6 +384,7 @@ export async function start(options = {}) {
|
|
|
400
384
|
uninstallHooks();
|
|
401
385
|
await cleanupCaptureDirectories();
|
|
402
386
|
leaseReaper?.stop();
|
|
387
|
+
chatDrives?.stop();
|
|
403
388
|
await pool?.shutdown();
|
|
404
389
|
await owned?.dispose();
|
|
405
390
|
await bridge.close();
|
|
@@ -414,21 +399,38 @@ export async function start(options = {}) {
|
|
|
414
399
|
*/
|
|
415
400
|
export async function startDaemon(options = {}) {
|
|
416
401
|
const port = options.port ?? RETICLE_DEFAULT_PORT;
|
|
402
|
+
let pushHarnessConfig = () => undefined;
|
|
403
|
+
const configForRoot = harnessConfigsByRoot(loadHarnessConfig, (root) => pushHarnessConfig(root));
|
|
417
404
|
// The SAME line as in `start`, because these are two entry points that each wire their own world
|
|
418
405
|
// and the daemon is the one that actually serves people. Wired only in `start`, the impact record
|
|
419
406
|
// was never opened in the process the HUD talks to: tool calls still recorded (the dispatch
|
|
420
407
|
// chokepoint opens it lazily), but a tab that connected before the first tool call was pushed
|
|
421
408
|
// nothing, so the report read "nothing recorded yet" over a file with history in it.
|
|
422
409
|
initImpact({
|
|
410
|
+
sdkVersion: SERVER_VERSION,
|
|
423
411
|
reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT),
|
|
424
412
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
425
413
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
426
414
|
// to reach for the other.
|
|
427
|
-
|
|
415
|
+
configForRoot,
|
|
416
|
+
coverageForRoot: (root) => () => coveragePercents(root),
|
|
428
417
|
});
|
|
429
418
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
430
419
|
const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
|
|
431
|
-
const bridge = new Bridge({
|
|
420
|
+
const bridge = new Bridge({
|
|
421
|
+
port,
|
|
422
|
+
server: shared.httpServer,
|
|
423
|
+
sdkFix: sdkFixForProject,
|
|
424
|
+
...security,
|
|
425
|
+
});
|
|
426
|
+
const repaint = (only) => {
|
|
427
|
+
for (const session of bridge.sessions.all()) {
|
|
428
|
+
const root = session.artifactRoot ?? options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
|
|
429
|
+
if (only === undefined || only === root)
|
|
430
|
+
session.pushImpact(() => impactSnapshot(root), true);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
pushHarnessConfig = repaint;
|
|
432
434
|
// The daemon owns listen (below), so the real bind error is reported there; absorb bridge.ready's
|
|
433
435
|
// mirror rejection so a port collision can't surface as an unhandled promise rejection.
|
|
434
436
|
void bridge.ready.catch(() => undefined);
|
|
@@ -470,7 +472,7 @@ export async function startDaemon(options = {}) {
|
|
|
470
472
|
const flows = new FlowStore(fs, reticleRoot, { now });
|
|
471
473
|
// Built here rather than inside `deps` below, so teardown can save what a drive recorded. Both
|
|
472
474
|
// paths pass the same pair — `daemon-parity.test.ts` is what keeps them from drifting apart.
|
|
473
|
-
const recordings = new RecordingStore();
|
|
475
|
+
const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
|
|
474
476
|
/*
|
|
475
477
|
* Bound AFTER the sync daemon exists, read only when a run is actually written.
|
|
476
478
|
*
|
|
@@ -523,11 +525,17 @@ export async function startDaemon(options = {}) {
|
|
|
523
525
|
// `syncNow`, not `nudge`: a nudge schedules a cycle soon, which is right for "a run landed" and
|
|
524
526
|
// wrong for a button somebody is watching. Never awaited.
|
|
525
527
|
bridge.attachSyncRequest(() => void cloudSync.syncNow());
|
|
526
|
-
// The panel's harness switch
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
//
|
|
530
|
-
|
|
528
|
+
// The panel's harness switch, written through to the platform so console and panel cannot disagree.
|
|
529
|
+
if (options.hudSignIn !== undefined)
|
|
530
|
+
bridge.attachSigninRequest(options.hudSignIn(() => repaint()));
|
|
531
|
+
// logout and link run in another process: repaint when their files change. See account-watch.ts.
|
|
532
|
+
const accountFiles = watchAccountFiles(() => repaint());
|
|
533
|
+
accountFiles.watch(join(homedir(), ReticleDir.ROOT, SESSION_FILE));
|
|
534
|
+
bridge.attachSessionReady((s) => accountFiles.watch(join(s.artifactRoot ?? reticleRoot, ReticleDir.CLOUD_LINK_FILE)));
|
|
535
|
+
bridge.attachHarnessRequest((on, s) => {
|
|
536
|
+
const root = s.artifactRoot ?? reticleRoot;
|
|
537
|
+
applyHarnessSwitch(configForRoot, root, on, platformEnvFor(root));
|
|
538
|
+
});
|
|
531
539
|
// Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
|
|
532
540
|
// never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
|
|
533
541
|
// Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
|
|
@@ -556,26 +564,29 @@ export async function startDaemon(options = {}) {
|
|
|
556
564
|
// daemon — the common case — so flows were listed, loaded and healed against wherever the
|
|
557
565
|
// daemon happened to be launched. Wired in only one of the two places, a resolver is a resolver
|
|
558
566
|
// that does not run.
|
|
559
|
-
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
567
|
+
artifactRootFor: artifactRootResolver(reticleRoot, { servingDirectory: servingDirectoryOf }),
|
|
560
568
|
now,
|
|
561
569
|
bridgePort: port,
|
|
562
570
|
browserProbe: probeLaunchableChromium,
|
|
571
|
+
firstRun: firstRunWiring({ port, cliPath: process.argv[1] ?? '' }),
|
|
563
572
|
// A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
|
|
564
573
|
onRunPersisted: () => cloudSync.nudge(),
|
|
574
|
+
syncNow: () => cloudSync.syncNow(),
|
|
565
575
|
};
|
|
566
576
|
const profile = resolveToolSurface(options.toolProfile);
|
|
567
577
|
const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
|
|
568
578
|
// Read per attach, not once: a project that gets wired while this daemon is alive should stop
|
|
569
579
|
// being told to wire itself on the next agent that connects.
|
|
570
|
-
shared.attachMcp(() => createMcpServer(
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
// `reticle drive <url>` when this daemon already owns the port: it asks HERE instead of trying to
|
|
575
|
-
// bind a port we are holding, and gets the same pooled context an agent's reticle_lease returns —
|
|
576
|
-
// through runTool, so it is counted and reported like any other call rather than being a second,
|
|
577
|
-
// invisible dispatch path. See cli/drive/drive-attach.ts for why attaching beats refereeing the race.
|
|
580
|
+
shared.attachMcp((peerSkew) => createMcpServer(peerSkew === undefined
|
|
581
|
+
? effectiveDeps
|
|
582
|
+
: { ...effectiveDeps, peerSkew: connectionSkew(peerSkew) }, profile, instructionStateAt(port, bridge.sessions.count())));
|
|
583
|
+
// `reticle drive <url>` against this daemon's port, through runTool. See cli/drive/drive-attach.ts.
|
|
578
584
|
shared.attachDrive((url) => runTool(LEASE_ACQUIRE_TOOL, effectiveDeps, { url }));
|
|
585
|
+
// The platform chat's apps and drives, run here. See chat-drives.ts.
|
|
586
|
+
const chatDrives = startChatDrives(effectiveDeps, bridge.sessions, fs, {
|
|
587
|
+
sessionCloud: sessionCloudPort(fs, homedir(), process.env, (id) => sessionRoot(effectiveDeps, id)),
|
|
588
|
+
version: SERVER_VERSION,
|
|
589
|
+
}, log);
|
|
579
590
|
// Optional OEM/CI verify endpoint: a host platform POSTs to /verify and gets an ReticleVerificationRun,
|
|
580
591
|
// driving the same flow-replay machinery the agent uses — no MCP stdio, no human. Each verdict is
|
|
581
592
|
// persisted via RunStore. Localhost-bound + token-guarded. Off unless `reticle serve --http`.
|
|
@@ -593,16 +604,9 @@ export async function startDaemon(options = {}) {
|
|
|
593
604
|
// verdict into the same activity log they watch the agent in. The page animates via the normal
|
|
594
605
|
// replay path, so they see it re-drive and the ✓/⚠/✗ land.
|
|
595
606
|
bridge.attachReplay((sessionId, flowName) => {
|
|
596
|
-
|
|
597
|
-
if (session === undefined)
|
|
607
|
+
if (bridge.sessions.get(sessionId) === undefined)
|
|
598
608
|
return;
|
|
599
|
-
|
|
600
|
-
replayAndLearn(effectiveDeps, { flowName, sessionId })
|
|
601
|
-
.then((result) => session.pushNarration(replayVerdictLine(result)))
|
|
602
|
-
.catch((error) => {
|
|
603
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
604
|
-
session.pushNarration(`✗ Replay "${flowName}" failed — ${message}`);
|
|
605
|
-
});
|
|
609
|
+
void replayFromHud(effectiveDeps, (id) => bridge.sessions.get(id), sessionId, flowName);
|
|
606
610
|
});
|
|
607
611
|
// On connect, hand the panel the replayable flows so it can render the ▶ list. Scoped to the
|
|
608
612
|
// connecting session's project (a shared daemon serves many apps; each panel shows only its own
|
|
@@ -672,6 +676,8 @@ export async function startDaemon(options = {}) {
|
|
|
672
676
|
if (vh !== undefined)
|
|
673
677
|
await new Promise((resolve) => vh.server.close(() => resolve()));
|
|
674
678
|
leaseReaper.stop();
|
|
679
|
+
chatDrives.stop();
|
|
680
|
+
accountFiles.close();
|
|
675
681
|
await cloudSync.flush(); // not stop(): the last run written is the one nobody has yet
|
|
676
682
|
await loopbackAlias.close?.();
|
|
677
683
|
await pool.shutdown();
|
|
@@ -681,12 +687,8 @@ export async function startDaemon(options = {}) {
|
|
|
681
687
|
},
|
|
682
688
|
};
|
|
683
689
|
}
|
|
684
|
-
// The OpenVerification binding, exported so a conformance run can be driven from outside this
|
|
685
|
-
//
|
|
686
|
-
// It was written and then not reachable: `conformanceClient` takes a `WebRealm`, a `WebRealm`
|
|
687
|
-
// takes a live `Session`, and a `Session` exists only inside a running daemon. A runner that
|
|
688
|
-
// cannot import it cannot score anything, which would have made the whole conformance chain
|
|
689
|
-
// complete and unusable.
|
|
690
|
+
// The OpenVerification binding, exported so a conformance run can be driven from outside this
|
|
691
|
+
// package: a `WebRealm` needs a live `Session`, which exists only inside a running daemon.
|
|
690
692
|
export { WebRealm } from './portal/realm/web-realm.js';
|
|
691
693
|
export { conformanceClient } from './portal/realm/conformance-client.js';
|
|
692
694
|
/**
|
|
@@ -42,10 +42,10 @@ declare const CapsuleSchema: z.ZodObject<{
|
|
|
42
42
|
seen: z.ZodOptional<z.ZodNumber>;
|
|
43
43
|
lastSeenAt: z.ZodOptional<z.ZodNumber>;
|
|
44
44
|
}, "strip", z.ZodTypeAny, {
|
|
45
|
-
expected: string;
|
|
46
|
-
origin: string;
|
|
47
45
|
version: 1;
|
|
48
46
|
id: string;
|
|
47
|
+
origin: string;
|
|
48
|
+
expected: string;
|
|
49
49
|
steps: FlowStep[];
|
|
50
50
|
createdAt: number;
|
|
51
51
|
observed: string;
|
|
@@ -53,10 +53,10 @@ declare const CapsuleSchema: z.ZodObject<{
|
|
|
53
53
|
seen?: number | undefined;
|
|
54
54
|
lastSeenAt?: number | undefined;
|
|
55
55
|
}, {
|
|
56
|
-
expected: string;
|
|
57
|
-
origin: string;
|
|
58
56
|
version: 1;
|
|
59
57
|
id: string;
|
|
58
|
+
origin: string;
|
|
59
|
+
expected: string;
|
|
60
60
|
steps: FlowStep[];
|
|
61
61
|
createdAt: number;
|
|
62
62
|
observed: string;
|
|
@@ -23,6 +23,7 @@ export function computeVerdict(input) {
|
|
|
23
23
|
.length + input.checks.filter((c) => c.status === Verified.YES).length;
|
|
24
24
|
const fails = input.flows.filter((f) => f.status === RunFlowStatus.FAIL).length +
|
|
25
25
|
input.checks.filter((c) => c.status === Verified.NO).length;
|
|
26
|
+
const skippedFlows = input.flows.filter((f) => f.status === RunFlowStatus.SKIPPED).length;
|
|
26
27
|
const blockingRisks = input.risks.filter((r) => r.gated).length;
|
|
27
28
|
const reasons = [];
|
|
28
29
|
for (const f of input.flows) {
|
|
@@ -31,6 +32,9 @@ export function computeVerdict(input) {
|
|
|
31
32
|
? `flow ${f.name}: ${f.failureReason}`
|
|
32
33
|
: `flow ${f.name} failed`);
|
|
33
34
|
}
|
|
35
|
+
else if (f.status === RunFlowStatus.SKIPPED && f.failureReason !== undefined) {
|
|
36
|
+
reasons.push(`flow ${f.name}: ${f.failureReason}`);
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
39
|
for (const c of input.checks) {
|
|
36
40
|
if (c.status === Verified.NO)
|
|
@@ -47,6 +51,8 @@ export function computeVerdict(input) {
|
|
|
47
51
|
status = VerdictStatus.PARTIAL;
|
|
48
52
|
else if (fails > 0)
|
|
49
53
|
status = VerdictStatus.FAIL;
|
|
54
|
+
else if (skippedFlows > 0 && passes > 0)
|
|
55
|
+
status = VerdictStatus.PARTIAL;
|
|
50
56
|
else if (0 === passes) {
|
|
51
57
|
// Nothing passed and nothing failed, so nothing was proved -- an empty run, or one whose every
|
|
52
58
|
// flow was skipped. Reporting that as PASS is the false green this whole file exists to prevent:
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* "why" is lifted from the replay's decision envelope (or its error) into failureReason.
|
|
6
6
|
*/
|
|
7
7
|
import { ReplayStatus, RunFlowStatus, type FlowFile, type FlowReplayResult, type RunFlowResult } from '@reticlehq/core';
|
|
8
|
-
/** OK → PASS; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
|
|
8
|
+
/** OK → PASS; UNVERIFIABLE → SKIPPED; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
|
|
9
9
|
export declare function runFlowStatusOf(status: ReplayStatus): RunFlowStatus;
|
|
10
10
|
/** Map one replay (plus its measured duration) into a RunFlowResult for the artifact's flows[]. */
|
|
11
11
|
export declare function mapReplayToFlowResult(replay: FlowReplayResult, durationMs: number, flow?: FlowFile): RunFlowResult;
|