@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
|
@@ -25,6 +25,8 @@ export interface WaitFacts {
|
|
|
25
25
|
readonly quietForMs: number;
|
|
26
26
|
/** Milliseconds since the wait began. */
|
|
27
27
|
readonly elapsedMs: number;
|
|
28
|
+
/** Explicit caller limit; otherwise a busy build keeps the default ceiling. */
|
|
29
|
+
readonly budgetMs?: number | undefined;
|
|
28
30
|
/**
|
|
29
31
|
* How long silence may mean "still starting" on THIS machine. Defaults to QUIET_MEANS_HUNG_MS.
|
|
30
32
|
*
|
|
@@ -57,6 +59,14 @@ export declare const WAIT_CEILING_MS: number;
|
|
|
57
59
|
*/
|
|
58
60
|
export declare function judgeWait(facts: WaitFacts): WaitVerdict;
|
|
59
61
|
export declare function portBusyMessage(output: string): string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* The dev server died before serving: say so, and quote the end of what it printed.
|
|
64
|
+
*
|
|
65
|
+
* Without the tail the reason was invisible: a missing `vite` binary printed `vite: command not
|
|
66
|
+
* found` to its own log, setup said only "exited without serving anything", and its first remedy
|
|
67
|
+
* ("start the dev server yourself") failed the same way.
|
|
68
|
+
*/
|
|
69
|
+
export declare function devServerDiedMessage(output: string): string;
|
|
60
70
|
export declare function announcedUrl(output: string): string | undefined;
|
|
61
71
|
/**
|
|
62
72
|
* The url to watch: whatever the tool announced, else one composed from a port we OBSERVED our own
|
|
@@ -38,7 +38,7 @@ export function judgeWait(facts) {
|
|
|
38
38
|
return WaitVerdict.READY;
|
|
39
39
|
if (facts.launcherExited)
|
|
40
40
|
return WaitVerdict.DEAD;
|
|
41
|
-
if (facts.elapsedMs >= WAIT_CEILING_MS)
|
|
41
|
+
if (facts.elapsedMs >= (facts.budgetMs ?? WAIT_CEILING_MS))
|
|
42
42
|
return WaitVerdict.HUNG;
|
|
43
43
|
// Output IS progress. Silence is what distinguishes a build from a wedge.
|
|
44
44
|
return facts.quietForMs >= (facts.quietMeansHungMs ?? QUIET_MEANS_HUNG_MS)
|
|
@@ -61,6 +61,25 @@ export function portBusyMessage(output) {
|
|
|
61
61
|
return (`port ${port} is already in use — something else is serving there, so this launcher did not ` +
|
|
62
62
|
`bind. Pass --url http://localhost:${port} if that process is this app, or free the port and re-run.`);
|
|
63
63
|
}
|
|
64
|
+
/** How many of the dev server's last lines a failure quotes. Enough for a stack's first frames. */
|
|
65
|
+
const DIED_TAIL_LINES = 6;
|
|
66
|
+
/**
|
|
67
|
+
* The dev server died before serving: say so, and quote the end of what it printed.
|
|
68
|
+
*
|
|
69
|
+
* Without the tail the reason was invisible: a missing `vite` binary printed `vite: command not
|
|
70
|
+
* found` to its own log, setup said only "exited without serving anything", and its first remedy
|
|
71
|
+
* ("start the dev server yourself") failed the same way.
|
|
72
|
+
*/
|
|
73
|
+
export function devServerDiedMessage(output) {
|
|
74
|
+
const tail = output
|
|
75
|
+
.split('\n')
|
|
76
|
+
.map((line) => line.trimEnd())
|
|
77
|
+
.filter((line) => 0 < line.trim().length)
|
|
78
|
+
.slice(-DIED_TAIL_LINES);
|
|
79
|
+
return 0 === tail.length
|
|
80
|
+
? 'The dev server exited without serving anything, and printed nothing.'
|
|
81
|
+
: `The dev server exited without serving anything. The last it printed:\n${tail.map((line) => ` ${line}`).join('\n')}`;
|
|
82
|
+
}
|
|
64
83
|
/** A url the dev server announced, if it has announced one. Never composed, only read. */
|
|
65
84
|
const URL_IN_OUTPUT = /https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):\d+/;
|
|
66
85
|
export function announcedUrl(output) {
|
|
@@ -12,4 +12,8 @@ export declare function portForInit(explicit: number | undefined, configured: nu
|
|
|
12
12
|
daemonProjects: (port: number) => Promise<readonly string[]>;
|
|
13
13
|
daemonPresent: (port: number) => Promise<boolean>;
|
|
14
14
|
pickPort: (preferred: number) => Promise<number>;
|
|
15
|
+
/** The daemon on `port` was started from this project's directory or one above it. */
|
|
16
|
+
daemonStartedHere?: (port: number) => boolean;
|
|
15
17
|
}): Promise<number | undefined>;
|
|
18
|
+
/** Is `dir` the same directory as `cwd`, or an ancestor of it? */
|
|
19
|
+
export declare function isSameOrAbove(dir: string, cwd: string): boolean;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Undefined means the default.
|
|
10
10
|
*/
|
|
11
|
+
import { relative, isAbsolute } from 'node:path';
|
|
11
12
|
import { RETICLE_DEFAULT_PORT, ReticleEnv } from '@reticlehq/core';
|
|
12
13
|
const MAX_PORT = 65_535;
|
|
13
14
|
/**
|
|
@@ -34,6 +35,10 @@ export async function portForInit(explicit, configured, projectId, deps) {
|
|
|
34
35
|
// config as a `port` nobody chose.
|
|
35
36
|
if (!(await deps.daemonPresent(RETICLE_DEFAULT_PORT)))
|
|
36
37
|
return undefined;
|
|
38
|
+
// Started here — by this project's own editor MCP, typically — so it is ours whatever project id
|
|
39
|
+
// it recorded. Moving off it strands that agent: an MCP resolves its port once, at startup.
|
|
40
|
+
if (true === deps.daemonStartedHere?.(RETICLE_DEFAULT_PORT))
|
|
41
|
+
return undefined;
|
|
37
42
|
// The bridge's own rule: it refuses a page only when it has served exactly ONE other project. A
|
|
38
43
|
// daemon serving nobody, several projects, or ours accepts this one. A project that has never
|
|
39
44
|
// been through init has no id yet, so a single named holder is somebody else.
|
|
@@ -42,3 +47,8 @@ export async function portForInit(explicit, configured, projectId, deps) {
|
|
|
42
47
|
return undefined;
|
|
43
48
|
return deps.pickPort(RETICLE_DEFAULT_PORT + 1);
|
|
44
49
|
}
|
|
50
|
+
/** Is `dir` the same directory as `cwd`, or an ancestor of it? */
|
|
51
|
+
export function isSameOrAbove(dir, cwd) {
|
|
52
|
+
const rel = relative(dir, cwd);
|
|
53
|
+
return '' === rel || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
54
|
+
}
|
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
* every command harder to read.
|
|
7
7
|
*/
|
|
8
8
|
import { type InitResult } from '@reticlehq/init';
|
|
9
|
+
/**
|
|
10
|
+
* Does the served document name the SDK, so its absence means a stale dev server?
|
|
11
|
+
*
|
|
12
|
+
* A Next App Router page names its client components in the flight data inlined into the HTML —
|
|
13
|
+
* `reticle-dev.tsx`, measured on Next 16 under Turbopack and webpack alike. Reported from a Next app
|
|
14
|
+
* whose dev server was already running before init: setup waited on a page that could never connect
|
|
15
|
+
* and said nothing, because Next was assumed to carry the SDK only in its bundle. The Pages Router
|
|
16
|
+
* names chunks, not modules, so it stays silent.
|
|
17
|
+
*/
|
|
18
|
+
export declare function htmlCarriesSdk(framework: string, exists: (path: string) => boolean): boolean;
|
|
9
19
|
/** Just enough of the parsed command to decide and run. */
|
|
10
20
|
interface InitRuntimeArgs {
|
|
11
21
|
readonly port: number | undefined;
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* every command harder to read.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { InitConfirmation, RETICLE_DEFAULT_PORT } from '@reticlehq/core';
|
|
11
|
+
import { FEEDBACK_HINT, Framework, InitFailure } from '@reticlehq/init';
|
|
11
12
|
import { confirmInstall, nodeConfirmDeps } from '../terminal/confirm.js';
|
|
12
13
|
import { writeLicenseKey } from '../license-key.js';
|
|
13
14
|
import { registerOtherAgents, runSetupCommand } from '../setup-command.js';
|
|
@@ -20,6 +21,8 @@ import { probeDaemon } from '../../../surface/mcp/proxy/proxy-daemon-probe.js';
|
|
|
20
21
|
import { fetchStatus } from '../../daemon/binding/daemon-status-probe.js';
|
|
21
22
|
import { collectEnv, DEFAULT_PHASE_TIMEOUT_MS } from '../setup-options.js';
|
|
22
23
|
import { staticPageDevCommand } from '../bringup/static-page-server.js';
|
|
24
|
+
import { SetupPhase } from '../run-setup.js';
|
|
25
|
+
import { reportInitOutcome } from '../../../telemetry/init-telemetry.js';
|
|
23
26
|
/** How often the runtime phases look again: fast enough not to be the wait, slow enough to be free. */
|
|
24
27
|
const POLL_MS = 250;
|
|
25
28
|
/**
|
|
@@ -28,6 +31,22 @@ const POLL_MS = 250;
|
|
|
28
31
|
* of the document can never see it. See `htmlCarriesSdk` in run-setup.ts.
|
|
29
32
|
*/
|
|
30
33
|
const HTML_CARRIES_SDK = new Set([Framework.VITE, Framework.HTML]);
|
|
34
|
+
/** The connect component `init` writes for a Next App Router project. */
|
|
35
|
+
const NEXT_APP_CONNECT = ['app', 'src/app'].flatMap((d) => ['tsx', 'jsx', 'js'].map((ext) => `${d}/reticle-dev.${ext}`));
|
|
36
|
+
/**
|
|
37
|
+
* Does the served document name the SDK, so its absence means a stale dev server?
|
|
38
|
+
*
|
|
39
|
+
* A Next App Router page names its client components in the flight data inlined into the HTML —
|
|
40
|
+
* `reticle-dev.tsx`, measured on Next 16 under Turbopack and webpack alike. Reported from a Next app
|
|
41
|
+
* whose dev server was already running before init: setup waited on a page that could never connect
|
|
42
|
+
* and said nothing, because Next was assumed to carry the SDK only in its bundle. The Pages Router
|
|
43
|
+
* names chunks, not modules, so it stays silent.
|
|
44
|
+
*/
|
|
45
|
+
export function htmlCarriesSdk(framework, exists) {
|
|
46
|
+
if (HTML_CARRIES_SDK.has(framework))
|
|
47
|
+
return true;
|
|
48
|
+
return Framework.NEXT === framework && NEXT_APP_CONNECT.some(exists);
|
|
49
|
+
}
|
|
31
50
|
/** Registration is global MCP wiring, so both flags that disown it are read here. */
|
|
32
51
|
function wantsAgents(parsed) {
|
|
33
52
|
return false !== parsed.agents && false !== parsed.mcp;
|
|
@@ -59,9 +78,9 @@ function printRelaunch(parsed, io, cwd) {
|
|
|
59
78
|
}
|
|
60
79
|
export async function continueAfterInit(parsed, result, io, cwd) {
|
|
61
80
|
const port = parsed.port ?? RETICLE_DEFAULT_PORT;
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
if (undefined !== parsed.licenseKey) {
|
|
81
|
+
// Real installs persist the key whichever way setup ends. A preview must leave .env and
|
|
82
|
+
// .gitignore untouched, just as it leaves project wiring and agent configuration untouched.
|
|
83
|
+
if (!parsed.dryRun && undefined !== parsed.licenseKey) {
|
|
65
84
|
const written = writeLicenseKey(cwd, parsed.licenseKey, licenseIo);
|
|
66
85
|
io.print(written.message);
|
|
67
86
|
}
|
|
@@ -79,9 +98,12 @@ export async function continueAfterInit(parsed, result, io, cwd) {
|
|
|
79
98
|
// has: nothing reaches back into a machine that installed Reticle a version ago, so the
|
|
80
99
|
// upgrade path is re-running init, and the light form of init has to be enough to carry it.
|
|
81
100
|
// A dry run writes nothing anywhere, including here.
|
|
82
|
-
if (true === parsed.filesOnly && wantsAgents(parsed))
|
|
101
|
+
if (!parsed.dryRun && true === parsed.filesOnly && wantsAgents(parsed)) {
|
|
83
102
|
registerOtherAgents(io.print);
|
|
103
|
+
}
|
|
84
104
|
return confirmInstall(result, io, nodeConfirmDeps(port)).then(() => {
|
|
105
|
+
if (true === parsed.json)
|
|
106
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
85
107
|
if (!result.ok)
|
|
86
108
|
process.exit(1);
|
|
87
109
|
});
|
|
@@ -96,8 +118,15 @@ export async function continueAfterInit(parsed, result, io, cwd) {
|
|
|
96
118
|
if (context === undefined) {
|
|
97
119
|
// Nothing was established, so there is nothing to run against. init has already said why.
|
|
98
120
|
ask();
|
|
121
|
+
if (true === parsed.json)
|
|
122
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
99
123
|
process.exit(1);
|
|
100
124
|
}
|
|
125
|
+
const reportFailure = (reason) => {
|
|
126
|
+
if (result.outcome !== undefined) {
|
|
127
|
+
reportInitOutcome({ ...result.outcome, ok: false, reason });
|
|
128
|
+
}
|
|
129
|
+
};
|
|
101
130
|
// A PENDING connect step is not a reason to skip the runtime phase, and treating it as one made
|
|
102
131
|
// `init` stop with "paste this snippet" while never looking at the app.
|
|
103
132
|
//
|
|
@@ -113,10 +142,17 @@ export async function continueAfterInit(parsed, result, io, cwd) {
|
|
|
113
142
|
// Before the connect wait, never after: a bridge held by a stranger makes a session impossible,
|
|
114
143
|
// so going ahead spends the entire budget and then reports what reads as an instrumentation
|
|
115
144
|
// problem — the one place that is fine. See bridge-port.ts.
|
|
116
|
-
const refusal = bridgeOccupied(await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus })
|
|
145
|
+
const refusal = bridgeOccupied(await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus }).catch((error) => {
|
|
146
|
+
reportFailure(InitFailure.RUNTIME_ERROR);
|
|
147
|
+
throw error;
|
|
148
|
+
}), port);
|
|
117
149
|
if (refusal !== undefined) {
|
|
150
|
+
reportFailure(InitFailure.BRIDGE_OCCUPIED);
|
|
118
151
|
io.print(refusal);
|
|
119
152
|
ask();
|
|
153
|
+
if (true === parsed.json) {
|
|
154
|
+
process.stdout.write(`${JSON.stringify({ ok: false, reason: InitFailure.BRIDGE_OCCUPIED }, null, 2)}\n`);
|
|
155
|
+
}
|
|
120
156
|
process.exit(1);
|
|
121
157
|
}
|
|
122
158
|
return runSetupCommand({
|
|
@@ -136,9 +172,12 @@ export async function continueAfterInit(parsed, result, io, cwd) {
|
|
|
136
172
|
// keeps deciding, so nobody who passed nothing waits less than they used to.
|
|
137
173
|
...(undefined === parsed.timeoutSeconds
|
|
138
174
|
? {}
|
|
139
|
-
: {
|
|
175
|
+
: {
|
|
176
|
+
connectBudgetMs: parsed.timeoutSeconds * 1000,
|
|
177
|
+
startupBudgetMs: parsed.timeoutSeconds * 1000,
|
|
178
|
+
}),
|
|
140
179
|
pollMs: POLL_MS,
|
|
141
|
-
htmlCarriesSdk:
|
|
180
|
+
htmlCarriesSdk: htmlCarriesSdk(context.framework, (rel) => existsSync(join(context.appDir, rel))),
|
|
142
181
|
// A plain page with no dev script gets Reticle's own static server, so the page is actually
|
|
143
182
|
// served and the connect can be proved, instead of stopping at "start the app yourself".
|
|
144
183
|
...(() => {
|
|
@@ -153,6 +192,26 @@ export async function continueAfterInit(parsed, result, io, cwd) {
|
|
|
153
192
|
})(),
|
|
154
193
|
...(undefined === parsed.url ? {} : { suppliedUrl: parsed.url }),
|
|
155
194
|
}, (line) => io.print(line)).then((outcome) => {
|
|
195
|
+
if (result.outcome !== undefined) {
|
|
196
|
+
const init = {
|
|
197
|
+
...result.outcome,
|
|
198
|
+
ok: outcome.ok,
|
|
199
|
+
};
|
|
200
|
+
if (outcome.ok) {
|
|
201
|
+
delete init.reason;
|
|
202
|
+
init.confirmation = InitConfirmation.CONNECTED;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// A failed startup does not establish whether a daemon or instrumented page exists.
|
|
206
|
+
// Keep the watcher's no_daemon/no_session/no_page classifications for observed facts.
|
|
207
|
+
delete init.confirmation;
|
|
208
|
+
init.reason =
|
|
209
|
+
outcome.reachedPhase === SetupPhase.DEV_SERVER
|
|
210
|
+
? InitFailure.DEV_SERVER
|
|
211
|
+
: InitFailure.APP_CONNECTION;
|
|
212
|
+
}
|
|
213
|
+
reportInitOutcome(init);
|
|
214
|
+
}
|
|
156
215
|
// One object, so an agent reads a result instead of interpreting a report.
|
|
157
216
|
if (true === parsed.json) {
|
|
158
217
|
process.stdout.write(`${JSON.stringify(outcome, null, 2)}\n`);
|
|
@@ -201,5 +260,8 @@ export async function continueAfterInit(parsed, result, io, cwd) {
|
|
|
201
260
|
io.print(` ${i + 1}. ${step}`);
|
|
202
261
|
ask();
|
|
203
262
|
process.exit(1);
|
|
263
|
+
}, (error) => {
|
|
264
|
+
reportFailure(InitFailure.RUNTIME_ERROR);
|
|
265
|
+
throw error;
|
|
204
266
|
});
|
|
205
267
|
}
|
|
@@ -23,6 +23,8 @@ import { reticleStateHome } from '../daemon/daemon.js';
|
|
|
23
23
|
import { descendants, parseLsofPorts, parseNetstatListeners, parseWmicProcesses, } from './terminal/listeners.js';
|
|
24
24
|
import { isAddressMiss, loopbackProbeUrls } from './probe/loopback-probe-urls.js';
|
|
25
25
|
const WINDOWS = 'win32' === process.platform;
|
|
26
|
+
/** Astro otherwise daemonizes automatically when it detects an agent terminal. */
|
|
27
|
+
const ASTRO_DEV_BACKGROUND_ENV = 'ASTRO_DEV_BACKGROUND';
|
|
26
28
|
/** A page fetch that is slow is a page fetch that failed, for our purposes. */
|
|
27
29
|
const PROBE_TIMEOUT_MS = 5_000;
|
|
28
30
|
/** The supervisor that owns the server's output and keeps its log bounded. */
|
|
@@ -64,7 +66,10 @@ export class OwnedDevServer {
|
|
|
64
66
|
detached: !WINDOWS,
|
|
65
67
|
// Nothing of ours: the supervisor holds the server's pipes, and init holds none of its.
|
|
66
68
|
stdio: 'ignore',
|
|
67
|
-
|
|
69
|
+
// Reticle already provides the detached supervisor. Astro's agent auto-backgrounding
|
|
70
|
+
// would escape its process group and leave the recorded handover PID pointing at a dead
|
|
71
|
+
// launcher, so re-init and cleanup could no longer find or stop the actual server.
|
|
72
|
+
env: { ...process.env, ...env, [ASTRO_DEV_BACKGROUND_ENV]: '1' },
|
|
68
73
|
});
|
|
69
74
|
this.log = log;
|
|
70
75
|
this.cwd = cwd;
|
|
@@ -101,7 +106,8 @@ export class OwnedDevServer {
|
|
|
101
106
|
catch {
|
|
102
107
|
/* not written yet */
|
|
103
108
|
}
|
|
104
|
-
|
|
109
|
+
// Filesystem timestamps can include a fractional millisecond beyond Date.now()'s integer.
|
|
110
|
+
return Math.max(0, Date.now() - lastOutputAt);
|
|
105
111
|
}
|
|
106
112
|
/** Ports anything in this server's process tree is listening on. */
|
|
107
113
|
listeningPorts() {
|
|
@@ -34,7 +34,8 @@ export function remainingSteps(p) {
|
|
|
34
34
|
'`reticle_*` tools, ending in `reticle_act_and_wait` or `reticle_assert` — those two are ' +
|
|
35
35
|
'what produce a verdict. Or hand the whole drive to Reticle with `reticle_verify { action: ' +
|
|
36
36
|
'"explore", persona: "<who does what>" }`, which records what it drove so later runs replay ' +
|
|
37
|
-
'with no model in the loop; that route needs
|
|
37
|
+
'with no model in the loop; that route runs on the Reticle platform and needs a linked ' +
|
|
38
|
+
'project (`reticle connect`); every plan, Free included, has monthly Harness credits.');
|
|
38
39
|
}
|
|
39
40
|
// Whether anything is actually outstanding, read BEFORE the docs pointer is added -- that line is
|
|
40
41
|
// an offer, not a task, and a run with nothing left still gets it.
|
|
@@ -55,6 +55,11 @@ export interface DaemonReason {
|
|
|
55
55
|
export interface OwnedBrowser {
|
|
56
56
|
readonly sessionId: string;
|
|
57
57
|
readonly release: () => Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* The page never dialled in, so the lease injected Reticle's own reader. That session proves the
|
|
60
|
+
* reader, not this install, and must never end onboarding as "connected".
|
|
61
|
+
*/
|
|
62
|
+
readonly zeroInstall?: boolean;
|
|
58
63
|
}
|
|
59
64
|
export interface SetupEffects {
|
|
60
65
|
/** Start the dev server. Resolves once started; the caller owns stopping it. */
|
|
@@ -132,6 +137,8 @@ export interface SetupInput {
|
|
|
132
137
|
* before setup could say what was wrong.
|
|
133
138
|
*/
|
|
134
139
|
readonly connectBudgetMs?: number | undefined;
|
|
140
|
+
/** Explicit --timeout for startup; absent preserves the adaptive build wait. */
|
|
141
|
+
readonly startupBudgetMs?: number | undefined;
|
|
135
142
|
readonly openBrowser: boolean;
|
|
136
143
|
/** Web, Electron or Tauri. Desktop changes three things; see desktop-shape.ts. */
|
|
137
144
|
readonly shape: AppShape;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* way of going wrong, and the alternative to injecting them is a test that boots a real dev server
|
|
14
14
|
* and a real browser to find out what happens when neither works.
|
|
15
15
|
*/
|
|
16
|
-
import { judgeWait, portBusyMessage, QUIET_MEANS_HUNG_MS, urlToWatch, WaitVerdict, } from './bringup/dev-server-wait.js';
|
|
16
|
+
import { judgeWait, devServerDiedMessage, portBusyMessage, QUIET_MEANS_HUNG_MS, urlToWatch, WaitVerdict, } from './bringup/dev-server-wait.js';
|
|
17
17
|
import { connectProgressLine, waitProgressLine } from './terminal/wait-progress.js';
|
|
18
18
|
/**
|
|
19
19
|
* Windows gets longer to say something before silence counts as a wedge.
|
|
@@ -159,6 +159,7 @@ export async function runSetupPhases(input, fx) {
|
|
|
159
159
|
serving,
|
|
160
160
|
quietForMs: fx.devServerQuietForMs(),
|
|
161
161
|
elapsedMs: fx.now() - startedAt,
|
|
162
|
+
budgetMs: input.startupBudgetMs,
|
|
162
163
|
quietMeansHungMs: WINDOWS_QUIET_MEANS_HUNG_MS_APPLIES
|
|
163
164
|
? WINDOWS_QUIET_MEANS_HUNG_MS
|
|
164
165
|
: QUIET_MEANS_HUNG_MS,
|
|
@@ -176,16 +177,22 @@ export async function runSetupPhases(input, fx) {
|
|
|
176
177
|
break;
|
|
177
178
|
}
|
|
178
179
|
if (WaitVerdict.DEAD === verdict) {
|
|
179
|
-
note(portBusyMessage(fx.devServerOutput()) ??
|
|
180
|
-
'The dev server exited without serving anything.');
|
|
180
|
+
note(portBusyMessage(fx.devServerOutput()) ?? devServerDiedMessage(fx.devServerOutput()));
|
|
181
181
|
return stop(input, SetupPhase.DEV_SERVER, {}, notes);
|
|
182
182
|
}
|
|
183
183
|
if (WaitVerdict.HUNG === verdict) {
|
|
184
184
|
// Says BOTH were checked. A server that prints nothing but IS listening is the CRA case and
|
|
185
185
|
// must not be failed, so a reader has to be able to tell "we looked at the log" from "we
|
|
186
186
|
// looked at the log AND the ports".
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
const within = input.startupBudgetMs !== undefined && fx.now() - startedAt >= input.startupBudgetMs
|
|
188
|
+
? ` within ${String(input.startupBudgetMs)}ms`
|
|
189
|
+
: undefined;
|
|
190
|
+
note((undefined === watching
|
|
191
|
+
? `The dev server neither printed a URL nor bound a port${within ?? ''}, so setup has nothing to open. `
|
|
192
|
+
: undefined !== within
|
|
193
|
+
? `The dev server did not become ready${within}. `
|
|
194
|
+
: `The dev server stopped making progress before ${watching} became ready. `) +
|
|
195
|
+
'Check its log, increase --timeout for a slow build, or pass --url with the address it serves.');
|
|
189
196
|
return stop(input, SetupPhase.DEV_SERVER, {}, notes);
|
|
190
197
|
}
|
|
191
198
|
await fx.sleep(input.pollMs);
|
|
@@ -227,6 +234,23 @@ export async function runSetupPhases(input, fx) {
|
|
|
227
234
|
// dialled the bridge" over a correct install. With a lease to fall back on it is entered too, and
|
|
228
235
|
// only the system browser is skipped.
|
|
229
236
|
const canLease = undefined !== fx.openLease;
|
|
237
|
+
const injected = new Set();
|
|
238
|
+
/**
|
|
239
|
+
* The lease to keep, or undefined. A lease whose page only connected through Reticle's injected
|
|
240
|
+
* reader is handed straight back and its session excluded: it would otherwise be picked below and
|
|
241
|
+
* report a plain server with no SDK as an instrumented app.
|
|
242
|
+
*/
|
|
243
|
+
const keepLease = async (owned) => {
|
|
244
|
+
if ('failed' in owned) {
|
|
245
|
+
note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
if (true !== owned.zeroInstall)
|
|
249
|
+
return owned;
|
|
250
|
+
injected.add(owned.sessionId);
|
|
251
|
+
await releaseLease(owned);
|
|
252
|
+
return undefined;
|
|
253
|
+
};
|
|
230
254
|
if (null === alreadyConnected && policy.openBrowser && (input.openBrowser || canLease)) {
|
|
231
255
|
// SERVED is the precondition, not SDK_PRESENT: a url that answers nothing is the only state
|
|
232
256
|
// where a window is certainly useless. Whether the SDK is in the served HTML is a DIFFERENT
|
|
@@ -255,14 +279,9 @@ export async function runSetupPhases(input, fx) {
|
|
|
255
279
|
const launcherFailed = input.openBrowser ? await fx.openBrowser(url) : undefined;
|
|
256
280
|
openedBrowser = input.openBrowser;
|
|
257
281
|
if (undefined !== fx.openLease && (!input.openBrowser || undefined !== launcherFailed)) {
|
|
258
|
-
|
|
259
|
-
if (
|
|
260
|
-
note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
lease = owned;
|
|
282
|
+
lease = await keepLease(await fx.openLease(url));
|
|
283
|
+
if (undefined !== lease)
|
|
264
284
|
openedBrowser = true;
|
|
265
|
-
}
|
|
266
285
|
}
|
|
267
286
|
}
|
|
268
287
|
}
|
|
@@ -283,7 +302,8 @@ export async function runSetupPhases(input, fx) {
|
|
|
283
302
|
while (null === session) {
|
|
284
303
|
// On a desktop app, only the desktop window counts. AppShape and the runtime a page reports use
|
|
285
304
|
// the same three names, so the shape IS the requirement — see session-pick.
|
|
286
|
-
|
|
305
|
+
const listedNow = (await fx.listSessions()).filter((s) => !injected.has(s.sessionId));
|
|
306
|
+
session = pickSession(listedNow, url, before, requiredRuntime);
|
|
287
307
|
if (null !== session)
|
|
288
308
|
break;
|
|
289
309
|
if (deadline <= fx.now())
|
|
@@ -296,13 +316,7 @@ export async function runSetupPhases(input, fx) {
|
|
|
296
316
|
!isDesktop(input.shape) &&
|
|
297
317
|
fx.now() - waitStartedAt >= SYSTEM_BROWSER_GRACE_MS) {
|
|
298
318
|
leaseFallbackTried = true;
|
|
299
|
-
|
|
300
|
-
if ('failed' in owned) {
|
|
301
|
-
note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
lease = owned;
|
|
305
|
-
}
|
|
319
|
+
lease = await keepLease(await fx.openLease(url));
|
|
306
320
|
continue;
|
|
307
321
|
}
|
|
308
322
|
// Not in silence: a desktop run used to print one line and then nothing for ten minutes.
|
|
@@ -363,9 +377,8 @@ export async function runSetupPhases(input, fx) {
|
|
|
363
377
|
// `<url>` stays a placeholder in this string on purpose: `guidance-commands-run` feeds every
|
|
364
378
|
// command we print to the real parser, and an interpolated value reads there as a missing operand.
|
|
365
379
|
// The live url is named in the sentence instead, where a reader needs it anyway.
|
|
366
|
-
// Both routes, because `explore`
|
|
367
|
-
//
|
|
368
|
-
// without a key, which is the same defect this release spent its time removing everywhere else.
|
|
380
|
+
// Both routes, because `explore` runs on the platform: without a linked project it refuses.
|
|
381
|
+
// Naming only that one hands the reader a dead end on any machine without one.
|
|
369
382
|
// A heading and a numbered list rather than one paragraph: a reader scans for what to do next.
|
|
370
383
|
//
|
|
371
384
|
// This was a single 524-character note, measured on a real first run: eighty-three words, no
|
|
@@ -385,8 +398,8 @@ export async function runSetupPhases(input, fx) {
|
|
|
385
398
|
note(' 1. Drive one flow and end it with `reticle_act_and_wait` or `reticle_assert`. Those two ' +
|
|
386
399
|
'produce a verdict; nothing else does.');
|
|
387
400
|
note(' 2. Or hand over the whole drive: `reticle_verify { action: "explore", persona: "<who does ' +
|
|
388
|
-
'what>" }` records what it drove, so later runs replay with no model in the loop.
|
|
389
|
-
'
|
|
401
|
+
'what>" }` records what it drove, so later runs replay with no model in the loop. Runs on ' +
|
|
402
|
+
'the Reticle platform: needs a linked project (`reticle connect`); Free includes monthly Harness credits.');
|
|
390
403
|
return {
|
|
391
404
|
ok: true,
|
|
392
405
|
reachedPhase: SetupPhase.CONNECT,
|
|
@@ -30,7 +30,8 @@ interface SetupCommandInput extends Omit<SetupInput, 'shape'> {
|
|
|
30
30
|
/** The bridge's pairing token, for the MCP transport a connect-proof lease is opened over. */
|
|
31
31
|
readonly pairingToken?: string | undefined;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
/** Returns how many agents it wrote a config for, so a caller does not then say "none found". */
|
|
34
|
+
export declare function registerOtherAgents(print: (line: string) => void): number;
|
|
34
35
|
type SetupCommandResult = SetupOutcome;
|
|
35
36
|
export declare function runSetupCommand(input: SetupCommandInput, print: (line: string) => void): Promise<SetupCommandResult>;
|
|
36
37
|
export {};
|
|
@@ -89,6 +89,7 @@ function hasElectronDependency(dir) {
|
|
|
89
89
|
*/
|
|
90
90
|
/** `agent`, `agents`. */
|
|
91
91
|
const agentWord = (n) => (1 === n ? 'agent' : 'agents');
|
|
92
|
+
/** Returns how many agents it wrote a config for, so a caller does not then say "none found". */
|
|
92
93
|
export function registerOtherAgents(print) {
|
|
93
94
|
const platform = process.platform;
|
|
94
95
|
const home = homedir();
|
|
@@ -124,6 +125,7 @@ export function registerOtherAgents(print) {
|
|
|
124
125
|
for (const noted of approvals.filter((a) => undefined !== a.warn)) {
|
|
125
126
|
print(` ⚠ ${noted.name}: ${String(noted.warn)}`);
|
|
126
127
|
}
|
|
128
|
+
return wrote.length;
|
|
127
129
|
}
|
|
128
130
|
/**
|
|
129
131
|
* Run the phases against the real world.
|
|
@@ -32,6 +32,8 @@ export interface SetupMcpResult {
|
|
|
32
32
|
readonly detected: readonly string[];
|
|
33
33
|
readonly registered: readonly string[];
|
|
34
34
|
readonly alreadyThere: readonly string[];
|
|
35
|
+
/** Automatic registration was attempted but failed. */
|
|
36
|
+
readonly failed: readonly string[];
|
|
35
37
|
/**
|
|
36
38
|
* Clients whose config this will NOT rewrite, and which therefore still need a human.
|
|
37
39
|
*
|
|
@@ -4,6 +4,7 @@ export function setupMcp(io) {
|
|
|
4
4
|
const detected = [];
|
|
5
5
|
const registered = [];
|
|
6
6
|
const alreadyThere = [];
|
|
7
|
+
const failed = [];
|
|
7
8
|
const manual = [];
|
|
8
9
|
/*
|
|
9
10
|
* Claude Code first, because nothing else can find it.
|
|
@@ -30,6 +31,8 @@ export function setupMcp(io) {
|
|
|
30
31
|
const cmd = claudeAddCommand();
|
|
31
32
|
if (io.runCli(cmd.command, cmd.args))
|
|
32
33
|
registered.push(McpClient.CLAUDE_CODE);
|
|
34
|
+
else
|
|
35
|
+
failed.push(McpClient.CLAUDE_CODE);
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
for (const client of detectMcpClients(io)) {
|
|
@@ -47,8 +50,13 @@ export function setupMcp(io) {
|
|
|
47
50
|
manual.push({ id: client.id, configPath: client.configPath, docs: spec.docs });
|
|
48
51
|
continue;
|
|
49
52
|
}
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
try {
|
|
54
|
+
io.writeFile(client.configPath, merged.content);
|
|
55
|
+
registered.push(client.id);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
failed.push(client.id);
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
61
|
/*
|
|
54
62
|
* Both INSTALL steps, reported from the one place that knows the answer.
|
|
@@ -69,7 +77,8 @@ export function setupMcp(io) {
|
|
|
69
77
|
* SKIPPED when there was nothing to register with — not failed. Nothing went wrong; there was
|
|
70
78
|
* simply no agent here, and counting that as our failure would hide the ones that are.
|
|
71
79
|
*
|
|
72
|
-
* FAILED when
|
|
80
|
+
* FAILED when no detected client is usable, whether registration failed or needs a hand.
|
|
81
|
+
* That case used to fall through to COMPLETED,
|
|
73
82
|
* so the cohort with the tools registered NOWHERE was counted among the successful installs, and
|
|
74
83
|
* the one number that says "did this machine get Reticle" read yes for the people it read no
|
|
75
84
|
* for. Registering some and not others is still COMPLETED: the agent they are using may well be
|
|
@@ -79,11 +88,11 @@ export function setupMcp(io) {
|
|
|
79
88
|
? OnboardingStepStatus.SKIPPED
|
|
80
89
|
: 0 === registered.length && alreadyThere.length > 0
|
|
81
90
|
? OnboardingStepStatus.SKIPPED
|
|
82
|
-
: 0 === registered.length && 0 === alreadyThere.length
|
|
91
|
+
: 0 === registered.length && 0 === alreadyThere.length
|
|
83
92
|
? OnboardingStepStatus.FAILED
|
|
84
93
|
: OnboardingStepStatus.COMPLETED,
|
|
85
94
|
});
|
|
86
|
-
return { detected, registered, alreadyThere, manual };
|
|
95
|
+
return { detected, registered, alreadyThere, failed, manual };
|
|
87
96
|
}
|
|
88
97
|
/** Everything this machine could be asked about, for the report when nothing was found. */
|
|
89
98
|
export function knownClientLabels() {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export declare const PEER_VERSION_PARAM = "peerVersion";
|
|
2
2
|
export declare const PEER_CONTRACT_PARAM = "peerContract";
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Compare an attaching agent process against this daemon: the skew to tell THAT connection, or
|
|
5
|
+
* undefined when they agree. Returned rather than queued daemon-wide, because it is true only for the
|
|
6
|
+
* agent that announced it (#1136).
|
|
7
|
+
*/
|
|
8
|
+
export declare function agentPeerSkew(version: string | null, contract: string | null): string | undefined;
|
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
|
|
15
15
|
import { SERVER_VERSION } from './identity/server-version.js';
|
|
16
|
-
import { daemonFix, describeSkew
|
|
17
|
-
import { noteVersionSkew } from './version-nudge.js';
|
|
16
|
+
import { daemonFix, describeSkew } from './version-skew.js';
|
|
18
17
|
export const PEER_VERSION_PARAM = 'peerVersion';
|
|
19
18
|
export const PEER_CONTRACT_PARAM = 'peerContract';
|
|
20
|
-
/**
|
|
21
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Compare an attaching agent process against this daemon: the skew to tell THAT connection, or
|
|
21
|
+
* undefined when they agree. Returned rather than queued daemon-wide, because it is true only for the
|
|
22
|
+
* agent that announced it (#1136).
|
|
23
|
+
*/
|
|
24
|
+
export function agentPeerSkew(version, contract) {
|
|
22
25
|
const skew = describeSkew({
|
|
23
26
|
what: "the agent's MCP server",
|
|
24
27
|
version: version ?? undefined,
|
|
@@ -27,6 +30,5 @@ export function noteAgentPeer(version, contract) {
|
|
|
27
30
|
// MCP server, so the daemon is the newer half whenever the announcing agent is behind.
|
|
28
31
|
fix: daemonFix(SERVER_VERSION, version ?? undefined),
|
|
29
32
|
}, { version: SERVER_VERSION, contract: CONTRACT_FINGERPRINT });
|
|
30
|
-
|
|
31
|
-
noteVersionSkew(SkewPair.DAEMON, skew);
|
|
33
|
+
return skew;
|
|
32
34
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* result, then goes quiet until the skew CHANGES. Told, not nagged: a banner on every call is one an
|
|
12
12
|
* agent learns to skip, and it would cost those tokens on every call for the rest of the session.
|
|
13
13
|
*/
|
|
14
|
-
import
|
|
14
|
+
import { SkewPair } from './version-skew.js';
|
|
15
15
|
interface VersionSkewNudge {
|
|
16
16
|
pair: SkewPair;
|
|
17
17
|
action: string;
|
|
@@ -21,8 +21,20 @@ interface VersionSkewNudge {
|
|
|
21
21
|
* a second tab on a third version is news; the same tab reconnecting is not.
|
|
22
22
|
*/
|
|
23
23
|
export declare function noteVersionSkew(pair: SkewPair, action: string): void;
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* A daemon skew bound to the one MCP connection whose server announced it, told once (#1136).
|
|
26
|
+
*
|
|
27
|
+
* The page pair stays in the daemon-wide queue: it is a fact about a tab, true for whoever asks.
|
|
28
|
+
* This pair is a fact about the CALLER, and telling it to a different caller is advice to restart
|
|
29
|
+
* a server that is fine, or to run `reticle stop` and cut every agent on the daemon.
|
|
30
|
+
*/
|
|
31
|
+
export declare function connectionSkew(action: string): {
|
|
32
|
+
take(): string | undefined;
|
|
33
|
+
};
|
|
34
|
+
/** The next undelivered skew, once: this connection's own first, then the daemon-wide queue. */
|
|
35
|
+
export declare function takeVersionSkew(connection?: {
|
|
36
|
+
take(): string | undefined;
|
|
37
|
+
}): VersionSkewNudge | undefined;
|
|
26
38
|
/**
|
|
27
39
|
* Splice pending skew onto a tool-error payload.
|
|
28
40
|
*
|
|
@@ -36,6 +48,8 @@ export declare function takeVersionSkewOnto(payload: {
|
|
|
36
48
|
error: string;
|
|
37
49
|
recovery?: string;
|
|
38
50
|
feedback?: string;
|
|
51
|
+
}, connection?: {
|
|
52
|
+
take(): string | undefined;
|
|
39
53
|
}): Record<string, unknown>;
|
|
40
54
|
/** Tests only — drop the module state so each case starts clean. */
|
|
41
55
|
export declare function resetVersionSkew(): void;
|