@reticlehq/server 3.1.0 → 3.3.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 +30 -28
- package/dist/command/cli/adhoc-suite.d.ts +19 -0
- package/dist/command/cli/adhoc-suite.js +88 -0
- package/dist/command/cli/adhoc-verdict.d.ts +23 -2
- package/dist/command/cli/adhoc-verdict.js +18 -10
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-doctor.js +37 -5
- package/dist/command/cli/cli-flow-commands.d.ts +2 -1
- package/dist/command/cli/cli-kill.js +1 -1
- package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
- package/dist/command/cli/cli-parse-grammar.js +7 -0
- package/dist/command/cli/cli-parse-verify.d.ts +2 -5
- package/dist/command/cli/cli-parse-verify.js +86 -13
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +38 -7
- package/dist/command/cli/cli-verify.d.ts +59 -0
- package/dist/command/cli/cli-verify.js +131 -19
- package/dist/command/cli/cloud-cli.js +7 -6
- package/dist/command/cli/cloud-kit.d.ts +0 -2
- package/dist/command/cli/cloud-kit.js +3 -7
- package/dist/command/cli/config/config-discovery.d.ts +12 -0
- package/dist/command/cli/config/config-discovery.js +19 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
- package/dist/command/cli/doctor/doctor-rows.js +12 -0
- package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +1 -1
- package/dist/command/cli/launch/cli-launch.d.ts +3 -23
- package/dist/command/cli/launch/cli-launch.js +29 -70
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
- package/dist/command/cli/mcp-command.js +5 -3
- package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
- package/dist/command/cli/ports/resolve/cli-port.js +50 -2
- package/dist/command/cli/report-command.d.ts +16 -0
- package/dist/command/cli/report-command.js +97 -0
- package/dist/command/cli/status/status-lines.d.ts +28 -0
- package/dist/command/cli/status/status-lines.js +117 -0
- package/dist/command/cli.d.ts +1 -1
- package/dist/command/cli.js +29 -177
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
- package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
- package/dist/command/daemon/binding/loopback-reach.js +63 -0
- package/dist/command/daemon/binding/port-presence.d.ts +1 -1
- package/dist/command/daemon/daemon-resilience.d.ts +13 -1
- package/dist/command/daemon/daemon-resilience.js +12 -1
- package/dist/command/daemon/daemon.d.ts +2 -11
- package/dist/command/daemon/daemon.js +8 -11
- package/dist/command/daemon/idle-shutdown.d.ts +2 -1
- package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
- package/dist/command/daemon/lifetime/heartbeat.js +10 -0
- package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
- package/dist/command/daemon/lifetime/log-cap.js +115 -0
- package/dist/command/setup/agent-notice-memory.d.ts +16 -0
- package/dist/command/setup/agent-notice-memory.js +65 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
- package/dist/command/setup/bringup/dev-server-wait.js +16 -0
- package/dist/command/setup/bringup/ensure-daemon.js +1 -1
- package/dist/command/setup/init/init-host.js +8 -0
- package/dist/command/setup/init/init-runtime.js +13 -1
- package/dist/command/setup/node-effects.js +1 -1
- package/dist/command/setup/probe/page-probe.js +9 -2
- package/dist/command/setup/run-setup.d.ts +25 -7
- package/dist/command/setup/run-setup.js +51 -9
- package/dist/command/setup/setup-command.js +31 -3
- package/dist/command/setup/terminal/confirm.js +2 -1
- package/dist/command/update/updater.d.ts +27 -0
- package/dist/command/update/updater.js +35 -2
- package/dist/features/crawl/crawl-tools.js +3 -1
- package/dist/features/crawl/crawl.d.ts +2 -0
- package/dist/features/crawl/crawl.js +3 -1
- package/dist/features/harness/drive-report.d.ts +73 -0
- package/dist/features/harness/drive-report.js +196 -0
- package/dist/features/harness/driver.js +29 -8
- package/dist/features/harness/drivers.d.ts +19 -0
- package/dist/features/harness/drivers.js +19 -0
- package/dist/features/harness/fill-values.d.ts +67 -0
- package/dist/features/harness/fill-values.js +153 -0
- package/dist/features/harness/jev-driver.d.ts +188 -0
- package/dist/features/harness/jev-driver.js +661 -0
- package/dist/features/harness/openai-driver.d.ts +46 -0
- package/dist/features/harness/openai-driver.js +197 -0
- package/dist/features/harness/platform-config.d.ts +62 -0
- package/dist/features/harness/platform-config.js +97 -0
- package/dist/features/visual/visual-tools.js +26 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +80 -130
- package/dist/judgement/capsule/capsule-store.d.ts +31 -2
- package/dist/judgement/capsule/capsule-store.js +58 -6
- package/dist/judgement/capsule/capsule.js +5 -0
- package/dist/judgement/capsule/causal-summary.d.ts +13 -1
- package/dist/judgement/capsule/causal-summary.js +7 -0
- package/dist/judgement/domain/domain-model.js +38 -9
- package/dist/judgement/domain/domain-tools.js +4 -2
- package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
- package/dist/judgement/phenomena/phenomena.d.ts +2 -1
- package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
- package/dist/judgement/runs/artifact/gap-report.js +46 -0
- package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
- package/dist/judgement/runs/artifact/gap-summary.js +85 -0
- package/dist/judgement/runs/artifact/render-report.js +4 -1
- package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
- package/dist/judgement/runs/artifact/to-artifact.js +1 -1
- package/dist/judgement/runs/context-tools.js +21 -3
- package/dist/judgement/runs/drive-run.js +7 -2
- package/dist/judgement/runs/verification-sync.d.ts +2 -2
- package/dist/judgement/runs/verification-sync.js +9 -2
- package/dist/language/flows/affected-tools.js +6 -3
- package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
- package/dist/language/flows/annotate-notes/annotate.js +66 -18
- package/dist/language/flows/flow-anchor.d.ts +12 -0
- package/dist/language/flows/flow-anchor.js +20 -0
- package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
- package/dist/language/flows/flow-expect-grammar.js +92 -11
- package/dist/language/flows/flow-intent.js +1 -1
- package/dist/language/flows/flow-learning.d.ts +3 -0
- package/dist/language/flows/flow-learning.js +39 -8
- package/dist/language/flows/flow-replay-run.d.ts +55 -10
- package/dist/language/flows/flow-replay-run.js +217 -58
- package/dist/language/flows/flow-replay-types.d.ts +9 -0
- package/dist/language/flows/flow-replay.d.ts +29 -11
- package/dist/language/flows/flow-replay.js +221 -203
- package/dist/language/flows/flow-result.d.ts +18 -1
- package/dist/language/flows/flow-result.js +18 -2
- package/dist/language/flows/flow-step-runners.d.ts +26 -10
- package/dist/language/flows/flow-step-runners.js +20 -4
- package/dist/language/flows/flow-store-for-session.d.ts +2 -1
- package/dist/language/flows/flow-success.d.ts +21 -6
- package/dist/language/flows/flow-success.js +50 -138
- package/dist/language/flows/flow-tools.js +8 -4
- package/dist/language/flows/flows.d.ts +15 -10
- package/dist/language/flows/flows.js +17 -4
- package/dist/language/flows/heal.d.ts +9 -1
- package/dist/language/flows/heal.js +24 -2
- package/dist/language/flows/learned-sources.d.ts +21 -0
- package/dist/language/flows/learned-sources.js +84 -0
- package/dist/language/flows/narrow-write.d.ts +4 -4
- package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
- package/dist/language/flows/recording/recording-backtrack.js +1 -1
- package/dist/language/flows/recording/replay-halt.d.ts +2 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
- package/dist/language/flows/replay.js +13 -9
- package/dist/language/flows/stores/annotation-store.d.ts +5 -5
- package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
- package/dist/language/flows/stores/flake-store.js +2 -3
- package/dist/language/flows/suite/coverage.d.ts +1 -1
- package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
- package/dist/language/flows/suite-selection.d.ts +15 -1
- package/dist/language/flows/suite-selection.js +1 -1
- package/dist/language/flows/verify-change-tools.js +21 -4
- package/dist/memory/cloud/account-state.d.ts +1 -1
- package/dist/memory/cloud/account-state.js +5 -4
- package/dist/memory/cloud/cloud-config.d.ts +9 -0
- package/dist/memory/cloud/cloud-config.js +9 -0
- package/dist/memory/cloud/cloud-sync.d.ts +7 -4
- package/dist/memory/cloud/cloud-sync.js +11 -6
- package/dist/memory/cloud/harness-config.d.ts +12 -0
- package/dist/memory/cloud/harness-config.js +58 -0
- package/dist/memory/cloud/harness-offer.d.ts +44 -0
- package/dist/memory/cloud/harness-offer.js +146 -0
- package/dist/memory/cloud/harness-switch.d.ts +11 -0
- package/dist/memory/cloud/harness-switch.js +53 -0
- package/dist/memory/cloud/sync-cycle.d.ts +12 -0
- package/dist/memory/cloud/sync-cycle.js +29 -4
- package/dist/memory/cloud/sync-daemon.d.ts +35 -2
- package/dist/memory/cloud/sync-daemon.js +45 -0
- package/dist/memory/cloud/sync-disk.js +11 -6
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +2 -1
- package/dist/memory/impact/impact-store.d.ts +5 -0
- package/dist/memory/impact/impact-store.js +25 -0
- package/dist/memory/intent/inline-intent.js +8 -3
- package/dist/memory/intent/intent-shard-store.d.ts +33 -47
- package/dist/memory/intent/intent-shard-store.js +253 -141
- package/dist/memory/intent/intent-shard.d.ts +22 -23
- package/dist/memory/intent/intent-shard.js +1 -1
- package/dist/memory/intent/intent-store.d.ts +13 -8
- package/dist/memory/intent/intent-store.js +46 -90
- package/dist/memory/intent/intent-tools.js +34 -23
- package/dist/memory/journal/ambient-store.js +2 -3
- package/dist/memory/journal/attach-journal.d.ts +11 -0
- package/dist/memory/journal/attach-journal.js +1 -1
- package/dist/memory/journal/deviation-report.js +4 -1
- package/dist/memory/journal/deviation-service.js +10 -2
- package/dist/memory/journal/drive-flow.d.ts +2 -2
- package/dist/memory/journal/drive-flow.js +21 -12
- package/dist/memory/journal/envelope-store.js +2 -3
- package/dist/memory/journal/journal-query.d.ts +35 -1
- package/dist/memory/journal/journal-query.js +46 -0
- package/dist/memory/journal/journal-recorder.d.ts +71 -2
- package/dist/memory/journal/journal-recorder.js +45 -7
- package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
- package/dist/memory/journal/on-disk/retain-policy.js +44 -0
- package/dist/memory/journal/on-disk/retention.d.ts +91 -2
- package/dist/memory/journal/on-disk/retention.js +181 -11
- package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
- package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -2
- package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
- package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
- package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
- package/dist/memory/journal/route-template.d.ts +22 -0
- package/dist/memory/journal/route-template.js +44 -0
- package/dist/memory/journal/self-observation.d.ts +3 -0
- package/dist/memory/journal/self-observation.js +67 -0
- package/dist/memory/journal/session-end.d.ts +17 -4
- package/dist/memory/journal/session-end.js +85 -7
- package/dist/memory/journal/session-journal.d.ts +122 -5
- package/dist/memory/journal/session-journal.js +489 -32
- package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
- package/dist/memory/project/artifact-root-resolver.js +89 -0
- package/dist/memory/project/artifact-root.d.ts +39 -2
- package/dist/memory/project/artifact-root.js +58 -1
- package/dist/memory/project/baselines.d.ts +1 -1
- package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
- package/dist/memory/project/dir/fill-value-store.js +88 -0
- package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
- package/dist/memory/project/dir/reticle-dir.js +11 -0
- package/dist/memory/project/fs/fs-port.d.ts +22 -1
- package/dist/memory/project/fs/fs-port.js +72 -5
- package/dist/memory/project/fs/write-atomic.d.ts +26 -0
- package/dist/memory/project/fs/write-atomic.js +48 -0
- package/dist/memory/project/learned-routes.d.ts +16 -2
- package/dist/memory/project/learned-routes.js +26 -10
- package/dist/memory/project/project-for-root.d.ts +30 -0
- package/dist/memory/project/project-for-root.js +43 -0
- package/dist/memory/project/project-tools.js +10 -5
- package/dist/memory/project/session-root.d.ts +35 -6
- package/dist/memory/project/session-root.js +70 -8
- package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
- package/dist/memory/recall/prior/connection-memory.js +8 -0
- package/dist/memory/recall/registered-projects.d.ts +2 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
- package/dist/portal/bridge/auth-failure-reason.js +20 -3
- package/dist/portal/bridge/bridge.d.ts +6 -1
- package/dist/portal/bridge/bridge.js +36 -2
- package/dist/portal/input/real-input.js +2 -1
- package/dist/portal/pool/browser-pool.d.ts +10 -0
- package/dist/portal/pool/browser-pool.js +10 -0
- package/dist/portal/pool/playwright-launcher.js +1 -0
- package/dist/portal/pool/pool-contract.d.ts +2 -0
- package/dist/portal/pool/storage-seed.d.ts +2 -1
- package/dist/portal/pool/zero-install.d.ts +27 -0
- package/dist/portal/pool/zero-install.js +54 -0
- package/dist/portal/realm/web-realm.js +6 -0
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
- package/dist/portal/session/dev-server/served-document.d.ts +21 -0
- package/dist/portal/session/dev-server/served-document.js +57 -0
- package/dist/portal/session/facts/background-traffic.d.ts +9 -0
- package/dist/portal/session/facts/background-traffic.js +25 -0
- package/dist/portal/session/facts/session-replaced.d.ts +19 -0
- package/dist/portal/session/facts/session-replaced.js +22 -0
- package/dist/portal/session/live-control-tools.js +48 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -60
- package/dist/portal/session/no-session-next-action.js +3 -2
- package/dist/portal/session/no-session-watch.js +14 -10
- package/dist/portal/session/presence/attachment-history.d.ts +13 -2
- package/dist/portal/session/rebind.d.ts +18 -0
- package/dist/portal/session/rebind.js +38 -0
- package/dist/portal/session/session-health.d.ts +61 -0
- package/dist/portal/session/session-health.js +40 -1
- package/dist/portal/session/session-info.d.ts +6 -0
- package/dist/portal/session/session-manager.d.ts +13 -0
- package/dist/portal/session/session-manager.js +42 -3
- package/dist/portal/session/session-successor.d.ts +11 -0
- package/dist/portal/session/session-successor.js +14 -1
- package/dist/portal/session/session-verdict-facts.d.ts +28 -0
- package/dist/portal/session/session-verdict-facts.js +15 -0
- package/dist/portal/session/session.d.ts +26 -10
- package/dist/portal/session/session.js +48 -49
- package/dist/status-payload.d.ts +12 -1
- package/dist/status-payload.js +4 -1
- package/dist/surface/http-server.js +3 -5
- package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
- package/dist/surface/mcp/faults/mcp-outage.js +20 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
- package/dist/surface/mcp/mcp-proxy.js +23 -12
- package/dist/surface/mcp/mcp.d.ts +10 -1
- package/dist/surface/mcp/mcp.js +59 -29
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
- package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
- package/dist/surface/mcp/proxy/proxy-log.js +7 -22
- package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
- package/dist/surface/mcp/server-instructions.js +12 -1
- package/dist/surface/tools/act/act-preflight.js +6 -1
- package/dist/surface/tools/act/already-true.d.ts +40 -0
- package/dist/surface/tools/act/already-true.js +36 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
- package/dist/surface/tools/act/sequence-grade.js +13 -0
- package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
- package/dist/surface/tools/act/settle-in-flight.js +29 -6
- package/dist/surface/tools/act-capsule.js +49 -8
- package/dist/surface/tools/act-tools.js +25 -24
- package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
- package/dist/surface/tools/args/numeric-bounds.js +30 -3
- package/dist/surface/tools/assert/assert-grade.js +14 -2
- package/dist/surface/tools/assert/assert-verdict.js +74 -21
- package/dist/surface/tools/browser-tools.js +4 -3
- package/dist/surface/tools/coverage-tools.js +5 -2
- package/dist/surface/tools/dynamic-tools.js +43 -46
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +15 -0
- package/dist/surface/tools/explore-tools.js +80 -7
- package/dist/surface/tools/feedback-tools.d.ts +2 -1
- package/dist/surface/tools/feedback-tools.js +50 -3
- package/dist/surface/tools/first-sentence.d.ts +24 -0
- package/dist/surface/tools/first-sentence.js +0 -0
- package/dist/surface/tools/harness-explore.d.ts +125 -1
- package/dist/surface/tools/harness-explore.js +401 -15
- package/dist/surface/tools/harness-plan.d.ts +76 -0
- package/dist/surface/tools/harness-plan.js +156 -0
- package/dist/surface/tools/harness-toolset.js +12 -1
- package/dist/surface/tools/invoke-tool.js +82 -9
- package/dist/surface/tools/lease-hint.d.ts +8 -0
- package/dist/surface/tools/lease-hint.js +10 -0
- package/dist/surface/tools/lease-tools.d.ts +16 -28
- package/dist/surface/tools/lease-tools.js +53 -5
- package/dist/surface/tools/lineage-tools.js +1 -1
- package/dist/surface/tools/live-call-text.d.ts +6 -0
- package/dist/surface/tools/live-call-text.js +36 -2
- package/dist/surface/tools/observe-tools.js +69 -15
- package/dist/surface/tools/read/query-windowed.d.ts +1 -1
- package/dist/surface/tools/read/query-windowed.js +1 -1
- package/dist/surface/tools/read-tools.js +1 -2
- package/dist/surface/tools/real-input-attempt.d.ts +0 -5
- package/dist/surface/tools/real-input-attempt.js +1 -1
- package/dist/surface/tools/tool-kit.d.ts +23 -3
- package/dist/surface/tools/tool-kit.js +6 -1
- package/dist/surface/tools/tool-surface.js +17 -5
- package/dist/surface/tools/tools-helpers.d.ts +8 -7
- package/dist/surface/tools/tools-helpers.js +11 -13
- package/dist/surface/tools/tools.js +70 -30
- package/dist/telemetry/bug-found.js +32 -2
- package/dist/telemetry/feedback-context.js +3 -3
- package/dist/telemetry/feedback-local.d.ts +1 -0
- package/dist/telemetry/feedback-local.js +1 -3
- package/dist/telemetry/feedback.d.ts +1 -1
- package/dist/telemetry/harness-drive.d.ts +6 -0
- package/dist/telemetry/harness-drive.js +39 -0
- package/dist/telemetry/hud-metrics.d.ts +32 -0
- package/dist/telemetry/hud-metrics.js +95 -0
- package/dist/telemetry/mcp-connection.d.ts +14 -1
- package/dist/telemetry/mcp-connection.js +17 -2
- package/dist/telemetry/onboarding-firsts.d.ts +30 -1
- package/dist/telemetry/onboarding-firsts.js +54 -12
- package/dist/telemetry/session-metrics.d.ts +5 -0
- package/dist/telemetry/session-metrics.js +15 -1
- package/dist/telemetry/verification-of.js +31 -3
- package/dist/wire-journal.d.ts +31 -0
- package/dist/wire-journal.js +100 -0
- package/docs/README.md +1 -0
- package/docs/agent-cheatsheet.md +7 -3
- package/docs/autodrive.md +42 -2
- package/docs/benchmarks.md +1 -1
- package/docs/capabilities.mdx +1 -1
- package/docs/cli/cloud.mdx +11 -9
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/init.mdx +1 -0
- package/docs/cli/report.mdx +44 -0
- package/docs/cli/status.mdx +35 -20
- package/docs/cli.mdx +8 -18
- package/docs/desktop.mdx +1 -1
- package/docs/faq.mdx +1 -1
- package/docs/flows.md +11 -0
- package/docs/frameworks.mdx +113 -1
- package/docs/install-agentic.mdx +36 -0
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +144 -4
- package/docs/system-map.md +6 -4
- package/docs/telemetry.md +2 -1
- package/docs/token-efficiency.md +2 -2
- package/docs/tools/overview.mdx +1 -1
- package/docs/tools/query.mdx +10 -10
- package/docs/troubleshooting.mdx +8 -2
- package/docs/usage.md +2 -2
- package/docs/what-is-recorded.md +62 -0
- package/package.json +5 -5
- package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
- package/dist/judgement/outcome/predicate-to-expect.js +0 -148
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 3..reticle.json "port" field in the cwd ← per-project isolation
|
|
8
8
|
* 4. RETICLE_DEFAULT_PORT (4400)
|
|
9
9
|
*/
|
|
10
|
+
import { type ProjectId } from '@reticlehq/core';
|
|
10
11
|
/** The project config `reticle init` writes. */
|
|
11
12
|
export declare const RETICLE_CONFIG_BASENAME = ".reticle.json";
|
|
12
13
|
/**
|
|
@@ -39,7 +40,20 @@ export declare function readProjectPort(cwd: string): number | undefined;
|
|
|
39
40
|
* uses it as the default resolve scope so auto-selection stays within the active app. Returns
|
|
40
41
|
* undefined if the file is absent/unreadable or has no non-empty string projectId.
|
|
41
42
|
*/
|
|
42
|
-
export declare function readProjectId(cwd: string):
|
|
43
|
+
export declare function readProjectId(cwd: string): ProjectId | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* Every project id this directory can speak for, for "has THIS project ever connected".
|
|
46
|
+
*
|
|
47
|
+
* `readProjectId` knows only what `init` wrote. The build plugins derive the same id from the package
|
|
48
|
+
* name and the app root WITHOUT writing `.reticle.json`, so a monorepo root (the app lives in
|
|
49
|
+
* `apps/web`) and an app wired by the plugin alone both read `undefined`, and the handshake told a
|
|
50
|
+
* working project nothing had ever connected. Deriving the plugin's own id cannot reopen the opposite
|
|
51
|
+
* lie: an id only counts once an app with exactly that id has dialled in.
|
|
52
|
+
*
|
|
53
|
+
* Workspace discovery is init's `findWorkspaceApps`, reused so the two cannot disagree about which
|
|
54
|
+
* directories are apps. Best-effort: a directory it cannot read contributes nothing.
|
|
55
|
+
*/
|
|
56
|
+
export declare function projectIdsAt(cwd: string): ProjectId[];
|
|
43
57
|
/**
|
|
44
58
|
* The framework `reticle init` stamped into `.reticle.json`, when there is one.
|
|
45
59
|
*
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* 4. RETICLE_DEFAULT_PORT (4400)
|
|
9
9
|
*/
|
|
10
10
|
import { readFileSync } from 'node:fs';
|
|
11
|
-
import { dirname, resolve } from 'node:path';
|
|
11
|
+
import { dirname, join, resolve } from 'node:path';
|
|
12
|
+
import { SILENT_HOST, buildNodeIo, deriveProjectId, findWorkspaceApps, packageName, } from '@reticlehq/init';
|
|
13
|
+
import { asProjectId } from '@reticlehq/core';
|
|
12
14
|
/** The project config `reticle init` writes. */
|
|
13
15
|
export const RETICLE_CONFIG_BASENAME = '.reticle.json';
|
|
14
16
|
/**
|
|
@@ -76,10 +78,56 @@ export function readProjectPort(cwd) {
|
|
|
76
78
|
*/
|
|
77
79
|
export function readProjectId(cwd) {
|
|
78
80
|
const id = findProjectConfig(cwd)?.['projectId'];
|
|
81
|
+
// MINTED HERE. The other provenance boundary: the `projectId` key of a project's own
|
|
82
|
+
// `.reticle.json`. Like HELLO's field, it is a project's id because of WHERE it was read, not
|
|
83
|
+
// because of what it looks like.
|
|
79
84
|
if ('string' === typeof id && id.length > 0)
|
|
80
|
-
return id;
|
|
85
|
+
return asProjectId(id);
|
|
81
86
|
return undefined;
|
|
82
87
|
}
|
|
88
|
+
const PACKAGE_JSON = 'package.json';
|
|
89
|
+
/** The package name in `dir/package.json`, or undefined when there is no readable one. */
|
|
90
|
+
function packageNameAt(dir) {
|
|
91
|
+
try {
|
|
92
|
+
return packageName(JSON.parse(readFileSync(join(dir, PACKAGE_JSON), 'utf8')));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** The id an app at `dir` carries: the one `init` recorded, else the one the build plugin derives. */
|
|
99
|
+
function projectIdFor(dir) {
|
|
100
|
+
return readProjectId(dir) ?? asProjectId(deriveProjectId(packageNameAt(dir), dir));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Every project id this directory can speak for, for "has THIS project ever connected".
|
|
104
|
+
*
|
|
105
|
+
* `readProjectId` knows only what `init` wrote. The build plugins derive the same id from the package
|
|
106
|
+
* name and the app root WITHOUT writing `.reticle.json`, so a monorepo root (the app lives in
|
|
107
|
+
* `apps/web`) and an app wired by the plugin alone both read `undefined`, and the handshake told a
|
|
108
|
+
* working project nothing had ever connected. Deriving the plugin's own id cannot reopen the opposite
|
|
109
|
+
* lie: an id only counts once an app with exactly that id has dialled in.
|
|
110
|
+
*
|
|
111
|
+
* Workspace discovery is init's `findWorkspaceApps`, reused so the two cannot disagree about which
|
|
112
|
+
* directories are apps. Best-effort: a directory it cannot read contributes nothing.
|
|
113
|
+
*/
|
|
114
|
+
export function projectIdsAt(cwd) {
|
|
115
|
+
const recorded = readProjectId(cwd);
|
|
116
|
+
if (recorded !== undefined)
|
|
117
|
+
return [recorded];
|
|
118
|
+
const ids = [];
|
|
119
|
+
if (packageNameAt(cwd) !== undefined)
|
|
120
|
+
ids.push(projectIdFor(cwd));
|
|
121
|
+
try {
|
|
122
|
+
for (const app of findWorkspaceApps(buildNodeIo(cwd, SILENT_HOST))) {
|
|
123
|
+
ids.push(projectIdFor(join(cwd, app)));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Discovery touches the filesystem; an unreadable tree is simply no further evidence.
|
|
128
|
+
}
|
|
129
|
+
return [...new Set(ids)];
|
|
130
|
+
}
|
|
83
131
|
/**
|
|
84
132
|
* The framework `reticle init` stamped into `.reticle.json`, when there is one.
|
|
85
133
|
*
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ReportInput {
|
|
2
|
+
cwd: string;
|
|
3
|
+
now: number;
|
|
4
|
+
/** Did the working tree change? The hook stays silent on a turn that edited nothing. */
|
|
5
|
+
treeChanged: boolean;
|
|
6
|
+
session?: string;
|
|
7
|
+
hook?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function reportFor(input: ReportInput): Promise<{
|
|
10
|
+
lines: string[];
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* The command. As a hook it prints Claude Code's `systemMessage` shape and always exits 0: it is
|
|
14
|
+
* print-only by design, so the human sees the gap and the agent is never blocked by it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function handleReport(session: string | undefined, hook: boolean): Promise<void>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle report [--session <id>] [--hook]` — what this project's latest session claimed, and what held.
|
|
3
|
+
*
|
|
4
|
+
* Read from disk, with no daemon: the journal is on disk, and a Stop hook has to answer in the second
|
|
5
|
+
* after the agent stops, when there may be nothing running to ask. The fold is `gapSummary`, the same
|
|
6
|
+
* one `reticle_context` serves, so the terminal and the agent cannot be told two different stories.
|
|
7
|
+
*/
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { DiscoveryInvite, ReticleDir } from '@reticlehq/core';
|
|
12
|
+
import { gapSummary } from '../../judgement/runs/artifact/gap-summary.js';
|
|
13
|
+
import { SessionJournal } from '../../memory/journal/session-journal.js';
|
|
14
|
+
import { createNodeFileSystem } from '../../memory/project/fs/fs-port.js';
|
|
15
|
+
import { gapHookLine, gapReportLines } from '../../judgement/runs/artifact/gap-report.js';
|
|
16
|
+
/**
|
|
17
|
+
* A session last written longer ago than this is not the turn a hook is asking about.
|
|
18
|
+
*
|
|
19
|
+
* ponytail: a fixed window, not a turn boundary — the Stop hook is not told when the turn began. A
|
|
20
|
+
* drive older than an hour reads as "no claim this session", which errs toward saying so.
|
|
21
|
+
*/
|
|
22
|
+
const HOOK_WINDOW_MS = 60 * 60 * 1000;
|
|
23
|
+
/** The session whose action ledger was written most recently, if any. */
|
|
24
|
+
function newestSession(root) {
|
|
25
|
+
const sessionsDir = join(root, ReticleDir.SESSIONS_SUBDIR);
|
|
26
|
+
if (!existsSync(sessionsDir))
|
|
27
|
+
return undefined;
|
|
28
|
+
let newest;
|
|
29
|
+
for (const id of readdirSync(sessionsDir)) {
|
|
30
|
+
const ledger = join(sessionsDir, id, ReticleDir.JOURNAL_ACTIONS_FILE);
|
|
31
|
+
if (!existsSync(ledger))
|
|
32
|
+
continue;
|
|
33
|
+
const writtenAt = statSync(ledger).mtimeMs;
|
|
34
|
+
if (newest === undefined || writtenAt > newest.writtenAt)
|
|
35
|
+
newest = { id, writtenAt };
|
|
36
|
+
}
|
|
37
|
+
return newest;
|
|
38
|
+
}
|
|
39
|
+
export async function reportFor(input) {
|
|
40
|
+
if (true === input.hook && !input.treeChanged)
|
|
41
|
+
return { lines: [] };
|
|
42
|
+
const root = join(input.cwd, ReticleDir.ROOT);
|
|
43
|
+
const found = input.session === undefined ? newestSession(root) : { id: input.session, writtenAt: input.now };
|
|
44
|
+
const current = found !== undefined && (true !== input.hook || input.now - found.writtenAt <= HOOK_WINDOW_MS)
|
|
45
|
+
? found
|
|
46
|
+
: undefined;
|
|
47
|
+
const actions = current === undefined
|
|
48
|
+
? []
|
|
49
|
+
: await new SessionJournal(createNodeFileSystem(), root, current.id).readActions();
|
|
50
|
+
const gap = gapSummary(actions);
|
|
51
|
+
if (true === input.hook) {
|
|
52
|
+
const line = gapHookLine(gap);
|
|
53
|
+
// A finish with nothing proved is the moment Reticle let somebody down: say who to tell.
|
|
54
|
+
return { lines: line === undefined ? [] : [line, DiscoveryInvite.HUMAN] };
|
|
55
|
+
}
|
|
56
|
+
const header = current === undefined ? [] : [`session ${current.id}`];
|
|
57
|
+
return { lines: [...header, ...gapReportLines(gap), DiscoveryInvite.HUMAN] };
|
|
58
|
+
}
|
|
59
|
+
/** Uncommitted changes in `cwd`. Outside a git repository nothing can say, so it answers yes. */
|
|
60
|
+
function treeChanged(cwd) {
|
|
61
|
+
try {
|
|
62
|
+
return '' !== execFileSync('git', ['status', '--porcelain'], { cwd, encoding: 'utf8' }).trim();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The command. As a hook it prints Claude Code's `systemMessage` shape and always exits 0: it is
|
|
70
|
+
* print-only by design, so the human sees the gap and the agent is never blocked by it.
|
|
71
|
+
*/
|
|
72
|
+
export async function handleReport(session, hook) {
|
|
73
|
+
const cwd = process.cwd();
|
|
74
|
+
let lines;
|
|
75
|
+
try {
|
|
76
|
+
({ lines } = await reportFor({
|
|
77
|
+
cwd,
|
|
78
|
+
now: Date.now(),
|
|
79
|
+
treeChanged: hook ? treeChanged(cwd) : true,
|
|
80
|
+
...(session === undefined ? {} : { session }),
|
|
81
|
+
hook,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// A hook that throws turns the agent's stop into an error the user did not cause; it says nothing
|
|
86
|
+
// instead. Run by hand, the reason is the answer (an id that is not a session, a journal it
|
|
87
|
+
// could not read), so it is printed and the exit is non-zero.
|
|
88
|
+
if (hook)
|
|
89
|
+
return;
|
|
90
|
+
process.stderr.write(`reticle report: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (0 === lines.length)
|
|
95
|
+
return;
|
|
96
|
+
process.stdout.write(hook ? `${JSON.stringify({ systemMessage: lines.join('\n') })}\n` : `${lines.join('\n')}\n`);
|
|
97
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle status`, rendered for a person.
|
|
3
|
+
*
|
|
4
|
+
* The command printed one line of JSON and nothing else. Its own source called it "the second-most
|
|
5
|
+
* run command and the one a HUMAN types" — and the answer to that human was
|
|
6
|
+
* `{"t":"…","event":"reticle_status","port":4400,"running":true,…}`. It is worse than that now that
|
|
7
|
+
* agents run it: an agent's terminal output is read by the person sitting behind it, so a machine
|
|
8
|
+
* line is a line two readers have to decode and only one of them can.
|
|
9
|
+
*
|
|
10
|
+
* The JSON stays, unchanged, on stderr — it is documented, and it is what a log is for. This is the
|
|
11
|
+
* same facts on stdout, in the shape `doctor` already uses, so the two commands read as one tool.
|
|
12
|
+
*
|
|
13
|
+
* Pure, and tolerant: it is handed the very object the log line is built from, narrows it without
|
|
14
|
+
* trusting it, and prints what it can recognise. A status that cannot be rendered must still be
|
|
15
|
+
* logged, so nothing here may throw.
|
|
16
|
+
*/
|
|
17
|
+
/** Every row label this block can print. The one spelling; nothing writes a label inline. */
|
|
18
|
+
export declare const StatusLabel: {
|
|
19
|
+
readonly DAEMON: "daemon";
|
|
20
|
+
readonly SESSIONS: "sessions";
|
|
21
|
+
readonly AGENT_LINK: "agent link";
|
|
22
|
+
readonly NEXT: "next";
|
|
23
|
+
readonly SPLIT_BRAIN: "split brain";
|
|
24
|
+
readonly UPDATE: "update";
|
|
25
|
+
};
|
|
26
|
+
export type StatusLabel = (typeof StatusLabel)[keyof typeof StatusLabel];
|
|
27
|
+
/** The block to print on stdout, one string per line, heading first. */
|
|
28
|
+
export declare function statusLines(fields: Record<string, unknown>): string[];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle status`, rendered for a person.
|
|
3
|
+
*
|
|
4
|
+
* The command printed one line of JSON and nothing else. Its own source called it "the second-most
|
|
5
|
+
* run command and the one a HUMAN types" — and the answer to that human was
|
|
6
|
+
* `{"t":"…","event":"reticle_status","port":4400,"running":true,…}`. It is worse than that now that
|
|
7
|
+
* agents run it: an agent's terminal output is read by the person sitting behind it, so a machine
|
|
8
|
+
* line is a line two readers have to decode and only one of them can.
|
|
9
|
+
*
|
|
10
|
+
* The JSON stays, unchanged, on stderr — it is documented, and it is what a log is for. This is the
|
|
11
|
+
* same facts on stdout, in the shape `doctor` already uses, so the two commands read as one tool.
|
|
12
|
+
*
|
|
13
|
+
* Pure, and tolerant: it is handed the very object the log line is built from, narrows it without
|
|
14
|
+
* trusting it, and prints what it can recognise. A status that cannot be rendered must still be
|
|
15
|
+
* logged, so nothing here may throw.
|
|
16
|
+
*/
|
|
17
|
+
/** Every row label this block can print. The one spelling; nothing writes a label inline. */
|
|
18
|
+
export const StatusLabel = {
|
|
19
|
+
DAEMON: 'daemon',
|
|
20
|
+
SESSIONS: 'sessions',
|
|
21
|
+
AGENT_LINK: 'agent link',
|
|
22
|
+
NEXT: 'next',
|
|
23
|
+
SPLIT_BRAIN: 'split brain',
|
|
24
|
+
UPDATE: 'update',
|
|
25
|
+
};
|
|
26
|
+
const LABELS = Object.values(StatusLabel);
|
|
27
|
+
/** The heading, so a reader can tell which command produced the block. */
|
|
28
|
+
const HEADING = 'reticle status';
|
|
29
|
+
const INDENT = ' ';
|
|
30
|
+
/** Padded off the longest label, for the same reason `doctor` does it: the column IS the legibility. */
|
|
31
|
+
const LABEL_COLUMN = LABELS.reduce((widest, l) => Math.max(widest, l.length), 0) + 2;
|
|
32
|
+
const OK = '✓';
|
|
33
|
+
const NOT_OK = '✗';
|
|
34
|
+
function row(label, rest) {
|
|
35
|
+
return `${INDENT}${label.padEnd(LABEL_COLUMN)}${rest}`;
|
|
36
|
+
}
|
|
37
|
+
/** A continuation line under a row: indented past the label column, carrying no label of its own. */
|
|
38
|
+
function continuation(rest) {
|
|
39
|
+
return `${INDENT}${' '.repeat(LABEL_COLUMN)}${rest}`;
|
|
40
|
+
}
|
|
41
|
+
function str(fields, key) {
|
|
42
|
+
const v = fields[key];
|
|
43
|
+
return 'string' === typeof v && v.length > 0 ? v : undefined;
|
|
44
|
+
}
|
|
45
|
+
function num(fields, key) {
|
|
46
|
+
const v = fields[key];
|
|
47
|
+
return 'number' === typeof v ? v : undefined;
|
|
48
|
+
}
|
|
49
|
+
function sessionViews(fields) {
|
|
50
|
+
const raw = fields['sessions'];
|
|
51
|
+
if (!Array.isArray(raw))
|
|
52
|
+
return [];
|
|
53
|
+
return raw.flatMap((s) => {
|
|
54
|
+
if ('object' !== typeof s || null === s)
|
|
55
|
+
return [];
|
|
56
|
+
const r = s;
|
|
57
|
+
// Each of these explains something a reader would otherwise call a bug: a throttled or hidden
|
|
58
|
+
// tab answers slowly, a stale one answers about a page that has moved on, and an unresponsive
|
|
59
|
+
// one does not answer at all.
|
|
60
|
+
const notes = [
|
|
61
|
+
true === r['throttled'] ? 'throttled' : undefined,
|
|
62
|
+
true === r['hidden'] ? 'hidden' : undefined,
|
|
63
|
+
true === r['stale'] ? 'stale' : undefined,
|
|
64
|
+
true === r['unresponsive'] ? 'unresponsive' : undefined,
|
|
65
|
+
].filter((n) => n !== undefined);
|
|
66
|
+
return [{ url: str(r, 'url') ?? '(no url reported)', notes }];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** The block to print on stdout, one string per line, heading first. */
|
|
70
|
+
export function statusLines(fields) {
|
|
71
|
+
const port = num(fields, 'port');
|
|
72
|
+
const at = port === undefined ? '' : ` on :${String(port)}`;
|
|
73
|
+
const pid = num(fields, 'pid');
|
|
74
|
+
const running = true === fields['running'];
|
|
75
|
+
const lines = [HEADING];
|
|
76
|
+
lines.push(row(StatusLabel.DAEMON, running
|
|
77
|
+
? `${OK} running${at}${pid === undefined ? '' : ` (pid ${String(pid)})`}`
|
|
78
|
+
: `${NOT_OK} not running${at}`));
|
|
79
|
+
// What was actually ON the port, when the pid file said nothing useful. "A stranger holds this"
|
|
80
|
+
// and "nothing is here" were the same output once, and the true sentence was said by nothing a
|
|
81
|
+
// person sees — so the word belongs in the prose, not only in the event.
|
|
82
|
+
const presence = str(fields, 'presence');
|
|
83
|
+
if (presence !== undefined)
|
|
84
|
+
lines.push(continuation(`port presence: ${presence}`));
|
|
85
|
+
// The refusal a FOREIGN port produces. It is the whole answer when it is present, so it rides the
|
|
86
|
+
// daemon row rather than waiting for a `next` that is not built in that branch.
|
|
87
|
+
const reason = str(fields, 'reason');
|
|
88
|
+
if (reason !== undefined)
|
|
89
|
+
lines.push(continuation(reason));
|
|
90
|
+
if (running) {
|
|
91
|
+
const count = num(fields, 'sessionCount') ?? 0;
|
|
92
|
+
lines.push(row(StatusLabel.SESSIONS, count > 0
|
|
93
|
+
? `${OK} ${String(count)} page${1 === count ? '' : 's'} connected`
|
|
94
|
+
: `${NOT_OK} no page connected`));
|
|
95
|
+
for (const s of sessionViews(fields)) {
|
|
96
|
+
lines.push(continuation(`${s.url}${0 === s.notes.length ? '' : ` (${s.notes.join(', ')})`}`));
|
|
97
|
+
}
|
|
98
|
+
// The daemon's own explanation for an empty session list, which outranks anything this command
|
|
99
|
+
// could infer — it is the one holding the connection.
|
|
100
|
+
const why = str(fields, 'why');
|
|
101
|
+
if (why !== undefined)
|
|
102
|
+
lines.push(continuation(why));
|
|
103
|
+
}
|
|
104
|
+
const client = str(fields, 'mcpClient');
|
|
105
|
+
if (client !== undefined)
|
|
106
|
+
lines.push(row(StatusLabel.AGENT_LINK, client));
|
|
107
|
+
for (const [label, key] of [
|
|
108
|
+
[StatusLabel.NEXT, 'nextAction'],
|
|
109
|
+
[StatusLabel.SPLIT_BRAIN, 'splitBrain'],
|
|
110
|
+
[StatusLabel.UPDATE, 'updateAvailable'],
|
|
111
|
+
]) {
|
|
112
|
+
const value = str(fields, key);
|
|
113
|
+
if (value !== undefined)
|
|
114
|
+
lines.push(row(label, value));
|
|
115
|
+
}
|
|
116
|
+
return lines;
|
|
117
|
+
}
|
package/dist/command/cli.d.ts
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
import { parseCliArgs, CLI_USAGE } from './cli/cli-parse.js';
|
|
3
3
|
export { parseCliArgs, CLI_USAGE };
|
|
4
4
|
export type { CliResult } from './cli/cli-parse.js';
|
|
5
|
-
export declare function handleStatus(port: number): Promise<void>;
|
|
5
|
+
export declare function handleStatus(port: number, json?: boolean): Promise<void>;
|
|
6
6
|
export declare function main(): void;
|
package/dist/command/cli.js
CHANGED
|
@@ -8,15 +8,15 @@ import { openFailureNote } from './cli/answers/open-note.js';
|
|
|
8
8
|
import { realpathSync } from 'node:fs';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { installCrashGuard } from './setup/setup-command.js';
|
|
11
|
-
import { stateDirProblem } from './daemon/state-dir.js';
|
|
12
11
|
import { readDevServers } from './daemon/dev-servers.js';
|
|
13
12
|
import { hasAnyAppConnectedBefore, hasProjectConnectedBefore, } from '../memory/recall/prior/connection-memory.js';
|
|
14
13
|
import { attachStatusFields } from '../surface/mcp/attach-memory.js';
|
|
15
14
|
import { splitBrainFields, withNextAction } from './cli/status-fields.js';
|
|
16
15
|
import { reticleStateHome } from './daemon/daemon.js';
|
|
17
16
|
import { handleMcp } from './cli/mcp-command.js';
|
|
17
|
+
import { handleReport } from './cli/report-command.js';
|
|
18
18
|
import { resolveDaemonForProject } from './daemon/daemon-resolve.js';
|
|
19
|
-
import {
|
|
19
|
+
import { daemonStartOptions } from './cli/daemon-start-options.js';
|
|
20
20
|
import { handleWatch, handleCapsules, handleGate, loadNamedFlows, resolveChangedFiles, } from './cli/cli-flow-commands.js';
|
|
21
21
|
import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv, devServersForProject, } from '@reticlehq/core';
|
|
22
22
|
import { loadDotEnv } from '../telemetry/dev-repo.js';
|
|
@@ -26,20 +26,20 @@ import { createNodeFileSystem } from '../memory/project/fs/fs-port.js';
|
|
|
26
26
|
import { affectedSavedFlows } from '../language/flows/change/flow-sources.js';
|
|
27
27
|
import { availableUpdate } from './update/update-nudge.js';
|
|
28
28
|
import { handleUpdate, handleRollback } from './cli/cli-update-commands.js';
|
|
29
|
-
import { startDaemon
|
|
30
|
-
import { verifyEndpointMismatch } from '../status-payload.js';
|
|
29
|
+
import { startDaemon } from '../index.js';
|
|
31
30
|
import { isCloudCommand, runCloudCommand } from './cli/cloud-cli.js';
|
|
32
31
|
import { SERVER_VERSION } from './version/identity/server-version.js';
|
|
33
32
|
import { log } from '../log.js';
|
|
34
|
-
import { readPid, isAlive, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry,
|
|
33
|
+
import { readPid, isAlive, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry, } from './daemon/daemon.js';
|
|
35
34
|
import { PortPresence, probePresence, presenceIsUsable, describePresence, } from './daemon/binding/port-presence.js';
|
|
36
35
|
import { waitForDaemon, probeDaemon } from '../surface/mcp/mcp-proxy.js';
|
|
37
36
|
import { installDaemonResilience, recordExitReason, DaemonExitReason, } from './daemon/daemon-resilience.js';
|
|
38
37
|
import { IdleShutdown, resolveIdleShutdownMs, resolveIdleCheckMs } from './daemon/idle-shutdown.js';
|
|
39
38
|
import { DaemonHeartbeat, resolveHeartbeatMs } from './daemon/lifetime/heartbeat.js';
|
|
40
39
|
import { everServedToolCall } from './daemon/lifetime/daemon-usefulness.js';
|
|
41
|
-
import { DAEMON_START_FAILED_EVENT
|
|
42
|
-
import {
|
|
40
|
+
import { DAEMON_START_FAILED_EVENT } from './daemon/lifetime/startup-failure.js';
|
|
41
|
+
import { summarizeStatus, warnOnDaemonSkew, decideOpen, openInBrowser, openCommand, } from './cli/launch/cli-launch.js';
|
|
42
|
+
import { fetchStatus } from './daemon/binding/daemon-status-probe.js';
|
|
43
43
|
import { handleDrive } from './cli/drive/drive-command.js';
|
|
44
44
|
import { handleVerify } from './cli/cli-verify.js';
|
|
45
45
|
import { runKill } from './cli/cli-kill.js';
|
|
@@ -70,6 +70,7 @@ function handleInit(parsed) {
|
|
|
70
70
|
// past a missing package manager, and could not honour that while never being told about it.
|
|
71
71
|
...(parsed.url === undefined ? {} : { url: parsed.url }),
|
|
72
72
|
captureBodies: true === parsed.captureBodies,
|
|
73
|
+
hooks: true === parsed.hooks,
|
|
73
74
|
// The outcome is reported by confirmInstall instead, once it knows whether an app connected —
|
|
74
75
|
// `init` writing files was never the same thing as `init` working (#269).
|
|
75
76
|
deferOutcome: true,
|
|
@@ -77,178 +78,26 @@ function handleInit(parsed) {
|
|
|
77
78
|
}, io);
|
|
78
79
|
void continueAfterInit(parsed, result, io, cwd);
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
81
|
+
// `serve`, `stop` and `restart` live in `cli/lifecycle/daemon-lifecycle.ts`: one idea, and the
|
|
82
|
+
// largest group this file could give up without splitting something that belongs together.
|
|
83
|
+
import { handleServe, handleStop, handleRestart } from './cli/lifecycle/daemon-lifecycle.js';
|
|
84
|
+
import { statusLines } from './cli/status/status-lines.js';
|
|
83
85
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* `serve` used to log `reticle_daemon_spawned` and exit 0 unconditionally, while the child died at
|
|
87
|
-
* `reticle_daemon_start_failed` with an EADDRINUSE nothing joined back to the parent. Three surfaces
|
|
88
|
-
* then disagreed about what was true and none of them named the port.
|
|
86
|
+
* Log the status event AND print it for a person.
|
|
89
87
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
88
|
+
* Both, not either: the JSON line is documented and is what a log is for, and the block is for
|
|
89
|
+
* whoever is reading the terminal — which, once an agent is running the command, is somebody who
|
|
90
|
+
* did not type it and cannot be assumed to parse it.
|
|
93
91
|
*/
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
status: fetchStatus,
|
|
98
|
-
});
|
|
99
|
-
if (presence === PortPresence.DAEMON) {
|
|
100
|
-
// A daemon that is already up was started with ITS flags, not these — `serve` only attaches.
|
|
101
|
-
// Exiting 0 here regardless is how `--http-port` came to be accepted and ignored (#687): the
|
|
102
|
-
// running daemon kept serving whatever it was started with, and the flag vanished without a
|
|
103
|
-
// word. Ask the daemon which verify port it actually serves and refuse when it is not the one
|
|
104
|
-
// requested — a flag that cannot be honoured must say so, not report success.
|
|
105
|
-
if (parsed.http) {
|
|
106
|
-
const mismatch = verifyEndpointMismatch(await fetchStatus(parsed.port), parsed.httpPort ?? RETICLE_VERIFY_DEFAULT_PORT);
|
|
107
|
-
if (mismatch !== undefined) {
|
|
108
|
-
log('reticle_daemon_start_refused', { port: parsed.port, reason: mismatch });
|
|
109
|
-
process.stderr.write(`${mismatch}\n`);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
log('reticle_daemon_already_running', { port: parsed.port });
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (presence === PortPresence.FOREIGN) {
|
|
118
|
-
log('reticle_daemon_start_refused', {
|
|
119
|
-
port: parsed.port,
|
|
120
|
-
presence,
|
|
121
|
-
reason: describePresence(presence, parsed.port),
|
|
122
|
-
});
|
|
123
|
-
process.stderr.write(`${describePresence(presence, parsed.port)}\n`);
|
|
124
|
-
process.exit(1);
|
|
92
|
+
function reportStatus(fields, json) {
|
|
93
|
+
if (json) {
|
|
94
|
+
log('reticle_status', fields);
|
|
125
95
|
return;
|
|
126
96
|
}
|
|
127
|
-
|
|
128
|
-
if (scriptPath === undefined) {
|
|
129
|
-
log('reticle_serve_no_script', {});
|
|
130
|
-
process.exit(1);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const daemonArgs = daemonSpawnArgs(parsed);
|
|
134
|
-
const startupStartedAt = Date.now();
|
|
135
|
-
spawnDaemon(process.execPath, scriptPath, daemonArgs, parsed.port);
|
|
136
|
-
// The child binds asynchronously and, when it cannot, exits 1 long after this process would have
|
|
137
|
-
// reported success. Wait for it to ANSWER — `/status` responding is the only evidence a daemon
|
|
138
|
-
// exists that does not come from the pid file the child may never have earned.
|
|
139
|
-
const bound = await waitForPresence(parsed.port, PortPresence.DAEMON, SERVE_BIND_TIMEOUT_MS);
|
|
140
|
-
if (!bound) {
|
|
141
|
-
const settled = await probePresence(parsed.port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
142
|
-
const startupCause = readDaemonStartupCause(logPath(parsed.port), startupStartedAt);
|
|
143
|
-
log('reticle_daemon_start_failed_parent', {
|
|
144
|
-
port: parsed.port,
|
|
145
|
-
presence: settled,
|
|
146
|
-
reason: describePresence(settled, parsed.port),
|
|
147
|
-
log: logPath(parsed.port),
|
|
148
|
-
stateDir: stateDirProblem(reticleStateHome()) === undefined ? 'writable' : 'unwritable',
|
|
149
|
-
...(startupCause === undefined ? {} : { cause: startupCause }),
|
|
150
|
-
});
|
|
151
|
-
// Name the CAUSE when we can see it. An unwritable state directory is a first-run failure mode
|
|
152
|
-
// (locked-down home, read-only container mount, managed profile) that produced only "nothing is
|
|
153
|
-
// listening" — the symptom restated — and then pointed at a log INSIDE that directory, which
|
|
154
|
-
// cannot exist. Reproduced with `chmod 555 ~/.reticle`.
|
|
155
|
-
const dirProblem = stateDirProblem(reticleStateHome());
|
|
156
|
-
process.stderr.write(dirProblem === undefined
|
|
157
|
-
? startupCause === undefined
|
|
158
|
-
? `the daemon did not come up on :${String(parsed.port)} — ${describePresence(settled, parsed.port)}\n` +
|
|
159
|
-
`see ${logPath(parsed.port)}\n`
|
|
160
|
-
: `the daemon did not come up on :${String(parsed.port)}.\n${startupCause}\n` +
|
|
161
|
-
`see ${logPath(parsed.port)}\n`
|
|
162
|
-
: `the daemon did not come up on :${String(parsed.port)}.\n${dirProblem}\n`);
|
|
163
|
-
process.exit(1);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
log('reticle_daemon_spawned', { port: parsed.port, ...(parsed.http ? { http: true } : {}) });
|
|
97
|
+
process.stdout.write(`${statusLines(fields).join('\n')}\n`);
|
|
167
98
|
}
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
const PRESENCE_POLL_MS = 150;
|
|
171
|
-
/** How long a daemon gets to shut down on its own before `stop` stops asking politely. */
|
|
172
|
-
const GRACEFUL_STOP_MS = 5000;
|
|
173
|
-
/** How long after SIGKILL before we accept the process is not ours to kill and say so. */
|
|
174
|
-
const FORCED_STOP_MS = 2000;
|
|
175
|
-
async function waitForPresence(port, want, timeoutMs) {
|
|
176
|
-
const deadline = Date.now() + timeoutMs;
|
|
177
|
-
for (;;) {
|
|
178
|
-
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
179
|
-
if (presence === want)
|
|
180
|
-
return true;
|
|
181
|
-
if (Date.now() >= deadline)
|
|
182
|
-
return false;
|
|
183
|
-
await new Promise((resolve) => setTimeout(resolve, PRESENCE_POLL_MS));
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function handleStop(port, quiet) {
|
|
187
|
-
const pid = readPid(port);
|
|
188
|
-
if (null === pid || !isAlive(pid)) {
|
|
189
|
-
removePid(port);
|
|
190
|
-
if (!quiet)
|
|
191
|
-
log('reticle_daemon_not_running', { port });
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
process.kill(pid, 'SIGTERM');
|
|
195
|
-
const started = Date.now();
|
|
196
|
-
let escalated = false;
|
|
197
|
-
const poll = setInterval(() => {
|
|
198
|
-
if (!isAlive(pid)) {
|
|
199
|
-
clearInterval(poll);
|
|
200
|
-
removePid(port);
|
|
201
|
-
if (!quiet)
|
|
202
|
-
log('reticle_daemon_stopped', { port, pid, ...(escalated ? { escalated } : {}) });
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const waited = Date.now() - started;
|
|
206
|
-
// A daemon that ignores SIGTERM is WEDGED — which is the one situation `stop` exists for. Giving
|
|
207
|
-
// up here left the port held, the pid file stale, and a human running `kill -9` by hand at the
|
|
208
|
-
// first two minutes of setup. Escalate instead: graceful first, then unconditional.
|
|
209
|
-
if (!escalated && waited > GRACEFUL_STOP_MS) {
|
|
210
|
-
escalated = true;
|
|
211
|
-
try {
|
|
212
|
-
process.kill(pid, 'SIGKILL');
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
// Already gone between the liveness check and here — the next tick reports it stopped.
|
|
216
|
-
}
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
if (waited > GRACEFUL_STOP_MS + FORCED_STOP_MS) {
|
|
220
|
-
clearInterval(poll);
|
|
221
|
-
// Surviving SIGKILL means the pid is not ours to kill (permissions) or is an unkillable
|
|
222
|
-
// zombie. Neither is retryable, and both need the pid named so a human can act on it.
|
|
223
|
-
if (!quiet)
|
|
224
|
-
log('reticle_daemon_stop_timeout', { port, pid, escalated });
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
}, 100);
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* `reticle restart` — free the port, then put a daemon back on it and prove it bound.
|
|
231
|
-
*
|
|
232
|
-
* The two halves are reported separately on purpose. A restart that killed the old daemon and then
|
|
233
|
-
* could not start a new one leaves the user in a different place from one that could not kill
|
|
234
|
-
* anything at all, and a single `ok: false` cannot tell them which happened.
|
|
235
|
-
*/
|
|
236
|
-
async function handleRestart(port, force) {
|
|
237
|
-
const freed = await runKill(port, force);
|
|
238
|
-
if (!freed) {
|
|
239
|
-
log('reticle_restart_aborted', {
|
|
240
|
-
port,
|
|
241
|
-
reason: 'the port was not freed, so nothing was started — the old holder is still there',
|
|
242
|
-
});
|
|
243
|
-
process.exit(1);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
// Reuses `serve`'s path, which waits for the daemon to ANSWER before claiming it started and exits
|
|
247
|
-
// non-zero when it does not. A restart that reports success for a daemon that never bound is the
|
|
248
|
-
// same lie `serve` already stopped telling.
|
|
249
|
-
await serveWithHonestExit({ port, headless: true, http: false });
|
|
250
|
-
}
|
|
251
|
-
export async function handleStatus(port) {
|
|
99
|
+
export async function handleStatus(port, json = false) {
|
|
100
|
+
const report = (fields) => reportStatus(fields, json);
|
|
252
101
|
const pid = readPid(port);
|
|
253
102
|
// Durable, so it survives the daemon idling out — which is the state `status` is most often run in.
|
|
254
103
|
const projectId = readProjectId(process.cwd());
|
|
@@ -281,7 +130,7 @@ export async function handleStatus(port) {
|
|
|
281
130
|
// process exists; a Reticle daemon is running only when the shared port probe reaches /status.
|
|
282
131
|
const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
|
|
283
132
|
if (!presenceIsUsable(presence)) {
|
|
284
|
-
|
|
133
|
+
report({
|
|
285
134
|
port,
|
|
286
135
|
running: false,
|
|
287
136
|
presence,
|
|
@@ -310,7 +159,7 @@ export async function handleStatus(port) {
|
|
|
310
159
|
const update = availableUpdate();
|
|
311
160
|
const nudge = update === undefined ? {} : { updateAvailable: update };
|
|
312
161
|
if (payload === undefined) {
|
|
313
|
-
|
|
162
|
+
report({
|
|
314
163
|
port,
|
|
315
164
|
running: true,
|
|
316
165
|
pid,
|
|
@@ -342,7 +191,7 @@ export async function handleStatus(port) {
|
|
|
342
191
|
devServerPorts,
|
|
343
192
|
})
|
|
344
193
|
: {};
|
|
345
|
-
|
|
194
|
+
report({
|
|
346
195
|
port,
|
|
347
196
|
running: true,
|
|
348
197
|
pid,
|
|
@@ -752,7 +601,7 @@ export function main() {
|
|
|
752
601
|
void handleRestart(parsed.port, parsed.force);
|
|
753
602
|
break;
|
|
754
603
|
case 'status':
|
|
755
|
-
void handleStatus(parsed.port);
|
|
604
|
+
void handleStatus(parsed.port, parsed.json);
|
|
756
605
|
break;
|
|
757
606
|
case 'license':
|
|
758
607
|
handleLicense();
|
|
@@ -822,6 +671,9 @@ export function main() {
|
|
|
822
671
|
case 'gate':
|
|
823
672
|
void handleGate(parsed.files, parsed.since, parsed.hook);
|
|
824
673
|
break;
|
|
674
|
+
case 'report':
|
|
675
|
+
void handleReport(parsed.session, parsed.hook);
|
|
676
|
+
break;
|
|
825
677
|
case 'watch':
|
|
826
678
|
handleWatch();
|
|
827
679
|
break;
|