@reticlehq/server 3.6.0 → 3.7.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 (614) hide show
  1. package/SKILL.md +122 -123
  2. package/dist/command/cli/adhoc-suite.d.ts +14 -0
  3. package/dist/command/cli/adhoc-suite.js +42 -7
  4. package/dist/command/cli/adhoc-verdict.d.ts +18 -2
  5. package/dist/command/cli/adhoc-verdict.js +70 -6
  6. package/dist/command/cli/answers/gate-exit.d.ts +23 -0
  7. package/dist/command/cli/answers/gate-exit.js +45 -0
  8. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  9. package/dist/command/cli/answers/gate-hook-message.js +3 -1
  10. package/dist/command/cli/auth/device-flow.d.ts +13 -2
  11. package/dist/command/cli/auth/device-flow.js +17 -0
  12. package/dist/command/cli/cli-doctor.js +14 -0
  13. package/dist/command/cli/cli-flow-commands.d.ts +0 -11
  14. package/dist/command/cli/cli-flow-commands.js +28 -105
  15. package/dist/command/cli/cli-kill.js +3 -2
  16. package/dist/command/cli/cli-parse-grammar.d.ts +24 -0
  17. package/dist/command/cli/cli-parse-grammar.js +75 -0
  18. package/dist/command/cli/cli-parse-verify.d.ts +2 -0
  19. package/dist/command/cli/cli-parse-verify.js +18 -0
  20. package/dist/command/cli/cli-parse.d.ts +19 -28
  21. package/dist/command/cli/cli-parse.js +82 -182
  22. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  23. package/dist/command/cli/cli-update-commands.js +61 -9
  24. package/dist/command/cli/cli-usage.d.ts +33 -0
  25. package/dist/command/cli/cli-usage.js +287 -0
  26. package/dist/command/cli/cli-verify.d.ts +34 -4
  27. package/dist/command/cli/cli-verify.js +81 -12
  28. package/dist/command/cli/cloud-cli.d.ts +13 -0
  29. package/dist/command/cli/cloud-cli.js +145 -333
  30. package/dist/command/cli/cloud-kit.d.ts +12 -0
  31. package/dist/command/cli/cloud-kit.js +41 -2
  32. package/dist/command/cli/cloud-link.d.ts +57 -0
  33. package/dist/command/cli/cloud-link.js +308 -0
  34. package/dist/command/cli/cloud-login.d.ts +15 -1
  35. package/dist/command/cli/cloud-login.js +67 -13
  36. package/dist/command/cli/context-command.d.ts +1 -0
  37. package/dist/command/cli/context-command.js +33 -0
  38. package/dist/command/cli/daemon-start-options.d.ts +3 -0
  39. package/dist/command/cli/daemon-start-options.js +24 -3
  40. package/dist/command/cli/doctor/doctor-coverage-line.d.ts +2 -0
  41. package/dist/command/cli/doctor/doctor-coverage-line.js +47 -0
  42. package/dist/command/cli/doctor/doctor-project-line.d.ts +0 -12
  43. package/dist/command/cli/doctor/doctor-project-line.js +5 -4
  44. package/dist/command/cli/doctor/doctor-rows.d.ts +6 -0
  45. package/dist/command/cli/doctor/doctor-rows.js +6 -0
  46. package/dist/command/cli/doctor/doctor-signing-line.d.ts +38 -0
  47. package/dist/command/cli/doctor/doctor-signing-line.js +114 -0
  48. package/dist/command/cli/known-regression.d.ts +13 -0
  49. package/dist/command/cli/known-regression.js +34 -0
  50. package/dist/command/cli/launch/sdk-version-match.d.ts +12 -0
  51. package/dist/command/cli/launch/sdk-version-match.js +25 -0
  52. package/dist/command/cli/lifecycle/daemon-lifecycle.js +11 -2
  53. package/dist/command/cli/mcp-command.d.ts +6 -0
  54. package/dist/command/cli/mcp-command.js +13 -5
  55. package/dist/command/cli/ports/port-holder.js +1 -1
  56. package/dist/command/cli/project-name.d.ts +29 -1
  57. package/dist/command/cli/project-name.js +107 -2
  58. package/dist/command/cli/setup-mcp-cli.d.ts +17 -1
  59. package/dist/command/cli/setup-mcp-cli.js +29 -8
  60. package/dist/command/cli/status/status-lines.d.ts +1 -0
  61. package/dist/command/cli/status/status-lines.js +47 -2
  62. package/dist/command/cli/status/workspace-links.d.ts +10 -0
  63. package/dist/command/cli/status/workspace-links.js +29 -0
  64. package/dist/command/cli/try-command.d.ts +96 -0
  65. package/dist/command/cli/try-command.js +362 -0
  66. package/dist/command/cli/tutorial.d.ts +0 -100
  67. package/dist/command/cli/tutorial.js +0 -178
  68. package/dist/command/cli/verify-results.d.ts +19 -0
  69. package/dist/command/cli/verify-results.js +29 -0
  70. package/dist/command/cli-onboarding.d.ts +0 -11
  71. package/dist/command/cli-onboarding.js +0 -15
  72. package/dist/command/cli.d.ts +14 -0
  73. package/dist/command/cli.js +103 -83
  74. package/dist/command/daemon/daemon-resilience.js +4 -4
  75. package/dist/command/daemon/daemon-resolve.d.ts +9 -1
  76. package/dist/command/daemon/daemon-resolve.js +49 -10
  77. package/dist/command/setup/bringup/bridge-port.d.ts +3 -1
  78. package/dist/command/setup/bringup/bridge-port.js +13 -3
  79. package/dist/command/setup/bringup/dev-server-log-cap.js +2 -0
  80. package/dist/command/setup/bringup/ensure-daemon.js +9 -2
  81. package/dist/command/setup/init/agent-restart.d.ts +23 -0
  82. package/dist/command/setup/init/agent-restart.js +28 -0
  83. package/dist/command/setup/init/init-port.d.ts +10 -1
  84. package/dist/command/setup/init/init-port.js +22 -2
  85. package/dist/command/setup/init/init-runtime.d.ts +12 -2
  86. package/dist/command/setup/init/init-runtime.js +96 -37
  87. package/dist/command/setup/node-effects.d.ts +2 -0
  88. package/dist/command/setup/node-effects.js +36 -8
  89. package/dist/command/setup/probe/existing-app.d.ts +12 -0
  90. package/dist/command/setup/probe/existing-app.js +14 -0
  91. package/dist/command/setup/remaining-steps.js +1 -1
  92. package/dist/command/setup/run-setup.d.ts +29 -9
  93. package/dist/command/setup/run-setup.js +34 -28
  94. package/dist/command/setup/setup-command.d.ts +23 -0
  95. package/dist/command/setup/setup-command.js +57 -4
  96. package/dist/command/setup/setup-mcp.js +25 -2
  97. package/dist/command/setup/tauri-signing.d.ts +19 -0
  98. package/dist/command/setup/tauri-signing.js +79 -0
  99. package/dist/command/setup/terminal/listeners.d.ts +10 -1
  100. package/dist/command/setup/terminal/listeners.js +37 -6
  101. package/dist/command/update/update-nudge.d.ts +9 -0
  102. package/dist/command/update/update-nudge.js +1 -1
  103. package/dist/command/update/updater.js +11 -15
  104. package/dist/command/version/version-nudge.js +3 -0
  105. package/dist/command/version/version-skew.d.ts +20 -1
  106. package/dist/command/version/version-skew.js +38 -2
  107. package/dist/features/crawl/crawl.js +14 -1
  108. package/dist/features/exhaust/ledger.d.ts +14 -14
  109. package/dist/features/harness/drive-report.d.ts +39 -5
  110. package/dist/features/harness/drive-report.js +271 -54
  111. package/dist/features/harness/drive-runs.d.ts +120 -0
  112. package/dist/features/harness/drive-runs.js +289 -0
  113. package/dist/features/harness/drivers.d.ts +1 -1
  114. package/dist/features/harness/drivers.js +1 -1
  115. package/dist/features/harness/explore/console-pages.d.ts +15 -0
  116. package/dist/features/harness/explore/console-pages.js +146 -0
  117. package/dist/features/harness/explore/disparity.d.ts +19 -0
  118. package/dist/features/harness/explore/disparity.js +71 -0
  119. package/dist/features/harness/explore/fake-app.d.ts +16 -0
  120. package/dist/features/harness/explore/fake-app.js +49 -0
  121. package/dist/features/harness/explore/goal-search.d.ts +26 -0
  122. package/dist/features/harness/explore/goal-search.js +144 -0
  123. package/dist/features/harness/explore/page-tree.d.ts +49 -0
  124. package/dist/features/harness/explore/page-tree.js +148 -0
  125. package/dist/features/harness/explore/site-map.d.ts +34 -0
  126. package/dist/features/harness/explore/site-map.js +150 -0
  127. package/dist/features/harness/goals.d.ts +68 -7
  128. package/dist/features/harness/goals.js +289 -36
  129. package/dist/features/harness/harness.d.ts +30 -1
  130. package/dist/features/harness/harness.js +141 -7
  131. package/dist/features/harness/platform/local-apps.d.ts +36 -3
  132. package/dist/features/harness/platform/local-apps.js +81 -5
  133. package/dist/features/harness/platform/persona-credentials.d.ts +20 -0
  134. package/dist/features/harness/platform/persona-credentials.js +69 -0
  135. package/dist/features/harness/platform/platform-drives.d.ts +61 -0
  136. package/dist/features/harness/platform/platform-drives.js +114 -0
  137. package/dist/features/harness/platform/refusal.d.ts +52 -0
  138. package/dist/features/harness/platform/refusal.js +71 -0
  139. package/dist/features/harness/platform/remote-drive.d.ts +8 -2
  140. package/dist/features/harness/platform/remote-drive.js +22 -9
  141. package/dist/features/harness/platform/script.d.ts +57 -11
  142. package/dist/features/harness/platform/script.js +144 -12
  143. package/dist/features/harness/platform/server-driver.d.ts +17 -4
  144. package/dist/features/harness/platform/server-driver.js +29 -26
  145. package/dist/features/harness/platform-config.d.ts +38 -5
  146. package/dist/features/harness/platform-config.js +93 -8
  147. package/dist/features/harness/route-guess.d.ts +13 -0
  148. package/dist/features/harness/route-guess.js +49 -0
  149. package/dist/features/harness/script/script-run.d.ts +30 -1
  150. package/dist/features/harness/script/script-run.js +355 -48
  151. package/dist/features/license/license.d.ts +2 -2
  152. package/dist/features/visual/step-shots.d.ts +31 -0
  153. package/dist/features/visual/step-shots.js +231 -0
  154. package/dist/features/visual/visual-tools.js +22 -14
  155. package/dist/hooks/client-directory.d.ts +18 -0
  156. package/dist/hooks/client-directory.js +57 -0
  157. package/dist/hooks/coding-agents.d.ts +34 -0
  158. package/dist/hooks/coding-agents.js +117 -0
  159. package/dist/hooks/driven-by.d.ts +21 -2
  160. package/dist/hooks/driven-by.js +42 -2
  161. package/dist/hooks/hook-bus.d.ts +4 -0
  162. package/dist/hooks/hook-bus.js +18 -0
  163. package/dist/index.d.ts +1 -0
  164. package/dist/index.js +26 -183
  165. package/dist/judgement/capsule/capsule-store.d.ts +2 -2
  166. package/dist/judgement/capsule/causal-summary.d.ts +15 -1
  167. package/dist/judgement/capsule/causal-summary.js +33 -0
  168. package/dist/judgement/runs/artifact/build-verification-run.d.ts +1 -1
  169. package/dist/judgement/runs/artifact/build-verification-run.js +20 -4
  170. package/dist/judgement/runs/artifact/gap-summary.d.ts +0 -18
  171. package/dist/judgement/runs/artifact/gap-summary.js +12 -3
  172. package/dist/judgement/runs/artifact/replay-mapping.js +25 -9
  173. package/dist/judgement/runs/artifact/run-store.d.ts +13 -1
  174. package/dist/judgement/runs/artifact/run-store.js +175 -8
  175. package/dist/judgement/runs/context-tools.js +59 -1
  176. package/dist/judgement/runs/default-run-id.d.ts +3 -0
  177. package/dist/judgement/runs/default-run-id.js +16 -0
  178. package/dist/judgement/runs/drive-run.d.ts +53 -5
  179. package/dist/judgement/runs/drive-run.js +234 -20
  180. package/dist/judgement/runs/resume.d.ts +44 -0
  181. package/dist/judgement/runs/resume.js +137 -0
  182. package/dist/judgement/runs/reticle-runner.js +5 -2
  183. package/dist/judgement/runs/runner-port.d.ts +5 -1
  184. package/dist/judgement/runs/runner-port.js +8 -2
  185. package/dist/judgement/runs/verification-sync.d.ts +2 -0
  186. package/dist/judgement/runs/verification-sync.js +36 -15
  187. package/dist/language/flows/decision.js +2 -1
  188. package/dist/language/flows/fields/flow-secret-field.d.ts +12 -0
  189. package/dist/language/flows/fields/flow-secret-field.js +25 -2
  190. package/dist/language/flows/flow-anchor.d.ts +7 -2
  191. package/dist/language/flows/flow-anchor.js +10 -3
  192. package/dist/language/flows/flow-intent.d.ts +1 -0
  193. package/dist/language/flows/flow-journey.d.ts +6 -0
  194. package/dist/language/flows/flow-journey.js +20 -0
  195. package/dist/language/flows/flow-learning.d.ts +4 -0
  196. package/dist/language/flows/flow-learning.js +37 -3
  197. package/dist/language/flows/flow-replay-run.js +16 -18
  198. package/dist/language/flows/flow-replay-types.d.ts +1 -1
  199. package/dist/language/flows/flow-replay.d.ts +18 -1
  200. package/dist/language/flows/flow-replay.js +22 -7
  201. package/dist/language/flows/flow-result.d.ts +22 -1
  202. package/dist/language/flows/flow-result.js +47 -2
  203. package/dist/language/flows/flow-step-runners.d.ts +2 -0
  204. package/dist/language/flows/flow-step-runners.js +12 -6
  205. package/dist/language/flows/flow-store-for-session.js +5 -1
  206. package/dist/language/flows/flow-tools.d.ts +10 -0
  207. package/dist/language/flows/flow-tools.js +61 -62
  208. package/dist/language/flows/flows.d.ts +16 -1
  209. package/dist/language/flows/flows.js +69 -10
  210. package/dist/language/flows/narrow-write.d.ts +7 -0
  211. package/dist/language/flows/narrow-write.js +36 -1
  212. package/dist/language/flows/recording/recording-backtrack.js +14 -1
  213. package/dist/language/flows/recording/tape/recordings.d.ts +29 -1
  214. package/dist/language/flows/recording/tape/recordings.js +60 -10
  215. package/dist/language/flows/replay.js +29 -6
  216. package/dist/language/flows/single-replay.d.ts +7 -0
  217. package/dist/language/flows/single-replay.js +26 -0
  218. package/dist/language/flows/verify-change-tools.d.ts +1 -0
  219. package/dist/language/flows/verify-change-tools.js +200 -193
  220. package/dist/memory/cloud/cloud-config.d.ts +10 -1
  221. package/dist/memory/cloud/cloud-config.js +19 -5
  222. package/dist/memory/cloud/cloud-sync.js +3 -3
  223. package/dist/memory/cloud/harness-config.d.ts +49 -0
  224. package/dist/memory/cloud/harness-config.js +16 -20
  225. package/dist/memory/cloud/memory-scope.d.ts +17 -0
  226. package/dist/memory/cloud/memory-scope.js +22 -0
  227. package/dist/memory/cloud/sync-cycle.d.ts +32 -4
  228. package/dist/memory/cloud/sync-cycle.js +113 -12
  229. package/dist/memory/cloud/sync-daemon.d.ts +11 -0
  230. package/dist/memory/cloud/sync-daemon.js +114 -4
  231. package/dist/memory/cloud/sync-disk.js +26 -2
  232. package/dist/memory/cloud/sync-screenshots.d.ts +32 -0
  233. package/dist/memory/cloud/sync-screenshots.js +105 -0
  234. package/dist/memory/cloud/unsynced-roots.d.ts +47 -0
  235. package/dist/memory/cloud/unsynced-roots.js +166 -0
  236. package/dist/memory/impact/account-watch.d.ts +16 -1
  237. package/dist/memory/impact/account-watch.js +26 -1
  238. package/dist/memory/impact/impact-store.d.ts +1 -4
  239. package/dist/memory/impact/impact-store.js +32 -42
  240. package/dist/memory/intent/inline-intent.d.ts +1 -0
  241. package/dist/memory/intent/intent-shard-store.js +16 -4
  242. package/dist/memory/intent/intent-shard.d.ts +12 -0
  243. package/dist/memory/intent/intent-store.d.ts +1 -0
  244. package/dist/memory/intent/intent-tools.js +42 -2
  245. package/dist/memory/intent/open-intents.d.ts +6 -0
  246. package/dist/memory/intent/open-intents.js +10 -1
  247. package/dist/memory/intent/settled-words.d.ts +24 -0
  248. package/dist/memory/intent/settled-words.js +130 -0
  249. package/dist/memory/journal/drive-flow.d.ts +16 -0
  250. package/dist/memory/journal/drive-flow.js +34 -2
  251. package/dist/memory/journal/drive-run-flush.d.ts +2 -0
  252. package/dist/memory/journal/drive-run-flush.js +20 -5
  253. package/dist/memory/journal/journal-recorder.js +46 -3
  254. package/dist/memory/journal/on-disk/retention.d.ts +5 -0
  255. package/dist/memory/journal/on-disk/retention.js +27 -2
  256. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  257. package/dist/memory/journal/session-end.d.ts +15 -1
  258. package/dist/memory/journal/session-end.js +38 -7
  259. package/dist/memory/project/artifact-root-resolver.js +33 -5
  260. package/dist/memory/project/artifact-root.d.ts +4 -3
  261. package/dist/memory/project/artifact-root.js +2 -2
  262. package/dist/memory/project/dir/reticle-dir.d.ts +14 -0
  263. package/dist/memory/project/dir/reticle-dir.js +23 -2
  264. package/dist/memory/project/link-directory.d.ts +15 -0
  265. package/dist/memory/project/link-directory.js +44 -0
  266. package/dist/memory/project/root-claims.d.ts +6 -0
  267. package/dist/memory/project/root-claims.js +39 -0
  268. package/dist/memory/project/session-root.d.ts +2 -0
  269. package/dist/memory/project/session-root.js +9 -1
  270. package/dist/memory/project/sync-status.d.ts +46 -0
  271. package/dist/memory/project/sync-status.js +135 -1
  272. package/dist/memory/recall/memory-tools.js +46 -3
  273. package/dist/memory/recall/platform-memory.d.ts +34 -0
  274. package/dist/memory/recall/platform-memory.js +139 -0
  275. package/dist/memory/recall/project-memory.js +36 -8
  276. package/dist/portal/bridge/bridge.d.ts +9 -5
  277. package/dist/portal/bridge/bridge.js +66 -23
  278. package/dist/portal/bridge/harness-request.d.ts +23 -0
  279. package/dist/portal/bridge/harness-request.js +27 -0
  280. package/dist/portal/bridge/wrong-path.d.ts +17 -0
  281. package/dist/portal/bridge/wrong-path.js +35 -0
  282. package/dist/portal/input/network-mock-tools.js +11 -2
  283. package/dist/portal/input/real-input.d.ts +2 -0
  284. package/dist/portal/input/real-input.js +13 -10
  285. package/dist/portal/pool/browser-pool.d.ts +48 -0
  286. package/dist/portal/pool/browser-pool.js +120 -2
  287. package/dist/portal/pool/context-permissions.d.ts +33 -0
  288. package/dist/portal/pool/context-permissions.js +33 -0
  289. package/dist/portal/pool/lease-marker-stamp.d.ts +23 -0
  290. package/dist/portal/pool/lease-marker-stamp.js +49 -0
  291. package/dist/portal/pool/playwright-launcher.d.ts +27 -5
  292. package/dist/portal/pool/playwright-launcher.js +59 -14
  293. package/dist/portal/pool/pool-contract.d.ts +21 -1
  294. package/dist/portal/session/agent-link.d.ts +8 -0
  295. package/dist/portal/session/agent-link.js +17 -0
  296. package/dist/portal/session/command-payload.js +6 -1
  297. package/dist/portal/session/daemon-answers.d.ts +3 -3
  298. package/dist/portal/session/daemon-answers.js +4 -2
  299. package/dist/portal/session/dev-server/dev-command.d.ts +9 -0
  300. package/dist/portal/session/dev-server/dev-command.js +27 -1
  301. package/dist/portal/session/facts/observed-state.d.ts +16 -18
  302. package/dist/portal/session/facts/observed-state.js +46 -3
  303. package/dist/portal/session/human/live-control.d.ts +9 -2
  304. package/dist/portal/session/human/live-control.js +12 -3
  305. package/dist/portal/session/live-control-tools.js +82 -13
  306. package/dist/portal/session/narration-args.d.ts +5 -0
  307. package/dist/portal/session/narration-args.js +17 -0
  308. package/dist/portal/session/no-session-diagnosis.d.ts +1 -1
  309. package/dist/portal/session/no-session-diagnosis.js +31 -9
  310. package/dist/portal/session/no-session-next-action.d.ts +20 -0
  311. package/dist/portal/session/no-session-next-action.js +134 -61
  312. package/dist/portal/session/no-session-watch.js +179 -22
  313. package/dist/portal/session/presence/session-lease.js +1 -1
  314. package/dist/portal/session/recorded-gaps.d.ts +97 -0
  315. package/dist/portal/session/recorded-gaps.js +251 -0
  316. package/dist/portal/session/session-health.d.ts +11 -0
  317. package/dist/portal/session/session-health.js +3 -1
  318. package/dist/portal/session/session-info.js +2 -1
  319. package/dist/portal/session/session-manager.d.ts +18 -0
  320. package/dist/portal/session/session-manager.js +54 -3
  321. package/dist/portal/session/session-successor.d.ts +8 -0
  322. package/dist/portal/session/session-successor.js +1 -1
  323. package/dist/portal/session/session.d.ts +15 -28
  324. package/dist/portal/session/session.js +60 -61
  325. package/dist/portal/session/timing/command-timeout.js +25 -1
  326. package/dist/portal/session/timing/wall-clock.d.ts +1 -1
  327. package/dist/portal/session/timing/wall-clock.js +1 -1
  328. package/dist/portal/session/zero-install-first-call.d.ts +31 -0
  329. package/dist/portal/session/zero-install-first-call.js +88 -0
  330. package/dist/start-options.d.ts +7 -1
  331. package/dist/start-wiring.d.ts +63 -0
  332. package/dist/start-wiring.js +169 -0
  333. package/dist/status-payload.d.ts +13 -1
  334. package/dist/status-payload.js +13 -1
  335. package/dist/surface/http-server.js +7 -1
  336. package/dist/surface/mcp/attach-memory.d.ts +1 -0
  337. package/dist/surface/mcp/attach-memory.js +12 -1
  338. package/dist/surface/mcp/mcp-proxy.d.ts +4 -0
  339. package/dist/surface/mcp/mcp-proxy.js +29 -8
  340. package/dist/surface/mcp/mcp.d.ts +23 -0
  341. package/dist/surface/mcp/mcp.js +34 -4
  342. package/dist/surface/mcp/proxy/drive-calls.d.ts +10 -0
  343. package/dist/surface/mcp/proxy/drive-calls.js +27 -0
  344. package/dist/surface/mcp/proxy/mcp-post-transport.js +5 -0
  345. package/dist/surface/mcp/server-instructions.js +22 -14
  346. package/dist/surface/mcp/shared-params.js +1 -1
  347. package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
  348. package/dist/surface/mcp/surface-vocabulary.js +1 -0
  349. package/dist/surface/tools/account-tool.d.ts +22 -0
  350. package/dist/surface/tools/account-tool.js +157 -0
  351. package/dist/surface/tools/act/act-danger.d.ts +11 -0
  352. package/dist/surface/tools/act/act-danger.js +38 -1
  353. package/dist/surface/tools/act/act-observation.d.ts +33 -0
  354. package/dist/surface/tools/act/act-observation.js +57 -0
  355. package/dist/surface/tools/act/act-preflight.js +25 -2
  356. package/dist/surface/tools/act/act-target.js +8 -3
  357. package/dist/surface/tools/act/resolve-target.d.ts +1 -1
  358. package/dist/surface/tools/act/resolve-target.js +18 -3
  359. package/dist/surface/tools/act/screen-request.d.ts +34 -0
  360. package/dist/surface/tools/act/screen-request.js +113 -0
  361. package/dist/surface/tools/act/sequence-grade.d.ts +8 -0
  362. package/dist/surface/tools/act/sequence-grade.js +13 -2
  363. package/dist/surface/tools/act-sequence-tool.js +136 -27
  364. package/dist/surface/tools/act-tools.d.ts +0 -9
  365. package/dist/surface/tools/act-tools.js +48 -34
  366. package/dist/surface/tools/agent/app-notes-learning.d.ts +7 -0
  367. package/dist/surface/tools/agent/app-notes-learning.js +80 -0
  368. package/dist/surface/tools/agent/app-notes.d.ts +4 -0
  369. package/dist/surface/tools/agent/app-notes.js +48 -0
  370. package/dist/surface/tools/agent/build-staleness.d.ts +7 -0
  371. package/dist/surface/tools/agent/build-staleness.js +62 -0
  372. package/dist/surface/tools/agent/compact-envelope.d.ts +80 -0
  373. package/dist/surface/tools/agent/compact-envelope.js +374 -0
  374. package/dist/surface/tools/agent/for-the-agent.d.ts +12 -0
  375. package/dist/surface/tools/agent/for-the-agent.js +44 -0
  376. package/dist/surface/tools/agent/notices.d.ts +50 -0
  377. package/dist/surface/tools/agent/notices.js +152 -0
  378. package/dist/surface/tools/agent/reteach.d.ts +11 -0
  379. package/dist/surface/tools/agent/reteach.js +50 -0
  380. package/dist/surface/tools/args/alias-args.d.ts +5 -0
  381. package/dist/surface/tools/args/alias-args.js +10 -0
  382. package/dist/surface/tools/args/confirm-dangerous-arg.d.ts +6 -0
  383. package/dist/surface/tools/args/confirm-dangerous-arg.js +6 -0
  384. package/dist/surface/tools/assert/assert-source.d.ts +15 -0
  385. package/dist/surface/tools/assert/assert-source.js +40 -0
  386. package/dist/surface/tools/assert/assert-verdict.js +25 -15
  387. package/dist/surface/tools/browser-tools.js +43 -1
  388. package/dist/surface/tools/chat-drives.d.ts +75 -0
  389. package/dist/surface/tools/chat-drives.js +265 -21
  390. package/dist/surface/tools/coverage-tools.d.ts +2 -10
  391. package/dist/surface/tools/coverage-tools.js +2 -53
  392. package/dist/surface/tools/dynamic-tools.js +29 -1
  393. package/dist/surface/tools/empty-session-list.d.ts +10 -0
  394. package/dist/surface/tools/empty-session-list.js +45 -5
  395. package/dist/surface/tools/error-recovery.d.ts +8 -0
  396. package/dist/surface/tools/error-recovery.js +69 -5
  397. package/dist/surface/tools/explore-tools.d.ts +23 -2
  398. package/dist/surface/tools/explore-tools.js +377 -74
  399. package/dist/surface/tools/feedback-tools.d.ts +2 -3
  400. package/dist/surface/tools/feedback-tools.js +4 -3
  401. package/dist/surface/tools/gap-novelty.d.ts +3 -1
  402. package/dist/surface/tools/gap-novelty.js +8 -1
  403. package/dist/surface/tools/harness-explore.d.ts +52 -73
  404. package/dist/surface/tools/harness-explore.js +177 -146
  405. package/dist/surface/tools/harness-gate.d.ts +88 -0
  406. package/dist/surface/tools/harness-gate.js +235 -0
  407. package/dist/surface/tools/harness-hunt.d.ts +43 -0
  408. package/dist/surface/tools/harness-hunt.js +215 -0
  409. package/dist/surface/tools/harness-plan.d.ts +5 -0
  410. package/dist/surface/tools/harness-plan.js +7 -0
  411. package/dist/surface/tools/harness-script.d.ts +22 -0
  412. package/dist/surface/tools/harness-script.js +256 -38
  413. package/dist/surface/tools/harness-tab-guard.d.ts +5 -0
  414. package/dist/surface/tools/harness-tab-guard.js +27 -0
  415. package/dist/surface/tools/harness-toolset.d.ts +8 -0
  416. package/dist/surface/tools/harness-toolset.js +27 -4
  417. package/dist/surface/tools/human-notes.d.ts +16 -0
  418. package/dist/surface/tools/human-notes.js +27 -0
  419. package/dist/surface/tools/invoke-tool.d.ts +5 -2
  420. package/dist/surface/tools/invoke-tool.js +220 -93
  421. package/dist/surface/tools/lease-hint.js +26 -5
  422. package/dist/surface/tools/lease-permissions.d.ts +33 -0
  423. package/dist/surface/tools/lease-permissions.js +73 -0
  424. package/dist/surface/tools/lease-readiness.d.ts +38 -0
  425. package/dist/surface/tools/lease-readiness.js +57 -0
  426. package/dist/surface/tools/lease-seed.d.ts +20 -0
  427. package/dist/surface/tools/lease-seed.js +45 -1
  428. package/dist/surface/tools/lease-tools.d.ts +10 -1
  429. package/dist/surface/tools/lease-tools.js +119 -69
  430. package/dist/surface/tools/live-call-text.js +2 -2
  431. package/dist/surface/tools/merge-tools.js +42 -2
  432. package/dist/surface/tools/merged-name-redirect.js +4 -0
  433. package/dist/surface/tools/next-step.d.ts +11 -1
  434. package/dist/surface/tools/next-step.js +73 -18
  435. package/dist/surface/tools/observe-tools.js +43 -5
  436. package/dist/surface/tools/platform-moment.d.ts +60 -0
  437. package/dist/surface/tools/platform-moment.js +132 -4
  438. package/dist/surface/tools/read-tools.js +65 -14
  439. package/dist/surface/tools/reticle-result.d.ts +120 -0
  440. package/dist/surface/tools/reticle-result.js +364 -0
  441. package/dist/surface/tools/tool-kit.d.ts +68 -2
  442. package/dist/surface/tools/tool-kit.js +73 -1
  443. package/dist/surface/tools/tools.js +25 -13
  444. package/dist/telemetry/account-envelope.d.ts +7 -0
  445. package/dist/telemetry/account-envelope.js +61 -0
  446. package/dist/telemetry/agent-client.d.ts +15 -0
  447. package/dist/telemetry/agent-client.js +57 -0
  448. package/dist/telemetry/app-instrumented.js +9 -2
  449. package/dist/telemetry/argument-shape.d.ts +7 -0
  450. package/dist/telemetry/argument-shape.js +86 -1
  451. package/dist/telemetry/automation-hint.d.ts +2 -3
  452. package/dist/telemetry/automation-hint.js +33 -8
  453. package/dist/telemetry/cli-telemetry.js +7 -2
  454. package/dist/telemetry/daemon-telemetry.js +3 -0
  455. package/dist/telemetry/error-fingerprint.d.ts +10 -1
  456. package/dist/telemetry/error-fingerprint.js +61 -5
  457. package/dist/telemetry/feedback-cli.d.ts +0 -13
  458. package/dist/telemetry/feedback-cli.js +0 -77
  459. package/dist/telemetry/feedback.d.ts +13 -0
  460. package/dist/telemetry/feedback.js +183 -17
  461. package/dist/telemetry/harness-run.d.ts +18 -0
  462. package/dist/telemetry/harness-run.js +80 -0
  463. package/dist/telemetry/hud-events.d.ts +16 -0
  464. package/dist/telemetry/hud-events.js +27 -0
  465. package/dist/telemetry/hud-metrics.d.ts +27 -6
  466. package/dist/telemetry/hud-metrics.js +99 -25
  467. package/dist/telemetry/init-telemetry.js +2 -2
  468. package/dist/telemetry/install-source.d.ts +13 -25
  469. package/dist/telemetry/install-source.js +44 -3
  470. package/dist/telemetry/mcp-connection.d.ts +0 -14
  471. package/dist/telemetry/mcp-connection.js +5 -1
  472. package/dist/telemetry/onboarding-firsts.d.ts +32 -0
  473. package/dist/telemetry/onboarding-firsts.js +81 -6
  474. package/dist/telemetry/platform-linked.d.ts +5 -0
  475. package/dist/telemetry/platform-linked.js +32 -0
  476. package/dist/telemetry/project-profile.js +5 -3
  477. package/dist/telemetry/proxy-telemetry.js +4 -1
  478. package/dist/telemetry/session-metrics.d.ts +15 -4
  479. package/dist/telemetry/session-metrics.js +88 -52
  480. package/dist/telemetry/telemetry.d.ts +26 -2
  481. package/dist/telemetry/telemetry.js +102 -21
  482. package/dist/telemetry/verification-of.d.ts +3 -1
  483. package/dist/telemetry/verification-of.js +12 -4
  484. package/dist/wire-journal.d.ts +2 -2
  485. package/dist/wire-journal.js +16 -5
  486. package/docs/README.md +2 -2
  487. package/docs/actions.mdx +6 -3
  488. package/docs/adapters.md +1 -1
  489. package/docs/agent-cheatsheet.md +24 -27
  490. package/docs/architecture.md +5 -5
  491. package/docs/autodrive.md +13 -12
  492. package/docs/benchmarks.md +5 -5
  493. package/docs/best-practices.mdx +27 -9
  494. package/docs/capabilities.mdx +14 -16
  495. package/docs/cli/affected.mdx +1 -1
  496. package/docs/cli/cloud.mdx +54 -75
  497. package/docs/cli/doctor.mdx +8 -3
  498. package/docs/cli/drive.mdx +12 -12
  499. package/docs/cli/feedback.mdx +3 -3
  500. package/docs/cli/gate.mdx +7 -2
  501. package/docs/cli/help.mdx +50 -55
  502. package/docs/cli/init.mdx +32 -15
  503. package/docs/cli/license.mdx +12 -8
  504. package/docs/cli/mcp.mdx +4 -4
  505. package/docs/cli/open.mdx +3 -3
  506. package/docs/cli/report.mdx +4 -2
  507. package/docs/cli/restart.mdx +2 -2
  508. package/docs/cli/rollback.mdx +14 -8
  509. package/docs/cli/serve.mdx +4 -4
  510. package/docs/cli/setup.mdx +23 -0
  511. package/docs/cli/status.mdx +13 -4
  512. package/docs/cli/stop.mdx +67 -8
  513. package/docs/cli/try.mdx +62 -0
  514. package/docs/cli/update.mdx +4 -3
  515. package/docs/cli/verify.mdx +17 -6
  516. package/docs/cli/version.mdx +3 -2
  517. package/docs/cli.mdx +110 -41
  518. package/docs/debugging.md +3 -3
  519. package/docs/deploy-checks.md +1 -1
  520. package/docs/desktop.mdx +5 -3
  521. package/docs/enterprise.md +11 -10
  522. package/docs/environment.mdx +5 -2
  523. package/docs/faq.mdx +3 -3
  524. package/docs/first-drive.md +5 -4
  525. package/docs/flows.md +35 -27
  526. package/docs/for-agents.md +4 -5
  527. package/docs/frameworks.mdx +32 -21
  528. package/docs/getting-started.md +66 -64
  529. package/docs/human-control.md +15 -14
  530. package/docs/index.mdx +15 -43
  531. package/docs/install-agentic.mdx +22 -37
  532. package/docs/install-manual.mdx +22 -12
  533. package/docs/instrumentation.mdx +5 -3
  534. package/docs/integration-patterns.md +7 -7
  535. package/docs/license-activation.mdx +1 -1
  536. package/docs/multi-agent-testing.md +24 -6
  537. package/docs/packages/babel-plugin.mdx +4 -2
  538. package/docs/packages/browser.mdx +16 -10
  539. package/docs/packages/core.mdx +12 -5
  540. package/docs/packages/electron.mdx +3 -2
  541. package/docs/packages/engine.mdx +1 -1
  542. package/docs/packages/eslint-plugin.mdx +18 -12
  543. package/docs/packages/next.mdx +8 -7
  544. package/docs/packages/open-verification.mdx +1 -1
  545. package/docs/packages/server.mdx +5 -5
  546. package/docs/packages/tauri.mdx +8 -8
  547. package/docs/packages/test.mdx +4 -10
  548. package/docs/packages/vite-plugin.mdx +11 -4
  549. package/docs/packages.mdx +11 -15
  550. package/docs/platform-integration.md +6 -5
  551. package/docs/predicates.mdx +40 -24
  552. package/docs/quickstart.mdx +58 -29
  553. package/docs/required-checks.md +8 -7
  554. package/docs/reticle-manual.md +15 -5
  555. package/docs/skill-file.mdx +17 -79
  556. package/docs/skill-setup.md +9 -9
  557. package/docs/skill.md +200 -0
  558. package/docs/system-map.md +7 -7
  559. package/docs/telemetry.md +22 -24
  560. package/docs/testing.md +4 -4
  561. package/docs/token-efficiency.md +12 -12
  562. package/docs/tools/act-and-wait.mdx +9 -2
  563. package/docs/tools/act-sequence.mdx +6 -7
  564. package/docs/tools/act.mdx +2 -2
  565. package/docs/tools/assert.mdx +18 -17
  566. package/docs/tools/console.mdx +9 -9
  567. package/docs/tools/inspect.mdx +16 -7
  568. package/docs/tools/look.mdx +24 -0
  569. package/docs/tools/navigate.mdx +5 -3
  570. package/docs/tools/network.mdx +2 -2
  571. package/docs/tools/observe.mdx +6 -6
  572. package/docs/tools/overview.mdx +34 -33
  573. package/docs/tools/query.mdx +5 -5
  574. package/docs/tools/session-and-feedback.mdx +13 -4
  575. package/docs/tools/sessions.mdx +8 -8
  576. package/docs/tools/snapshot.mdx +6 -5
  577. package/docs/tools/state.mdx +9 -9
  578. package/docs/tools/tools-and-run.mdx +26 -24
  579. package/docs/tools/verify.mdx +28 -0
  580. package/docs/tools/wait-for.mdx +8 -4
  581. package/docs/troubleshooting.mdx +21 -6
  582. package/docs/usage.md +51 -45
  583. package/docs/use-cases.mdx +3 -3
  584. package/docs/vs-chrome-devtools-mcp.mdx +9 -6
  585. package/docs/vs-playwright-mcp.mdx +3 -1
  586. package/docs/what-is-recorded.md +7 -5
  587. package/docs/what-is-reticle.mdx +2 -2
  588. package/docs/why-reticle.mdx +1 -1
  589. package/package.json +6 -5
  590. package/dist/command/cli/demo/demo-page.d.ts +0 -78
  591. package/dist/command/cli/demo/demo-page.js +0 -249
  592. package/dist/command/cli/demo/demo-run.d.ts +0 -38
  593. package/dist/command/cli/demo/demo-run.js +0 -138
  594. package/dist/judgement/hunt/hunt-report.d.ts +0 -49
  595. package/dist/judgement/hunt/hunt-report.js +0 -84
  596. package/dist/language/flows/buddy-status.d.ts +0 -25
  597. package/dist/language/flows/buddy-status.js +0 -32
  598. package/dist/language/flows/change/watch-batcher.d.ts +0 -18
  599. package/dist/language/flows/change/watch-batcher.js +0 -19
  600. package/dist/memory/cloud/harness-offer.d.ts +0 -44
  601. package/dist/memory/cloud/harness-offer.js +0 -145
  602. package/dist/surface/tools/feature-capture.d.ts +0 -151
  603. package/dist/surface/tools/feature-capture.js +0 -235
  604. package/dist/surface/tools/tool-hit-rate.d.ts +0 -87
  605. package/dist/surface/tools/tool-hit-rate.js +0 -90
  606. package/dist/telemetry/harness-drive.d.ts +0 -4
  607. package/dist/telemetry/harness-drive.js +0 -35
  608. package/dist/telemetry/identify.d.ts +0 -22
  609. package/dist/telemetry/identify.js +0 -73
  610. package/docs/cli/capsules.mdx +0 -70
  611. package/docs/cli/hunt.mdx +0 -73
  612. package/docs/cli/identify.mdx +0 -76
  613. package/docs/cli/kill.mdx +0 -91
  614. package/docs/cli/watch.mdx +0 -62
@@ -14,6 +14,31 @@
14
14
  */
15
15
  import { NoSessionAction, RETICLE_URL_PARAM, ReticleEnv, redactUrl } from '@reticlehq/core';
16
16
  import { detectNonJsEcosystem, noPackageJsonMessage } from '@reticlehq/init';
17
+ /** The port a tombstone URL names, when it names one — anything unparseable names no port. */
18
+ export function portOfUrl(url) {
19
+ try {
20
+ const raw = new URL(url).port;
21
+ if ('' === raw)
22
+ return undefined;
23
+ const port = Number(raw);
24
+ return Number.isSafeInteger(port) && port > 0 ? port : undefined;
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ /**
31
+ * The departed session's own port no longer answers. Direct evidence, because the scan always
32
+ * covers that port — unlike an empty scan, which proves nothing the moment another project
33
+ * happens to be listening (#1421). A slow listener is up, not gone, and a url with no explicit
34
+ * port was never scanned, so neither counts.
35
+ */
36
+ export function departedPortGone(facts) {
37
+ const port = facts.lastKnownUrl === undefined ? undefined : portOfUrl(facts.lastKnownUrl);
38
+ return (port !== undefined &&
39
+ !facts.listening.includes(port) &&
40
+ !(facts.slowListeners ?? []).includes(port));
41
+ }
17
42
  /**
18
43
  * The departed session's url as a human would type it: the params a Reticle launcher stamps on a
19
44
  * lease are stripped, because reopening WITH them would adopt the dead lease's identity. Undefined
@@ -36,10 +61,29 @@ function reopenableUrl(raw) {
36
61
  const port = '' === url.port ? ('https:' === url.protocol ? 443 : 80) : Number(url.port);
37
62
  return { url: url.toString(), port };
38
63
  }
39
- /** `reticle init`, the only command here that is Reticle's own and so cannot be wrong. */
40
- const INIT_COMMAND = 'reticle init';
41
- const OPEN_COMMAND = 'reticle open';
64
+ /**
65
+ * Reticle's own commands, the only ones here that cannot be wrong. Spelled through `npx`, like the
66
+ * CLI's own messages: the default MCP registration runs Reticle through `npx` and puts no `reticle`
67
+ * bin on PATH, so a bare `reticle init` fails with "command not found" exactly where it is handed out.
68
+ */
69
+ const CLI = 'npx @reticlehq/server';
70
+ const INIT_COMMAND = `${CLI} init`;
71
+ const OPEN_COMMAND = `${CLI} open`;
42
72
  const LOCALHOST = 'http://localhost';
73
+ /**
74
+ * What a lease on an app with no SDK can see. The lease supplies the SDK itself (the zero-install
75
+ * reader), so an unwired app still gets a verdict — on what the page does, not on its source.
76
+ */
77
+ const ZERO_INSTALL_SEES = 'the lease supplies the SDK itself: DOM, network, console and routes, but no component state ' +
78
+ 'or source file:line until the app is wired';
79
+ /** The zero-install call, with the URL only when it can be named without guessing. */
80
+ function zeroInstallLease(url) {
81
+ return `reticle_lease {action:"acquire", url:"${url ?? "<the app's URL>"}"}`;
82
+ }
83
+ /** Nothing here says this project was ever wired: no config, no recorded connection, no session. */
84
+ function neverWired(facts) {
85
+ return !facts.initialized && true !== facts.previouslyConnected && !facts.everConnected;
86
+ }
43
87
  export function nextActionFor(facts) {
44
88
  // FIRST, above every other cause, because it is the only one supported by positive evidence about
45
89
  // a daemon rather than by an absence. This project's own connection record names a live daemon
@@ -71,7 +115,11 @@ export function nextActionFor(facts) {
71
115
  // (`connectedSinceLastClosure` in the manager). Read as the weaker "a refusal was recorded at
72
116
  // some point", the flip trades this bug for its mirror image and blames the token for a tab the
73
117
  // human closed. The ordering belongs to the fact; what this branch does with it is then obvious.
74
- if (facts.everConnected && true !== facts.authRefused) {
118
+ // A departed session whose port is ALSO gone is the one shape where "reopen that" points at a
119
+ // dead socket and forbids the restart that fixes it (#1421). Keyed on that port itself, not on
120
+ // an empty scan: another project listening elsewhere says nothing about this one.
121
+ const departedPortIsGone = facts.everConnected && true !== facts.authRefused && departedPortGone(facts);
122
+ if (facts.everConnected && true !== facts.authRefused && !departedPortIsGone) {
75
123
  const listening = facts.listening;
76
124
  const only = 1 === listening.length ? listening[0] : undefined;
77
125
  // The departed session's own url first: it is where this project's app demonstrably was, while
@@ -82,8 +130,8 @@ export function nextActionFor(facts) {
82
130
  : 0 === listening.length
83
131
  ? ''
84
132
  : only === undefined
85
- ? ` An app is already listening on ${listening.join(', ')}; just open the URL the human names — do not start a second stack.`
86
- : ` An app is already listening on ${String(only)}; just open ${LOCALHOST}:${String(only)} — do not start a second stack.`;
133
+ ? ` Something is serving a page on ${listening.map((p) => `:${String(p)}`).join(', ')}; it may not be this app — open the URL the human names rather than starting a second stack.`
134
+ : ` Something is serving a page on :${String(only)}; it may not be this app — just open ${LOCALHOST}:${String(only)} rather than starting a second stack.`;
87
135
  const target = known !== undefined
88
136
  ? { command: `${OPEN_COMMAND} ${known.url}`, port: known.port }
89
137
  : only === undefined
@@ -124,8 +172,64 @@ export function nextActionFor(facts) {
124
172
  `point it there), then open the app.${leaseAlternative}`,
125
173
  };
126
174
  }
127
- if (0 === facts.listening.length) {
175
+ // A current pairing-token refusal is positive evidence that the app is running and instrumented.
176
+ // It must outrank an empty port scan, which only says the app is outside this daemon's scan set.
177
+ const ports = facts.listening.join(', ');
178
+ const only = 1 === facts.listening.length ? facts.listening[0] : undefined;
179
+ const where = '' === ports ? 'on a port this daemon did not scan' : `${ports} listening`;
180
+ // A refusal against a project that IS initialised here. The daemon has the config, so this is not
181
+ // a scope problem — the page reached the right daemon and presented a credential it would not
182
+ // accept. The usual cause is a dev server that shares no filesystem with the daemon (Docker, a
183
+ // devcontainer, WSL): the build plugin reads-or-mints the pairing token from `$HOME/.reticle`,
184
+ // which over there is a different file, so it mints its own and every page is refused. The daemon
185
+ // holds every fact needed to say so, which is why the reason below says it rather than repeating
186
+ // `authentication failed`.
187
+ if (true === facts.initialized && true === facts.authRefused) {
188
+ return {
189
+ action: NoSessionAction.OPEN_APP,
190
+ ...(only === undefined ? {} : { port: only }),
191
+ reason: `a page dialled this daemon and was refused on the pairing token (${where}), so ` +
192
+ 'the app IS running and instrumented and this project IS wired — the page simply presented ' +
193
+ 'a token this daemon does not own. The usual cause is a dev server that does not share a ' +
194
+ 'filesystem with the daemon (Docker, a devcontainer, WSL): the build plugin reads the token ' +
195
+ 'from `$HOME/.reticle/pairing-token`, which over there is a different file, so it mints its ' +
196
+ "own. Point the plugin at the daemon's copy — mount `~/.reticle/pairing-token` into the " +
197
+ `container read-only and set \`${ReticleEnv.PAIRING_TOKEN_DIR}\` to the directory holding ` +
198
+ 'it — then ' +
199
+ 'restart the dev server, because the token is inlined when Vite resolves its config and ' +
200
+ 'kept in the prebundled module. Reloading the page serves that same token, whether the ' +
201
+ 'dev server is on this machine or in a container.',
202
+ };
203
+ }
204
+ // A refused dial outranks the absence of a local config, for the same reason `previouslyConnected`
205
+ // does: it is positive evidence about the app, and `initialized` only ever answers "is there a
206
+ // `.reticle.json` in the ONE directory this daemon stands in". A globally-registered daemon stands
207
+ // in `/` or `$HOME`, so that answer is no for every project it serves -- and every page it turns
208
+ // away is proof that the project it turned away is wired.
209
+ if (!facts.initialized && true === facts.authRefused) {
210
+ return {
211
+ action: NoSessionAction.OPEN_APP,
212
+ ...(only === undefined ? {} : { port: only }),
213
+ reason: `a page dialled this daemon and was refused on the pairing token, so the app IS running ` +
214
+ `and instrumented (${where}) — this is a SCOPE problem, not an install problem. ` +
215
+ 'This daemon was started outside the project (an IDE that registers the MCP server ' +
216
+ 'globally starts it in `/` or your home directory), so it has no `.reticle.json` to match ' +
217
+ 'the page against. Do NOT run `reticle init`: the config it would write is already correct ' +
218
+ 'somewhere else. Take a context you own instead with reticle_lease {action:"acquire", url, ' +
219
+ "projectId}, or restart the daemon from the app's own directory.",
220
+ };
221
+ }
222
+ if (0 === facts.listening.length || departedPortIsGone) {
128
223
  const dev = facts.dev;
224
+ // The start advice below follows a departed session whose port is gone. Name it, so the restart
225
+ // does not read as if nothing had ever run here (#1421).
226
+ const stopped = departedPortIsGone ? reopenableUrl(facts.lastKnownUrl) : undefined;
227
+ const stoppedNote = stopped === undefined
228
+ ? ''
229
+ : ` The session that was on ${redactUrl(stopped.url)} is gone and nothing listens there now.`;
230
+ const notRunning = departedPortIsGone
231
+ ? "nothing answers on the port this project's app was last on, so the app is probably not running — "
232
+ : 'nothing is listening on the ports Reticle scans, so the app is probably not running — ';
129
233
  if (dev === undefined) {
130
234
  if (facts.exists !== undefined && !facts.exists('package.json')) {
131
235
  const ecosystem = detectNonJsEcosystem(facts.exists);
@@ -143,10 +247,11 @@ export function nextActionFor(facts) {
143
247
  }
144
248
  return {
145
249
  action: NoSessionAction.START_DEV_SERVER,
146
- reason: 'nothing is listening on the ports Reticle scans, so the app is probably not running — ' +
250
+ reason: notRunning +
147
251
  'but this project declares no dev script (no `dev`, `develop` or `start` in its ' +
148
252
  'package.json), so there is no command to hand you. Ask the human how their app is ' +
149
- 'started, and for the URL it serves on.',
253
+ 'started, and for the URL it serves on.' +
254
+ stoppedNote,
150
255
  };
151
256
  }
152
257
  // The app can live one directory down; the command has to be runnable from where the daemon
@@ -168,69 +273,37 @@ export function nextActionFor(facts) {
168
273
  // server on any other port is invisible to it, and that a running app should be checked for
169
274
  // rather than assumed away. Measured on a machine with three dev servers up on ports the scan
170
275
  // does not cover, it reported the app was not running.
171
- reason: 'nothing is listening on the ports Reticle scans, so the app is probably not running — ' +
276
+ reason: notRunning +
172
277
  'though that scan is narrow, so if it IS up on another port, ask for its URL instead of ' +
173
278
  `starting a second one. This is the project's own ` +
174
279
  (undefined === dev.script ? 'launcher' : `\`${dev.script}\` script`) +
175
280
  (dev.directory === undefined ? '' : `, in \`${dev.directory}\``) +
176
281
  ` — run it in the ` +
177
- 'background, tell the human it is running, then call reticle_sessions again.',
282
+ 'background, tell the human it is running, then call reticle_sessions again.' +
283
+ stoppedNote +
284
+ (neverWired(facts)
285
+ ? ` Once it is up, ${zeroInstallLease(dev.port === undefined ? undefined : `${LOCALHOST}:${String(dev.port)}`)} ` +
286
+ `gets a verdict with nothing installed (${ZERO_INSTALL_SEES}); ` +
287
+ `\`${facts.init ?? INIT_COMMAND}\` wires it for good — then restart the dev server and reload.`
288
+ : ''),
178
289
  };
179
290
  }
180
291
  // Something IS up. Whatever else is true, do NOT hand back a start command: a second dev server on
181
292
  // a second port is the exact confusion the probe exists to prevent.
182
- const ports = facts.listening.join(', ');
183
- const only = 1 === facts.listening.length ? facts.listening[0] : undefined;
184
- // A refused dial outranks the absence of a local config, for the same reason `previouslyConnected`
185
- // does: it is positive evidence about the app, and `initialized` only ever answers "is there a
186
- // `.reticle.json` in the ONE directory this daemon stands in". A globally-registered daemon stands
187
- // in `/` or `$HOME`, so that answer is no for every project it serves -- and every page it turns
188
- // away is proof that the project it turned away is wired.
189
- // A refusal against a project that IS initialised here. The daemon has the config, so this is not
190
- // a scope problem — the page reached the right daemon and presented a credential it would not
191
- // accept. The usual cause is a dev server that shares no filesystem with the daemon (Docker, a
192
- // devcontainer, WSL): the build plugin reads-or-mints the pairing token from `$HOME/.reticle`,
193
- // which over there is a different file, so it mints its own and every page is refused. The daemon
194
- // holds every fact needed to say so, which is why the reason below says it rather than repeating
195
- // `authentication failed`.
196
- if (true === facts.initialized && true === facts.authRefused) {
197
- return {
198
- action: NoSessionAction.OPEN_APP,
199
- ...(only === undefined ? {} : { port: only }),
200
- reason: `a page dialled this daemon and was refused on the pairing token (${ports} listening), so ` +
201
- 'the app IS running and instrumented and this project IS wired — the page simply presented ' +
202
- 'a token this daemon does not own. The usual cause is a dev server that does not share a ' +
203
- 'filesystem with the daemon (Docker, a devcontainer, WSL): the build plugin reads the token ' +
204
- 'from `$HOME/.reticle/pairing-token`, which over there is a different file, so it mints its ' +
205
- "own. Point the plugin at the daemon's copy — mount `~/.reticle/pairing-token` into the " +
206
- `container read-only and set \`${ReticleEnv.PAIRING_TOKEN_DIR}\` to the directory holding ` +
207
- 'it — then ' +
208
- 'restart the dev server, because the token is inlined when Vite resolves its config and ' +
209
- 'kept in the prebundled module. Reloading the page serves that same token, whether the ' +
210
- 'dev server is on this machine or in a container.',
211
- };
212
- }
213
- if (!facts.initialized && true === facts.authRefused) {
214
- return {
215
- action: NoSessionAction.OPEN_APP,
216
- ...(only === undefined ? {} : { port: only }),
217
- reason: `a page dialled this daemon and was refused on the pairing token, so the app IS running ` +
218
- `and instrumented (${ports} listening) — this is a SCOPE problem, not an install problem. ` +
219
- 'This daemon was started outside the project (an IDE that registers the MCP server ' +
220
- 'globally starts it in `/` or your home directory), so it has no `.reticle.json` to match ' +
221
- 'the page against. Do NOT run `reticle init`: the config it would write is already correct ' +
222
- 'somewhere else. Take a context you own instead with reticle_lease {action:"acquire", url, ' +
223
- "projectId}, or restart the daemon from the app's own directory.",
224
- };
225
- }
226
293
  if (!facts.initialized && true !== facts.previouslyConnected) {
294
+ // The port the project's own dev script pins outranks a lone listener: the scan is machine-wide.
295
+ const pinned = facts.dev?.port;
296
+ const target = pinned !== undefined && facts.listening.includes(pinned) ? pinned : only;
227
297
  return {
228
298
  action: NoSessionAction.RUN_INIT,
229
- command: INIT_COMMAND,
230
- ...(only === undefined ? {} : { port: only }),
299
+ command: facts.init ?? INIT_COMMAND,
300
+ ...(target === undefined ? {} : { port: target }),
231
301
  reason: `something is listening (${ports}) but no \`.reticle.json\` was found, so the app may ` +
232
- "carry no Reticle SDK. Run this in the APP's own directory — in a monorepo that is not " +
233
- 'where this daemon stands — then restart the dev server.',
302
+ 'carry no Reticle SDK. For a verdict NOW with nothing installed: ' +
303
+ `${zeroInstallLease(target === undefined ? undefined : `${LOCALHOST}:${String(target)}`)} — ` +
304
+ `${ZERO_INSTALL_SEES}. To wire it for good, run this command yourself (it runs in the ` +
305
+ "APP's own folder, not where this daemon stands), then restart the dev server and reload " +
306
+ 'the page.',
234
307
  };
235
308
  }
236
309
  if (only === undefined) {
@@ -258,12 +331,12 @@ export function nextActionFor(facts) {
258
331
  command: `${OPEN_COMMAND} ${LOCALHOST}:${String(only)}`,
259
332
  port: only,
260
333
  reason: neverConnected
261
- ? 'this project is wired and a dev server is listening, but no app has ever connected to ' +
334
+ ? 'this project is wired and something is listening on that port (it may not be this app), but no app has ever connected to ' +
262
335
  'this daemon — Reticle only ever sees a page a browser has LOADED, and nothing has loaded ' +
263
336
  'one. If opening it does not produce a session, the dev server is older than the Reticle ' +
264
337
  'plugin and its bundle carries no SDK: restart the dev server (do not rely on HMR) and ' +
265
338
  'load the page again.'
266
- : 'this project is wired and a dev server is listening — Reticle only ever sees a page a ' +
339
+ : 'this project is wired and something is listening on that port (it may not be this app) — Reticle only ever sees a page a ' +
267
340
  'browser has LOADED, and nothing has loaded one.',
268
341
  };
269
342
  }
@@ -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 } from 'node:path';
16
+ import { join, resolve } from 'node:path';
17
+ import { callingClientDirectory } from '../../hooks/client-directory.js';
17
18
  import { registeredElsewhere } from '../../memory/recall/registered-projects.js';
18
19
  import { explainNoSession } from './no-session-diagnosis.js';
19
- import { detectDevCommandInProject } from './dev-server/dev-command.js';
20
- import { nextActionFor, renderNextAction } from './no-session-next-action.js';
20
+ import { detectDevCommandInProject, initCommandIn } from './dev-server/dev-command.js';
21
+ import { nextActionFor, portOfUrl, renderNextAction } from './no-session-next-action.js';
21
22
  import { DEV_SERVER_PORTS, readProjectFramework, readProjectId, readProjectIsDesktop, readProjectPort, } from '../../command/cli/ports/resolve/cli-port.js';
22
23
  import { discoverProjectConfigs } from '../../command/cli/config/config-discovery.js';
23
24
  import { hasProjectConnectedBefore, rememberConnected, } from '../../memory/recall/prior/connection-memory.js';
@@ -49,19 +50,6 @@ function attachFailureClause(port, reason) {
49
50
  'rather than treating this as a problem with the app.'
50
51
  : ''));
51
52
  }
52
- /** The port a tombstone URL names, when it names one — anything unparseable names no port. */
53
- function portOfUrl(url) {
54
- try {
55
- const raw = new URL(url).port;
56
- if ('' === raw)
57
- return undefined;
58
- const port = Number(raw);
59
- return Number.isSafeInteger(port) && port > 0 ? port : undefined;
60
- }
61
- catch {
62
- return undefined;
63
- }
64
- }
65
53
  /**
66
54
  * Start the watch. Returns a stop function; the timer is unref'd so it never holds the daemon up.
67
55
  *
@@ -70,6 +58,8 @@ function portOfUrl(url) {
70
58
  */
71
59
  export function startNoSessionWatch(options) {
72
60
  let listening = [];
61
+ /** Ports the last scan was asked about. A port absent from this set was not observed, so it is not "down". */
62
+ let probed = new Set();
73
63
  /**
74
64
  * Ports that accepted a connection and then answered nothing in time.
75
65
  *
@@ -159,8 +149,8 @@ export function startNoSessionWatch(options) {
159
149
  * this one starts, which is precisely the window the agent then spends being told to start a dev
160
150
  * server that is already running.
161
151
  */
162
- const splitBrain = () => {
163
- const projectId = readProjectId(directory);
152
+ const splitBrain = (dir = directory) => {
153
+ const projectId = readProjectId(dir);
164
154
  if (projectId === undefined) {
165
155
  return projectlessNote(options.port, projectDaemonsElsewhere(options.port, stateDir, isAlive));
166
156
  }
@@ -211,6 +201,13 @@ export function startNoSessionWatch(options) {
211
201
  const knownPort = known === undefined ? undefined : portOfUrl(known.url);
212
202
  if (knownPort !== undefined)
213
203
  ports.add(knownPort);
204
+ // Every departed project, not only the newest. B can still be serving on :3005 after A leaves
205
+ // on :5173, and a scan that never asked :3005 would report B's app as stopped.
206
+ for (const url of options.sessions.tombstoneUrls?.() ?? []) {
207
+ const port = portOfUrl(url);
208
+ if (port !== undefined)
209
+ ports.add(port);
210
+ }
214
211
  return [...ports];
215
212
  };
216
213
  /**
@@ -255,7 +252,10 @@ export function startNoSessionWatch(options) {
255
252
  }
256
253
  };
257
254
  const refresh = () => {
258
- // Nothing to diagnose while a session is live, and no reason to scan.
255
+ // Nothing to diagnose while a session is live, and no reason to scan. What the last scan saw
256
+ // predates this session, so it is no observation of where the session goes on to depart from.
257
+ if (options.sessions.count() > 0)
258
+ probed = new Set();
259
259
  if (running || options.sessions.count() > 0)
260
260
  return;
261
261
  running = true;
@@ -267,6 +267,7 @@ export function startNoSessionWatch(options) {
267
267
  })
268
268
  : options.probe(candidates))
269
269
  .then(async (ports) => {
270
+ probed = new Set(candidates);
270
271
  listening = ports;
271
272
  siblingListeners =
272
273
  options.occupiedSiblings !== undefined
@@ -329,6 +330,143 @@ export function startNoSessionWatch(options) {
329
330
  return undefined;
330
331
  return { reason: closure.reason, ...closure.page };
331
332
  };
333
+ /**
334
+ * The diagnosis for a client standing in a different project than this daemon.
335
+ *
336
+ * `everConnected` and the newest tombstone are facts about the process. Spent on a second
337
+ * project they say that project's wiring is done and name the first project's URL. A directory
338
+ * that has never had a session of its own has never connected, whatever else this daemon has seen.
339
+ * Ports count only when this project announced them or its own last session was on them, so the
340
+ * reopen command cannot fall through to somebody else's dev server.
341
+ */
342
+ const diagnosisFor = (dir) => {
343
+ const projectId = readProjectId(dir);
344
+ const initialized = projectId !== undefined;
345
+ // A tab that reported no project cannot be another project's: it is the daemon's own last tab,
346
+ // never "this daemon has never seen one" beside a refusal that names where it last saw it. A
347
+ // tab that named another project stays that project's (a second app is not told to reopen it).
348
+ const newest = options.sessions.lastKnown?.();
349
+ const unclaimed = newest !== undefined && newest.projectId === undefined ? newest : undefined;
350
+ const known = (projectId === undefined ? undefined : options.sessions.lastKnownFor?.(projectId)) ??
351
+ unclaimed;
352
+ const everConnected = (projectId !== undefined && true === options.sessions.everConnectedTo?.(projectId)) ||
353
+ unclaimed !== undefined;
354
+ const ownPorts = new Set(devServersForProject(readDevServers(stateDir), { projectId, root: dir }).map((entry) => entry.port));
355
+ if (known !== undefined) {
356
+ const port = portOfUrl(known.url);
357
+ if (port !== undefined)
358
+ ownPorts.add(port);
359
+ }
360
+ const heard = listening.filter((port) => ownPorts.has(port));
361
+ // Not in the last scan means we have not looked, not that the server stopped. Treating the
362
+ // gap as "down" is what tells a still-running app to start a second one.
363
+ for (const port of ownPorts) {
364
+ if (!probed.has(port) && !heard.includes(port))
365
+ heard.push(port);
366
+ }
367
+ const slow = slowListeners.filter((port) => ownPorts.has(port));
368
+ const closure = options.sessions.lastClosure?.();
369
+ const refusalIsThisProject = closure !== undefined &&
370
+ true !== options.sessions.connectedSinceLastClosure?.() &&
371
+ projectId !== undefined &&
372
+ closure.page?.projectId === projectId;
373
+ const helloRefused = refusalIsThisProject &&
374
+ closure !== undefined &&
375
+ closure.page !== undefined &&
376
+ !isAuthRefusalReason(closure.reason)
377
+ ? { reason: closure.reason, ...closure.page }
378
+ : undefined;
379
+ const discovery = initialized ? undefined : discoverProjectConfigs(dir);
380
+ const found = discovery === undefined ? [] : discovery.found.filter((config) => config.directory !== dir);
381
+ const registered = 0 < found.length ? [] : registeredElsewhere(homedir(), dir);
382
+ const configsElsewhere = 0 < found.length
383
+ ? found.map((config) => ({
384
+ directory: config.directory,
385
+ ...(config.projectId === undefined ? {} : { projectId: config.projectId }),
386
+ }))
387
+ : 0 < registered.length
388
+ ? registered.map((project) => ({
389
+ directory: project.directory,
390
+ ...(project.projectId === undefined ? {} : { projectId: project.projectId }),
391
+ }))
392
+ : undefined;
393
+ const framework = readProjectFramework(dir);
394
+ const configured = readProjectPort(dir);
395
+ const previouslyConnected = hasProjectConnectedBefore(stateDir, options.port, projectId);
396
+ const authRefused = refusalIsThisProject && closure !== undefined && isAuthRefusalReason(closure.reason);
397
+ const status = known !== undefined && lastKnownStatus !== undefined && lastKnownStatus.url === known.url
398
+ ? { lastKnownStatus: lastKnownStatus.status }
399
+ : {};
400
+ const departedTo = known?.departedTo;
401
+ const departed = departedTo === undefined || '' === departedTo ? {} : { departedTo };
402
+ const upMs = stallUptime(Date.now());
403
+ const facts = {
404
+ everConnected,
405
+ initialized,
406
+ ...(configsElsewhere === undefined ? {} : { configsElsewhere }),
407
+ ...(discovery !== undefined && configsElsewhere === undefined
408
+ ? { searchedDirectories: discovery.searched }
409
+ : {}),
410
+ listening: heard,
411
+ slowListeners: slow,
412
+ port: options.port,
413
+ directory: dir,
414
+ previouslyConnected,
415
+ authRefused,
416
+ ...(helloRefused === undefined ? {} : { helloRefused }),
417
+ ...(framework === undefined ? {} : { framework }),
418
+ ...(readProjectIsDesktop(dir) ? { desktop: true } : {}),
419
+ leaseExpired: known !== undefined && (options.wasReapedLease?.(known.id) ?? false),
420
+ ...(known === undefined ? {} : { lastKnownUrl: known.url, ...status, ...departed }),
421
+ ...(upMs === undefined ? {} : { daemonUpMs: upMs }),
422
+ ...(configured === undefined ? {} : { projectPort: configured }),
423
+ ...(0 === siblingListeners.length ? {} : { siblingListeners }),
424
+ };
425
+ const split = splitBrain(dir);
426
+ const next = nextActionFor({
427
+ everConnected,
428
+ initialized,
429
+ ...(configsElsewhere === undefined ? {} : { configsElsewhere }),
430
+ previouslyConnected,
431
+ exists: (file) => existsSync(join(dir, file)),
432
+ authRefused,
433
+ ...(helloRefused === undefined ? {} : { helloRefused: helloRefused.reason }),
434
+ ...(split === undefined ? {} : { splitBrain: split }),
435
+ listening: heard,
436
+ slowListeners: slow,
437
+ ...(known === undefined
438
+ ? {}
439
+ : {
440
+ lastKnownUrl: known.url,
441
+ ...(known.departedTo === undefined ? {} : { departedTo: known.departedTo }),
442
+ }),
443
+ dev: detectDevCommandInProject(dir),
444
+ init: initCommandIn(dir),
445
+ });
446
+ return { facts, next };
447
+ };
448
+ /**
449
+ * A call that named the client's project. Undefined means nobody named one (an embed, a test, a
450
+ * daemon answering itself), and the path below stays the one this watch was started with.
451
+ */
452
+ const callerAnswer = () => {
453
+ const caller = callingClientDirectory();
454
+ if (caller === undefined)
455
+ return undefined;
456
+ return diagnosisFor(caller);
457
+ };
458
+ /**
459
+ * `listening`, plus the departed session's port when no scan has looked at it since: not looked
460
+ * at is not stopped. Without it `reticle status` called a running Vite stopped because the only
461
+ * scan ran before the app was started.
462
+ */
463
+ const observedListening = () => {
464
+ const known = options.sessions.lastKnown?.();
465
+ const port = known === undefined ? undefined : portOfUrl(known.url);
466
+ return port === undefined || probed.has(port) || listening.includes(port)
467
+ ? listening
468
+ : [...listening, port];
469
+ };
332
470
  const nextAction = (scope) => {
333
471
  const split = splitBrain();
334
472
  return nextActionFor({
@@ -345,7 +483,8 @@ export function startNoSessionWatch(options) {
345
483
  return refused === undefined ? {} : { helloRefused: refused.reason };
346
484
  })(),
347
485
  ...(split === undefined ? {} : { splitBrain: split }),
348
- listening,
486
+ listening: observedListening(),
487
+ slowListeners,
349
488
  // The url the departed tab was on — the same tombstone the prose diagnosis quotes, so the
350
489
  // command and the sentence beside it name the same page.
351
490
  ...(() => {
@@ -360,13 +499,21 @@ export function startNoSessionWatch(options) {
360
499
  // Read when asked, like everything else here: a `package.json` can gain a dev script, and a
361
500
  // daemon that cached "there is none" at boot would keep saying so for the rest of the day.
362
501
  dev: detectDevCommandInProject(directory),
502
+ // This project's own runner and app folder, so the agent can wire it itself.
503
+ init: initCommandIn(directory),
363
504
  });
364
505
  };
365
- options.sessions.setNoSessionNextAction(() => nextAction(projectScopeFacts()));
506
+ options.sessions.setNoSessionNextAction(() => {
507
+ const caller = callerAnswer();
508
+ return caller === undefined ? nextAction(projectScopeFacts()) : caller.next;
509
+ });
366
510
  // ONE call for both registrations below. The prose and the branch code have to come from the
367
511
  // same evaluation or they can describe different branches - the facts are read when asked, so two
368
512
  // calls a moment apart can genuinely disagree (#615).
369
513
  const explain = () => {
514
+ const caller = callerAnswer();
515
+ if (caller !== undefined)
516
+ return explainNoSession(caller.facts);
370
517
  const scope = projectScopeFacts();
371
518
  return explainNoSession({
372
519
  everConnected: options.sessions.everConnected(),
@@ -377,7 +524,7 @@ export function startNoSessionWatch(options) {
377
524
  // framework and its projectId, and whose real problem was a dev server older than the plugin.
378
525
  // The boot value still counts: it is the one the daemon scoped its sessions with.
379
526
  ...scope,
380
- listening,
527
+ listening: observedListening(),
381
528
  slowListeners,
382
529
  port: options.port,
383
530
  // The directory `initialized` was decided in. Named in the message because "there is no
@@ -450,6 +597,16 @@ export function startNoSessionWatch(options) {
450
597
  // scope facts so a discovered workspace config cannot become an `init` recommendation below it,
451
598
  // and both come from ONE `explain()` call so they cannot describe different branches.
452
599
  const rendered = () => {
600
+ const caller = callerAnswer();
601
+ if (caller !== undefined) {
602
+ const { message, detail } = explainNoSession(caller.facts);
603
+ // The attach failure is about the daemon's own project. A call from that project still hears
604
+ // it. A call from another project must not be told that opening this one failed.
605
+ const callerDir = callingClientDirectory();
606
+ const ownProject = callerDir !== undefined && resolve(callerDir) === resolve(directory);
607
+ const lead = `${message} ${renderNextAction(caller.next)}${ownProject ? (attachFailure ?? '') : ''}`;
608
+ return { lead, full: undefined === detail ? lead : `${lead} ${detail}` };
609
+ }
453
610
  const scope = projectScopeFacts();
454
611
  const { message, detail } = explain();
455
612
  const lead = `${message} ${renderNextAction(nextAction(scope))}${attachFailure ?? ''}`;
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Any edit here is a per-session tax on every user: say it in as few words as still work.
10
10
  */
11
- const SESSION_LEASE_NOTICE = 'When you stop driving, call reticle_session {action:"yield", mode:"waiting"|"ask"}. The panel reads "live" until you do. {action:"end"} when the task is done; it revives on your next action.';
11
+ const SESSION_LEASE_NOTICE = 'When you stop driving, call reticle_session {action:"yield", mode:"waiting"|"ask"}. The panel reads "live" until you do. {action:"end"} only when the task is done: it is final.';
12
12
  export function buildSessionLease(sessionId, openedAt) {
13
13
  return { sessionId, opened_at: openedAt, IMPORTANT: SESSION_LEASE_NOTICE };
14
14
  }