@reticlehq/server 2.12.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +67 -109
- package/dist/bridge/auth-failure-reason.d.ts +6 -2
- package/dist/bridge/auth-failure-reason.js +15 -8
- package/dist/bridge/bridge.d.ts +18 -1
- package/dist/bridge/bridge.js +247 -164
- package/dist/bridge/pairing-token.d.ts +5 -0
- package/dist/bridge/pairing-token.js +28 -0
- package/dist/bridge/protocol-skew.d.ts +37 -0
- package/dist/bridge/protocol-skew.js +45 -0
- package/dist/capsule/capsule-store.d.ts +6 -6
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/cli/cli-doctor.js +31 -1
- package/dist/cli/cli-launch.d.ts +11 -1
- package/dist/cli/cli-launch.js +59 -10
- package/dist/cli/cli-parse.d.ts +15 -1
- package/dist/cli/cli-parse.js +212 -8
- package/dist/cli/cli-verify.d.ts +1 -0
- package/dist/cli/cli-verify.js +3 -2
- package/dist/cli/cloud-cli.js +428 -259
- package/dist/cli/cloud-keystore.d.ts +37 -0
- package/dist/cli/cloud-keystore.js +97 -0
- package/dist/cli/cloud-kit.d.ts +37 -0
- package/dist/cli/cloud-kit.js +121 -0
- package/dist/cli/cloud-login.d.ts +17 -0
- package/dist/cli/cloud-login.js +183 -0
- package/dist/cli/cloud-session.d.ts +48 -0
- package/dist/cli/cloud-session.js +53 -0
- package/dist/cli/config-discovery.js +1 -0
- package/dist/cli/daemon-start-options.d.ts +32 -0
- package/dist/cli/daemon-start-options.js +38 -0
- package/dist/cli/device-flow.d.ts +76 -0
- package/dist/cli/device-flow.js +56 -0
- package/dist/cli/doctor-rows.d.ts +9 -0
- package/dist/cli/doctor-rows.js +9 -0
- package/dist/cli/drive-attach.d.ts +13 -0
- package/dist/cli/drive-attach.js +18 -0
- package/dist/cli/drive-command.js +7 -3
- package/dist/cli/mcp-command.js +51 -20
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/status-next-action.d.ts +14 -0
- package/dist/cli/status-next-action.js +4 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +134 -80
- package/dist/cloud/cloud-config.d.ts +22 -1
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +5 -5
- package/dist/cloud/cloud-sync.js +33 -2
- package/dist/cloud/sync-cycle.d.ts +110 -0
- package/dist/cloud/sync-cycle.js +270 -0
- package/dist/cloud/sync-daemon.d.ts +69 -0
- package/dist/cloud/sync-daemon.js +260 -0
- package/dist/cloud/sync-disk.d.ts +9 -0
- package/dist/cloud/sync-disk.js +191 -0
- package/dist/cloud/sync-hash.d.ts +2 -0
- package/dist/cloud/sync-hash.js +35 -0
- package/dist/crawl/crawl-tools.js +5 -3
- package/dist/crawl/crawl.d.ts +3 -0
- package/dist/crawl/crawl.js +5 -0
- package/dist/daemon/daemon-resolve.d.ts +43 -0
- package/dist/daemon/daemon-resolve.js +69 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +18 -0
- package/dist/events/contradictions.js +134 -21
- package/dist/events/declared.d.ts +7 -4
- package/dist/events/declared.js +15 -6
- package/dist/events/echo-mismatch.js +64 -14
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/predicate-eval.js +53 -5
- package/dist/events/predicate-precheck.d.ts +8 -0
- package/dist/events/predicate-precheck.js +41 -0
- package/dist/events/predicate-route.d.ts +14 -0
- package/dist/events/predicate-route.js +36 -8
- package/dist/events/predicate.d.ts +5 -0
- package/dist/events/predicate.js +37 -5
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/flake.d.ts +6 -6
- package/dist/flows/flow-memory-consult.d.ts +55 -0
- package/dist/flows/flow-memory-consult.js +38 -0
- package/dist/flows/flow-replay-run.d.ts +18 -7
- package/dist/flows/flow-replay-run.js +239 -22
- package/dist/flows/flow-replay.js +11 -2
- package/dist/flows/flow-step-runners.d.ts +8 -0
- package/dist/flows/flow-step-runners.js +8 -2
- package/dist/flows/flow-store-for-session.d.ts +31 -0
- package/dist/flows/flow-store-for-session.js +33 -0
- package/dist/flows/flow-tools.js +58 -32
- package/dist/flows/flows.d.ts +8 -0
- package/dist/flows/flows.js +468 -2
- package/dist/flows/heal-run.js +5 -2
- package/dist/flows/predicate-to-expect.d.ts +17 -12
- package/dist/flows/predicate-to-expect.js +30 -12
- package/dist/flows/recordings.d.ts +13 -1
- package/dist/flows/recordings.js +17 -2
- package/dist/flows/replay.d.ts +22 -2
- package/dist/flows/replay.js +28 -2
- package/dist/flows/verify-change-tools.js +5 -2
- package/dist/honesty/blind-spots.d.ts +55 -2
- package/dist/honesty/blind-spots.js +63 -3
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +9 -0
- package/dist/honesty/instrumentation-gaps.js +29 -2
- package/dist/honesty/verified.d.ts +2 -1
- package/dist/honesty/verified.js +16 -1
- package/dist/http-server.js +12 -0
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +15 -1
- package/dist/impact/impact-store.js +43 -2
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +13 -6
- package/dist/init/agent-rules.js +23 -12
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -1
- package/dist/init/capabilities.js +22 -2
- package/dist/init/confirm.d.ts +31 -7
- package/dist/init/confirm.js +48 -8
- package/dist/init/cra.d.ts +20 -4
- package/dist/init/cra.js +57 -17
- package/dist/init/csp-check.js +17 -2
- package/dist/init/csp-doctor.d.ts +8 -1
- package/dist/init/csp-doctor.js +8 -3
- package/dist/init/dev-script.d.ts +3 -1
- package/dist/init/dev-script.js +8 -5
- package/dist/init/format-generated.d.ts +18 -0
- package/dist/init/format-generated.js +39 -0
- package/dist/init/install-hint.d.ts +9 -0
- package/dist/init/install-hint.js +29 -0
- package/dist/init/mcp-clients.d.ts +7 -0
- package/dist/init/mcp-clients.js +7 -0
- package/dist/init/mcp.d.ts +1 -1
- package/dist/init/mcp.js +1 -1
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.js +81 -45
- package/dist/init/plan.d.ts +20 -7
- package/dist/init/plan.js +20 -45
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/run.d.ts +32 -9
- package/dist/init/run.js +141 -119
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +41 -25
- package/dist/init/vite-config.d.ts +1 -1
- package/dist/init/vite-config.js +39 -25
- package/dist/init/workspace-apps.d.ts +17 -10
- package/dist/init/workspace-apps.js +110 -0
- package/dist/input/network-mock-tools.js +3 -6
- package/dist/input/real-input.js +25 -5
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +10 -19
- package/dist/intent/inline-intent.js +91 -1
- package/dist/intent/intent-shard-store.d.ts +72 -0
- package/dist/intent/intent-shard-store.js +203 -0
- package/dist/intent/intent-shard.d.ts +411 -0
- package/dist/intent/intent-shard.js +176 -0
- package/dist/intent/intent-store.d.ts +12 -0
- package/dist/intent/intent-store.js +21 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.js +65 -2
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +15 -0
- package/dist/license/license-env.d.ts +29 -0
- package/dist/license/license-env.js +148 -0
- package/dist/loopback-agent.d.ts +31 -0
- package/dist/loopback-agent.js +57 -0
- package/dist/mcp/attach-memory.d.ts +18 -2
- package/dist/mcp/attach-memory.js +50 -7
- package/dist/mcp/client-identity.d.ts +31 -0
- package/dist/mcp/client-identity.js +51 -0
- package/dist/mcp/mcp-post-transport.d.ts +26 -0
- package/dist/mcp/mcp-post-transport.js +109 -0
- package/dist/mcp/mcp-proxy.d.ts +2 -1
- package/dist/mcp/mcp-proxy.js +52 -62
- package/dist/mcp/mcp.js +7 -1
- package/dist/mcp/proxy-backoff.d.ts +11 -0
- package/dist/mcp/proxy-backoff.js +13 -0
- package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
- package/dist/mcp/proxy-daemon-probe.js +7 -1
- package/dist/mcp/proxy-handshake.d.ts +23 -0
- package/dist/mcp/proxy-handshake.js +31 -0
- package/dist/mcp/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +12 -0
- package/dist/memory/memory-tools.d.ts +2 -0
- package/dist/memory/memory-tools.js +82 -0
- package/dist/memory/project-memory.d.ts +34 -0
- package/dist/memory/project-memory.js +96 -0
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/pool/browser-pool.d.ts +63 -4
- package/dist/pool/browser-pool.js +113 -4
- package/dist/pool/playwright-launcher.js +9 -0
- package/dist/project/project-tools.js +3 -3
- package/dist/project/reticle-dir.d.ts +15 -2
- package/dist/project/reticle-dir.js +21 -4
- package/dist/runs/run-store.d.ts +10 -0
- package/dist/runs/run-store.js +10 -0
- package/dist/runs/verification-sync.d.ts +0 -11
- package/dist/runs/verification-sync.js +22 -1
- package/dist/runs/verify-server.d.ts +8 -1
- package/dist/runs/verify-server.js +14 -2
- package/dist/session/command-timeout.d.ts +5 -2
- package/dist/session/command-timeout.js +5 -4
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/no-session-diagnosis.d.ts +23 -0
- package/dist/session/no-session-diagnosis.js +58 -17
- package/dist/session/no-session-next-action.d.ts +7 -0
- package/dist/session/no-session-next-action.js +42 -3
- package/dist/session/no-session-watch.d.ts +10 -6
- package/dist/session/no-session-watch.js +51 -19
- package/dist/session/pending-commands.d.ts +11 -1
- package/dist/session/pending-commands.js +12 -2
- package/dist/session/resolve-within.d.ts +34 -0
- package/dist/session/resolve-within.js +53 -0
- package/dist/session/session-info.d.ts +18 -0
- package/dist/session/session-info.js +11 -0
- package/dist/session/session-manager.d.ts +14 -0
- package/dist/session/session-manager.js +61 -3
- package/dist/session/session-mirror.d.ts +16 -0
- package/dist/session/session-mirror.js +22 -0
- package/dist/session/session-recommendation.d.ts +19 -5
- package/dist/session/session-recommendation.js +25 -9
- package/dist/session/session-successor.js +36 -1
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +22 -2
- package/dist/session/session.js +87 -7
- package/dist/session/wall-clock.d.ts +12 -0
- package/dist/session/wall-clock.js +14 -0
- package/dist/setup/agent-configs.d.ts +96 -0
- package/dist/setup/agent-configs.js +333 -0
- package/dist/setup/agent-io.d.ts +9 -0
- package/dist/setup/agent-io.js +16 -0
- package/dist/setup/agent-writer.d.ts +51 -0
- package/dist/setup/agent-writer.js +115 -0
- package/dist/setup/approval-migration.d.ts +47 -0
- package/dist/setup/approval-migration.js +68 -0
- package/dist/setup/auto-approve.d.ts +85 -0
- package/dist/setup/auto-approve.js +150 -0
- package/dist/setup/bridge-port.d.ts +15 -0
- package/dist/setup/bridge-port.js +24 -0
- package/dist/setup/desktop-shape.d.ts +57 -0
- package/dist/setup/desktop-shape.js +63 -0
- package/dist/setup/dev-server-wait.d.ts +60 -0
- package/dist/setup/dev-server-wait.js +65 -0
- package/dist/setup/drive-agent.d.ts +71 -0
- package/dist/setup/drive-agent.js +171 -0
- package/dist/setup/drive-plan.d.ts +68 -0
- package/dist/setup/drive-plan.js +130 -0
- package/dist/setup/ensure-daemon.d.ts +29 -0
- package/dist/setup/ensure-daemon.js +58 -0
- package/dist/setup/init-runtime.d.ts +47 -0
- package/dist/setup/init-runtime.js +160 -0
- package/dist/setup/interrupt.d.ts +25 -0
- package/dist/setup/interrupt.js +40 -0
- package/dist/setup/license-key.d.ts +39 -0
- package/dist/setup/license-key.js +72 -0
- package/dist/setup/listeners.d.ts +37 -0
- package/dist/setup/listeners.js +85 -0
- package/dist/setup/node-effects.d.ts +60 -0
- package/dist/setup/node-effects.js +210 -0
- package/dist/setup/page-probe.d.ts +39 -0
- package/dist/setup/page-probe.js +49 -0
- package/dist/setup/relaunch.d.ts +36 -0
- package/dist/setup/relaunch.js +57 -0
- package/dist/setup/remaining-steps.d.ts +19 -0
- package/dist/setup/remaining-steps.js +31 -0
- package/dist/setup/run-setup.d.ts +99 -0
- package/dist/setup/run-setup.js +187 -0
- package/dist/setup/session-pick.d.ts +47 -0
- package/dist/setup/session-pick.js +51 -0
- package/dist/setup/setup-command.d.ts +45 -0
- package/dist/setup/setup-command.js +199 -0
- package/dist/setup/setup-options.d.ts +74 -0
- package/dist/setup/setup-options.js +67 -0
- package/dist/setup/transcripts.d.ts +16 -0
- package/dist/setup/transcripts.js +43 -0
- package/dist/status-payload.d.ts +14 -1
- package/dist/status-payload.js +23 -1
- package/dist/telemetry/daemon-telemetry.d.ts +8 -1
- package/dist/telemetry/daemon-telemetry.js +52 -5
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +9 -1
- package/dist/telemetry/feedback-context.js +60 -29
- package/dist/telemetry/feedback-local.d.ts +15 -0
- package/dist/telemetry/feedback-local.js +56 -0
- package/dist/telemetry/feedback.d.ts +11 -0
- package/dist/telemetry/feedback.js +19 -6
- package/dist/telemetry/init-telemetry.d.ts +2 -0
- package/dist/telemetry/init-telemetry.js +2 -0
- package/dist/telemetry/install-source.d.ts +14 -0
- package/dist/telemetry/install-source.js +30 -0
- package/dist/telemetry/license-activation.d.ts +7 -1
- package/dist/telemetry/license-activation.js +9 -5
- package/dist/telemetry/mcp-connection.js +7 -0
- package/dist/telemetry/project-profile.d.ts +12 -1
- package/dist/telemetry/project-profile.js +11 -6
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/session-metrics.d.ts +12 -0
- package/dist/telemetry/session-metrics.js +35 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +3 -1
- package/dist/telemetry/tool-refused.js +34 -2
- package/dist/tools/act-capsule.d.ts +10 -0
- package/dist/tools/act-capsule.js +8 -2
- package/dist/tools/act-danger.d.ts +2 -1
- package/dist/tools/act-danger.js +11 -3
- package/dist/tools/act-observation.d.ts +41 -0
- package/dist/tools/act-observation.js +36 -0
- package/dist/tools/act-preflight.d.ts +20 -0
- package/dist/tools/act-preflight.js +98 -0
- package/dist/tools/act-sequence-retry.d.ts +45 -0
- package/dist/tools/act-sequence-retry.js +58 -0
- package/dist/tools/act-sequence-tool.d.ts +13 -0
- package/dist/tools/act-sequence-tool.js +135 -0
- package/dist/tools/act-target.d.ts +14 -0
- package/dist/tools/act-target.js +47 -0
- package/dist/tools/act-tools.d.ts +21 -1
- package/dist/tools/act-tools.js +100 -213
- package/dist/tools/assert-verdict.js +22 -16
- package/dist/tools/browser-tools.js +6 -2
- package/dist/tools/dynamic-tools.js +24 -5
- package/dist/tools/error-recovery.d.ts +1 -0
- package/dist/tools/error-recovery.js +8 -0
- package/dist/tools/invoke-tool.js +59 -9
- package/dist/tools/lease-tools.d.ts +30 -4
- package/dist/tools/lease-tools.js +169 -5
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +47 -40
- package/dist/tools/read-tools.js +28 -7
- package/dist/tools/real-input-attempt.d.ts +10 -0
- package/dist/tools/real-input-attempt.js +59 -1
- package/dist/tools/reconcile-tools.js +4 -4
- package/dist/tools/tool-kit.d.ts +12 -0
- package/dist/tools/tool-kit.js +6 -1
- package/dist/tools/tool-names.d.ts +2 -0
- package/dist/tools/tool-names.js +2 -0
- package/dist/tools/tools-helpers.d.ts +19 -0
- package/dist/tools/tools-helpers.js +46 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +56 -8
- package/dist/update/reticle-deps.d.ts +9 -0
- package/dist/update/reticle-deps.js +27 -0
- package/dist/update/sdk-sync.d.ts +1 -2
- package/dist/update/sdk-sync.js +1 -21
- package/dist/update/update-nudge.d.ts +18 -0
- package/dist/update/update-nudge.js +20 -0
- package/dist/version/peer-announce.js +4 -2
- package/dist/version/sdk-fix.d.ts +47 -0
- package/dist/version/sdk-fix.js +127 -0
- package/dist/version/version-nudge.d.ts +14 -0
- package/dist/version/version-nudge.js +21 -0
- package/dist/version/version-skew.d.ts +38 -3
- package/dist/version/version-skew.js +83 -7
- package/dist/visual/visual-store.d.ts +6 -6
- package/dist/visual/visual-store.js +18 -16
- package/dist/visual/visual-tools.js +57 -12
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +1 -1
- package/docs/getting-started.md +19 -3
- package/docs/index.mdx +9 -6
- package/docs/install-agentic.mdx +38 -12
- package/docs/install-manual.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/usage.md +1 -1
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The real world, bound to the injected shape `runSetupPhases` expects.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is a thin binding to something that already exists — `openInBrowser`,
|
|
5
|
+
* `fetchStatus`, the driver table — with one exception: the dev server. Nothing else in the package
|
|
6
|
+
* owns a spawned process, and owning one properly is most of this file.
|
|
7
|
+
*
|
|
8
|
+
* The promise this makes about that process: setup leaves it running on success, because an
|
|
9
|
+
* instrumented app the user can watch IS the deliverable, and stops it on every other ending. A
|
|
10
|
+
* server nobody started, holding a port nobody can account for, surviving the terminal that spawned
|
|
11
|
+
* it, is the failure this file exists to avoid — measured, an interrupted run used to leave one
|
|
12
|
+
* listening indefinitely.
|
|
13
|
+
*/
|
|
14
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
15
|
+
import { Socket } from 'node:net';
|
|
16
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { fetchStatus } from '../cli/cli-launch.js';
|
|
19
|
+
import { descendants, parseLsofPorts, parseNetstatListeners, parseWmicProcesses, } from './listeners.js';
|
|
20
|
+
const WINDOWS = 'win32' === process.platform;
|
|
21
|
+
/** A page fetch that is slow is a page fetch that failed, for our purposes. */
|
|
22
|
+
const PROBE_TIMEOUT_MS = 5_000;
|
|
23
|
+
/**
|
|
24
|
+
* Stop an inherited stdio pipe from holding the event loop open.
|
|
25
|
+
*
|
|
26
|
+
* A child's piped stdout is a Socket at runtime and a Readable in the types, which has no `unref`.
|
|
27
|
+
* Narrowing through Socket keeps that honest rather than asserting it.
|
|
28
|
+
*/
|
|
29
|
+
function releasePipe(stream) {
|
|
30
|
+
if (stream instanceof Socket)
|
|
31
|
+
stream.unref();
|
|
32
|
+
}
|
|
33
|
+
/** The dev server this process started, and the only one it will ever stop. */
|
|
34
|
+
export class OwnedDevServer {
|
|
35
|
+
child;
|
|
36
|
+
buffer = '';
|
|
37
|
+
lastOutputAt = Date.now();
|
|
38
|
+
handedOver = false;
|
|
39
|
+
start(command, cwd, env) {
|
|
40
|
+
const child = spawn(command, {
|
|
41
|
+
cwd,
|
|
42
|
+
shell: true,
|
|
43
|
+
// Its own process group, so stopping it stops what it started rather than only the wrapper.
|
|
44
|
+
detached: !WINDOWS,
|
|
45
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
46
|
+
env: { ...process.env, ...env },
|
|
47
|
+
});
|
|
48
|
+
const collect = (chunk) => {
|
|
49
|
+
this.buffer += String(chunk);
|
|
50
|
+
this.lastOutputAt = Date.now();
|
|
51
|
+
};
|
|
52
|
+
child.stdout?.on('data', collect);
|
|
53
|
+
child.stderr?.on('data', collect);
|
|
54
|
+
this.child = child;
|
|
55
|
+
}
|
|
56
|
+
/** The process group leader, for a caller that has to clean up what it handed over. */
|
|
57
|
+
pid() {
|
|
58
|
+
return this.child?.pid;
|
|
59
|
+
}
|
|
60
|
+
output() {
|
|
61
|
+
return this.buffer;
|
|
62
|
+
}
|
|
63
|
+
exited() {
|
|
64
|
+
return undefined !== this.child && null !== this.child.exitCode;
|
|
65
|
+
}
|
|
66
|
+
quietForMs() {
|
|
67
|
+
return Date.now() - this.lastOutputAt;
|
|
68
|
+
}
|
|
69
|
+
/** Ports anything in this server's process tree is listening on. */
|
|
70
|
+
listeningPorts() {
|
|
71
|
+
const pid = this.child?.pid;
|
|
72
|
+
if (undefined === pid)
|
|
73
|
+
return [];
|
|
74
|
+
if (WINDOWS) {
|
|
75
|
+
const tree = new Set(descendants(windowsProcessPairs(), pid));
|
|
76
|
+
return [
|
|
77
|
+
...new Set(parseNetstatListeners(run('netstat', ['-ano']))
|
|
78
|
+
.filter((r) => tree.has(r.pid))
|
|
79
|
+
.map((r) => r.port)),
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
const pids = run('sh', ['-c', `ps -o pid= -g ${pid} 2>/dev/null | tr -d ' '`])
|
|
83
|
+
.split('\n')
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
if (0 === pids.length)
|
|
86
|
+
return [];
|
|
87
|
+
return parseLsofPorts(run('sh', [
|
|
88
|
+
'-c',
|
|
89
|
+
`lsof -a -p ${pids.join(',')} -iTCP -sTCP:LISTEN -P -n 2>/dev/null | awk 'NR>1{print $9}'`,
|
|
90
|
+
]));
|
|
91
|
+
}
|
|
92
|
+
/** Hand the running server to the user. After this, `stop()` does nothing. */
|
|
93
|
+
/**
|
|
94
|
+
* Leave the server running and stop holding it.
|
|
95
|
+
*
|
|
96
|
+
* `unref` on the child is NOT enough: its stdout and stderr are pipes owned by this process, and
|
|
97
|
+
* an open pipe keeps the event loop alive on its own. So `init` printed "setup complete" and then
|
|
98
|
+
* hung until something killed it, turning a successful run into a non-zero exit — which is
|
|
99
|
+
* exactly what the install gate reported, on a run whose every other assertion passed.
|
|
100
|
+
*
|
|
101
|
+
* The streams are unref'd rather than destroyed: the server is still writing to them, and
|
|
102
|
+
* destroying the read end of a live pipe risks an EPIPE in somebody else's dev server.
|
|
103
|
+
*/
|
|
104
|
+
handOver() {
|
|
105
|
+
this.handedOver = true;
|
|
106
|
+
const child = this.child;
|
|
107
|
+
releasePipe(child?.stdout);
|
|
108
|
+
releasePipe(child?.stderr);
|
|
109
|
+
child?.unref();
|
|
110
|
+
}
|
|
111
|
+
stop() {
|
|
112
|
+
const child = this.child;
|
|
113
|
+
if (this.handedOver || undefined === child?.pid)
|
|
114
|
+
return;
|
|
115
|
+
if (WINDOWS)
|
|
116
|
+
run('taskkill', ['/PID', String(child.pid), '/T', '/F']);
|
|
117
|
+
else {
|
|
118
|
+
try {
|
|
119
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
try {
|
|
123
|
+
child.kill();
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* already gone */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
this.child = undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function run(file, args) {
|
|
134
|
+
const r = spawnSync(file, args, { encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
135
|
+
return r.stdout ?? '';
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Every (pid, ppid) pair on a Windows machine — from `wmic` if it is there, PowerShell if not.
|
|
139
|
+
*
|
|
140
|
+
* `wmic` was the only source, and Microsoft has been removing it: deprecated since 21H1 and absent
|
|
141
|
+
* from current Windows images. When it is missing `run` returns '' — the honest answer for a tool
|
|
142
|
+
* that does not exist, and a catastrophic one here, because an empty process list means an empty
|
|
143
|
+
* process TREE, which means no observed ports, which means the dev server's url is never found.
|
|
144
|
+
* `init` then waits out the full quiet window and reports "the dev server stopped producing output
|
|
145
|
+
* without ever serving a page" about a Vite server that was serving perfectly the whole time.
|
|
146
|
+
*
|
|
147
|
+
* The fallback prints the same two-integers-per-line shape, so `parseWmicProcesses` reads either
|
|
148
|
+
* without knowing which produced it. Tried in this order because `wmic` is an order of magnitude
|
|
149
|
+
* faster to start than PowerShell, and on a machine that still has it nothing needs to change.
|
|
150
|
+
*/
|
|
151
|
+
const POWERSHELL_PAIRS = 'Get-CimInstance Win32_Process | ForEach-Object { "$($_.ParentProcessId) $($_.ProcessId)" }';
|
|
152
|
+
function windowsProcessPairs() {
|
|
153
|
+
const fromWmic = parseWmicProcesses(run('wmic', ['process', 'get', 'ParentProcessId,ProcessId']));
|
|
154
|
+
if (0 < fromWmic.length)
|
|
155
|
+
return fromWmic;
|
|
156
|
+
return parseWmicProcesses(run('powershell', ['-NoProfile', '-NonInteractive', '-Command', POWERSHELL_PAIRS]));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Fetch the page and say what came back.
|
|
160
|
+
*
|
|
161
|
+
* A refused certificate is reported separately because the server ANSWERED: a self-signed dev cert
|
|
162
|
+
* is an ordinary local setup, and calling it "nothing is listening" sends somebody to start a
|
|
163
|
+
* server that is already running.
|
|
164
|
+
*/
|
|
165
|
+
export async function probePage(url) {
|
|
166
|
+
try {
|
|
167
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
|
|
168
|
+
const body = await res.text();
|
|
169
|
+
return { served: true, sdkInPage: /@reticlehq|@reticle-connect|reticle-dev/.test(body) };
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
const message = String(err?.cause?.message ??
|
|
173
|
+
err?.message ??
|
|
174
|
+
'');
|
|
175
|
+
const tlsRefused = /certificate|SELF_SIGNED|DEPTH_ZERO|ERR_TLS|unable to verify/i.test(message);
|
|
176
|
+
return { served: false, sdkInPage: false, tlsRefused };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** Sessions the daemon is holding, in the shape the picker reads. */
|
|
180
|
+
export async function listSessions(port) {
|
|
181
|
+
try {
|
|
182
|
+
const payload = await fetchStatus(port);
|
|
183
|
+
const sessions = payload.sessions;
|
|
184
|
+
return Array.isArray(sessions) ? sessions : [];
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** Whether this project has a saved flow, wherever the project keeps one. */
|
|
191
|
+
export function flowsSaved(roots) {
|
|
192
|
+
for (const root of roots) {
|
|
193
|
+
try {
|
|
194
|
+
if (0 < readdirSync(join(root, '.reticle', 'flows')).length)
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
/* no flows kept there */
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
/** On PATH at all. The separate question of whether it RUNS is asked by chooseDriver. */
|
|
204
|
+
export function binaryExists(bin) {
|
|
205
|
+
return 0 === spawnSync(WINDOWS ? 'where' : 'which', [bin], { stdio: 'ignore' }).status;
|
|
206
|
+
}
|
|
207
|
+
/** Whether a directory looks like a project setup has already wired. */
|
|
208
|
+
export function alreadyWired(dir) {
|
|
209
|
+
return existsSync(join(dir, '.reticle.json'));
|
|
210
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one thing the daemon cannot know: what the page actually contains.
|
|
3
|
+
*
|
|
4
|
+
* `diagnoseNoSession` in session/no-session-diagnosis.ts already tells the four daemon-side cases
|
|
5
|
+
* apart — a session that came and went, something listening that never dialled, a wired project on
|
|
6
|
+
* the wrong port, nothing listening at all — and its sentence is the one to lead with. None of that
|
|
7
|
+
* requires fetching anything, which is also its limit: the daemon never sees the HTML.
|
|
8
|
+
*
|
|
9
|
+
* So this adds only the page-side fact and nothing else. Whether the SDK is IN what the server
|
|
10
|
+
* returned splits the most common failure of all — a dev server still serving the bundle it built
|
|
11
|
+
* before the build config was edited — from a connect that ran and returned early.
|
|
12
|
+
*/
|
|
13
|
+
/** What a single fetch of the app's url established. */
|
|
14
|
+
export interface PageProbe {
|
|
15
|
+
/** The server answered at all. */
|
|
16
|
+
readonly served: boolean;
|
|
17
|
+
/** The SDK appears in what it answered with. */
|
|
18
|
+
readonly sdkInPage: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The server ANSWERED and its certificate was refused. A self-signed dev cert is an ordinary
|
|
21
|
+
* local setup, so treating this as "nothing is listening" tells someone to start a server that is
|
|
22
|
+
* already running.
|
|
23
|
+
*/
|
|
24
|
+
readonly tlsRefused?: boolean | undefined;
|
|
25
|
+
}
|
|
26
|
+
export declare const PageFinding: {
|
|
27
|
+
readonly NOT_SERVED: "not-served";
|
|
28
|
+
readonly TLS_REFUSED: "tls-refused";
|
|
29
|
+
readonly SDK_MISSING: "sdk-missing";
|
|
30
|
+
readonly SDK_PRESENT: "sdk-present";
|
|
31
|
+
};
|
|
32
|
+
export type PageFinding = (typeof PageFinding)[keyof typeof PageFinding];
|
|
33
|
+
/** Pure, so the mapping is testable without a server or a browser. */
|
|
34
|
+
export declare function readPage(probe: PageProbe): PageFinding;
|
|
35
|
+
/**
|
|
36
|
+
* What the page adds to the daemon's account. Deliberately a sentence to APPEND, not a replacement:
|
|
37
|
+
* the daemon's diagnosis names the fix, and this says what the page looked like when we checked.
|
|
38
|
+
*/
|
|
39
|
+
export declare function describePage(finding: PageFinding, url: string): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one thing the daemon cannot know: what the page actually contains.
|
|
3
|
+
*
|
|
4
|
+
* `diagnoseNoSession` in session/no-session-diagnosis.ts already tells the four daemon-side cases
|
|
5
|
+
* apart — a session that came and went, something listening that never dialled, a wired project on
|
|
6
|
+
* the wrong port, nothing listening at all — and its sentence is the one to lead with. None of that
|
|
7
|
+
* requires fetching anything, which is also its limit: the daemon never sees the HTML.
|
|
8
|
+
*
|
|
9
|
+
* So this adds only the page-side fact and nothing else. Whether the SDK is IN what the server
|
|
10
|
+
* returned splits the most common failure of all — a dev server still serving the bundle it built
|
|
11
|
+
* before the build config was edited — from a connect that ran and returned early.
|
|
12
|
+
*/
|
|
13
|
+
export const PageFinding = {
|
|
14
|
+
NOT_SERVED: 'not-served',
|
|
15
|
+
TLS_REFUSED: 'tls-refused',
|
|
16
|
+
SDK_MISSING: 'sdk-missing',
|
|
17
|
+
SDK_PRESENT: 'sdk-present',
|
|
18
|
+
};
|
|
19
|
+
/** Pure, so the mapping is testable without a server or a browser. */
|
|
20
|
+
export function readPage(probe) {
|
|
21
|
+
if (true === probe.tlsRefused)
|
|
22
|
+
return PageFinding.TLS_REFUSED;
|
|
23
|
+
if (!probe.served)
|
|
24
|
+
return PageFinding.NOT_SERVED;
|
|
25
|
+
return probe.sdkInPage ? PageFinding.SDK_PRESENT : PageFinding.SDK_MISSING;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* What the page adds to the daemon's account. Deliberately a sentence to APPEND, not a replacement:
|
|
29
|
+
* the daemon's diagnosis names the fix, and this says what the page looked like when we checked.
|
|
30
|
+
*/
|
|
31
|
+
export function describePage(finding, url) {
|
|
32
|
+
switch (finding) {
|
|
33
|
+
case PageFinding.NOT_SERVED:
|
|
34
|
+
// Leads with the cause. The break-matrix asserts this phrasing because it is what a reader
|
|
35
|
+
// greps for after a timeout, and "nothing answered" reads as an observation about the check
|
|
36
|
+
// rather than a statement about the machine.
|
|
37
|
+
return `nothing is serving ${url} — the dev server is not up, so the SDK was never given the chance to load.`;
|
|
38
|
+
case PageFinding.TLS_REFUSED:
|
|
39
|
+
return (`${url} answered over HTTPS with a certificate this process will not accept, so setup could ` +
|
|
40
|
+
'not read the page. The app itself may be perfectly fine; re-run with ' +
|
|
41
|
+
'NODE_TLS_REJECT_UNAUTHORIZED=0 if you trust this dev certificate, or use the http origin.');
|
|
42
|
+
case PageFinding.SDK_MISSING:
|
|
43
|
+
return (`${url} is served, and the SDK is NOT in the page. That is almost always a dev server still ` +
|
|
44
|
+
'serving the bundle it built before the build config was edited: restart it and hard-reload.');
|
|
45
|
+
case PageFinding.SDK_PRESENT:
|
|
46
|
+
return (`The SDK IS in the page at ${url} and never dialled the bridge, so it loaded and returned ` +
|
|
47
|
+
'early: a localhost guard, a production-only check, or a bridge port that differs on the two sides.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What restarting the calling client should do — decided, not performed.
|
|
3
|
+
*
|
|
4
|
+
* The dangerous case is the one this exists for: `--resume <id>` on an id with NO transcript opens
|
|
5
|
+
* an EMPTY conversation under that id, silently. Nothing errors, the client starts, the session
|
|
6
|
+
* looks real, and every tool the restart existed to load is missing. A restart that lands there is
|
|
7
|
+
* worse than no restart, because it is indistinguishable from success.
|
|
8
|
+
*
|
|
9
|
+
* The decision is pure and testable; opening a terminal is neither, and is left to the caller.
|
|
10
|
+
*/
|
|
11
|
+
/** What the caller should do, once the environment has been read. */
|
|
12
|
+
export declare const RelaunchOutcome: {
|
|
13
|
+
/** Claude Code named its own session and a transcript backs it. */
|
|
14
|
+
readonly CLAUDE: "claude";
|
|
15
|
+
/** No claude session, but a codex rollout in this directory identifies one. */
|
|
16
|
+
readonly CODEX: "codex";
|
|
17
|
+
/** A session id with nothing behind it — the case that looks like success. */
|
|
18
|
+
readonly REFUSED: "refused";
|
|
19
|
+
/** The client tells a child process nothing about which conversation it is. */
|
|
20
|
+
readonly UNKNOWN_CLIENT: "unknown-client";
|
|
21
|
+
};
|
|
22
|
+
export type RelaunchOutcome = (typeof RelaunchOutcome)[keyof typeof RelaunchOutcome];
|
|
23
|
+
export interface RelaunchEnv {
|
|
24
|
+
/** Claude Code puts its session id in the environment; nothing else does. */
|
|
25
|
+
readonly claudeSessionId?: string | undefined;
|
|
26
|
+
/** Codex is recognised from the rollout file it is writing, not from the environment. */
|
|
27
|
+
readonly codexSessionId?: string | undefined;
|
|
28
|
+
/** Does a transcript actually exist for this id. */
|
|
29
|
+
transcriptExists(sessionId: string): boolean;
|
|
30
|
+
readonly cwd: string;
|
|
31
|
+
}
|
|
32
|
+
export interface RelaunchDecision {
|
|
33
|
+
readonly outcome: RelaunchOutcome;
|
|
34
|
+
readonly message: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function relaunchDecision(env: RelaunchEnv): RelaunchDecision;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What restarting the calling client should do — decided, not performed.
|
|
3
|
+
*
|
|
4
|
+
* The dangerous case is the one this exists for: `--resume <id>` on an id with NO transcript opens
|
|
5
|
+
* an EMPTY conversation under that id, silently. Nothing errors, the client starts, the session
|
|
6
|
+
* looks real, and every tool the restart existed to load is missing. A restart that lands there is
|
|
7
|
+
* worse than no restart, because it is indistinguishable from success.
|
|
8
|
+
*
|
|
9
|
+
* The decision is pure and testable; opening a terminal is neither, and is left to the caller.
|
|
10
|
+
*/
|
|
11
|
+
/** What the caller should do, once the environment has been read. */
|
|
12
|
+
export const RelaunchOutcome = {
|
|
13
|
+
/** Claude Code named its own session and a transcript backs it. */
|
|
14
|
+
CLAUDE: 'claude',
|
|
15
|
+
/** No claude session, but a codex rollout in this directory identifies one. */
|
|
16
|
+
CODEX: 'codex',
|
|
17
|
+
/** A session id with nothing behind it — the case that looks like success. */
|
|
18
|
+
REFUSED: 'refused',
|
|
19
|
+
/** The client tells a child process nothing about which conversation it is. */
|
|
20
|
+
UNKNOWN_CLIENT: 'unknown-client',
|
|
21
|
+
};
|
|
22
|
+
/** The prompt a resumed session is handed, so the restart continues the work rather than idling. */
|
|
23
|
+
const RESUME_PROMPT = 'Reticle is installed. Continue where we left off.';
|
|
24
|
+
export function relaunchDecision(env) {
|
|
25
|
+
const { claudeSessionId, codexSessionId } = env;
|
|
26
|
+
// A claude id wins whenever there is one: that client named ITSELF, and a stale rollout file from
|
|
27
|
+
// an earlier codex run in the same directory must not redirect the restart.
|
|
28
|
+
if (claudeSessionId !== undefined && '' !== claudeSessionId) {
|
|
29
|
+
if (!env.transcriptExists(claudeSessionId)) {
|
|
30
|
+
return {
|
|
31
|
+
outcome: RelaunchOutcome.REFUSED,
|
|
32
|
+
message: `refusing to restart: no transcript exists yet for ${claudeSessionId}, and \`--resume\` ` +
|
|
33
|
+
'on an id with no transcript opens an EMPTY conversation that looks exactly like it ' +
|
|
34
|
+
'worked. Say something in this session first, then re-run with --relaunch.',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
outcome: RelaunchOutcome.CLAUDE,
|
|
39
|
+
message: 'restart this conversation with the tools loaded:\n ' +
|
|
40
|
+
`cd ${JSON.stringify(env.cwd)} && claude --resume ${claudeSessionId} ` +
|
|
41
|
+
`${JSON.stringify(RESUME_PROMPT)}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (codexSessionId !== undefined && '' !== codexSessionId) {
|
|
45
|
+
return {
|
|
46
|
+
outcome: RelaunchOutcome.CODEX,
|
|
47
|
+
message: 'restart this conversation with the tools loaded:\n ' +
|
|
48
|
+
`cd ${JSON.stringify(env.cwd)} && codex resume ${codexSessionId} ` +
|
|
49
|
+
`${JSON.stringify(RESUME_PROMPT)}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
outcome: RelaunchOutcome.UNKNOWN_CLIENT,
|
|
54
|
+
message: 'this client does not tell a child process which conversation it is, so nothing here can ' +
|
|
55
|
+
'resume it. Restart it once and the reticle_* tools will be there.',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is left to do by hand, from wherever setup stopped.
|
|
3
|
+
*
|
|
4
|
+
* Naming a cause is not the same as being recoverable. Setup can misbehave, and when it does the
|
|
5
|
+
* caller should not have to re-read the whole procedure and work out for itself which parts already
|
|
6
|
+
* worked — it should pick up at the step that did not. Ordered, and only ever the REMAINING steps.
|
|
7
|
+
*/
|
|
8
|
+
/** How far a run got. Everything here is knowable from the result object setup already builds. */
|
|
9
|
+
export interface Progress {
|
|
10
|
+
readonly initDone: boolean;
|
|
11
|
+
readonly devServerUp: boolean;
|
|
12
|
+
readonly sessionConnected: boolean;
|
|
13
|
+
readonly flowSaved: boolean;
|
|
14
|
+
/** Set when the caller supplied a url, so nobody is told to start a server they already run. */
|
|
15
|
+
readonly urlSuppliedByCaller: boolean;
|
|
16
|
+
readonly url?: string | undefined;
|
|
17
|
+
readonly devCommand?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
export declare function remainingSteps(p: Progress): string[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is left to do by hand, from wherever setup stopped.
|
|
3
|
+
*
|
|
4
|
+
* Naming a cause is not the same as being recoverable. Setup can misbehave, and when it does the
|
|
5
|
+
* caller should not have to re-read the whole procedure and work out for itself which parts already
|
|
6
|
+
* worked — it should pick up at the step that did not. Ordered, and only ever the REMAINING steps.
|
|
7
|
+
*/
|
|
8
|
+
const DOCS_INDEX = 'https://docs.reticle.sh/llms.txt';
|
|
9
|
+
export function remainingSteps(p) {
|
|
10
|
+
const steps = [];
|
|
11
|
+
if (!p.initDone) {
|
|
12
|
+
steps.push('Run `npx @reticlehq/server@latest init` here, and fix every ⚠ it reports.');
|
|
13
|
+
}
|
|
14
|
+
if (!p.devServerUp && !p.urlSuppliedByCaller) {
|
|
15
|
+
steps.push(`Start the dev server yourself: ${p.devCommand ?? 'the dev script in package.json'}, then open the app in a browser.`);
|
|
16
|
+
}
|
|
17
|
+
if (!p.sessionConnected) {
|
|
18
|
+
steps.push('Confirm a session appears with `reticle_sessions`. If the list is empty, read its ' +
|
|
19
|
+
'`next_action`: the usual cause is a dev server that was already running when the build ' +
|
|
20
|
+
'config was edited, so restart it and hard-reload' +
|
|
21
|
+
(undefined === p.url ? '.' : ` ${p.url}.`));
|
|
22
|
+
}
|
|
23
|
+
if (!p.flowSaved) {
|
|
24
|
+
steps.push('Drive one flow and produce a verdict: `reticle_snapshot({mode:"interactive"})` once, ' +
|
|
25
|
+
'`reticle_act_sequence` for the setup steps, then ONE `reticle_act_and_wait({ref,action,until})`. ' +
|
|
26
|
+
'Wrap it in `reticle_record` start/stop and `reticle_flow_save`, and check the grade it ' +
|
|
27
|
+
'returns is `asserted` — anything else only acts, so it passes even when the feature breaks.');
|
|
28
|
+
}
|
|
29
|
+
steps.push(`The whole procedure, if you need it: \`curl ${DOCS_INDEX}\`.`);
|
|
30
|
+
return steps;
|
|
31
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The half of setup that happens after the files are written.
|
|
3
|
+
*
|
|
4
|
+
* `init` wires a project; this gets the app running with the SDK inside it, proves a session
|
|
5
|
+
* connected, and drives one flow to a verdict. Measured against an agent doing the same steps by
|
|
6
|
+
* hand from SKILL.md across five real applications: 176 model turns, $9.92, forty minutes, and a
|
|
7
|
+
* verdict in two runs out of five — three ended by asking a human to restart their client, having
|
|
8
|
+
* shown them nothing.
|
|
9
|
+
*
|
|
10
|
+
* Every effect is injected. That is not ceremony: the sequence has five phases, each with its own
|
|
11
|
+
* way of going wrong, and the alternative to injecting them is a test that boots a real dev server
|
|
12
|
+
* and a real browser to find out what happens when neither works.
|
|
13
|
+
*/
|
|
14
|
+
import { type CandidateSession } from './session-pick.js';
|
|
15
|
+
import { type PageProbe } from './page-probe.js';
|
|
16
|
+
import { AppShape } from './desktop-shape.js';
|
|
17
|
+
/** Where a run got to, and why it stopped if it did. */
|
|
18
|
+
export declare const SetupPhase: {
|
|
19
|
+
readonly DEV_SERVER: "dev-server";
|
|
20
|
+
readonly CONNECT: "connect";
|
|
21
|
+
readonly DRIVE: "drive";
|
|
22
|
+
readonly DONE: "done";
|
|
23
|
+
};
|
|
24
|
+
export type SetupPhase = (typeof SetupPhase)[keyof typeof SetupPhase];
|
|
25
|
+
export interface SetupOutcome {
|
|
26
|
+
/** True only when a flow was driven AND saved. Writing files is not an install. */
|
|
27
|
+
readonly ok: boolean;
|
|
28
|
+
readonly reachedPhase: SetupPhase;
|
|
29
|
+
readonly url?: string | undefined;
|
|
30
|
+
readonly sessionId?: string | undefined;
|
|
31
|
+
/** The drive's own report, kept verbatim and never trusted as a pass on its own. */
|
|
32
|
+
readonly verdict?: string | undefined;
|
|
33
|
+
readonly flowSaved: boolean;
|
|
34
|
+
/** What the caller should do next, when this did not finish. */
|
|
35
|
+
readonly fallback: string[];
|
|
36
|
+
readonly notes: string[];
|
|
37
|
+
}
|
|
38
|
+
/** Everything the sequence needs from the world, so none of it is reached for directly. */
|
|
39
|
+
export interface SetupEffects {
|
|
40
|
+
/** Start the dev server. Resolves once started; the caller owns stopping it. */
|
|
41
|
+
readonly startDevServer: (command: string, cwd: string) => Promise<void>;
|
|
42
|
+
/** Everything the dev server has printed so far. */
|
|
43
|
+
readonly devServerOutput: () => string;
|
|
44
|
+
readonly devServerExited: () => boolean;
|
|
45
|
+
/** Milliseconds since it last printed anything. */
|
|
46
|
+
readonly devServerQuietForMs: () => number;
|
|
47
|
+
/** Ports our own process group is listening on. */
|
|
48
|
+
readonly observedPorts: () => number[];
|
|
49
|
+
/** Fetch the url and report what came back. */
|
|
50
|
+
readonly probePage: (url: string) => Promise<PageProbe>;
|
|
51
|
+
readonly openBrowser: (url: string) => Promise<void>;
|
|
52
|
+
readonly listSessions: () => Promise<CandidateSession[]>;
|
|
53
|
+
/** Drive one flow. Returns the agent's report, or null when nobody could. */
|
|
54
|
+
readonly drive: (url: string, session: CandidateSession) => Promise<string | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Whether this machine has an agent CLI at all.
|
|
57
|
+
*
|
|
58
|
+
* Separate from `drive` returning null, because those are different facts and only one of them is
|
|
59
|
+
* a failure. A drive that RAN and proved nothing is a result worth a non-zero exit. A drive that
|
|
60
|
+
* could not run is the absence of a tool on the machine, and reporting the install as failed for
|
|
61
|
+
* it told CI runners — where no agent CLI is ever installed — that a perfectly good install had
|
|
62
|
+
* not worked.
|
|
63
|
+
*/
|
|
64
|
+
readonly driverAvailable: () => boolean;
|
|
65
|
+
readonly flowsSaved: () => boolean;
|
|
66
|
+
readonly now: () => number;
|
|
67
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
68
|
+
readonly note: (line: string) => void;
|
|
69
|
+
}
|
|
70
|
+
export interface SetupInput {
|
|
71
|
+
readonly appDir: string;
|
|
72
|
+
readonly devCommand?: string | undefined;
|
|
73
|
+
/** The app is already served here, so nothing is started. */
|
|
74
|
+
readonly suppliedUrl?: string | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* The caller's own budget for the connect wait, when they named one.
|
|
77
|
+
*
|
|
78
|
+
* Absent, the shape's policy decides — a desktop shell genuinely needs longer than a browser tab.
|
|
79
|
+
* Present, it WINS, including when it is shorter: the deadline used to be
|
|
80
|
+
* `max(phaseTimeoutMs, policy)`, so `--timeout 3` could only ever lengthen the wait. A timeout the
|
|
81
|
+
* tool ignores is a lie, and it left every hostile-environment check killed by its own harness
|
|
82
|
+
* before setup could say what was wrong.
|
|
83
|
+
*/
|
|
84
|
+
readonly connectBudgetMs?: number | undefined;
|
|
85
|
+
readonly openBrowser: boolean;
|
|
86
|
+
readonly drive: boolean;
|
|
87
|
+
/** Web, Electron or Tauri. Desktop changes three things; see desktop-shape.ts. */
|
|
88
|
+
readonly shape: AppShape;
|
|
89
|
+
readonly phaseTimeoutMs: number;
|
|
90
|
+
readonly pollMs: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Run the phases. Stops at the first one that cannot continue, and always says what is left.
|
|
94
|
+
*
|
|
95
|
+
* A phase that fails is not an error to throw: the caller is usually an agent, and a thrown
|
|
96
|
+
* exception loses the four things it needs — how far this got, the url, the session, and what to do
|
|
97
|
+
* about it.
|
|
98
|
+
*/
|
|
99
|
+
export declare function runSetupPhases(input: SetupInput, fx: SetupEffects): Promise<SetupOutcome>;
|