@reticlehq/server 3.8.2 → 3.10.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.
Files changed (477) hide show
  1. package/LICENSE +201 -104
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/SKILL.md +13 -12
  5. package/bin/launch.js +13 -0
  6. package/bin/package.json +3 -0
  7. package/bin/reticle.js +66 -8
  8. package/dist/chromium-launch-options.d.ts +25 -0
  9. package/dist/chromium-launch-options.js +21 -1
  10. package/dist/command/cli/adhoc-suite.js +2 -0
  11. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  12. package/dist/command/cli/adhoc-verdict.js +50 -160
  13. package/dist/command/cli/answers/gate-exit.d.ts +6 -2
  14. package/dist/command/cli/answers/gate-exit.js +12 -2
  15. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  16. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  17. package/dist/command/cli/ci-hint.d.ts +44 -0
  18. package/dist/command/cli/ci-hint.js +72 -0
  19. package/dist/command/cli/cli-doctor.d.ts +6 -1
  20. package/dist/command/cli/cli-doctor.js +46 -5
  21. package/dist/command/cli/cli-flow-commands.d.ts +7 -1
  22. package/dist/command/cli/cli-flow-commands.js +39 -24
  23. package/dist/command/cli/cli-kill.d.ts +21 -0
  24. package/dist/command/cli/cli-kill.js +47 -4
  25. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  26. package/dist/command/cli/cli-parse-compare.js +75 -0
  27. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  28. package/dist/command/cli/cli-parse-grade.js +75 -0
  29. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  30. package/dist/command/cli/cli-parse-grammar.js +18 -20
  31. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  32. package/dist/command/cli/cli-parse-verify.js +63 -14
  33. package/dist/command/cli/cli-parse.d.ts +22 -17
  34. package/dist/command/cli/cli-parse.js +54 -32
  35. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  36. package/dist/command/cli/cli-update-commands.js +29 -20
  37. package/dist/command/cli/cli-usage.js +61 -31
  38. package/dist/command/cli/cli-verify.d.ts +17 -3
  39. package/dist/command/cli/cli-verify.js +59 -21
  40. package/dist/command/cli/cloud-cli.js +2 -119
  41. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  42. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  43. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
  44. package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
  45. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  46. package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
  47. package/dist/command/cli/doctor/doctor-fix.js +89 -0
  48. package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
  49. package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
  50. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  51. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  52. package/dist/command/cli/doctor-repairs.d.ts +64 -0
  53. package/dist/command/cli/doctor-repairs.js +214 -0
  54. package/dist/command/cli/drive/drive-command.js +3 -7
  55. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  56. package/dist/command/cli/ensure-daemon.js +57 -0
  57. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  58. package/dist/command/cli/finished-run/grade-command.js +180 -0
  59. package/dist/command/cli/first-use-checks.d.ts +41 -0
  60. package/dist/command/cli/first-use-checks.js +127 -0
  61. package/dist/command/cli/first-verdict-node.d.ts +3 -0
  62. package/dist/command/cli/first-verdict-node.js +189 -0
  63. package/dist/command/cli/first-verdict.d.ts +126 -0
  64. package/dist/command/cli/first-verdict.js +267 -0
  65. package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
  66. package/dist/command/cli/launch/sdk-version-match.js +3 -2
  67. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  68. package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
  69. package/dist/command/cli/mcp-command.js +13 -4
  70. package/dist/command/cli/ports/port-holder.d.ts +11 -0
  71. package/dist/command/cli/ports/port-holder.js +36 -4
  72. package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
  73. package/dist/command/cli/setup-mcp-cli.js +98 -23
  74. package/dist/command/cli/status/status-lines.d.ts +2 -0
  75. package/dist/command/cli/status/status-lines.js +72 -17
  76. package/dist/command/cli/status-fields.d.ts +5 -1
  77. package/dist/command/cli/status-fields.js +24 -6
  78. package/dist/command/cli/try-command.d.ts +7 -0
  79. package/dist/command/cli/try-command.js +11 -3
  80. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  81. package/dist/command/cli/two-builds/compare-command.js +314 -0
  82. package/dist/command/cli/verify-against.d.ts +61 -0
  83. package/dist/command/cli/verify-against.js +187 -0
  84. package/dist/command/cli/verify-no-session.d.ts +2 -0
  85. package/dist/command/cli/verify-no-session.js +13 -0
  86. package/dist/command/cli/verify-results.d.ts +35 -2
  87. package/dist/command/cli/verify-results.js +76 -10
  88. package/dist/command/cli.js +112 -70
  89. package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
  90. package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
  91. package/dist/command/daemon/daemon-argv.d.ts +9 -0
  92. package/dist/command/daemon/daemon-argv.js +11 -0
  93. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  94. package/dist/command/daemon/daemon-resolve.js +27 -1
  95. package/dist/command/daemon/daemon.d.ts +22 -4
  96. package/dist/command/daemon/daemon.js +52 -10
  97. package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
  98. package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
  99. package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
  100. package/dist/command/daemon/lifetime/death-cause.js +149 -0
  101. package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
  102. package/dist/command/daemon/lifetime/heartbeat.js +11 -15
  103. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  104. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  105. package/dist/command/daemon/port-state.d.ts +14 -0
  106. package/dist/command/daemon/port-state.js +31 -0
  107. package/dist/command/setup/agent-configs.d.ts +2 -0
  108. package/dist/command/setup/agent-configs.js +42 -15
  109. package/dist/command/setup/agent-next-steps.d.ts +77 -0
  110. package/dist/command/setup/agent-next-steps.js +284 -0
  111. package/dist/command/setup/agent-writer.js +11 -6
  112. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  113. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  114. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  115. package/dist/command/setup/bringup/static-page-server.js +43 -9
  116. package/dist/command/setup/init/init-host.js +2 -0
  117. package/dist/command/setup/init/init-runtime.d.ts +6 -1
  118. package/dist/command/setup/init/init-runtime.js +63 -21
  119. package/dist/command/setup/mcp-launcher.d.ts +3 -0
  120. package/dist/command/setup/mcp-launcher.js +11 -0
  121. package/dist/command/setup/node-effects.d.ts +3 -2
  122. package/dist/command/setup/node-effects.js +9 -3
  123. package/dist/command/setup/remaining-steps.d.ts +8 -0
  124. package/dist/command/setup/remaining-steps.js +10 -0
  125. package/dist/command/setup/run-setup.d.ts +2 -0
  126. package/dist/command/setup/run-setup.js +9 -1
  127. package/dist/command/setup/setup-command.js +36 -15
  128. package/dist/command/setup/setup-mcp.d.ts +8 -0
  129. package/dist/command/setup/setup-mcp.js +47 -20
  130. package/dist/command/setup/start-dev-server.d.ts +8 -0
  131. package/dist/command/setup/start-dev-server.js +43 -0
  132. package/dist/command/update/updater.js +6 -0
  133. package/dist/command/version/identity/server-version.d.ts +2 -0
  134. package/dist/command/version/identity/server-version.js +2 -0
  135. package/dist/command/version/version-skew.d.ts +7 -0
  136. package/dist/command/version/version-skew.js +14 -3
  137. package/dist/features/compare/action-plan.d.ts +260 -0
  138. package/dist/features/compare/action-plan.js +543 -0
  139. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  140. package/dist/features/compare/behaviour-compare.js +85 -0
  141. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  142. package/dist/features/compare/behaviour-diff.js +202 -0
  143. package/dist/features/compare/behaviour-report.d.ts +40 -0
  144. package/dist/features/compare/behaviour-report.js +128 -0
  145. package/dist/features/compare/build-readings.d.ts +106 -0
  146. package/dist/features/compare/build-readings.js +250 -0
  147. package/dist/features/compare/compare-diff.d.ts +62 -0
  148. package/dist/features/compare/compare-diff.js +177 -0
  149. package/dist/features/compare/compare-report-html.d.ts +6 -0
  150. package/dist/features/compare/compare-report-html.js +102 -0
  151. package/dist/features/compare/compare-report.d.ts +15 -0
  152. package/dist/features/compare/compare-report.js +73 -0
  153. package/dist/features/compare/compare-run.d.ts +60 -0
  154. package/dist/features/compare/compare-run.js +123 -0
  155. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  156. package/dist/features/compare/outside/effect-lines.js +103 -0
  157. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  158. package/dist/features/compare/outside/state-sources.js +164 -0
  159. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  160. package/dist/features/compare/outside/value-dictionary.js +227 -0
  161. package/dist/features/compare/visible-content.d.ts +20 -0
  162. package/dist/features/compare/visible-content.js +125 -0
  163. package/dist/features/crawl/crawl-empty.js +2 -1
  164. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  165. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  166. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  167. package/dist/features/crawl/crawl-tools.js +28 -12
  168. package/dist/features/crawl/crawl.d.ts +8 -9
  169. package/dist/features/crawl/crawl.js +61 -65
  170. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  171. package/dist/features/exhaust/app-coverage.js +3 -1
  172. package/dist/features/exhaust/code-coverage.js +8 -4
  173. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  174. package/dist/features/exhaust/edge-cases.js +310 -0
  175. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  176. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  177. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  178. package/dist/features/exhaust/edges/edge-run.js +169 -0
  179. package/dist/features/exhaust/explorer.d.ts +87 -14
  180. package/dist/features/exhaust/explorer.js +159 -25
  181. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  182. package/dist/features/exhaust/failure-branches.js +29 -11
  183. package/dist/features/exhaust/invariants.d.ts +112 -0
  184. package/dist/features/exhaust/invariants.js +188 -0
  185. package/dist/features/exhaust/ledger.d.ts +68 -0
  186. package/dist/features/exhaust/ledger.js +121 -13
  187. package/dist/features/exhaust/session-fold.d.ts +10 -0
  188. package/dist/features/exhaust/session-fold.js +46 -3
  189. package/dist/features/exhaust/session-port.d.ts +3 -1
  190. package/dist/features/exhaust/session-port.js +97 -28
  191. package/dist/features/grade/grade-answer.d.ts +99 -0
  192. package/dist/features/grade/grade-answer.js +112 -0
  193. package/dist/features/grade/grade-noise.d.ts +27 -0
  194. package/dist/features/grade/grade-noise.js +55 -0
  195. package/dist/features/grade/grade-renumber.d.ts +12 -0
  196. package/dist/features/grade/grade-renumber.js +86 -0
  197. package/dist/features/grade/grade-state.d.ts +50 -0
  198. package/dist/features/grade/grade-state.js +93 -0
  199. package/dist/features/grade/grade-suggest.d.ts +33 -0
  200. package/dist/features/grade/grade-suggest.js +98 -0
  201. package/dist/features/license/license.d.ts +11 -71
  202. package/dist/features/license/license.js +43 -136
  203. package/dist/features/visual/desktop-capture.d.ts +33 -0
  204. package/dist/features/visual/desktop-capture.js +94 -0
  205. package/dist/features/visual/step-shots.js +139 -35
  206. package/dist/features/visual/visual-tools.d.ts +0 -18
  207. package/dist/features/visual/visual-tools.js +67 -99
  208. package/dist/import-graph.js +5 -6
  209. package/dist/index.d.ts +3 -1
  210. package/dist/index.js +19 -7
  211. package/dist/judgement/capsule/causal-summary.js +9 -2
  212. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  213. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  214. package/dist/judgement/runs/reticle-runner.js +16 -2
  215. package/dist/judgement/runs/run-ledger.d.ts +7 -5
  216. package/dist/judgement/runs/run-ledger.js +98 -69
  217. package/dist/judgement/runs/runner-port.d.ts +2 -0
  218. package/dist/judgement/runs/runner-port.js +23 -5
  219. package/dist/judgement/runs/verification-sync.d.ts +6 -3
  220. package/dist/judgement/runs/verification-sync.js +21 -10
  221. package/dist/language/flows/affected-tools.d.ts +1 -1
  222. package/dist/language/flows/affected-tools.js +49 -30
  223. package/dist/language/flows/change/git-changed.d.ts +2 -1
  224. package/dist/language/flows/change/git-changed.js +19 -2
  225. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  226. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  227. package/dist/language/flows/flow-anchor.d.ts +60 -2
  228. package/dist/language/flows/flow-anchor.js +125 -20
  229. package/dist/language/flows/flow-classify.d.ts +20 -0
  230. package/dist/language/flows/flow-classify.js +90 -12
  231. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  232. package/dist/language/flows/flow-cross-step.js +25 -1
  233. package/dist/language/flows/flow-journey.d.ts +38 -4
  234. package/dist/language/flows/flow-journey.js +138 -8
  235. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  236. package/dist/language/flows/flow-replay-run.js +76 -14
  237. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  238. package/dist/language/flows/flow-replay.d.ts +19 -1
  239. package/dist/language/flows/flow-replay.js +169 -52
  240. package/dist/language/flows/flow-replayable.js +7 -1
  241. package/dist/language/flows/flow-start-path.d.ts +34 -5
  242. package/dist/language/flows/flow-start-path.js +202 -15
  243. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  244. package/dist/language/flows/flow-step-runners.js +184 -109
  245. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  246. package/dist/language/flows/flow-store-for-session.js +51 -5
  247. package/dist/language/flows/flow-tools.js +46 -33
  248. package/dist/language/flows/flows.js +176 -76
  249. package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
  250. package/dist/language/flows/recording/tape/recordings.js +112 -49
  251. package/dist/language/flows/replay.d.ts +36 -12
  252. package/dist/language/flows/replay.js +193 -50
  253. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  254. package/dist/language/flows/steps/page-steps.js +198 -0
  255. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  256. package/dist/language/flows/suite/sequential-run.js +96 -0
  257. package/dist/language/flows/verify-change-tools.js +26 -15
  258. package/dist/launch-chromium.d.ts +1 -0
  259. package/dist/launch-chromium.js +49 -12
  260. package/dist/memory/cloud/background-sync.d.ts +13 -0
  261. package/dist/memory/cloud/background-sync.js +24 -3
  262. package/dist/memory/cloud/sync-daemon.js +26 -27
  263. package/dist/memory/cloud/sync-screenshots.js +6 -1
  264. package/dist/memory/journal/attach-journal.js +2 -1
  265. package/dist/memory/journal/drive-flow.d.ts +7 -0
  266. package/dist/memory/journal/drive-flow.js +44 -13
  267. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  268. package/dist/memory/journal/session-journal.d.ts +7 -0
  269. package/dist/memory/journal/session-journal.js +58 -10
  270. package/dist/memory/project/root-claims.d.ts +9 -0
  271. package/dist/memory/project/root-claims.js +9 -4
  272. package/dist/portal/bridge/bridge.d.ts +13 -4
  273. package/dist/portal/bridge/bridge.js +10 -9
  274. package/dist/portal/input/download-capture.d.ts +7 -0
  275. package/dist/portal/input/download-capture.js +92 -0
  276. package/dist/portal/input/network-detail.d.ts +6 -0
  277. package/dist/portal/input/network-detail.js +12 -1
  278. package/dist/portal/input/network-mock-tools.js +49 -13
  279. package/dist/portal/input/network-mock.d.ts +8 -0
  280. package/dist/portal/input/network-mock.js +30 -10
  281. package/dist/portal/input/page-capture.d.ts +24 -0
  282. package/dist/portal/input/page-capture.js +33 -0
  283. package/dist/portal/input/real-input.d.ts +35 -18
  284. package/dist/portal/input/real-input.js +99 -33
  285. package/dist/portal/input/viewport-tools.d.ts +8 -0
  286. package/dist/portal/input/viewport-tools.js +81 -21
  287. package/dist/portal/pool/browser-pool.d.ts +16 -30
  288. package/dist/portal/pool/browser-pool.js +64 -52
  289. package/dist/portal/pool/context-permissions.d.ts +34 -17
  290. package/dist/portal/pool/context-permissions.js +94 -1
  291. package/dist/portal/pool/csp-widen.d.ts +62 -0
  292. package/dist/portal/pool/csp-widen.js +132 -0
  293. package/dist/portal/pool/determinism.d.ts +52 -0
  294. package/dist/portal/pool/determinism.js +259 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  296. package/dist/portal/pool/lease-emulation.js +18 -0
  297. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  298. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  299. package/dist/portal/pool/lease-page-error.d.ts +56 -0
  300. package/dist/portal/pool/lease-page-error.js +160 -0
  301. package/dist/portal/pool/page-error-journal.d.ts +63 -0
  302. package/dist/portal/pool/page-error-journal.js +208 -0
  303. package/dist/portal/pool/playwright-launcher.js +71 -15
  304. package/dist/portal/pool/pool-contract.d.ts +49 -1
  305. package/dist/portal/pool/pool-lease.d.ts +70 -2
  306. package/dist/portal/pool/pool-lease.js +23 -0
  307. package/dist/portal/pool/zero-install.d.ts +8 -0
  308. package/dist/portal/pool/zero-install.js +91 -1
  309. package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
  310. package/dist/portal/session/dev-server/dev-command.js +110 -16
  311. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  312. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  313. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  314. package/dist/portal/session/dev-server/project-ports.js +62 -0
  315. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  316. package/dist/portal/session/facts/departed-network.js +142 -0
  317. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  318. package/dist/portal/session/no-session-diagnosis.js +76 -27
  319. package/dist/portal/session/no-session-next-action.d.ts +14 -0
  320. package/dist/portal/session/no-session-next-action.js +56 -7
  321. package/dist/portal/session/no-session-watch.d.ts +6 -0
  322. package/dist/portal/session/no-session-watch.js +41 -4
  323. package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
  324. package/dist/portal/session/page-commands/undeclared-command.js +16 -7
  325. package/dist/portal/session/recorded-gaps.d.ts +1 -1
  326. package/dist/portal/session/recorded-gaps.js +5 -2
  327. package/dist/portal/session/serving-directory.d.ts +10 -0
  328. package/dist/portal/session/serving-directory.js +39 -8
  329. package/dist/portal/session/session-manager.d.ts +10 -0
  330. package/dist/portal/session/session-manager.js +211 -81
  331. package/dist/portal/session/session.js +4 -3
  332. package/dist/portal/session/zero-install-first-call.d.ts +20 -1
  333. package/dist/portal/session/zero-install-first-call.js +34 -2
  334. package/dist/start-wiring.d.ts +30 -2
  335. package/dist/start-wiring.js +74 -9
  336. package/dist/surface/mcp/mcp-proxy.js +10 -2
  337. package/dist/surface/mcp/mcp.js +8 -5
  338. package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
  339. package/dist/surface/mcp/peer/agents-connected.js +58 -0
  340. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
  341. package/dist/surface/mcp/shared-params.d.ts +2 -0
  342. package/dist/surface/mcp/shared-params.js +31 -0
  343. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  344. package/dist/surface/tools/act/act-observation.js +118 -12
  345. package/dist/surface/tools/act/act-preflight.js +38 -18
  346. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  347. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  348. package/dist/surface/tools/act/act-target.js +3 -1
  349. package/dist/surface/tools/act/after-match.d.ts +10 -0
  350. package/dist/surface/tools/act/after-match.js +40 -0
  351. package/dist/surface/tools/act/already-true.js +1 -1
  352. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  353. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  354. package/dist/surface/tools/act/resolve-target.d.ts +17 -1
  355. package/dist/surface/tools/act/resolve-target.js +59 -9
  356. package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
  357. package/dist/surface/tools/act/sequence-grade.js +45 -27
  358. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  359. package/dist/surface/tools/act-command.d.ts +23 -0
  360. package/dist/surface/tools/act-command.js +50 -0
  361. package/dist/surface/tools/act-sequence-tool.js +81 -50
  362. package/dist/surface/tools/act-tools.d.ts +1 -13
  363. package/dist/surface/tools/act-tools.js +83 -95
  364. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  365. package/dist/surface/tools/agent/build-staleness.js +1 -1
  366. package/dist/surface/tools/assert/assert-verdict.js +18 -12
  367. package/dist/surface/tools/browser-tools.js +87 -13
  368. package/dist/surface/tools/compare-tools.d.ts +11 -0
  369. package/dist/surface/tools/compare-tools.js +94 -0
  370. package/dist/surface/tools/coverage-tools.js +1 -1
  371. package/dist/surface/tools/dynamic-tools.js +11 -3
  372. package/dist/surface/tools/empty-session-list.d.ts +2 -0
  373. package/dist/surface/tools/empty-session-list.js +4 -2
  374. package/dist/surface/tools/error-recovery.d.ts +12 -1
  375. package/dist/surface/tools/error-recovery.js +131 -40
  376. package/dist/surface/tools/explore-tools.js +1 -1
  377. package/dist/surface/tools/invoke-tool.js +29 -21
  378. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  379. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  380. package/dist/surface/tools/lease-hint.d.ts +42 -1
  381. package/dist/surface/tools/lease-hint.js +52 -3
  382. package/dist/surface/tools/lease-readiness.d.ts +47 -0
  383. package/dist/surface/tools/lease-readiness.js +126 -0
  384. package/dist/surface/tools/lease-seed.d.ts +16 -1
  385. package/dist/surface/tools/lease-seed.js +37 -4
  386. package/dist/surface/tools/lease-tools.d.ts +3 -41
  387. package/dist/surface/tools/lease-tools.js +104 -124
  388. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  389. package/dist/surface/tools/lease-url-identity.js +97 -0
  390. package/dist/surface/tools/merge-tools.js +99 -2
  391. package/dist/surface/tools/merged-name-redirect.js +5 -1
  392. package/dist/surface/tools/observe-tools.js +15 -9
  393. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  394. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  395. package/dist/surface/tools/read-tools.js +33 -30
  396. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  397. package/dist/surface/tools/real-input-attempt.js +143 -28
  398. package/dist/surface/tools/repeat-hint.d.ts +40 -0
  399. package/dist/surface/tools/repeat-hint.js +171 -0
  400. package/dist/surface/tools/run-report.d.ts +2 -2
  401. package/dist/surface/tools/run-report.js +27 -15
  402. package/dist/surface/tools/session-ports.d.ts +14 -0
  403. package/dist/surface/tools/session-ports.js +53 -0
  404. package/dist/surface/tools/tool-kit.d.ts +18 -0
  405. package/dist/surface/tools/tool-kit.js +9 -3
  406. package/dist/surface/tools/tool-surface.js +0 -1
  407. package/dist/surface/tools/tools.js +54 -18
  408. package/dist/surface/tools/type-spelling.d.ts +13 -0
  409. package/dist/surface/tools/type-spelling.js +36 -0
  410. package/dist/telemetry/agent-client.d.ts +6 -0
  411. package/dist/telemetry/agent-client.js +10 -0
  412. package/dist/telemetry/argument-shape.js +17 -7
  413. package/dist/telemetry/cli-telemetry.d.ts +10 -1
  414. package/dist/telemetry/cli-telemetry.js +15 -0
  415. package/dist/telemetry/init-telemetry.d.ts +2 -1
  416. package/dist/telemetry/init-telemetry.js +47 -2
  417. package/dist/telemetry/install-trace.js +23 -4
  418. package/dist/telemetry/onboarding-funnel.d.ts +19 -2
  419. package/dist/telemetry/onboarding-funnel.js +25 -3
  420. package/dist/telemetry/proxy-telemetry.d.ts +2 -1
  421. package/dist/telemetry/proxy-telemetry.js +2 -2
  422. package/dist/telemetry/session-metrics.d.ts +1 -1
  423. package/dist/telemetry/session-metrics.js +67 -27
  424. package/dist/telemetry/telemetry.d.ts +3 -1
  425. package/dist/telemetry/telemetry.js +60 -28
  426. package/dist/telemetry/tool-refused.d.ts +6 -3
  427. package/dist/telemetry/tool-refused.js +15 -3
  428. package/docs/actions.mdx +3 -3
  429. package/docs/agent-cheatsheet.md +1 -1
  430. package/docs/architecture.md +3 -6
  431. package/docs/capabilities.mdx +61 -1
  432. package/docs/cli/affected.mdx +9 -7
  433. package/docs/cli/cloud.mdx +3 -3
  434. package/docs/cli/compare.mdx +94 -0
  435. package/docs/cli/doctor.mdx +41 -2
  436. package/docs/cli/grade.mdx +97 -0
  437. package/docs/cli/help.mdx +4 -2
  438. package/docs/cli/license.mdx +13 -12
  439. package/docs/cli/restart.mdx +7 -5
  440. package/docs/cli/rollback.mdx +5 -5
  441. package/docs/cli/setup.mdx +13 -2
  442. package/docs/cli/status.mdx +4 -0
  443. package/docs/cli/stop.mdx +3 -3
  444. package/docs/cli/update.mdx +2 -2
  445. package/docs/cli/verify.mdx +23 -1
  446. package/docs/cli.mdx +34 -24
  447. package/docs/deploy-checks.md +25 -0
  448. package/docs/desktop.mdx +1 -1
  449. package/docs/enterprise.md +17 -22
  450. package/docs/environment.mdx +1 -0
  451. package/docs/first-drive.md +1 -1
  452. package/docs/flows.md +11 -1
  453. package/docs/frameworks.mdx +9 -9
  454. package/docs/getting-started.md +1 -1
  455. package/docs/install-manual.mdx +16 -1
  456. package/docs/license-activation.mdx +3 -3
  457. package/docs/packages/engine.mdx +1 -1
  458. package/docs/packages/server.mdx +1 -1
  459. package/docs/packages/tauri.mdx +1 -1
  460. package/docs/packages/test.mdx +1 -1
  461. package/docs/packages.mdx +1 -1
  462. package/docs/platform-integration.md +1 -1
  463. package/docs/predicates.mdx +1 -1
  464. package/docs/quickstart.mdx +68 -49
  465. package/docs/skill-setup.md +1 -1
  466. package/docs/skill.md +14 -13
  467. package/docs/telemetry.md +6 -4
  468. package/docs/tools/navigate.mdx +1 -1
  469. package/docs/tools/verify.mdx +4 -1
  470. package/docs/troubleshooting.mdx +1 -1
  471. package/docs/usage.md +17 -14
  472. package/package.json +18 -14
  473. package/LICENSE-ENTERPRISE +0 -37
  474. package/dist/features/ee/audit-log.d.ts +0 -14
  475. package/dist/features/ee/audit-log.js +0 -11
  476. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  477. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -55,8 +55,9 @@ const URL_THEN_LEASE = 'Once you have that URL, open it with reticle_lease {acti
55
55
  * diagnosed the problem.
56
56
  */
57
57
  function leaseAdvice(base, facts) {
58
- if (true === facts.desktop)
58
+ if (true === facts.desktop) {
59
59
  return DESKTOP_NO_BROWSER;
60
+ }
60
61
  const caveat = leaseCaveat(facts.leaseBrowser);
61
62
  return caveat === undefined ? base : `${base} ${caveat}`;
62
63
  }
@@ -87,12 +88,14 @@ const TAB_GONE_WHAT = 'The tab was closed, navigated away, or hard-reloaded.';
87
88
  * page down is no longer the same dead end as "the human closed the tab".
88
89
  */
89
90
  function tabGoneWhat(url) {
90
- if (undefined === url || '' === url)
91
+ if (undefined === url || '' === url) {
91
92
  return TAB_GONE_WHAT;
93
+ }
92
94
  const torn = pageTornDownWhileOn(url);
93
95
  const first = torn.charAt(0);
94
- if ('' === first)
96
+ if ('' === first) {
95
97
  return TAB_GONE_WHAT;
98
+ }
96
99
  return `${first.toUpperCase()}${torn.slice(1)}. ${TAB_GONE_WHAT}`;
97
100
  }
98
101
  /**
@@ -158,6 +161,12 @@ const DO_NOT_REINSTALL = 'Re-running the install is the wrong move here: it cann
158
161
  * evidence that was never gathered.
159
162
  */
160
163
  const SCANNED_PORTS = [...DEV_SERVER_PORTS].join(', ');
164
+ /**
165
+ * The project's own sources the scan adds to the well-known list (reticle#1367, reticle#1543).
166
+ *
167
+ * Named in the "nothing else" sentences so that "the scan is partial" says what it did check.
168
+ */
169
+ const PROJECT_PORT_SOURCES = "the port in this project's dev script and `.env` files, and the ports of earlier sessions";
161
170
  /**
162
171
  * How to say "open the app" to an agent that may have no `reticle` binary.
163
172
  *
@@ -178,15 +187,16 @@ const INIT_CMD = '`npx @reticlehq/server init`';
178
187
  * against it, and it has to be read before the "nothing is listening" sentence rather than after —
179
188
  * the reader acts on the first claim.
180
189
  */
181
- function slowListenerClause(facts) {
190
+ function slowListenerClause(facts, after = '') {
182
191
  const slow = facts.slowListeners ?? [];
183
- if (0 === slow.length)
192
+ if (0 === slow.length) {
184
193
  return '';
194
+ }
185
195
  const ports = slow.join(', ');
186
196
  const subject = 1 === slow.length ? `Port ${ports} ACCEPTED` : `Ports ${ports} ACCEPTED`;
187
197
  return (`${subject} a connection but did not answer in time, which is what a server-rendered dev ` +
188
198
  'server compiling its first route looks like — so something IS running there and you should ' +
189
- 'open it rather than start it. Setting that aside: ');
199
+ `open it rather than start it.${after}`);
190
200
  }
191
201
  /**
192
202
  * The commonest first-run state there is (#320).
@@ -216,6 +226,22 @@ function openTheApp(facts) {
216
226
  function orOpenCommand(facts) {
217
227
  return true === facts.desktop ? '' : ` (or run ${OPEN_CMD_BARE})`;
218
228
  }
229
+ /**
230
+ * The start advice's lead when processes in this folder listen. Empty when none do.
231
+ *
232
+ * Names ports and no URL. It ends where the caller's "start it" begins, so an agent whose listeners
233
+ * are only its own tools still gets the app started.
234
+ */
235
+ function folderListenerClause(facts) {
236
+ const ports = facts.folderListeners ?? [];
237
+ if (0 === ports.length) {
238
+ return '';
239
+ }
240
+ const named = ports.map((port) => `:${String(port)}`).join(', ');
241
+ return (`No port Reticle scans serves a page, but processes in this folder listen on ${named}. ` +
242
+ 'Reticle cannot tell if one is this app, because agent tools listen here too. ' +
243
+ 'If one of them IS this app, open its URL. If none is, ');
244
+ }
219
245
  /**
220
246
  * What a machine-wide port scan can and cannot say.
221
247
  *
@@ -224,7 +250,14 @@ function orOpenCommand(facts) {
224
250
  * is up and never dialled" is unsound: those ports can belong to anything, and the app's own port
225
251
  * may not be in the scanned set at all.
226
252
  */
227
- function unattributedListeners(listening) {
253
+ function unattributedListeners(facts) {
254
+ const { listening } = facts;
255
+ const folder = facts.folderListeners ?? [];
256
+ if (0 < folder.length && 0 === listening.length) {
257
+ const named = folder.map((port) => `:${String(port)}`).join(', ');
258
+ return (`No port the scan covers serves a page, but processes in this folder listen on ${named}. ` +
259
+ 'Reticle cannot tell if one is this app, because agent tools listen here too.');
260
+ }
228
261
  if (0 === listening.length) {
229
262
  return (`No listener found that I can attribute to this project: the scan covers ${SCANNED_PORTS} ` +
230
263
  'across the whole machine, so an app on any other port is invisible to it.');
@@ -242,8 +275,9 @@ function unattributedListeners(listening) {
242
275
  */
243
276
  function portMismatchClause(facts) {
244
277
  const configured = facts.projectPort;
245
- if (configured === undefined || configured === facts.port)
278
+ if (configured === undefined || configured === facts.port) {
246
279
  return '';
280
+ }
247
281
  return (` They already disagree: \`.reticle.json\` says port ${String(configured)} and this daemon is` +
248
282
  ` on ${String(facts.port)}, so the SDK is dialling ${String(configured)} and nothing is there.` +
249
283
  ` Either restart the daemon on ${String(configured)}, or update \`.reticle.json\` to` +
@@ -258,8 +292,9 @@ function portMismatchClause(facts) {
258
292
  */
259
293
  function siblingListenerClause(facts) {
260
294
  const occupied = facts.siblingListeners ?? [];
261
- if (0 === occupied.length)
295
+ if (0 === occupied.length) {
262
296
  return '';
297
+ }
263
298
  const mismatch = facts.projectPort;
264
299
  const extra = mismatch !== undefined && mismatch !== facts.port
265
300
  ? occupied.filter((port) => port !== mismatch)
@@ -291,8 +326,9 @@ function rankedCauses(facts) {
291
326
  */
292
327
  function configsElsewhereClause(facts) {
293
328
  const configs = facts.configsElsewhere ?? [];
294
- if (0 === configs.length)
329
+ if (0 === configs.length) {
295
330
  return '';
331
+ }
296
332
  const named = configs
297
333
  .map((c) => (c.projectId === undefined ? c.directory : `${c.directory} ('${c.projectId}')`))
298
334
  .join(', ');
@@ -303,10 +339,12 @@ function configsElsewhereClause(facts) {
303
339
  /** Where we looked, when we found nothing. An absence nobody can check is not evidence. */
304
340
  function searchedClause(facts) {
305
341
  const searched = facts.searchedDirectories ?? [];
306
- if (0 === searched.length)
342
+ if (0 === searched.length) {
307
343
  return '';
308
- if ((facts.configsElsewhere ?? []).length > 0)
344
+ }
345
+ if ((facts.configsElsewhere ?? []).length > 0) {
309
346
  return '';
347
+ }
310
348
  return ` I looked in: ${searched.join(', ')}. If your app is not in that list, that is the answer — this daemon is standing somewhere else.`;
311
349
  }
312
350
  /**
@@ -318,20 +356,24 @@ function searchedClause(facts) {
318
356
  */
319
357
  function stallClause(facts) {
320
358
  const { daemonUpMs, initialized, everConnected } = facts;
321
- if (everConnected)
359
+ if (everConnected) {
322
360
  return '';
323
- if (!initialized)
361
+ }
362
+ if (!initialized) {
324
363
  return '';
325
- if (daemonUpMs === undefined || daemonUpMs < STALL_AFTER_MS)
364
+ }
365
+ if (daemonUpMs === undefined || daemonUpMs < STALL_AFTER_MS) {
326
366
  return '';
367
+ }
327
368
  const minutes = Math.round(daemonUpMs / 60_000);
328
369
  return (`This daemon has been running for ${String(minutes)} minutes and no app has connected. ` +
329
370
  'The most likely cause is that the dev server was not restarted after adding the Reticle ' +
330
371
  'plugin, so the running bundle carries no SDK. Restart the dev server first. ');
331
372
  }
332
373
  function alreadyListeningClause(listening) {
333
- if (0 === listening.length)
374
+ if (0 === listening.length) {
334
375
  return '';
376
+ }
335
377
  if (1 === listening.length) {
336
378
  const port = listening[0];
337
379
  return (` Something is serving a page on :${String(port)}; it may not be this app — just open ` +
@@ -450,9 +492,9 @@ export function explainNoSession(facts) {
450
492
  // wiring is unproven, printed by a daemon holding proof that it is.
451
493
  if (true === facts.previouslyConnected) {
452
494
  const listeners = 0 === listening.length
453
- ? `${unattributedListeners(listening)} If the dev server is not running, start it first ` +
495
+ ? `${unattributedListeners(facts)} If the dev server is not running, start it first ` +
454
496
  '(the command is in `next_action`).'
455
- : unattributedListeners(listening);
497
+ : unattributedListeners(facts);
456
498
  return reason(NoSessionReason.APP_NOT_REOPENED, `${RESTARTED_LEAD} ${openTheApp(facts)} ${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`, `${DO_NOT_REINSTALL} ${listeners} ${rankedCauses(facts)}`);
457
499
  }
458
500
  // A config found elsewhere outranks every "you may have no SDK" branch below: those reason from
@@ -461,7 +503,7 @@ export function explainNoSession(facts) {
461
503
  if (!initialized && (facts.configsElsewhere ?? []).length > 0) {
462
504
  return reason(NoSessionReason.CONFIG_ELSEWHERE, 'no browser session connected, and this daemon has never seen one. ' +
463
505
  `${configsElsewhereClause(facts)} ${openTheApp(facts)} ${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`, 'This daemon was started somewhere that is not the project, which is the normal outcome when ' +
464
- `an editor launches it from your home directory. ${unattributedListeners(listening)} ` +
506
+ `an editor launches it from your home directory. ${unattributedListeners(facts)} ` +
465
507
  `${rankedCauses(facts)}`);
466
508
  }
467
509
  if (0 === listening.length) {
@@ -481,28 +523,35 @@ export function explainNoSession(facts) {
481
523
  // is expected rather than diagnostic.
482
524
  if (!initialized) {
483
525
  return reason(NoSessionReason.NO_LISTENER_NO_CONFIG, 'no browser session connected. Two things to weigh, and neither of them is proof. ' +
484
- `(1) ${slowListenerClause(facts)}Nothing is listening on the ports Reticle scans, so the ` +
485
- 'dev server may not be running: START IT YOURSELF, in the background, using the command ' +
526
+ `(1) ${slowListenerClause(facts, ' Setting that aside: ')}${folderListenerClause(facts) ||
527
+ 'Nothing is listening on the ports Reticle scans, so the dev server may not be running: '}START IT YOURSELF, in the background, using the command ` +
486
528
  'in `next_action`, and tell the human in one line that it is running. ' +
487
529
  `(2) There is no \`.reticle.json\` in ${where}, which is the file ${INIT_CMD} writes, so ` +
488
530
  `the app may carry no Reticle SDK. Check the app's OWN directory before re-running it. ` +
489
- `${ZERO_INSTALL} ${RETRY}`, `The scan covers ${SCANNED_PORTS} and nothing else, so a server on any other port is ` +
531
+ `${ZERO_INSTALL} ${RETRY}`, `The scan covers ${SCANNED_PORTS}, plus ${PROJECT_PORT_SOURCES}, and nothing else, so a server on any other port is ` +
490
532
  'invisible to it: if the app IS running, ask for its URL rather than assuming it is down, ' +
491
533
  `and open it with ${OPEN_CMD}. The missing config is not proof either — in a monorepo the ` +
492
534
  'daemon often runs at the root while the app lives in a subdirectory, and an app wired by ' +
493
535
  'the Vite or Babel plugin carries the SDK without that file at all.' +
494
536
  `${searchedClause(facts)}${siblingListenerClause(facts)} ${leaseAdvice(URL_THEN_LEASE, facts)}`);
495
537
  }
538
+ // A wired project whose own port accepted a connection and answered nothing yet is compiling,
539
+ // not down. "Start it yourself" here starts a second dev server.
540
+ if ((facts.slowListeners ?? []).length > 0) {
541
+ return reason(NoSessionReason.NO_LISTENER, `${stallClause(facts)}no browser session connected, and this daemon has never seen one. ` +
542
+ `${slowListenerClause(facts)} Do not start a second dev server. ` +
543
+ `Wait for the first compile to finish, then open the app (the command is in \`next_action\`). ${RETRY}`, `${leaseAdvice(URL_THEN_LEASE, facts)}${siblingListenerClause(facts)}`);
544
+ }
496
545
  return reason(NoSessionReason.NO_LISTENER, `${stallClause(facts)}no browser session connected, and this daemon has never seen one. ${openTheApp(facts)} ` +
497
- 'Nothing is listening on the ports Reticle scans either, and the usual reason is a dev ' +
498
- 'server that is not running: start it yourself in the background with the command in ' +
546
+ `${folderListenerClause(facts) ||
547
+ 'Nothing is listening on the ports Reticle scans either, and the usual reason is a dev server that is not running: '}start it yourself in the background with the command in ` +
499
548
  '`next_action`, tell the human in one line that it is running, then open the app in a ' +
500
549
  `browser. ${RETRY}`,
501
550
  // The caveat is in the detail rather than omitted because the scan is NARROW: an empty result
502
551
  // is not proof of absence. Reported twice, once against a server answering 200 on :7699 and
503
552
  // once against one on :5000 under a custom hostname. The common defaults are in the scanned
504
553
  // set, which narrows the gap and cannot close it — anything passed to `--port` is invisible.
505
- `The scan covers ${SCANNED_PORTS} and nothing else, so it is not proof: a server on any other ` +
554
+ `The scan covers ${SCANNED_PORTS}, plus ${PROJECT_PORT_SOURCES}, and nothing else, so it is not proof: a server on any other ` +
506
555
  'port is invisible to it. If the app IS running, ask the human for its URL rather than ' +
507
556
  // Deliberately NOT offering reticle_lease here, and a test pins that: a lease opens a URL, and
508
557
  // if nothing is listening there is nothing at any URL to open. Asking for the real one is the
@@ -519,11 +568,11 @@ export function explainNoSession(facts) {
519
568
  'there and restart the dev server; if it has one, the app is wired and simply has no page ' +
520
569
  `open — ${OPEN_CMD}. ${ZERO_INSTALL} ${RETRY}`, 'That absence is not proof: the same one is expected in a monorepo whose daemon runs at the ' +
521
570
  'root while the app lives in a subdirectory, or in an app wired by the Vite or Babel ' +
522
- `plugin. ${unattributedListeners(listening)}${searchedClause(facts)}${siblingListenerClause(facts)}`);
571
+ `plugin. ${unattributedListeners(facts)}${searchedClause(facts)}${siblingListenerClause(facts)}`);
523
572
  }
524
573
  return reason(NoSessionReason.SDK_NOT_REACHING_DAEMON, `${stallClause(facts)}no browser session connected, and this daemon has never seen one for this project, which is ` +
525
574
  `wired for Reticle. ${openTheApp(facts)} If the page IS open and still does not appear, the SDK ` +
526
- `is not reaching this daemon (on ${String(port)}). ${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`, `${unattributedListeners(listening)} ${rankedCauses(facts)}`);
575
+ `is not reaching this daemon (on ${String(port)}). ${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`, `${unattributedListeners(facts)} ${rankedCauses(facts)}`);
527
576
  }
528
577
  /**
529
578
  * Pair a branch's verdict with its prose, so neither can be produced without the other.
@@ -30,6 +30,8 @@ interface NextActionFacts {
30
30
  listening: readonly number[];
31
31
  /** Scanned ports that serve a page from a process that cannot be tied to this project. */
32
32
  otherListeners?: readonly number[];
33
+ /** Ports that processes in this folder listen on. See `NoSessionFacts.folderListeners`. */
34
+ folderListeners?: readonly number[];
33
35
  dev: DevCommand | undefined;
34
36
  /**
35
37
  * File-existence predicate for the project root, pure for testing.
@@ -102,6 +104,11 @@ interface NextActionFacts {
102
104
  * project's own runner (see `initCommandIn`). Absent means the generic npx spelling.
103
105
  */
104
106
  init?: string;
107
+ /**
108
+ * Several direct child folders are apps with a dev script, and this folder has no package.json.
109
+ * Absent or short means there is no choice to hand the agent. See `childDevApps`.
110
+ */
111
+ childApps?: readonly string[];
105
112
  }
106
113
  /** The port a tombstone URL names, when it names one — anything unparseable names no port. */
107
114
  export declare function portOfUrl(url: string): number | undefined;
@@ -123,6 +130,13 @@ export declare const INIT_COMMAND = "npx @reticlehq/server init";
123
130
  * agent asks first. The lease changes nothing, so it needs no question.
124
131
  */
125
132
  export declare function wiringConsent(init?: string): string;
133
+ /**
134
+ * The offer for the first session in an unwired web app: check it now with the lease, then ask
135
+ * before `init`. The one text for the plugin's SessionStart hook and the first tool call of each
136
+ * agent connection. The hook cannot import this, so it keeps a copy, and
137
+ * `plugin-init-nudge.test.ts` fails when the two are not the same.
138
+ */
139
+ export declare function firstSessionOffer(init?: string): string;
126
140
  /** The zero-install call, with the URL only when it can be named without guessing. */
127
141
  export declare function zeroInstallLease(url: string | undefined): string;
128
142
  export declare function nextActionFor(facts: NextActionFacts): NoSessionNextAction;
@@ -18,8 +18,9 @@ import { detectNonJsEcosystem, noPackageJsonMessage } from '@reticlehq/init';
18
18
  export function portOfUrl(url) {
19
19
  try {
20
20
  const raw = new URL(url).port;
21
- if ('' === raw)
21
+ if ('' === raw) {
22
22
  return undefined;
23
+ }
23
24
  const port = Number(raw);
24
25
  return Number.isSafeInteger(port) && port > 0 ? port : undefined;
25
26
  }
@@ -45,8 +46,9 @@ export function departedPortGone(facts) {
45
46
  * for a url that does not parse, or one with no port to name (a desktop webview's opaque origin).
46
47
  */
47
48
  function reopenableUrl(raw) {
48
- if (raw === undefined)
49
+ if (raw === undefined) {
49
50
  return undefined;
51
+ }
50
52
  let url;
51
53
  try {
52
54
  url = new URL(raw);
@@ -54,10 +56,12 @@ function reopenableUrl(raw) {
54
56
  catch {
55
57
  return undefined;
56
58
  }
57
- if ('http:' !== url.protocol && 'https:' !== url.protocol)
59
+ if ('http:' !== url.protocol && 'https:' !== url.protocol) {
58
60
  return undefined;
59
- for (const param of Object.values(RETICLE_URL_PARAM))
61
+ }
62
+ for (const param of Object.values(RETICLE_URL_PARAM)) {
60
63
  url.searchParams.delete(param);
64
+ }
61
65
  const port = '' === url.port ? ('https:' === url.protocol ? 443 : 80) : Number(url.port);
62
66
  return { url: url.toString(), port };
63
67
  }
@@ -78,6 +82,17 @@ export function wiringConsent(init = INIT_COMMAND) {
78
82
  'build config and starts the dev server. Ask the user before you wire the app. If they ' +
79
83
  `agree, run \`${init}\` in the app's folder. If they decline, do not ask again.`);
80
84
  }
85
+ /**
86
+ * The offer for the first session in an unwired web app: check it now with the lease, then ask
87
+ * before `init`. The one text for the plugin's SessionStart hook and the first tool call of each
88
+ * agent connection. The hook cannot import this, so it keeps a copy, and
89
+ * `plugin-init-nudge.test.ts` fails when the two are not the same.
90
+ */
91
+ export function firstSessionOffer(init = INIT_COMMAND) {
92
+ return (`This web app can be checked now with no install: \`${zeroInstallLease(undefined)}\` opens ` +
93
+ "the running app in Reticle's own browser and changes no file. To keep Reticle wired to this " +
94
+ `app: ${wiringConsent(init)}`);
95
+ }
81
96
  const OPEN_COMMAND = `${CLI} open`;
82
97
  const LOCALHOST = 'http://localhost';
83
98
  /**
@@ -99,7 +114,15 @@ function neverWired(facts) {
99
114
  * The start advice's first claim when no port is this project's. A scan that heard a page it could
100
115
  * not tie to this project says so, because "nothing is listening" is then false.
101
116
  */
102
- function notThisProject(others) {
117
+ function notThisProject(others, folder = []) {
118
+ // First: the folder is nearer to this app than a machine-wide scan. It is still no URL to hand
119
+ // over, because agent tools listen in this folder too (reticle#1543).
120
+ if (0 < folder.length) {
121
+ const ports = folder.map((port) => `:${String(port)}`).join(', ');
122
+ return (`processes in this folder listen on ${ports}, and Reticle cannot tell if one is this app, ` +
123
+ 'because agent tools listen here too — if one of them IS this app, open it instead of ' +
124
+ 'starting a second one, ');
125
+ }
103
126
  if (others === undefined || 0 === others.length) {
104
127
  return 'nothing is listening on the ports Reticle scans, so the app is probably not running — ';
105
128
  }
@@ -243,6 +266,19 @@ export function nextActionFor(facts) {
243
266
  'args: { action: "acquire", url, projectId } }, or restart the daemon from the app\'s own directory.',
244
267
  };
245
268
  }
269
+ // A wired project whose only port accepted a connection and answered nothing yet: a dev server
270
+ // compiling its first route. It is up, so a start command would start a second one.
271
+ const compiling = facts.slowListeners?.[0];
272
+ if (true === facts.initialized && 0 === facts.listening.length && compiling !== undefined) {
273
+ return {
274
+ action: NoSessionAction.OPEN_APP,
275
+ command: `${OPEN_COMMAND} ${LOCALHOST}:${String(compiling)}`,
276
+ port: compiling,
277
+ reason: `port ${String(compiling)} accepted a connection but did not answer in time, which is a dev ` +
278
+ 'server compiling its first route. The app IS running. Do not start a second dev server. ' +
279
+ 'Wait for the compile to finish, then open it.',
280
+ };
281
+ }
246
282
  if (0 === facts.listening.length || departedPortIsGone) {
247
283
  const dev = facts.dev;
248
284
  // The start advice below follows a departed session whose port is gone. Name it, so the restart
@@ -251,10 +287,23 @@ export function nextActionFor(facts) {
251
287
  const stoppedNote = stopped === undefined
252
288
  ? ''
253
289
  : ` The session that was on ${redactUrl(stopped.url)} is gone and nothing listens there now.`;
254
- const notRunning = departedPortIsGone
290
+ const folder = facts.folderListeners ?? [];
291
+ const notRunning = departedPortIsGone && 0 === folder.length
255
292
  ? "nothing answers on the port this project's app was last on, so the app is probably not running — "
256
- : notThisProject(facts.otherListeners);
293
+ : notThisProject(facts.otherListeners, folder);
257
294
  if (dev === undefined) {
295
+ const childApps = facts.childApps ?? [];
296
+ if (1 < childApps.length) {
297
+ return {
298
+ action: NoSessionAction.START_DEV_SERVER,
299
+ reason: notRunning +
300
+ `but this folder has no package.json, and ${childApps.length} child folders are apps ` +
301
+ `with a dev script: ${childApps.join(', ')}. Reticle cannot tell which one you mean, so ` +
302
+ 'there is no command to hand you. Pick one, then run Reticle from that folder or ask ' +
303
+ 'the human which app to start.' +
304
+ stoppedNote,
305
+ };
306
+ }
258
307
  if (facts.exists !== undefined && !facts.exists('package.json')) {
259
308
  const ecosystem = detectNonJsEcosystem(facts.exists);
260
309
  if (ecosystem !== undefined) {
@@ -74,6 +74,12 @@ interface NoSessionWatchOptions {
74
74
  * to it. Injected for tests; a test that injects `probe` and omits this observes no folder.
75
75
  */
76
76
  listenerDirectory?: (port: number) => string | undefined;
77
+ /**
78
+ * Every TCP port on this machine with a listener (reticle#1543).
79
+ *
80
+ * Injected for tests; a test that injects `probe` and omits this observes no listener.
81
+ */
82
+ listeningPorts?: () => readonly number[];
77
83
  /**
78
84
  * What the route the last session was on answers right now, as an HTTP status, or undefined
79
85
  * when there is no answer to report.
@@ -13,11 +13,12 @@ import { probeRouteStatus } from './dev-server/route-status-probe.js';
13
13
  import { probeDevServerStates } from './dev-server/dev-server-probe.js';
14
14
  import { existsSync } from 'node:fs';
15
15
  import { homedir } from 'node:os';
16
- import { join, resolve } from 'node:path';
16
+ import { join, parse, resolve } from 'node:path';
17
17
  import { callingClientDirectory } from '../../hooks/client-directory.js';
18
18
  import { registeredElsewhere } from '../../memory/recall/registered-projects.js';
19
19
  import { explainNoSession } from './no-session-diagnosis.js';
20
- import { detectDevCommandInProject, initCommandIn } from './dev-server/dev-command.js';
20
+ import { childDevApps, detectDevCommandInProject, initCommandIn, } from './dev-server/dev-command.js';
21
+ import { envFilePorts } from './dev-server/project-ports.js';
21
22
  import { nextActionFor, portOfUrl, renderNextAction } from './no-session-next-action.js';
22
23
  import { DEV_SERVER_PORTS, readProjectFramework, readProjectId, readProjectIsDesktop, readProjectPort, } from '../../command/cli/ports/resolve/cli-port.js';
23
24
  import { discoverProjectConfigs } from '../../command/cli/config/config-discovery.js';
@@ -27,10 +28,11 @@ import { daemonsServingProjectElsewhere, projectDaemonsElsewhere, projectlessNot
27
28
  import { stallUptime } from './stall-clock.js';
28
29
  import { probeDaemon } from '../../surface/mcp/mcp-proxy.js';
29
30
  import { findOccupiedSiblings } from '../../command/cli/ports/sibling-ports.js';
31
+ import { captureLookup, findListeningPorts } from '../../command/cli/ports/port-holder.js';
30
32
  import { isAuthRefusalReason } from '../bridge/auth-failure-reason.js';
31
33
  import { devServersForProject } from '@reticlehq/core';
32
34
  import { readDevServers } from '../../command/daemon/dev-servers.js';
33
- import { servingDirectoryOf } from './serving-directory.js';
35
+ import { listenerFolders, servingDirectoryOf } from './serving-directory.js';
34
36
  import { within } from './zero-install-first-call.js';
35
37
  /** Slow enough to be free, fast enough that a dev server started 15s ago is already reflected. */
36
38
  const REFRESH_MS = 15_000;
@@ -77,6 +79,16 @@ export function startNoSessionWatch(options) {
77
79
  (options.probe === undefined
78
80
  ? (port) => servingDirectoryOf(`${LOCALHOST}:${String(port)}`)
79
81
  : () => undefined);
82
+ const listeningPorts = options.listeningPorts ??
83
+ (options.probe === undefined
84
+ ? () => findListeningPorts(captureLookup)
85
+ : () => []);
86
+ /**
87
+ * The folder of each listener outside `listening`, `slowListeners` and the Reticle daemons.
88
+ *
89
+ * Beside `listenerDirs` and never in `listening`: see `NoSessionFacts.folderListeners`.
90
+ */
91
+ let unscannedFolders = new Map();
80
92
  let siblingListeners = [];
81
93
  /**
82
94
  * What the last-known route answered, cached beside the port scan for the same reason the scan
@@ -163,7 +175,7 @@ export function startNoSessionWatch(options) {
163
175
  if (projectId === undefined) {
164
176
  return projectlessNote(options.port, projectDaemonsElsewhere(options.port, stateDir, isAlive));
165
177
  }
166
- return splitBrainNote(options.port, daemonsServingProjectElsewhere(projectId, options.port, stateDir, isAlive, (port) => hasProjectConnectedBefore(stateDir, port, projectId)));
178
+ return splitBrainNote(options.port, daemonsServingProjectElsewhere(projectId, options.port, stateDir, isAlive, (port) => hasProjectConnectedBefore(stateDir, port, projectId)), { projectId, home: stateDir });
167
179
  };
168
180
  // Every path that registers a session goes through SessionManager.add, so this is the one hook
169
181
  // that makes the bit durable. Recorded per port + projectId so a shared 4400 cannot make one
@@ -208,6 +220,10 @@ export function startNoSessionWatch(options) {
208
220
  if (pinned !== undefined) {
209
221
  ports.add(pinned);
210
222
  }
223
+ // A script that reads its port from `.env` pins nothing in `package.json` (reticle#1543).
224
+ for (const port of envFilePorts(directory)) {
225
+ ports.add(port);
226
+ }
211
227
  const known = options.sessions.lastKnown?.();
212
228
  const knownPort = known === undefined ? undefined : portOfUrl(known.url);
213
229
  if (knownPort !== undefined) {
@@ -223,6 +239,19 @@ export function startNoSessionWatch(options) {
223
239
  }
224
240
  return [...ports];
225
241
  };
242
+ /**
243
+ * The ports that processes in `dir` listen on, without the ones in `shown`.
244
+ *
245
+ * Nothing for the home folder or a filesystem root. A daemon an editor starts there holds every
246
+ * process on the machine inside its "folder", so the answer would prove nothing.
247
+ */
248
+ const folderListeners = (dir, shown) => {
249
+ const folder = resolve(dir);
250
+ if (folder === homedir() || folder === parse(folder).root) {
251
+ return [];
252
+ }
253
+ return [...unscannedFolders.entries()].flatMap(([port, cwd]) => within(cwd, folder) && !shown.includes(port) ? [port] : []);
254
+ };
226
255
  /**
227
256
  * Is `port` demonstrably this project's app? Either its dev server announced it, or this
228
257
  * project's own last session was on it. Anything else is a guess, and a guess that opens a
@@ -300,6 +329,8 @@ export function startNoSessionWatch(options) {
300
329
  : options.probe === undefined
301
330
  ? await findOccupiedSiblings(options.port, probeDaemon)
302
331
  : [];
332
+ const reported = new Set([options.port, ...siblingListeners, ...ports, ...slowListeners]);
333
+ unscannedFolders = listenerFolders(listeningPorts().filter((port) => !reported.has(port)), unscannedFolders, listenerDirectory);
303
334
  // Asked once per departed URL, on the same background cadence as the port scan, and never
304
335
  // on the hint's own path. A url already answered is not asked again; a new departure is.
305
336
  const known = options.sessions.lastKnown?.();
@@ -447,6 +478,7 @@ export function startNoSessionWatch(options) {
447
478
  : {}),
448
479
  listening: heard,
449
480
  slowListeners: slow,
481
+ folderListeners: folderListeners(dir, heard),
450
482
  port: options.port,
451
483
  directory: dir,
452
484
  previouslyConnected,
@@ -473,6 +505,7 @@ export function startNoSessionWatch(options) {
473
505
  listening: heard,
474
506
  ...(0 === others.length ? {} : { otherListeners: others }),
475
507
  slowListeners: slow,
508
+ folderListeners: folderListeners(dir, heard),
476
509
  ...(known === undefined
477
510
  ? {}
478
511
  : {
@@ -480,6 +513,7 @@ export function startNoSessionWatch(options) {
480
513
  ...(known.departedTo === undefined ? {} : { departedTo: known.departedTo }),
481
514
  }),
482
515
  dev: detectDevCommandInProject(dir),
516
+ childApps: childDevApps(dir),
483
517
  init: initCommandIn(dir),
484
518
  });
485
519
  return { facts, next };
@@ -525,6 +559,7 @@ export function startNoSessionWatch(options) {
525
559
  ...(split === undefined ? {} : { splitBrain: split }),
526
560
  listening: observedListening(),
527
561
  slowListeners,
562
+ folderListeners: folderListeners(directory, observedListening()),
528
563
  // The url the departed tab was on — the same tombstone the prose diagnosis quotes, so the
529
564
  // command and the sentence beside it name the same page.
530
565
  ...(() => {
@@ -540,6 +575,7 @@ export function startNoSessionWatch(options) {
540
575
  // Read when asked, like everything else here: a `package.json` can gain a dev script, and a
541
576
  // daemon that cached "there is none" at boot would keep saying so for the rest of the day.
542
577
  dev: detectDevCommandInProject(directory),
578
+ childApps: childDevApps(directory),
543
579
  // This project's own runner and app folder, so the agent can wire it itself.
544
580
  init: initCommandIn(directory),
545
581
  });
@@ -568,6 +604,7 @@ export function startNoSessionWatch(options) {
568
604
  ...scope,
569
605
  listening: observedListening(),
570
606
  slowListeners,
607
+ folderListeners: folderListeners(directory, observedListening()),
571
608
  port: options.port,
572
609
  // The directory `initialized` was decided in. Named in the message because "there is no
573
610
  // `.reticle.json`" is a claim about ONE directory, and a reader standing somewhere else
@@ -18,8 +18,14 @@
18
18
  * answers rather than a set lookup at the call site.
19
19
  */
20
20
  import { type CommandResult } from '@reticlehq/core';
21
- /** Why nothing was sent, in a sentence, or undefined when the command should go. */
22
- export declare function refusalForUndeclared(name: string, declared: readonly string[] | undefined): string | undefined;
21
+ /**
22
+ * Why nothing was sent, in a sentence, or undefined when the command should go.
23
+ *
24
+ * `behind` is the caller's sentence for a page SDK older than this daemon. A newer command
25
+ * (`render`, `match`) is then missing because of the version, and the list of commands alone
26
+ * does not say that.
27
+ */
28
+ export declare function refusalForUndeclared(name: string, declared: readonly string[] | undefined, behind?: string): string | undefined;
23
29
  /**
24
30
  * The refusal as a finished result, or undefined when the command should be sent.
25
31
  *
@@ -27,4 +33,4 @@ export declare function refusalForUndeclared(name: string, declared: readonly st
27
33
  * cannot get the envelope subtly wrong -- an `ok: true` refusal would be read as a command that
28
34
  * worked, which is the one shape this must never produce.
29
35
  */
30
- export declare function refusedResult(name: string, declared: readonly string[] | undefined, id: string): CommandResult | undefined;
36
+ export declare function refusedResult(name: string, declared: readonly string[] | undefined, id: string, behind?: string): CommandResult | undefined;
@@ -18,16 +18,25 @@
18
18
  * answers rather than a set lookup at the call site.
19
19
  */
20
20
  import { MessageKind } from '@reticlehq/core';
21
- /** Why nothing was sent, in a sentence, or undefined when the command should go. */
22
- export function refusalForUndeclared(name, declared) {
21
+ /**
22
+ * Why nothing was sent, in a sentence, or undefined when the command should go.
23
+ *
24
+ * `behind` is the caller's sentence for a page SDK older than this daemon. A newer command
25
+ * (`render`, `match`) is then missing because of the version, and the list of commands alone
26
+ * does not say that.
27
+ */
28
+ export function refusalForUndeclared(name, declared, behind) {
23
29
  // Too old to say. Unknown, never "nothing".
24
- if (declared === undefined)
30
+ if (declared === undefined) {
25
31
  return undefined;
26
- if (declared.includes(name))
32
+ }
33
+ if (declared.includes(name)) {
27
34
  return undefined;
35
+ }
28
36
  return (`this page does not serve "${name}". It declared ${String(declared.length)} command(s) when ` +
29
37
  `it connected, and this is not one of them: ${declared.join(', ')}. Nothing was sent, so ` +
30
- 'nothing happened -- this is a refusal, not a failure of the app.');
38
+ 'nothing happened -- this is a refusal, not a failure of the app.' +
39
+ (behind === undefined ? '' : ` ${behind}`));
31
40
  }
32
41
  /**
33
42
  * The refusal as a finished result, or undefined when the command should be sent.
@@ -36,8 +45,8 @@ export function refusalForUndeclared(name, declared) {
36
45
  * cannot get the envelope subtly wrong -- an `ok: true` refusal would be read as a command that
37
46
  * worked, which is the one shape this must never produce.
38
47
  */
39
- export function refusedResult(name, declared, id) {
40
- const error = refusalForUndeclared(name, declared);
48
+ export function refusedResult(name, declared, id, behind) {
49
+ const error = refusalForUndeclared(name, declared, behind);
41
50
  return error === undefined
42
51
  ? undefined
43
52
  : { kind: MessageKind.COMMAND_RESULT, id, ok: false, error };
@@ -49,7 +49,7 @@ export declare function probeMarkers(tabs: readonly ProbedTab[]): Promise<boolea
49
49
  /**
50
50
  * The `engineBlindSpots` field for a verdict result: what this engine could not observe in the
51
51
  * session so far. Notes this verdict's own comparison first, so a `compare` clears the arithmetic
52
- * spot on the result that ran it. `claims`: the inline intent, then the run's declared intents.
52
+ * spot on the result that ran it. `claims`: the inline intent, or else the run's declared intents.
53
53
  */
54
54
  export declare function engineBlindSpotsField(sessionId: string, predicate: Predicate | undefined, intent: unknown, declared: readonly {
55
55
  statement: string;
@@ -162,12 +162,15 @@ const comparedSeen = new Set();
162
162
  /**
163
163
  * The `engineBlindSpots` field for a verdict result: what this engine could not observe in the
164
164
  * session so far. Notes this verdict's own comparison first, so a `compare` clears the arithmetic
165
- * spot on the result that ran it. `claims`: the inline intent, then the run's declared intents.
165
+ * spot on the result that ran it. `claims`: the inline intent, or else the run's declared intents.
166
166
  */
167
167
  export function engineBlindSpotsField(sessionId, predicate, intent, declared) {
168
168
  if (computesComparison(predicate))
169
169
  comparedSeen.add(sessionId);
170
- const claims = [text(intent), ...declared.map((each) => each.statement)];
170
+ // A call that names its own intent is judged on that intent alone. The run's open intents can be
171
+ // an earlier task's, and judging them reported that task's claim on this one (#1938).
172
+ const own = text(intent);
173
+ const claims = own === undefined ? declared.map((each) => each.statement) : [own];
171
174
  return {
172
175
  [ENGINE_BLIND_SPOTS_FIELD]: engineBlindSpots({
173
176
  canvasSeen: canvasSeen.has(sessionId),