@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
package/dist/command/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { hudSignIn } from './cli/cloud-login.js';
|
|
2
3
|
import { handleSetupMcp, handleSetupInstall } from './cli/setup-mcp-cli.js';
|
|
3
4
|
import { reportStepFromCli, reportTutorialShown } from './cli-onboarding.js';
|
|
4
5
|
import { runDemoTour } from './cli/demo/demo-run.js';
|
|
5
6
|
import { renderTutorial } from './cli/tutorial.js';
|
|
6
7
|
import { pathToFileURL } from 'node:url';
|
|
7
|
-
import { openFailureNote } from './cli/answers/open-note.js';
|
|
8
|
+
import { openFailureNote, openLaunchFailureNote } from './cli/answers/open-note.js';
|
|
8
9
|
import { realpathSync } from 'node:fs';
|
|
9
10
|
import { join } from 'node:path';
|
|
10
11
|
import { installCrashGuard } from './setup/setup-command.js';
|
|
@@ -15,9 +16,9 @@ import { splitBrainFields, withNextAction } from './cli/status-fields.js';
|
|
|
15
16
|
import { reticleStateHome } from './daemon/daemon.js';
|
|
16
17
|
import { handleMcp } from './cli/mcp-command.js';
|
|
17
18
|
import { handleReport } from './cli/report-command.js';
|
|
18
|
-
import { daemonProjectAt, resolveDaemonForProject } from './daemon/daemon-resolve.js';
|
|
19
|
+
import { daemonProjectAt, readDaemonRegistry, resolveDaemonForProject, } from './daemon/daemon-resolve.js';
|
|
19
20
|
import { pickDaemonPortToBind } from './daemon/binding/free-port.js';
|
|
20
|
-
import { portForInit, portFromEnv } from './setup/init/init-port.js';
|
|
21
|
+
import { isSameOrAbove, portForInit, portFromEnv } from './setup/init/init-port.js';
|
|
21
22
|
import { daemonStartOptions } from './cli/daemon-start-options.js';
|
|
22
23
|
import { handleWatch, handleCapsules, handleGate, loadNamedFlows, resolveChangedFiles, } from './cli/cli-flow-commands.js';
|
|
23
24
|
import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv, devServersForProject, } from '@reticlehq/core';
|
|
@@ -78,6 +79,10 @@ async function handleInit(parsed) {
|
|
|
78
79
|
},
|
|
79
80
|
daemonPresent: async (p) => presenceIsUsable(await probePresence(p, { tcpOpen: probeDaemon, status: fetchStatus })),
|
|
80
81
|
pickPort: (p) => pickDaemonPortToBind(p),
|
|
82
|
+
daemonStartedHere: (p) => {
|
|
83
|
+
const started = readDaemonRegistry(reticleStateHome()).find((e) => e.port === p)?.cwd;
|
|
84
|
+
return started !== undefined && isSameOrAbove(started, cwd);
|
|
85
|
+
},
|
|
81
86
|
});
|
|
82
87
|
const io = buildNodeIo(cwd, serverInitHost(), { stderr: true === parsed.json });
|
|
83
88
|
const result = runInit({
|
|
@@ -104,15 +109,18 @@ async function handleInit(parsed) {
|
|
|
104
109
|
import { handleServe, handleStop, handleRestart } from './cli/lifecycle/daemon-lifecycle.js';
|
|
105
110
|
import { statusLines } from './cli/status/status-lines.js';
|
|
106
111
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* whoever is reading the terminal — which, once an agent is running the command, is somebody who
|
|
111
|
-
* did not type it and cannot be assumed to parse it.
|
|
112
|
+
* Report status to the terminal. `--json` writes one structured line to STDOUT so it can be piped
|
|
113
|
+
* into `jq`; the human-readable block goes to stdout too. `log()` stays on stderr (the MCP
|
|
114
|
+
* transport lives on stdout), so `--json` must not go through it.
|
|
112
115
|
*/
|
|
113
|
-
function reportStatus(fields, json) {
|
|
116
|
+
export function reportStatus(fields, json) {
|
|
114
117
|
if (json) {
|
|
115
|
-
|
|
118
|
+
const line = JSON.stringify({
|
|
119
|
+
t: new Date().toISOString(),
|
|
120
|
+
event: 'reticle_status',
|
|
121
|
+
...fields,
|
|
122
|
+
});
|
|
123
|
+
process.stdout.write(`${line}\n`);
|
|
116
124
|
return;
|
|
117
125
|
}
|
|
118
126
|
process.stdout.write(`${statusLines(fields).join('\n')}\n`);
|
|
@@ -223,14 +231,13 @@ export async function handleStatus(port, json = false) {
|
|
|
223
231
|
...nudge,
|
|
224
232
|
});
|
|
225
233
|
}
|
|
226
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* Print the running package version (resolved once in server-version.ts), and nothing else.
|
|
236
|
+
*
|
|
237
|
+
* Bare on stdout so `V=$(reticle version)` works. It also wrote a JSON event to stderr, which a
|
|
238
|
+
* terminal shows: the first thing a new user typed after installing answered with a log line.
|
|
239
|
+
*/
|
|
227
240
|
function handleVersion() {
|
|
228
|
-
log('reticle_version', { version: SERVER_VERSION });
|
|
229
|
-
// Also on stdout, bare, because every diagnostic here starts by asking which build is running and
|
|
230
|
-
// that answer has to be copyable. The event alone looked fine in a terminal -- stderr is on the
|
|
231
|
-
// screen too -- but `V=$(reticle version)` came back empty, which is exactly the shape somebody
|
|
232
|
-
// reaches for when writing an issue template or a CI check. The event stays; machine-readable
|
|
233
|
-
// events all go to stderr and that consistency is worth keeping.
|
|
234
241
|
process.stdout.write(`${SERVER_VERSION}\n`);
|
|
235
242
|
}
|
|
236
243
|
/** `reticle license` — show enterprise activation resolved from the environment (offline; nothing leaves). */
|
|
@@ -395,9 +402,7 @@ function handleOpen(requestedPort, url) {
|
|
|
395
402
|
// Points at the OS handler, not at `reticle doctor`. Doctor's browser check is about
|
|
396
403
|
// Reticle's own Chromium, which this command never touches — the same misdirect the
|
|
397
404
|
// no-session note below spells out at length, left standing here for one more release.
|
|
398
|
-
recovery:
|
|
399
|
-
`(\`${openCommand(decision.url, process.platform).cmd}\` on this platform) and that ` +
|
|
400
|
-
'failed, so the fix is on the OS side: open the url yourself, or set a default browser.',
|
|
405
|
+
recovery: openLaunchFailureNote(decision.url, openCommand(decision.url, process.platform).cmd),
|
|
401
406
|
});
|
|
402
407
|
process.exit(1);
|
|
403
408
|
return;
|
|
@@ -431,7 +436,8 @@ function handleOpen(requestedPort, url) {
|
|
|
431
436
|
});
|
|
432
437
|
}
|
|
433
438
|
function handleDaemonInner(parsed) {
|
|
434
|
-
|
|
439
|
+
// The daemon the CLI starts is the one that can sign in for the panel: it owns ~/.reticle.
|
|
440
|
+
const options = { ...daemonStartOptions(parsed), hudSignIn };
|
|
435
441
|
startDaemon(options)
|
|
436
442
|
.then((server) => {
|
|
437
443
|
log('reticle_daemon_ready', { port: parsed.port, pid: process.pid });
|
|
@@ -722,7 +728,7 @@ export function main() {
|
|
|
722
728
|
handleOpen(parsed.port, parsed.url);
|
|
723
729
|
break;
|
|
724
730
|
case 'drive':
|
|
725
|
-
handleDrive(parsed);
|
|
731
|
+
handleDrive(parsed, readProjectId(process.cwd()));
|
|
726
732
|
break;
|
|
727
733
|
case 'verify':
|
|
728
734
|
handleVerify(parsed);
|
|
@@ -15,6 +15,8 @@ interface StatusSession {
|
|
|
15
15
|
sessionId: string;
|
|
16
16
|
url: string;
|
|
17
17
|
projectId?: string;
|
|
18
|
+
/** The page's own title. Absent when the page has none, or on a daemon too old to send it. */
|
|
19
|
+
title?: string;
|
|
18
20
|
throttled: boolean;
|
|
19
21
|
/**
|
|
20
22
|
* The tab is backgrounded. Reported by the daemon on every session; `false` on one too old to say,
|
|
@@ -102,6 +102,7 @@ export function summarizeStatus(payload) {
|
|
|
102
102
|
...('string' === typeof r['projectId'] && 0 < r['projectId'].length
|
|
103
103
|
? { projectId: r['projectId'] }
|
|
104
104
|
: {}),
|
|
105
|
+
...('string' === typeof r['title'] && 0 < r['title'].length ? { title: r['title'] } : {}),
|
|
105
106
|
throttled: true === r['throttled'],
|
|
106
107
|
hidden: true === r['hidden'],
|
|
107
108
|
stale: true === r['stale'],
|
|
@@ -76,6 +76,20 @@ export declare function daemonProjectAt(port: number, home: string): string | un
|
|
|
76
76
|
* Only LIVE daemons are named: a port a session used yesterday is history, not a split.
|
|
77
77
|
*/
|
|
78
78
|
export declare function daemonsServingProjectElsewhere(projectId: string | undefined, myPort: number, home: string, alive: (pid: number) => boolean, connectedOn: (port: number) => boolean): readonly number[];
|
|
79
|
+
/** Live daemons that serve a named project, on any port but `myPort`. */
|
|
80
|
+
export declare function projectDaemonsElsewhere(myPort: number, home: string, alive: (pid: number) => boolean): {
|
|
81
|
+
port: number;
|
|
82
|
+
projectId: string;
|
|
83
|
+
}[];
|
|
84
|
+
/**
|
|
85
|
+
* The sentence for a daemon started outside any project while projects run their own daemons, or
|
|
86
|
+
* `undefined` when there are none. Its sessions are empty because the apps dial their own ports,
|
|
87
|
+
* and "check your scope" is not something an agent inside an editor can act on (#1240).
|
|
88
|
+
*/
|
|
89
|
+
export declare function projectlessNote(myPort: number, others: readonly {
|
|
90
|
+
port: number;
|
|
91
|
+
projectId: string;
|
|
92
|
+
}[]): string | undefined;
|
|
79
93
|
/**
|
|
80
94
|
* The sentence for a split brain, or `undefined` when there is none.
|
|
81
95
|
*
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import { DaemonRegistryEntrySchema, daemonRegistryPort, pickDaemonPort, } from '@reticlehq/core/artifacts';
|
|
22
|
+
import { ReticleEnv } from '@reticlehq/core';
|
|
23
|
+
const RETICLE_PORT_ENV = ReticleEnv.PORT;
|
|
22
24
|
/**
|
|
23
25
|
* Every well-formed registry entry in `home`.
|
|
24
26
|
*
|
|
@@ -117,6 +119,30 @@ export function daemonsServingProjectElsewhere(projectId, myPort, home, alive, c
|
|
|
117
119
|
.map((entry) => entry.port)
|
|
118
120
|
.sort((a, b) => a - b);
|
|
119
121
|
}
|
|
122
|
+
/** Live daemons that serve a named project, on any port but `myPort`. */
|
|
123
|
+
export function projectDaemonsElsewhere(myPort, home, alive) {
|
|
124
|
+
return readDaemonRegistry(home)
|
|
125
|
+
.filter((entry) => entry.port !== myPort && alive(entry.pid))
|
|
126
|
+
.flatMap((entry) => entry.projectId === undefined || 0 === entry.projectId.length
|
|
127
|
+
? []
|
|
128
|
+
: [{ port: entry.port, projectId: entry.projectId }])
|
|
129
|
+
.sort((a, b) => a.port - b.port);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The sentence for a daemon started outside any project while projects run their own daemons, or
|
|
133
|
+
* `undefined` when there are none. Its sessions are empty because the apps dial their own ports,
|
|
134
|
+
* and "check your scope" is not something an agent inside an editor can act on (#1240).
|
|
135
|
+
*/
|
|
136
|
+
export function projectlessNote(myPort, others) {
|
|
137
|
+
const first = others[0];
|
|
138
|
+
if (first === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
const listed = others.map((o) => `${o.projectId} on :${String(o.port)}`).join(', ');
|
|
141
|
+
return (`this daemon on :${String(myPort)} was started outside any project, and the projects on this ` +
|
|
142
|
+
`machine run their own daemons: ${listed}. Their apps connect there, not here. Point the MCP ` +
|
|
143
|
+
`server at one — set ${RETICLE_PORT_ENV}=${String(first.port)} in its config, or launch it from ` +
|
|
144
|
+
'that project directory.');
|
|
145
|
+
}
|
|
120
146
|
/**
|
|
121
147
|
* The sentence for a split brain, or `undefined` when there is none.
|
|
122
148
|
*
|
|
@@ -179,8 +205,13 @@ export async function resolveMcpPort(preferred, projectId, home, deps) {
|
|
|
179
205
|
const mine = resolveDaemonForProject(projectId, home, deps.alive);
|
|
180
206
|
if (mine !== undefined)
|
|
181
207
|
return mine;
|
|
182
|
-
if (!(await deps.daemonPresent(preferred)))
|
|
183
|
-
|
|
208
|
+
if (!(await deps.daemonPresent(preferred))) {
|
|
209
|
+
// A caller outside any project (an editor's user-scope MCP, launched from $HOME) used to dial the
|
|
210
|
+
// default and never see the one project that had moved off it (#1240). With exactly one project
|
|
211
|
+
// daemon live, that is the one; with several, the no-session answer names them instead.
|
|
212
|
+
const projects = projectId === undefined ? projectDaemonsElsewhere(preferred, home, deps.alive) : [];
|
|
213
|
+
return 1 === projects.length ? (projects[0]?.port ?? preferred) : preferred;
|
|
214
|
+
}
|
|
184
215
|
return adoptable(daemonProjectAt(preferred, home), projectId)
|
|
185
216
|
? preferred
|
|
186
217
|
: deps.pickPort(preferred);
|
|
@@ -34,7 +34,7 @@ interface AgentWriteResult {
|
|
|
34
34
|
*/
|
|
35
35
|
export declare function applyAgentPlan(plan: readonly AgentPlanStep[], io: AgentWriterIo): AgentWriteResult[];
|
|
36
36
|
/** The `/reticle` instruction, for agents that load skills from a directory. */
|
|
37
|
-
export declare const RETICLE_SKILL = "---\nname: reticle\ndescription: Verify a change against the running app, from inside it, before calling it done.\n---\n\nReticle exposes the running app to you as `reticle_*` MCP tools. Look, act, observe, assert.\n\nOnly `reticle_act_and_wait` and `reticle_assert` produce a verdict. Everything else moves or\nreads the app and proves nothing, so a drive that ends without one of those has no result however\nmany tools it used.\n\n`verified: \"unknown\"` is not a pass: it means Reticle could not tell what happened.\n`verified: \"no-fault\"` is not a pass either, because nothing was declared to prove. Never weaken a\ncheck to make it pass.\n\nThe cheapest path that answers the question:\n\n- \"did my edit break anything?\" -> `reticle_verify { action: \"change\", files: [...] }`\n- \"does this known journey still work?\" -> `reticle_verify { action: \"flows\", names: [\"...\"] }`\n- \"does this new behaviour work?\" -> `reticle_act { steps: [...] }` for the setup, then ONE `reticle_act_and_wait`\n- \"nothing is recorded yet\" ->
|
|
37
|
+
export declare const RETICLE_SKILL = "---\nname: reticle\ndescription: Verify a change against the running app, from inside it, before calling it done.\n---\n\nReticle exposes the running app to you as `reticle_*` MCP tools. Look, act, observe, assert.\n\nOnly `reticle_act_and_wait` and `reticle_assert` produce a verdict. Everything else moves or\nreads the app and proves nothing, so a drive that ends without one of those has no result however\nmany tools it used.\n\n`verified: \"unknown\"` is not a pass: it means Reticle could not tell what happened.\n`verified: \"no-fault\"` is not a pass either, because nothing was declared to prove. Never weaken a\ncheck to make it pass.\n\nThe cheapest path that answers the question:\n\n- \"did my edit break anything?\" -> `reticle_verify { action: \"change\", files: [...] }`\n- \"does this known journey still work?\" -> `reticle_verify { action: \"flows\", names: [\"...\"] }`\n- \"does this new behaviour work?\" -> `reticle_act { steps: [...] }` for the setup, then ONE `reticle_act_and_wait`\n- \"nothing is recorded yet\" -> drive the journey yourself with `reticle_act_and_wait` and an `until` on its\n last step; it is saved as a flow, so the next run replays with no model in the loop. With a linked\n project, `reticle_verify { action: \"explore\", persona: \"<who does what>\" }` drives it for you\n";
|
|
38
38
|
interface SkillWriteResult {
|
|
39
39
|
readonly id: string;
|
|
40
40
|
readonly file: string;
|
|
@@ -84,8 +84,9 @@ The cheapest path that answers the question:
|
|
|
84
84
|
- "did my edit break anything?" -> \`reticle_verify { action: "change", files: [...] }\`
|
|
85
85
|
- "does this known journey still work?" -> \`reticle_verify { action: "flows", names: ["..."] }\`
|
|
86
86
|
- "does this new behaviour work?" -> \`reticle_act { steps: [...] }\` for the setup, then ONE \`reticle_act_and_wait\`
|
|
87
|
-
- "nothing is recorded yet" ->
|
|
88
|
-
|
|
87
|
+
- "nothing is recorded yet" -> drive the journey yourself with \`reticle_act_and_wait\` and an \`until\` on its
|
|
88
|
+
last step; it is saved as a flow, so the next run replays with no model in the loop. With a linked
|
|
89
|
+
project, \`reticle_verify { action: "explore", persona: "<who does what>" }\` drives it for you
|
|
89
90
|
`;
|
|
90
91
|
/**
|
|
91
92
|
* Write the skill for agents that have somewhere to put it, and only where they are installed.
|
|
@@ -59,6 +59,14 @@ export declare const WAIT_CEILING_MS: number;
|
|
|
59
59
|
*/
|
|
60
60
|
export declare function judgeWait(facts: WaitFacts): WaitVerdict;
|
|
61
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;
|
|
62
70
|
export declare function announcedUrl(output: string): string | undefined;
|
|
63
71
|
/**
|
|
64
72
|
* The url to watch: whatever the tool announced, else one composed from a port we OBSERVED our own
|
|
@@ -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,6 +6,7 @@
|
|
|
6
6
|
* every command harder to read.
|
|
7
7
|
*/
|
|
8
8
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
9
10
|
import { InitConfirmation, RETICLE_DEFAULT_PORT } from '@reticlehq/core';
|
|
10
11
|
import { FEEDBACK_HINT, Framework, InitFailure } from '@reticlehq/init';
|
|
11
12
|
import { confirmInstall, nodeConfirmDeps } from '../terminal/confirm.js';
|
|
@@ -30,6 +31,22 @@ const POLL_MS = 250;
|
|
|
30
31
|
* of the document can never see it. See `htmlCarriesSdk` in run-setup.ts.
|
|
31
32
|
*/
|
|
32
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
|
+
}
|
|
33
50
|
/** Registration is global MCP wiring, so both flags that disown it are read here. */
|
|
34
51
|
function wantsAgents(parsed) {
|
|
35
52
|
return false !== parsed.agents && false !== parsed.mcp;
|
|
@@ -160,7 +177,7 @@ export async function continueAfterInit(parsed, result, io, cwd) {
|
|
|
160
177
|
startupBudgetMs: parsed.timeoutSeconds * 1000,
|
|
161
178
|
}),
|
|
162
179
|
pollMs: POLL_MS,
|
|
163
|
-
htmlCarriesSdk:
|
|
180
|
+
htmlCarriesSdk: htmlCarriesSdk(context.framework, (rel) => existsSync(join(context.appDir, rel))),
|
|
164
181
|
// A plain page with no dev script gets Reticle's own static server, so the page is actually
|
|
165
182
|
// served and the connect can be proved, instead of stopping at "start the app yourself".
|
|
166
183
|
...(() => {
|
|
@@ -106,7 +106,8 @@ export class OwnedDevServer {
|
|
|
106
106
|
catch {
|
|
107
107
|
/* not written yet */
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
// Filesystem timestamps can include a fractional millisecond beyond Date.now()'s integer.
|
|
110
|
+
return Math.max(0, Date.now() - lastOutputAt);
|
|
110
111
|
}
|
|
111
112
|
/** Ports anything in this server's process tree is listening on. */
|
|
112
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. */
|
|
@@ -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.
|
|
@@ -177,18 +177,20 @@ export async function runSetupPhases(input, fx) {
|
|
|
177
177
|
break;
|
|
178
178
|
}
|
|
179
179
|
if (WaitVerdict.DEAD === verdict) {
|
|
180
|
-
note(portBusyMessage(fx.devServerOutput()) ??
|
|
181
|
-
'The dev server exited without serving anything.');
|
|
180
|
+
note(portBusyMessage(fx.devServerOutput()) ?? devServerDiedMessage(fx.devServerOutput()));
|
|
182
181
|
return stop(input, SetupPhase.DEV_SERVER, {}, notes);
|
|
183
182
|
}
|
|
184
183
|
if (WaitVerdict.HUNG === verdict) {
|
|
185
184
|
// Says BOTH were checked. A server that prints nothing but IS listening is the CRA case and
|
|
186
185
|
// must not be failed, so a reader has to be able to tell "we looked at the log" from "we
|
|
187
186
|
// looked at the log AND the ports".
|
|
188
|
-
|
|
189
|
-
? `
|
|
190
|
-
: undefined
|
|
191
|
-
|
|
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}. `
|
|
192
194
|
: `The dev server stopped making progress before ${watching} became ready. `) +
|
|
193
195
|
'Check its log, increase --timeout for a slow build, or pass --url with the address it serves.');
|
|
194
196
|
return stop(input, SetupPhase.DEV_SERVER, {}, notes);
|
|
@@ -232,6 +234,23 @@ export async function runSetupPhases(input, fx) {
|
|
|
232
234
|
// dialled the bridge" over a correct install. With a lease to fall back on it is entered too, and
|
|
233
235
|
// only the system browser is skipped.
|
|
234
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
|
+
};
|
|
235
254
|
if (null === alreadyConnected && policy.openBrowser && (input.openBrowser || canLease)) {
|
|
236
255
|
// SERVED is the precondition, not SDK_PRESENT: a url that answers nothing is the only state
|
|
237
256
|
// where a window is certainly useless. Whether the SDK is in the served HTML is a DIFFERENT
|
|
@@ -260,14 +279,9 @@ export async function runSetupPhases(input, fx) {
|
|
|
260
279
|
const launcherFailed = input.openBrowser ? await fx.openBrowser(url) : undefined;
|
|
261
280
|
openedBrowser = input.openBrowser;
|
|
262
281
|
if (undefined !== fx.openLease && (!input.openBrowser || undefined !== launcherFailed)) {
|
|
263
|
-
|
|
264
|
-
if (
|
|
265
|
-
note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
lease = owned;
|
|
282
|
+
lease = await keepLease(await fx.openLease(url));
|
|
283
|
+
if (undefined !== lease)
|
|
269
284
|
openedBrowser = true;
|
|
270
|
-
}
|
|
271
285
|
}
|
|
272
286
|
}
|
|
273
287
|
}
|
|
@@ -288,7 +302,8 @@ export async function runSetupPhases(input, fx) {
|
|
|
288
302
|
while (null === session) {
|
|
289
303
|
// On a desktop app, only the desktop window counts. AppShape and the runtime a page reports use
|
|
290
304
|
// the same three names, so the shape IS the requirement — see session-pick.
|
|
291
|
-
|
|
305
|
+
const listedNow = (await fx.listSessions()).filter((s) => !injected.has(s.sessionId));
|
|
306
|
+
session = pickSession(listedNow, url, before, requiredRuntime);
|
|
292
307
|
if (null !== session)
|
|
293
308
|
break;
|
|
294
309
|
if (deadline <= fx.now())
|
|
@@ -301,13 +316,7 @@ export async function runSetupPhases(input, fx) {
|
|
|
301
316
|
!isDesktop(input.shape) &&
|
|
302
317
|
fx.now() - waitStartedAt >= SYSTEM_BROWSER_GRACE_MS) {
|
|
303
318
|
leaseFallbackTried = true;
|
|
304
|
-
|
|
305
|
-
if ('failed' in owned) {
|
|
306
|
-
note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
lease = owned;
|
|
310
|
-
}
|
|
319
|
+
lease = await keepLease(await fx.openLease(url));
|
|
311
320
|
continue;
|
|
312
321
|
}
|
|
313
322
|
// Not in silence: a desktop run used to print one line and then nothing for ten minutes.
|
|
@@ -368,9 +377,8 @@ export async function runSetupPhases(input, fx) {
|
|
|
368
377
|
// `<url>` stays a placeholder in this string on purpose: `guidance-commands-run` feeds every
|
|
369
378
|
// command we print to the real parser, and an interpolated value reads there as a missing operand.
|
|
370
379
|
// The live url is named in the sentence instead, where a reader needs it anyway.
|
|
371
|
-
// Both routes, because `explore`
|
|
372
|
-
//
|
|
373
|
-
// 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.
|
|
374
382
|
// A heading and a numbered list rather than one paragraph: a reader scans for what to do next.
|
|
375
383
|
//
|
|
376
384
|
// This was a single 524-character note, measured on a real first run: eighty-three words, no
|
|
@@ -390,8 +398,8 @@ export async function runSetupPhases(input, fx) {
|
|
|
390
398
|
note(' 1. Drive one flow and end it with `reticle_act_and_wait` or `reticle_assert`. Those two ' +
|
|
391
399
|
'produce a verdict; nothing else does.');
|
|
392
400
|
note(' 2. Or hand over the whole drive: `reticle_verify { action: "explore", persona: "<who does ' +
|
|
393
|
-
'what>" }` records what it drove, so later runs replay with no model in the loop.
|
|
394
|
-
'
|
|
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.');
|
|
395
403
|
return {
|
|
396
404
|
ok: true,
|
|
397
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.
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* the wire changes and stays put when internals do. Versions still ride along, because they are what
|
|
23
23
|
* a human acts on.
|
|
24
24
|
*/
|
|
25
|
+
import { RETIRED_WIRE_NAMES } from '@reticlehq/core';
|
|
25
26
|
import { resolveSdkFix } from './sdk-fix.js';
|
|
26
27
|
/** Which pair disagreed. Named so the nudge can report each independently. */
|
|
27
28
|
export const SkewPair = {
|
|
@@ -38,13 +39,15 @@ export const SkewPair = {
|
|
|
38
39
|
* is not a browser to do -- there is no touch equivalent of a console error. A peer speaking a name
|
|
39
40
|
* we do not know was built against a contract this daemon does not have, and that is a genuine
|
|
40
41
|
* mismatch: one of us is stale, and tools will behave in ways neither side reports.
|
|
42
|
+
*
|
|
43
|
+
* A name we RETIRED is not unknown: an older peer still lists it, and we never send it (#1343).
|
|
41
44
|
*/
|
|
42
45
|
function namesWeDoNotKnow(peer, self) {
|
|
43
46
|
const unknown = [
|
|
44
47
|
...peer.commands.filter((name) => !self.commands.includes(name)),
|
|
45
48
|
...peer.events.filter((name) => !self.events.includes(name)),
|
|
46
49
|
...peer.actions.filter((name) => !self.actions.includes(name)),
|
|
47
|
-
];
|
|
50
|
+
].filter((name) => !RETIRED_WIRE_NAMES.includes(name));
|
|
48
51
|
return [...new Set(unknown)].sort();
|
|
49
52
|
}
|
|
50
53
|
function versionPhrase(peer, self) {
|
|
@@ -77,8 +80,14 @@ export function describeSkew(peer, self) {
|
|
|
77
80
|
const unknown = namesWeDoNotKnow(peer.contractParts, self.contractParts);
|
|
78
81
|
if (0 === unknown.length)
|
|
79
82
|
return undefined;
|
|
83
|
+
// Direction comes from the versions when both are known. An older peer naming something we do
|
|
84
|
+
// not know has a name we removed without retiring it, not a newer contract.
|
|
85
|
+
const order = compareVersions(peer.version, self.version);
|
|
86
|
+
const why = order !== undefined && order < 0
|
|
87
|
+
? 'though it is the older of the two — so this daemon dropped a name it still uses'
|
|
88
|
+
: 'so it was built against a newer contract than this one';
|
|
80
89
|
return (`version skew: ${versionPhrase(peer, self)}, and ${peer.what} speaks names this daemon does ` +
|
|
81
|
-
`not know (${unknown.join(', ')}) —
|
|
90
|
+
`not know (${unknown.join(', ')}) — ${why}. ` +
|
|
82
91
|
`Tools will behave in ways neither side reports. ${peer.fix}`);
|
|
83
92
|
}
|
|
84
93
|
if (peer.contract === undefined) {
|