@reticlehq/server 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +16 -8
- package/dist/chromium-launch-options.d.ts +3 -1
- package/dist/chromium-launch-options.js +2 -1
- package/dist/command/cli/adhoc-verdict.d.ts +1 -0
- package/dist/command/cli/adhoc-verdict.js +6 -3
- package/dist/command/cli/answers/open-note.d.ts +2 -0
- package/dist/command/cli/answers/open-note.js +22 -0
- package/dist/command/cli/cli-flow-commands.js +4 -8
- package/dist/command/cli/cli-parse.d.ts +1 -1
- package/dist/command/cli/cli-parse.js +10 -10
- package/dist/command/cli/cli-verify.d.ts +1 -1
- package/dist/command/cli/cli-verify.js +35 -25
- package/dist/command/cli/cloud-cli.js +51 -12
- package/dist/command/cli/cloud-login.d.ts +8 -0
- package/dist/command/cli/cloud-login.js +36 -2
- package/dist/command/cli/demo/demo-page.js +11 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
- package/dist/command/cli/drive/drive-attach.d.ts +27 -2
- package/dist/command/cli/drive/drive-attach.js +71 -4
- package/dist/command/cli/drive/drive-command.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +13 -7
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
- package/dist/command/cli/ports/port-holder.d.ts +10 -0
- package/dist/command/cli/ports/port-holder.js +24 -0
- package/dist/command/cli/setup-mcp-cli.js +6 -4
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +6 -9
- package/dist/command/cli.d.ts +6 -0
- package/dist/command/cli.js +28 -22
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
- package/dist/command/daemon/daemon-resolve.d.ts +14 -0
- package/dist/command/daemon/daemon-resolve.js +33 -2
- package/dist/command/setup/agent-writer.d.ts +1 -1
- package/dist/command/setup/agent-writer.js +3 -2
- package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
- package/dist/command/setup/bringup/connect-lease.js +1 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +8 -0
- package/dist/command/setup/bringup/dev-server-wait.js +19 -0
- package/dist/command/setup/init/init-port.d.ts +4 -0
- package/dist/command/setup/init/init-port.js +10 -0
- package/dist/command/setup/init/init-runtime.d.ts +10 -0
- package/dist/command/setup/init/init-runtime.js +18 -1
- package/dist/command/setup/node-effects.js +2 -1
- package/dist/command/setup/remaining-steps.js +2 -1
- package/dist/command/setup/run-setup.d.ts +5 -0
- package/dist/command/setup/run-setup.js +35 -27
- package/dist/command/setup/setup-command.d.ts +2 -1
- package/dist/command/setup/setup-command.js +2 -0
- package/dist/command/version/version-skew.js +11 -2
- package/dist/features/crawl/crawl-tools.js +4 -0
- package/dist/features/crawl/crawl.d.ts +6 -0
- package/dist/features/crawl/crawl.js +9 -1
- package/dist/features/exhaust/ledger.d.ts +7 -0
- package/dist/features/exhaust/ledger.js +35 -9
- package/dist/features/harness/app-findings.d.ts +2 -0
- package/dist/features/harness/app-findings.js +52 -0
- package/dist/features/harness/drive-report.d.ts +27 -1
- package/dist/features/harness/drive-report.js +162 -11
- package/dist/features/harness/drivers.d.ts +10 -14
- package/dist/features/harness/drivers.js +10 -14
- package/dist/features/harness/fill-values.d.ts +3 -73
- package/dist/features/harness/fill-values.js +3 -169
- package/dist/features/harness/goals.d.ts +21 -0
- package/dist/features/harness/goals.js +77 -0
- package/dist/features/harness/harness.d.ts +21 -0
- package/dist/features/harness/harness.js +69 -2
- package/dist/features/harness/platform/drive-target.d.ts +30 -0
- package/dist/features/harness/platform/drive-target.js +81 -0
- package/dist/features/harness/platform/local-apps.d.ts +94 -0
- package/dist/features/harness/platform/local-apps.js +204 -0
- package/dist/features/harness/platform/remote-drive.d.ts +180 -0
- package/dist/features/harness/platform/remote-drive.js +365 -0
- package/dist/features/harness/platform/script.d.ts +48 -0
- package/dist/features/harness/platform/script.js +128 -0
- package/dist/features/harness/platform/server-driver.d.ts +41 -0
- package/dist/features/harness/platform/server-driver.js +179 -0
- package/dist/features/harness/platform-config.d.ts +7 -0
- package/dist/features/harness/platform-config.js +14 -2
- package/dist/features/harness/script/app-controls.d.ts +5 -0
- package/dist/features/harness/script/app-controls.js +40 -0
- package/dist/features/harness/script/lane-ids.d.ts +14 -0
- package/dist/features/harness/script/lane-ids.js +16 -0
- package/dist/features/harness/script/script-run.d.ts +41 -0
- package/dist/features/harness/script/script-run.js +299 -0
- package/dist/features/license/license.d.ts +2 -2
- package/dist/features/visual/visual-tools.d.ts +10 -1
- package/dist/features/visual/visual-tools.js +40 -3
- package/dist/hooks/driven-by.d.ts +4 -0
- package/dist/hooks/driven-by.js +16 -0
- package/dist/hooks/hook-commands.js +15 -4
- package/dist/hooks/tool-log.d.ts +9 -0
- package/dist/hooks/tool-log.js +29 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +76 -82
- package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
- package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
- package/dist/judgement/runs/artifact/run-store.js +26 -1
- package/dist/judgement/runs/drive-run.d.ts +22 -0
- package/dist/judgement/runs/drive-run.js +99 -1
- package/dist/judgement/runs/repair-prompt.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +8 -1
- package/dist/judgement/runs/verification-sync.d.ts +3 -2
- package/dist/judgement/runs/verification-sync.js +4 -3
- package/dist/language/flows/change/gate.d.ts +10 -0
- package/dist/language/flows/change/gate.js +22 -0
- package/dist/language/flows/decision.js +34 -0
- package/dist/language/flows/flow-anchor.d.ts +54 -8
- package/dist/language/flows/flow-anchor.js +88 -3
- package/dist/language/flows/flow-cross-step.d.ts +15 -0
- package/dist/language/flows/flow-cross-step.js +21 -0
- package/dist/language/flows/flow-learning.js +8 -11
- package/dist/language/flows/flow-mutate-tools.js +5 -3
- package/dist/language/flows/flow-replay-run.d.ts +23 -9
- package/dist/language/flows/flow-replay-run.js +101 -94
- package/dist/language/flows/flow-replay.d.ts +23 -1
- package/dist/language/flows/flow-replay.js +92 -19
- package/dist/language/flows/flow-scope.js +5 -1
- package/dist/language/flows/flow-step-runners.d.ts +20 -2
- package/dist/language/flows/flow-step-runners.js +236 -42
- package/dist/language/flows/flow-store-for-session.d.ts +2 -2
- package/dist/language/flows/flow-store-for-session.js +2 -2
- package/dist/language/flows/flow-tools.js +58 -34
- package/dist/language/flows/flows.js +5 -2
- package/dist/language/flows/heal-run.js +6 -4
- package/dist/language/flows/recording/flake.d.ts +2 -2
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
- package/dist/language/flows/recording/tape/recordings.js +40 -8
- package/dist/language/flows/replay-from-hud.d.ts +20 -0
- package/dist/language/flows/replay-from-hud.js +51 -0
- package/dist/language/flows/replay.d.ts +9 -5
- package/dist/language/flows/replay.js +27 -9
- package/dist/language/flows/suite/suite-flakes.js +2 -0
- package/dist/language/flows/suite-selection.d.ts +2 -15
- package/dist/language/flows/suite-selection.js +4 -3
- package/dist/language/flows/verify-change-tools.js +8 -4
- package/dist/launch-chromium.d.ts +27 -5
- package/dist/launch-chromium.js +38 -8
- package/dist/memory/cloud/cloud-config.d.ts +12 -0
- package/dist/memory/cloud/cloud-config.js +15 -0
- package/dist/memory/cloud/cloud-sync.d.ts +17 -14
- package/dist/memory/cloud/cloud-sync.js +12 -1
- package/dist/memory/cloud/harness-config.d.ts +26 -1
- package/dist/memory/cloud/harness-config.js +99 -4
- package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
- package/dist/memory/cloud/hud-notices-source.js +128 -0
- package/dist/memory/cloud/memory-scope.d.ts +144 -0
- package/dist/memory/cloud/memory-scope.js +205 -0
- package/dist/memory/cloud/sync-cycle.d.ts +12 -3
- package/dist/memory/cloud/sync-cycle.js +16 -3
- package/dist/memory/cloud/sync-daemon.js +7 -5
- package/dist/memory/cloud/sync-disk.d.ts +1 -1
- package/dist/memory/cloud/sync-disk.js +5 -8
- package/dist/memory/impact/account-watch.d.ts +19 -0
- package/dist/memory/impact/account-watch.js +34 -0
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +12 -2
- package/dist/memory/impact/impact-store.d.ts +16 -0
- package/dist/memory/impact/impact-store.js +142 -26
- package/dist/memory/intent/intent-shard.d.ts +2 -2
- package/dist/memory/intent/intent-tools.js +80 -2
- package/dist/memory/journal/envelope.d.ts +8 -8
- package/dist/memory/journal/journal-recorder.js +7 -1
- package/dist/memory/journal/on-disk/retention.d.ts +3 -0
- package/dist/memory/journal/on-disk/retention.js +5 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
- package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
- package/dist/memory/journal/session-end.js +6 -4
- package/dist/memory/project/artifact-root-resolver.d.ts +26 -1
- package/dist/memory/project/artifact-root-resolver.js +94 -13
- package/dist/memory/project/artifact-root.d.ts +42 -5
- package/dist/memory/project/artifact-root.js +87 -8
- package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
- package/dist/memory/project/dir/reticle-dir.js +3 -1
- package/dist/memory/project/project-tools.js +22 -3
- package/dist/memory/project/session-root.d.ts +24 -1
- package/dist/memory/project/session-root.js +41 -5
- package/dist/memory/project/sync-status.d.ts +39 -0
- package/dist/memory/project/sync-status.js +90 -0
- package/dist/memory/recall/memory-tools.js +2 -0
- package/dist/memory/recall/project-memory.d.ts +19 -0
- package/dist/memory/recall/project-memory.js +45 -6
- package/dist/portal/bridge/bridge.d.ts +3 -1
- package/dist/portal/bridge/bridge.js +19 -2
- package/dist/portal/bridge/pairing-token.d.ts +9 -0
- package/dist/portal/bridge/pairing-token.js +17 -0
- package/dist/portal/input/real-input.d.ts +81 -10
- package/dist/portal/input/real-input.js +207 -22
- package/dist/portal/input/viewport-tools.js +3 -3
- package/dist/portal/pool/browser-pool.d.ts +22 -9
- package/dist/portal/pool/browser-pool.js +70 -35
- package/dist/portal/pool/playwright-launcher.d.ts +13 -0
- package/dist/portal/pool/playwright-launcher.js +17 -1
- package/dist/portal/pool/pool-contract.d.ts +7 -4
- package/dist/portal/session/command-payload.d.ts +8 -0
- package/dist/portal/session/command-payload.js +19 -0
- package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
- package/dist/portal/session/dev-server/dev-command.js +61 -12
- package/dist/portal/session/facts/departure-note.d.ts +22 -0
- package/dist/portal/session/facts/departure-note.js +47 -0
- package/dist/portal/session/facts/session-replaced.d.ts +5 -0
- package/dist/portal/session/facts/session-replaced.js +5 -0
- package/dist/portal/session/first-run-wiring.d.ts +42 -0
- package/dist/portal/session/first-run-wiring.js +132 -0
- package/dist/portal/session/human/notes-ledger.d.ts +20 -0
- package/dist/portal/session/human/notes-ledger.js +55 -0
- package/dist/portal/session/human/review-store.d.ts +12 -0
- package/dist/portal/session/human/review-store.js +18 -10
- package/dist/portal/session/live-control-tools.d.ts +0 -13
- package/dist/portal/session/live-control-tools.js +28 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
- package/dist/portal/session/no-session-diagnosis.js +15 -0
- package/dist/portal/session/no-session-next-action.d.ts +11 -0
- package/dist/portal/session/no-session-next-action.js +31 -5
- package/dist/portal/session/no-session-watch.d.ts +14 -1
- package/dist/portal/session/no-session-watch.js +63 -10
- package/dist/portal/session/presence/session-mirror.js +3 -0
- package/dist/portal/session/serving-directory.d.ts +27 -0
- package/dist/portal/session/serving-directory.js +45 -0
- package/dist/portal/session/session-health.d.ts +7 -0
- package/dist/portal/session/session-health.js +6 -0
- package/dist/portal/session/session-info.d.ts +8 -1
- package/dist/portal/session/session-info.js +6 -0
- package/dist/portal/session/session-manager.d.ts +21 -0
- package/dist/portal/session/session-manager.js +44 -8
- package/dist/portal/session/session-reaper.js +4 -1
- package/dist/portal/session/session-successor.d.ts +5 -0
- package/dist/portal/session/session-tools.js +26 -3
- package/dist/portal/session/session.d.ts +7 -2
- package/dist/portal/session/session.js +21 -18
- package/dist/start-options.d.ts +6 -0
- package/dist/status-payload.d.ts +10 -0
- package/dist/status-payload.js +23 -0
- package/dist/surface/http-server.js +11 -1
- package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
- package/dist/surface/mcp/mcp-proxy.js +36 -5
- package/dist/surface/mcp/mcp.d.ts +11 -6
- package/dist/surface/mcp/mcp.js +27 -13
- package/dist/surface/mcp/server-instructions.d.ts +4 -2
- package/dist/surface/mcp/server-instructions.js +14 -4
- package/dist/surface/mcp/shared-params.js +2 -2
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
- package/dist/surface/tools/act/settle-in-flight.js +25 -3
- package/dist/surface/tools/act-sequence-tool.js +2 -2
- package/dist/surface/tools/act-tools.js +23 -7
- package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +17 -2
- package/dist/surface/tools/browser-tools.js +8 -1
- package/dist/surface/tools/chat-drives.d.ts +26 -0
- package/dist/surface/tools/chat-drives.js +165 -0
- package/dist/surface/tools/empty-session-list.d.ts +41 -0
- package/dist/surface/tools/empty-session-list.js +80 -0
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +37 -4
- package/dist/surface/tools/explore-tools.d.ts +10 -1
- package/dist/surface/tools/explore-tools.js +82 -13
- package/dist/surface/tools/feedback-tools.js +14 -0
- package/dist/surface/tools/harness-explore.d.ts +96 -14
- package/dist/surface/tools/harness-explore.js +227 -206
- package/dist/surface/tools/harness-plan.d.ts +12 -0
- package/dist/surface/tools/harness-plan.js +31 -0
- package/dist/surface/tools/harness-script.d.ts +8 -0
- package/dist/surface/tools/harness-script.js +342 -0
- package/dist/surface/tools/harness-toolset.d.ts +14 -1
- package/dist/surface/tools/harness-toolset.js +77 -6
- package/dist/surface/tools/invoke-tool.js +52 -1
- package/dist/surface/tools/lease-seed.d.ts +29 -0
- package/dist/surface/tools/lease-seed.js +123 -0
- package/dist/surface/tools/lease-tools.d.ts +3 -23
- package/dist/surface/tools/lease-tools.js +30 -131
- package/dist/surface/tools/live-call-text.js +2 -2
- package/dist/surface/tools/next-step.d.ts +19 -0
- package/dist/surface/tools/next-step.js +159 -0
- package/dist/surface/tools/observe-tools.js +2 -4
- package/dist/surface/tools/platform-moment.d.ts +12 -0
- package/dist/surface/tools/platform-moment.js +168 -0
- package/dist/surface/tools/query-hint-schema.d.ts +7 -0
- package/dist/surface/tools/query-hint-schema.js +10 -0
- package/dist/surface/tools/read-tools.js +8 -2
- package/dist/surface/tools/real-input-attempt.js +96 -13
- package/dist/surface/tools/tool-kit.d.ts +15 -1
- package/dist/surface/tools/tool-kit.js +4 -6
- package/dist/surface/tools/tools.js +20 -48
- package/dist/telemetry/hud-metrics.d.ts +2 -0
- package/dist/telemetry/hud-metrics.js +17 -0
- package/dist/telemetry/telemetry.d.ts +12 -0
- package/dist/telemetry/telemetry.js +43 -14
- package/dist/telemetry/verification-of.js +1 -0
- package/dist/wire-journal.js +4 -1
- package/docs/README.md +1 -1
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/autodrive.md +29 -25
- package/docs/cli/doctor.mdx +6 -0
- package/docs/cli/drive.mdx +8 -2
- package/docs/cli/init.mdx +11 -13
- package/docs/cli/status.mdx +5 -1
- package/docs/cli/version.mdx +3 -3
- package/docs/cli.mdx +1 -1
- package/docs/desktop.mdx +5 -1
- package/docs/environment.mdx +3 -7
- package/docs/getting-started.md +13 -11
- package/docs/human-control.md +1 -2
- package/docs/index.mdx +4 -3
- package/docs/install-manual.mdx +2 -2
- package/docs/packages/tauri.mdx +2 -2
- package/docs/packages/vite-plugin.mdx +22 -1
- package/docs/packages.mdx +1 -1
- package/docs/quickstart.mdx +17 -7
- package/docs/telemetry.md +1 -1
- package/docs/tools/session-and-feedback.mdx +9 -9
- package/docs/usage.md +5 -2
- package/docs/what-is-recorded.md +13 -3
- package/docs/what-is-reticle.mdx +3 -1
- package/package.json +8 -12
- package/dist/features/harness/driver.d.ts +0 -50
- package/dist/features/harness/driver.js +0 -236
- package/dist/features/harness/jev-driver.d.ts +0 -188
- package/dist/features/harness/jev-driver.js +0 -678
- package/dist/features/harness/openai-driver.d.ts +0 -58
- package/dist/features/harness/openai-driver.js +0 -206
- package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
- package/dist/memory/project/dir/fill-value-store.js +0 -88
- package/docs/local-registry.md +0 -143
|
@@ -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,20 +1,23 @@
|
|
|
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';
|
|
@@ -25,7 +28,7 @@ import { connectionSkew } from './command/version/version-nudge.js';
|
|
|
25
28
|
import { SERVER_VERSION } from './command/version/identity/server-version.js';
|
|
26
29
|
import { BaselineStore } from './memory/project/baselines.js';
|
|
27
30
|
import { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
28
|
-
import { initImpact } from './memory/impact/impact-recorder.js';
|
|
31
|
+
import { initImpact, impactSnapshot } from './memory/impact/impact-recorder.js';
|
|
29
32
|
import { flowAuthor } from './language/flows/flow-author.js';
|
|
30
33
|
import { FlowStore } from './language/flows/flows.js';
|
|
31
34
|
import { buildFlowChips } from './language/flows/flow-scope.js';
|
|
@@ -40,7 +43,9 @@ import { createRunnerPort } from './judgement/runs/runner-port.js';
|
|
|
40
43
|
import { RunStore } from './judgement/runs/artifact/run-store.js';
|
|
41
44
|
import { startVerifyServer } from './judgement/runs/verify-server.js';
|
|
42
45
|
import { createMcpServer } from './surface/mcp/mcp.js';
|
|
46
|
+
import { instructionStateAt } from './surface/mcp/mcp-proxy.js';
|
|
43
47
|
import { LEASE_ACQUIRE_TOOL } from './surface/tools/lease-tools.js';
|
|
48
|
+
import { startChatDrives } from './surface/tools/chat-drives.js';
|
|
44
49
|
import { runTool } from './surface/tools/invoke-tool.js';
|
|
45
50
|
import { SessionReaper, endAllSessions, MCP_DISCONNECT_SUMMARY, } from './portal/session/session-reaper.js';
|
|
46
51
|
import { wireSessionScope } from './portal/session/no-session-watch.js';
|
|
@@ -53,9 +58,7 @@ import { BrowserPool } from './portal/pool/browser-pool.js';
|
|
|
53
58
|
import { AGENT_ALREADY_DRIVING_ELSEWHERE, shouldGreetWithLeaseNotice, } from './portal/session/lease-visibility.js';
|
|
54
59
|
import { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
|
|
55
60
|
import { LeaseReaper } from './portal/pool/lease-reaper.js';
|
|
56
|
-
import { findProjectConfig,
|
|
57
|
-
import { hasAnyProjectConnectedBefore } from './memory/recall/prior/connection-memory.js';
|
|
58
|
-
import { reticleStateHome } from './command/daemon/daemon.js';
|
|
61
|
+
import { findProjectConfig, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
|
|
59
62
|
import { probeLaunchableChromium } from './launch-chromium.js';
|
|
60
63
|
import { attachJournal } from './wire-journal.js';
|
|
61
64
|
import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
|
|
@@ -63,14 +66,7 @@ import { AMBIENT_RECORDING } from './language/flows/recording/tape/recordings.js
|
|
|
63
66
|
import { readRetainPolicy } from './memory/journal/on-disk/retain-policy.js';
|
|
64
67
|
import { log } from './log.js';
|
|
65
68
|
import { readReaderBundle, zeroInstallScript as zeroInstallScriptFor, } from './portal/pool/zero-install.js';
|
|
66
|
-
|
|
67
|
-
function replayVerdictLine(result) {
|
|
68
|
-
if (result.status === ReplayStatus.OK)
|
|
69
|
-
return `✓ "${result.name}" passed`;
|
|
70
|
-
if (result.status === ReplayStatus.DRIFT)
|
|
71
|
-
return `⚠ "${result.name}" drifted — a step no longer matches`;
|
|
72
|
-
return `✗ "${result.name}" failed — ${result.error?.message ?? 'could not replay'}`;
|
|
73
|
-
}
|
|
69
|
+
export { replayVerdictLine } from './language/flows/replay-from-hud.js';
|
|
74
70
|
// Re-exported from the contract, where the names now live: a consumer importing the server should
|
|
75
71
|
// not have to know that the vocabulary moved.
|
|
76
72
|
export { ReticleTool } from '@reticlehq/core';
|
|
@@ -138,6 +134,7 @@ function createBrowserPool(headless, reader) {
|
|
|
138
134
|
maxContexts,
|
|
139
135
|
genSessionId,
|
|
140
136
|
zeroInstallScript,
|
|
137
|
+
launchHeaded: playwrightLauncher({ headless: false }),
|
|
141
138
|
});
|
|
142
139
|
}
|
|
143
140
|
/**
|
|
@@ -253,40 +250,25 @@ function sdkFixForProject(projectId) {
|
|
|
253
250
|
* not. Deciding that here would mean reading every cloud.json on every tick.
|
|
254
251
|
*/
|
|
255
252
|
function knownProjectRoots() {
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
const path = join(homedir(), ReticleDir.ROOT, PROJECT_REGISTRY_FILE);
|
|
259
|
-
if (existsSync(path)) {
|
|
260
|
-
const registry = parseProjectRegistry(JSON.parse(readFileSync(path, 'utf8')));
|
|
261
|
-
for (const candidate of projectCandidates(registry))
|
|
262
|
-
roots.add(join(candidate.directory, ReticleDir.ROOT));
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
// A registry that cannot be read is an empty one — never a reason to stop syncing.
|
|
267
|
-
}
|
|
268
|
-
try {
|
|
269
|
-
for (const config of discoverProjectConfigs(process.cwd()).found)
|
|
270
|
-
roots.add(join(config.directory, ReticleDir.ROOT));
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
273
|
-
// Same: a diagnostic walk that throws must not take the sync loop with it.
|
|
274
|
-
}
|
|
275
|
-
return [...roots];
|
|
253
|
+
return [...new Set(knownProjectCandidates().map((c) => join(c.directory, ReticleDir.ROOT)))];
|
|
276
254
|
}
|
|
277
255
|
/** This project's platform credential (stored, else the env key), as the env the platform readers take. */
|
|
278
256
|
const platformEnvFor = (root) => platformEnvPort(createNodeFileSystem(), root ?? join(process.cwd(), ReticleDir.ROOT), homedir(), process.env);
|
|
257
|
+
const loadHarnessConfig = async (root) => fetchPlatformConfig(await platformEnvFor(root)());
|
|
279
258
|
export async function start(options = {}) {
|
|
280
259
|
const port = options.port ?? RETICLE_DEFAULT_PORT;
|
|
260
|
+
const configForRoot = harnessConfigsByRoot(loadHarnessConfig);
|
|
281
261
|
// Open the user's impact record before anything can connect. Not inside the MCP branch: a daemon
|
|
282
262
|
// serving a browser with no agent attached still has a HUD to answer, and a report that reads
|
|
283
263
|
// "nothing recorded yet" over a month of history on disk is the worst version of this feature.
|
|
284
264
|
initImpact({
|
|
265
|
+
sdkVersion: SERVER_VERSION,
|
|
285
266
|
reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT),
|
|
286
267
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
287
268
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
288
269
|
// to reach for the other.
|
|
289
|
-
|
|
270
|
+
configForRoot,
|
|
271
|
+
coverageForRoot: (root) => () => coveragePercents(root),
|
|
290
272
|
});
|
|
291
273
|
const uninstallHooks = wireHooks(options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT), readProjectId(process.cwd()));
|
|
292
274
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
@@ -302,7 +284,7 @@ export async function start(options = {}) {
|
|
|
302
284
|
bridge.sessions.setDefaultScope({ projectId: activeProjectId });
|
|
303
285
|
}
|
|
304
286
|
const baselines = new BaselineStore();
|
|
305
|
-
const recordings = new RecordingStore();
|
|
287
|
+
const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
|
|
306
288
|
// drive precedence: driveUrl (launch+own a browser) → CDP (attach) → none.
|
|
307
289
|
let pool;
|
|
308
290
|
let leaseReaper;
|
|
@@ -310,6 +292,7 @@ export async function start(options = {}) {
|
|
|
310
292
|
// page and the SDK session share an origin, so a NET_DETAIL is pushed to the matching connected session.
|
|
311
293
|
const routeNetworkDetail = makeNetworkDetailRouter(bridge, options.driveUrl);
|
|
312
294
|
const { realInput, owned } = await resolveRealInput(options, () => bridge.close(), routeNetworkDetail);
|
|
295
|
+
let chatDrives;
|
|
313
296
|
if (options.mcp !== false) {
|
|
314
297
|
// cwd/Date.now are confined to start — never inside reticle-dir.ts's pure logic (rule 7).
|
|
315
298
|
const fs = createNodeFileSystem();
|
|
@@ -367,7 +350,7 @@ export async function start(options = {}) {
|
|
|
367
350
|
fs,
|
|
368
351
|
reticleRoot,
|
|
369
352
|
linkedCloud: linkedCloudPort(fs, reticleRoot, homedir(), process.env),
|
|
370
|
-
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
353
|
+
artifactRootFor: artifactRootResolver(reticleRoot, { servingDirectory: servingDirectoryOf }),
|
|
371
354
|
now,
|
|
372
355
|
bridgePort: port,
|
|
373
356
|
browserProbe: probeLaunchableChromium,
|
|
@@ -376,13 +359,13 @@ export async function start(options = {}) {
|
|
|
376
359
|
...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
|
|
377
360
|
};
|
|
378
361
|
const profile = resolveToolSurface(options.toolProfile);
|
|
379
|
-
const
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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()));
|
|
386
369
|
// When the agent (the MCP client) disconnects cleanly, end every active session at once so the
|
|
387
370
|
// HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
|
|
388
371
|
// self-ends via SESSION_LIFECYCLE.BRIDGE_LOST_MS — see transport.ts.)
|
|
@@ -401,6 +384,7 @@ export async function start(options = {}) {
|
|
|
401
384
|
uninstallHooks();
|
|
402
385
|
await cleanupCaptureDirectories();
|
|
403
386
|
leaseReaper?.stop();
|
|
387
|
+
chatDrives?.stop();
|
|
404
388
|
await pool?.shutdown();
|
|
405
389
|
await owned?.dispose();
|
|
406
390
|
await bridge.close();
|
|
@@ -415,17 +399,21 @@ export async function start(options = {}) {
|
|
|
415
399
|
*/
|
|
416
400
|
export async function startDaemon(options = {}) {
|
|
417
401
|
const port = options.port ?? RETICLE_DEFAULT_PORT;
|
|
402
|
+
let pushHarnessConfig = () => undefined;
|
|
403
|
+
const configForRoot = harnessConfigsByRoot(loadHarnessConfig, (root) => pushHarnessConfig(root));
|
|
418
404
|
// The SAME line as in `start`, because these are two entry points that each wire their own world
|
|
419
405
|
// and the daemon is the one that actually serves people. Wired only in `start`, the impact record
|
|
420
406
|
// was never opened in the process the HUD talks to: tool calls still recorded (the dispatch
|
|
421
407
|
// chokepoint opens it lazily), but a tab that connected before the first tool call was pushed
|
|
422
408
|
// nothing, so the report read "nothing recorded yet" over a file with history in it.
|
|
423
409
|
initImpact({
|
|
410
|
+
sdkVersion: SERVER_VERSION,
|
|
424
411
|
reticleRoot: options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT),
|
|
425
412
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
426
413
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
427
414
|
// to reach for the other.
|
|
428
|
-
|
|
415
|
+
configForRoot,
|
|
416
|
+
coverageForRoot: (root) => () => coveragePercents(root),
|
|
429
417
|
});
|
|
430
418
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
431
419
|
const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
|
|
@@ -435,6 +423,14 @@ export async function startDaemon(options = {}) {
|
|
|
435
423
|
sdkFix: sdkFixForProject,
|
|
436
424
|
...security,
|
|
437
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;
|
|
438
434
|
// The daemon owns listen (below), so the real bind error is reported there; absorb bridge.ready's
|
|
439
435
|
// mirror rejection so a port collision can't surface as an unhandled promise rejection.
|
|
440
436
|
void bridge.ready.catch(() => undefined);
|
|
@@ -476,7 +472,7 @@ export async function startDaemon(options = {}) {
|
|
|
476
472
|
const flows = new FlowStore(fs, reticleRoot, { now });
|
|
477
473
|
// Built here rather than inside `deps` below, so teardown can save what a drive recorded. Both
|
|
478
474
|
// paths pass the same pair — `daemon-parity.test.ts` is what keeps them from drifting apart.
|
|
479
|
-
const recordings = new RecordingStore();
|
|
475
|
+
const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
|
|
480
476
|
/*
|
|
481
477
|
* Bound AFTER the sync daemon exists, read only when a run is actually written.
|
|
482
478
|
*
|
|
@@ -529,11 +525,17 @@ export async function startDaemon(options = {}) {
|
|
|
529
525
|
// `syncNow`, not `nudge`: a nudge schedules a cycle soon, which is right for "a run landed" and
|
|
530
526
|
// wrong for a button somebody is watching. Never awaited.
|
|
531
527
|
bridge.attachSyncRequest(() => void cloudSync.syncNow());
|
|
532
|
-
// The panel's harness switch
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
//
|
|
536
|
-
|
|
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
|
+
});
|
|
537
539
|
// Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
|
|
538
540
|
// never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
|
|
539
541
|
// Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
|
|
@@ -562,12 +564,14 @@ export async function startDaemon(options = {}) {
|
|
|
562
564
|
// daemon — the common case — so flows were listed, loaded and healed against wherever the
|
|
563
565
|
// daemon happened to be launched. Wired in only one of the two places, a resolver is a resolver
|
|
564
566
|
// that does not run.
|
|
565
|
-
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
567
|
+
artifactRootFor: artifactRootResolver(reticleRoot, { servingDirectory: servingDirectoryOf }),
|
|
566
568
|
now,
|
|
567
569
|
bridgePort: port,
|
|
568
570
|
browserProbe: probeLaunchableChromium,
|
|
571
|
+
firstRun: firstRunWiring({ port, cliPath: process.argv[1] ?? '' }),
|
|
569
572
|
// A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
|
|
570
573
|
onRunPersisted: () => cloudSync.nudge(),
|
|
574
|
+
syncNow: () => cloudSync.syncNow(),
|
|
571
575
|
};
|
|
572
576
|
const profile = resolveToolSurface(options.toolProfile);
|
|
573
577
|
const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
|
|
@@ -575,15 +579,14 @@ export async function startDaemon(options = {}) {
|
|
|
575
579
|
// being told to wire itself on the next agent that connects.
|
|
576
580
|
shared.attachMcp((peerSkew) => createMcpServer(peerSkew === undefined
|
|
577
581
|
? effectiveDeps
|
|
578
|
-
: { ...effectiveDeps, peerSkew: connectionSkew(peerSkew) }, profile,
|
|
579
|
-
//
|
|
580
|
-
bridge.sessions.count() > 0 ||
|
|
581
|
-
hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd()))));
|
|
582
|
-
// `reticle drive <url>` when this daemon already owns the port: it asks HERE instead of trying to
|
|
583
|
-
// bind a port we are holding, and gets the same pooled context an agent's reticle_lease returns —
|
|
584
|
-
// through runTool, so it is counted and reported like any other call rather than being a second,
|
|
585
|
-
// invisible dispatch path. See cli/drive/drive-attach.ts for why attaching beats refereeing the race.
|
|
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.
|
|
586
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);
|
|
587
590
|
// Optional OEM/CI verify endpoint: a host platform POSTs to /verify and gets an ReticleVerificationRun,
|
|
588
591
|
// driving the same flow-replay machinery the agent uses — no MCP stdio, no human. Each verdict is
|
|
589
592
|
// persisted via RunStore. Localhost-bound + token-guarded. Off unless `reticle serve --http`.
|
|
@@ -601,16 +604,9 @@ export async function startDaemon(options = {}) {
|
|
|
601
604
|
// verdict into the same activity log they watch the agent in. The page animates via the normal
|
|
602
605
|
// replay path, so they see it re-drive and the ✓/⚠/✗ land.
|
|
603
606
|
bridge.attachReplay((sessionId, flowName) => {
|
|
604
|
-
|
|
605
|
-
if (session === undefined)
|
|
607
|
+
if (bridge.sessions.get(sessionId) === undefined)
|
|
606
608
|
return;
|
|
607
|
-
|
|
608
|
-
replayAndLearn(effectiveDeps, { flowName, sessionId })
|
|
609
|
-
.then((result) => session.pushNarration(replayVerdictLine(result)))
|
|
610
|
-
.catch((error) => {
|
|
611
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
612
|
-
session.pushNarration(`✗ Replay "${flowName}" failed — ${message}`);
|
|
613
|
-
});
|
|
609
|
+
void replayFromHud(effectiveDeps, (id) => bridge.sessions.get(id), sessionId, flowName);
|
|
614
610
|
});
|
|
615
611
|
// On connect, hand the panel the replayable flows so it can render the ▶ list. Scoped to the
|
|
616
612
|
// connecting session's project (a shared daemon serves many apps; each panel shows only its own
|
|
@@ -680,6 +676,8 @@ export async function startDaemon(options = {}) {
|
|
|
680
676
|
if (vh !== undefined)
|
|
681
677
|
await new Promise((resolve) => vh.server.close(() => resolve()));
|
|
682
678
|
leaseReaper.stop();
|
|
679
|
+
chatDrives.stop();
|
|
680
|
+
accountFiles.close();
|
|
683
681
|
await cloudSync.flush(); // not stop(): the last run written is the one nobody has yet
|
|
684
682
|
await loopbackAlias.close?.();
|
|
685
683
|
await pool.shutdown();
|
|
@@ -689,12 +687,8 @@ export async function startDaemon(options = {}) {
|
|
|
689
687
|
},
|
|
690
688
|
};
|
|
691
689
|
}
|
|
692
|
-
// The OpenVerification binding, exported so a conformance run can be driven from outside this
|
|
693
|
-
//
|
|
694
|
-
// It was written and then not reachable: `conformanceClient` takes a `WebRealm`, a `WebRealm`
|
|
695
|
-
// takes a live `Session`, and a `Session` exists only inside a running daemon. A runner that
|
|
696
|
-
// cannot import it cannot score anything, which would have made the whole conformance chain
|
|
697
|
-
// 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.
|
|
698
692
|
export { WebRealm } from './portal/realm/web-realm.js';
|
|
699
693
|
export { conformanceClient } from './portal/realm/conformance-client.js';
|
|
700
694
|
/**
|
|
@@ -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;
|
|
@@ -6,9 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { ReplayStatus, RUN_RECORDING_MAX_BYTES, RUN_STEP_RESULTS_MAX, RUN_TEXT_MAX, RunFlowStatus, } from '@reticlehq/core';
|
|
8
8
|
import { SUCCESS_STEP_TOOL } from '../../../language/flows/flow-success.js';
|
|
9
|
-
/** OK → PASS; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
|
|
9
|
+
/** OK → PASS; UNVERIFIABLE → SKIPPED; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
|
|
10
10
|
export function runFlowStatusOf(status) {
|
|
11
|
-
|
|
11
|
+
if (status === ReplayStatus.OK)
|
|
12
|
+
return RunFlowStatus.PASS;
|
|
13
|
+
if (status === ReplayStatus.UNVERIFIABLE)
|
|
14
|
+
return RunFlowStatus.SKIPPED;
|
|
15
|
+
return RunFlowStatus.FAIL;
|
|
12
16
|
}
|
|
13
17
|
const clip = (text) => text.length > RUN_TEXT_MAX ? `${text.slice(0, RUN_TEXT_MAX - 1)}…` : text;
|
|
14
18
|
/** One step as synced: what it did, where, and whether it held. */
|
|
@@ -39,9 +43,12 @@ function recordingOf(flow) {
|
|
|
39
43
|
}
|
|
40
44
|
/** Map one replay (plus its measured duration) into a RunFlowResult for the artifact's flows[]. */
|
|
41
45
|
export function mapReplayToFlowResult(replay, durationMs, flow) {
|
|
42
|
-
// A replay that could not start
|
|
43
|
-
// every reader of a run (the verdict, the gate)
|
|
44
|
-
|
|
46
|
+
// A replay that could not start or complete returns UNVERIFIABLE (or carries an unverifiable caveat).
|
|
47
|
+
// Nothing was verified, so it is SKIPPED — which every reader of a run (the verdict, the gate)
|
|
48
|
+
// refuses to count as a pass.
|
|
49
|
+
const status = replay.status === ReplayStatus.UNVERIFIABLE || replay.unverifiable !== undefined
|
|
50
|
+
? RunFlowStatus.SKIPPED
|
|
51
|
+
: runFlowStatusOf(replay.status);
|
|
45
52
|
const failureReason = replay.unverifiable !== undefined
|
|
46
53
|
? replay.unverifiable.reason
|
|
47
54
|
: status === RunFlowStatus.FAIL
|
|
@@ -49,13 +56,16 @@ export function mapReplayToFlowResult(replay, durationMs, flow) {
|
|
|
49
56
|
replay.decision?.summary ??
|
|
50
57
|
replay.error?.message ??
|
|
51
58
|
'flow failed')
|
|
52
|
-
:
|
|
59
|
+
: status === RunFlowStatus.SKIPPED
|
|
60
|
+
? 'flow could not be graded'
|
|
61
|
+
: undefined;
|
|
53
62
|
// A flow with a success oracle gets a synthetic 'success' step appended by replay; surface its label
|
|
54
63
|
// as the run's `oracle` so the verdict counts this flow as consequence-backed (→ HIGH confidence),
|
|
55
64
|
// not a bare smoke click. Without this, an oracle-backed pass reads as MEDIUM and undersells itself.
|
|
56
65
|
const oracle = replay.steps.find((s) => s.tool === SUCCESS_STEP_TOOL)?.anchor;
|
|
57
66
|
return {
|
|
58
67
|
name: replay.name,
|
|
68
|
+
...(flow?.template === undefined ? {} : { template: flow.template }),
|
|
59
69
|
status,
|
|
60
70
|
steps: replay.steps.length,
|
|
61
71
|
durationMs,
|
|
@@ -38,7 +38,7 @@ export declare class RunStore {
|
|
|
38
38
|
* Guards runId as a safe path segment BEFORE building the path — a runId can originate from a caller
|
|
39
39
|
* (an OEM may set it), so an unsafe value must never escape .reticle/runs/ (mirrors the read guard).
|
|
40
40
|
*/
|
|
41
|
-
write(
|
|
41
|
+
write(given: ReticleVerificationRun): Promise<void>;
|
|
42
42
|
/** Never throws. Invalid id / missing → MISSING; bad JSON or failed schema → MALFORMED. */
|
|
43
43
|
read(runId: RunId): Promise<ReadRunResult>;
|
|
44
44
|
/** List run ids on disk (filenames minus.json). Empty when the runs dir is absent. */
|