@rho-workspace/rho 0.5.3 → 0.5.5

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 (277) hide show
  1. package/README.md +24 -5
  2. package/dist/browser-access.js +3 -2
  3. package/dist/capabilities.d.ts +29 -2
  4. package/dist/capabilities.js +79 -2
  5. package/dist/cli.js +237 -99
  6. package/dist/contract.generated.d.ts +441 -16
  7. package/dist/contract.generated.js +1 -1
  8. package/dist/contracts/owners.generated.js +459 -10
  9. package/dist/conversation-presentation.d.ts +3 -1
  10. package/dist/conversation-presentation.js +16 -6
  11. package/dist/conversations.d.ts +10 -6
  12. package/dist/conversations.js +17 -13
  13. package/dist/discovery.d.ts +1 -0
  14. package/dist/discovery.js +3 -3
  15. package/dist/extension-contract.d.ts +99 -0
  16. package/dist/extension-contract.js +52 -0
  17. package/dist/extension-directory.d.ts +9 -0
  18. package/dist/extension-directory.js +11 -0
  19. package/dist/extension-worker.d.ts +1 -0
  20. package/dist/extension-worker.js +36 -0
  21. package/dist/extensions.d.ts +157 -0
  22. package/dist/extensions.js +349 -0
  23. package/dist/installs.d.ts +11 -0
  24. package/dist/installs.js +179 -0
  25. package/dist/plugin-cli.d.ts +1 -0
  26. package/dist/plugin-cli.js +139 -0
  27. package/dist/plugin-inventory.json +18 -0
  28. package/dist/plugins.d.ts +3 -3
  29. package/dist/project-notes.js +2 -0
  30. package/dist/project-work.d.ts +3 -1
  31. package/dist/project-work.js +15 -2
  32. package/dist/protocol.d.ts +7 -0
  33. package/dist/protocol.js +11 -7
  34. package/dist/screen-check-skill.d.ts +2 -0
  35. package/dist/screen-check-skill.js +39 -0
  36. package/dist/server.d.ts +6 -1
  37. package/dist/server.js +105 -15
  38. package/dist/web/.vite/manifest.json +65 -65
  39. package/dist/web/assets/{canvas-BXhC8RDZ.js → canvas-D8rDdfDz.js} +1 -1
  40. package/dist/web/assets/choices-b0XhrGQV.js +1 -0
  41. package/dist/web/assets/{console-e1P5qYQ5.js → console-BSjLGe0T.js} +1 -1
  42. package/dist/web/assets/{dist-Dd3iAGFy.js → dist-Buk0lwzI.js} +2 -2
  43. package/dist/web/assets/dist-Cw8hRIlT.js +1 -0
  44. package/dist/web/assets/dist-Tzoe_zm-.js +3 -0
  45. package/dist/web/assets/{dist-DEorbOW0.js → dist-foH9IKc6.js} +1 -1
  46. package/dist/web/assets/explore-in-BVUy3ZW_.js +1 -0
  47. package/dist/web/assets/graph-1OdIJ9l7.js +2 -0
  48. package/dist/web/assets/index-CsC3sTRq.css +1 -0
  49. package/dist/web/assets/{index-BYuKpyoi.js → index-CxmItXqa.js} +220 -121
  50. package/dist/web/assets/{notes-meaning-B3HFbh84.js → notes-meaning-pVbbQUGP.js} +1 -1
  51. package/dist/web/assets/overview-B9UtVJ32.js +6 -0
  52. package/dist/web/assets/preview-CksD_5KS.js +1 -0
  53. package/dist/web/assets/{review-BVh9jWMB.js → review-CMxQlQ-C.js} +1 -1
  54. package/dist/web/assets/{skills-DPaEYkln.js → skills-BFNSyrnD.js} +1 -1
  55. package/dist/web/assets/{skills-work-dvjfy_tu.js → skills-work-BGPSnF_I.js} +1 -1
  56. package/dist/web/assets/{studio-process-BYm8mjRJ.js → studio-process-CsRoTHOh.js} +2 -2
  57. package/dist/web/assets/{timeline-events-CDZ7wiJX.js → timeline-events-BbuuFWHV.js} +1 -1
  58. package/dist/web/assets/{work-turns-BeCt303c.js → work-turns-BvJlIMmA.js} +1 -1
  59. package/dist/web/assets/workspace-CWlkjLNx.js +5 -0
  60. package/dist/web/composition.json +80 -80
  61. package/dist/web/index.html +3 -3
  62. package/dist/web/renderer.json +1 -1
  63. package/dist/web-assets.d.ts +2 -1
  64. package/dist/web-assets.js +3 -2
  65. package/dist/work-contract.js +2 -2
  66. package/docs/FRONTEND.md +30 -14
  67. package/docs/HEADLESS.md +60 -14
  68. package/docs/PLUGIN-AUTHORING.md +4 -0
  69. package/docs/THIRD-PARTY-PLUGINS.md +190 -0
  70. package/example/pa14-qs/.rho/rho/conversations/c-67f77cfebaba2cea.json +1 -1
  71. package/example/pa14-qs/.rho/rho/conversations/c-91aa2e46b1ac1a6c.json +1 -1
  72. package/example/pa14-qs/.rho/rho/conversations/c-fa24dfd80dfe4bcb.json +1 -1
  73. package/node_modules/@hono/node-server/dist/index.js +11 -0
  74. package/node_modules/@hono/node-server/dist/index.mjs +11 -0
  75. package/node_modules/@hono/node-server/dist/listener.js +11 -0
  76. package/node_modules/@hono/node-server/dist/listener.mjs +11 -0
  77. package/node_modules/@hono/node-server/dist/request.js +11 -0
  78. package/node_modules/@hono/node-server/dist/request.mjs +11 -0
  79. package/node_modules/@hono/node-server/dist/server.js +11 -0
  80. package/node_modules/@hono/node-server/dist/server.mjs +11 -0
  81. package/node_modules/@hono/node-server/dist/vercel.js +11 -0
  82. package/node_modules/@hono/node-server/dist/vercel.mjs +11 -0
  83. package/node_modules/@hono/node-server/package.json +1 -1
  84. package/node_modules/@rho-workspace/plugin-agent/README.md +42 -1
  85. package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +14 -3
  86. package/node_modules/@rho-workspace/plugin-agent/dist/activity-normalize.d.ts +1 -0
  87. package/node_modules/@rho-workspace/plugin-agent/dist/activity-normalize.js +2 -2
  88. package/node_modules/@rho-workspace/plugin-agent/dist/conversation.d.ts +1 -0
  89. package/node_modules/@rho-workspace/plugin-agent/dist/conversation.js +1 -0
  90. package/node_modules/@rho-workspace/plugin-agent/dist/dsh-session.d.ts +6 -0
  91. package/node_modules/@rho-workspace/plugin-agent/dist/dsh-session.js +174 -0
  92. package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.d.ts +82 -0
  93. package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +15 -0
  94. package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +73 -0
  95. package/node_modules/@rho-workspace/plugin-agent/dist/external-session.js +190 -0
  96. package/node_modules/@rho-workspace/plugin-agent/dist/generic-acp.d.ts +11 -0
  97. package/node_modules/@rho-workspace/plugin-agent/dist/generic-acp.js +7 -0
  98. package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +373 -0
  99. package/node_modules/@rho-workspace/plugin-agent/dist/index.js +1 -0
  100. package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +5 -1
  101. package/node_modules/@rho-workspace/plugin-agent/dist/operations.d.ts +33 -0
  102. package/node_modules/@rho-workspace/plugin-agent/dist/operations.js +3 -0
  103. package/node_modules/@rho-workspace/plugin-agent/dist/presentation.d.ts +14 -0
  104. package/node_modules/@rho-workspace/plugin-agent/dist/presentation.js +2 -0
  105. package/node_modules/@rho-workspace/plugin-agent/dist/ui-external-session.d.ts +15 -0
  106. package/node_modules/@rho-workspace/plugin-agent/dist/ui-external-session.js +37 -0
  107. package/node_modules/@rho-workspace/plugin-agent/package.json +5 -1
  108. package/node_modules/@rho-workspace/plugin-annotations/package.json +1 -1
  109. package/node_modules/@rho-workspace/plugin-documents/README.md +4 -0
  110. package/node_modules/@rho-workspace/plugin-documents/dist/index.d.ts +3 -0
  111. package/node_modules/@rho-workspace/plugin-documents/dist/index.js +1 -1
  112. package/node_modules/@rho-workspace/plugin-documents/package.json +1 -1
  113. package/node_modules/@rho-workspace/plugin-kernel/README.md +71 -10
  114. package/node_modules/@rho-workspace/plugin-kernel/dist/code.d.ts +2 -0
  115. package/node_modules/@rho-workspace/plugin-kernel/dist/code.js +4 -1
  116. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +5 -0
  117. package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +7 -0
  118. package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +55 -8
  119. package/node_modules/@rho-workspace/plugin-kernel/dist/lineage.d.ts +9 -6
  120. package/node_modules/@rho-workspace/plugin-kernel/dist/lineage.js +18 -41
  121. package/node_modules/@rho-workspace/plugin-kernel/dist/operations.js +5 -2
  122. package/node_modules/@rho-workspace/plugin-kernel/dist/script-flow.d.ts +39 -0
  123. package/node_modules/@rho-workspace/plugin-kernel/dist/script-flow.js +661 -0
  124. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +3 -0
  125. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.js +10 -0
  126. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-flow.d.ts +38 -4
  127. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-flow.js +194 -97
  128. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -2
  129. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +10 -2
  130. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +79 -18
  131. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +115 -45
  132. package/node_modules/@rho-workspace/plugin-kernel/dist/ui.d.ts +1 -1
  133. package/node_modules/@rho-workspace/plugin-kernel/dist/ui.js +1 -1
  134. package/node_modules/@rho-workspace/plugin-kernel/dist/value-graph.d.ts +63 -0
  135. package/node_modules/@rho-workspace/plugin-kernel/dist/value-graph.js +344 -0
  136. package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
  137. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +6 -3
  138. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +6 -3
  139. package/node_modules/express-rate-limit/dist/index.cjs +1 -1
  140. package/node_modules/express-rate-limit/dist/index.mjs +1 -1
  141. package/node_modules/express-rate-limit/package.json +1 -1
  142. package/node_modules/ip-address/dist/ipv6.js +1 -1
  143. package/node_modules/ip-address/package.json +1 -1
  144. package/node_modules/playwright-core/LICENSE +202 -0
  145. package/node_modules/playwright-core/NOTICE +5 -0
  146. package/node_modules/playwright-core/ThirdPartyNotices.txt +13 -0
  147. package/node_modules/playwright-core/bin/install_media_pack.ps1 +5 -0
  148. package/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +33 -0
  149. package/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +42 -0
  150. package/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +13 -0
  151. package/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +24 -0
  152. package/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +42 -0
  153. package/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +12 -0
  154. package/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +24 -0
  155. package/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +48 -0
  156. package/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +11 -0
  157. package/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +23 -0
  158. package/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +48 -0
  159. package/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +11 -0
  160. package/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +23 -0
  161. package/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +48 -0
  162. package/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +11 -0
  163. package/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +24 -0
  164. package/node_modules/playwright-core/browsers.json +52 -0
  165. package/node_modules/playwright-core/cli.js +22 -0
  166. package/node_modules/playwright-core/index.js +17 -0
  167. package/node_modules/playwright-core/index.mjs +28 -0
  168. package/node_modules/playwright-core/lib/bootstrap.js +149 -0
  169. package/node_modules/playwright-core/lib/coreBundle.js +78041 -0
  170. package/node_modules/playwright-core/lib/entry/cliDaemon.js +5 -0
  171. package/node_modules/playwright-core/lib/entry/dashboardApp.js +3 -0
  172. package/node_modules/playwright-core/lib/entry/mcp.js +10 -0
  173. package/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +3 -0
  174. package/node_modules/playwright-core/lib/package.js +50 -0
  175. package/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
  176. package/node_modules/playwright-core/lib/server/electron/loader.js +123 -0
  177. package/node_modules/playwright-core/lib/serverRegistry.js +1939 -0
  178. package/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +65 -0
  179. package/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +141 -0
  180. package/node_modules/playwright-core/lib/tools/cli-client/cli.js +7 -0
  181. package/node_modules/playwright-core/lib/tools/cli-client/help.json +808 -0
  182. package/node_modules/playwright-core/lib/tools/cli-client/minimist.js +128 -0
  183. package/node_modules/playwright-core/lib/tools/cli-client/output.js +352 -0
  184. package/node_modules/playwright-core/lib/tools/cli-client/program.js +422 -0
  185. package/node_modules/playwright-core/lib/tools/cli-client/registry.js +180 -0
  186. package/node_modules/playwright-core/lib/tools/cli-client/session.js +261 -0
  187. package/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
  188. package/node_modules/playwright-core/lib/tools/utils/extension.js +112 -0
  189. package/node_modules/playwright-core/lib/tools/utils/socketConnection.js +108 -0
  190. package/node_modules/playwright-core/lib/utilsBundle.js +86078 -0
  191. package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +1890 -0
  192. package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule--QdMvsKi.css +1 -0
  193. package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule-CQ8RZGBm.js +32 -0
  194. package/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
  195. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
  196. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
  197. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
  198. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-AwgwcYie.css +1 -0
  199. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-hRvO4yU2.js +12 -0
  200. package/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
  201. package/node_modules/playwright-core/lib/vite/dashboard/index.html +29 -0
  202. package/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +24 -0
  203. package/node_modules/playwright-core/lib/vite/htmlReport/index.html +16 -0
  204. package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -0
  205. package/node_modules/playwright-core/lib/vite/htmlReport/report.js +32 -0
  206. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  207. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DK72OMRi.js +32 -0
  208. package/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
  209. package/node_modules/playwright-core/lib/vite/recorder/assets/index-BZpYJZ-P.css +1 -0
  210. package/node_modules/playwright-core/lib/vite/recorder/assets/index-Cn0mmDuv.js +129 -0
  211. package/node_modules/playwright-core/lib/vite/recorder/index.html +29 -0
  212. package/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +9 -0
  213. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-BEB2e8M3.js +32 -0
  214. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-D-g2vrvf.js +184 -0
  215. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  216. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-B794rD2Z.js +7 -0
  217. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  218. package/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
  219. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.C8BeHuxd.css +1 -0
  220. package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  221. package/node_modules/playwright-core/lib/vite/traceViewer/index.html +44 -0
  222. package/node_modules/playwright-core/lib/vite/traceViewer/index.oNry3Hjv.js +1 -0
  223. package/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +16 -0
  224. package/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +9 -0
  225. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  226. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +10 -0
  227. package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -0
  228. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CYwC_gH1.js +5 -0
  229. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CyMfwkXJ.css +1 -0
  230. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +18 -0
  231. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  232. package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
  233. package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
  234. package/node_modules/playwright-core/lib/xdg-open +1267 -0
  235. package/node_modules/playwright-core/package.json +34 -0
  236. package/package.json +13 -7
  237. package/protocol/index.json +75 -21
  238. package/protocol/operations/agent.sessions.list.json +100 -0
  239. package/protocol/operations/agent.sessions.read.json +266 -0
  240. package/protocol/operations/conversation.settings.check.json +147 -4
  241. package/protocol/operations/conversation.settings.read.json +145 -3
  242. package/protocol/operations/conversation.settings.update.json +166 -4
  243. package/protocol/operations/conversation.start.json +2 -1
  244. package/protocol/operations/extension.call.json +132 -0
  245. package/protocol/operations/extension.content.json +78 -0
  246. package/protocol/operations/extension.describe.json +195 -0
  247. package/protocol/operations/extension.install.json +198 -0
  248. package/protocol/operations/extension.list.json +123 -0
  249. package/protocol/operations/extension.manage.json +55 -0
  250. package/protocol/operations/extension.read.json +112 -0
  251. package/protocol/operations/kernel.interrupt.json +20 -0
  252. package/protocol/operations/kernel.read.json +30 -0
  253. package/protocol/operations/kernel.run.json +36 -1
  254. package/protocol/operations/kernel.stop.json +10 -0
  255. package/protocol/operations/project.work.read.json +3 -2
  256. package/protocol/operations/session.grant.json +9 -0
  257. package/scripts/browser-checks.mjs +53 -0
  258. package/scripts/screen-check.mjs +193 -0
  259. package/scripts/third-party-acceptance.mjs +77 -0
  260. package/vendor/composition.json +111 -111
  261. package/vendor/rho-workspace-plugin-agent-0.4.29.tgz +0 -0
  262. package/vendor/rho-workspace-plugin-annotations-0.5.2.tgz +0 -0
  263. package/vendor/rho-workspace-plugin-documents-0.4.11.tgz +0 -0
  264. package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
  265. package/web/theme.css +17 -2
  266. package/dist/web/assets/choices-B-y2OYvE.js +0 -1
  267. package/dist/web/assets/dist-CBM2oq6g.js +0 -1
  268. package/dist/web/assets/dist-CH56yNDn.js +0 -3
  269. package/dist/web/assets/explore-in-muWXOGOY.js +0 -1
  270. package/dist/web/assets/graph-CK-df5lh.js +0 -2
  271. package/dist/web/assets/index-DBd77u4a.css +0 -1
  272. package/dist/web/assets/overview-D1Ev2OC_.js +0 -6
  273. package/dist/web/assets/preview-HbmQKUGY.js +0 -1
  274. package/dist/web/assets/workspace-d5Hw_ClZ.js +0 -5
  275. package/vendor/rho-workspace-plugin-agent-0.4.27.tgz +0 -0
  276. package/vendor/rho-workspace-plugin-documents-0.4.10.tgz +0 -0
  277. package/vendor/rho-workspace-plugin-kernel-0.5.2.tgz +0 -0
package/dist/cli.js CHANGED
@@ -10,12 +10,41 @@ function openBrowser(target) {
10
10
  const [command, args] = process.platform === 'darwin' ? ['open', [target]] : process.platform === 'win32' ? ['cmd', ['/c', 'start', '""', target]] : ['xdg-open', [target]];
11
11
  spawn(command, args, { stdio: 'ignore', detached: true }).on('error', () => process.stderr.write('Could not open a browser. Open openUrl from the output instead.\n')).unref();
12
12
  }
13
- const usage = `Verify a read-only evidence package:
13
+ // A generic ACP agent is any program that speaks ACP v1 over stdio. Its command is either a
14
+ // JSON argv array (safest for paths with spaces) or a simple command line.
15
+ function parseAcpCommand(spec) {
16
+ const text = (spec ?? '').trim();
17
+ if (!text)
18
+ throw new Error('--agent-backend acp needs --acp-command: a JSON argv array such as \'["/usr/bin/node","/path/agent.js"]\', or a command line.');
19
+ if (text.startsWith('[')) {
20
+ let parts;
21
+ try {
22
+ parts = JSON.parse(text);
23
+ }
24
+ catch {
25
+ throw new Error('--acp-command is not valid JSON.');
26
+ }
27
+ if (!Array.isArray(parts) || !parts.length || parts.some(x => typeof x !== 'string'))
28
+ throw new Error('--acp-command JSON must be a non-empty array of strings.');
29
+ return { file: parts[0], args: parts.slice(1) };
30
+ }
31
+ const parts = (text.match(/"[^"]*"|'[^']*'|\S+/g) ?? []).map(p => p.replace(/^(["'])(.*)\1$/, '$2'));
32
+ if (!parts.length)
33
+ throw new Error('--acp-command is empty.');
34
+ return { file: parts[0], args: parts.slice(1) };
35
+ }
36
+ const usage = `Develop, install and manage independent plugins:
37
+ rho plugin --help
38
+ Verify a read-only evidence package:
14
39
  rho --verify-evidence PATH
40
+ Start Rho on ~/Rho/projects and ~/Rho/state, and say where it is and what is missing:
41
+ rho
15
42
  Start Rho:
16
- rho --root PROJECTS_DIRECTORY --state-dir SERVICE_STATE [--core ABSOLUTE_CORE_BINARY] [--rscript ABSOLUTE_RSCRIPT] [--python PYTHON] [--ssh SSH] [--serverctl SERVERCTL] [--claude CLAUDE_CODE_BINARY] [--codex CODEX_BINARY] [--agent-backend claude-acp|codex-acp|claude-cli] [--port 0] [--limits JSON_FILE] [--plugins documents,files,...] [--example on|off] [--open]
43
+ rho --root PROJECTS_DIRECTORY --state-dir SERVICE_STATE [--core ABSOLUTE_CORE_BINARY] [--rscript ABSOLUTE_RSCRIPT] [--python PYTHON] [--ssh SSH] [--serverctl SERVERCTL] [--claude CLAUDE_CODE_BINARY] [--codex CODEX_BINARY] [--agent-backend claude-acp|codex-acp|claude-cli|acp] [--acp-command JSON_ARGV] [--port PORT] [--limits JSON_FILE] [--plugins documents,files,...] [--example on|off] [--open]
17
44
  A narrower browser link (optional):
18
45
  rho --pair PRIVATE_CONNECTION_FILE [--root PROJECTS_RELATIVE_DIRECTORY] [--open]
46
+ Add Rho to your own Claude Code or Codex for one project, as the project page's button does:
47
+ rho --grant PRIVATE_CONNECTION_FILE --project PROJECTS_RELATIVE_DIRECTORY --agent claude-code|codex
19
48
  Give an Agent access to one project, and the command that adds it to Claude Code:
20
49
  rho --grant PRIVATE_CONNECTION_FILE --project PROJECTS_RELATIVE_DIRECTORY [--session NAME]
21
50
  Serve that access to the Agent over MCP (stdio):
@@ -24,120 +53,229 @@ Observe an external ACP client (use this as its agent command):
24
53
  rho --acp /absolute/project --acp-agent claude|codex
25
54
  --example off starts Rho without the project it ships; a new state otherwise gets that project
26
55
  once, copied into the projects directory. --open opens the printed url in the default browser.
56
+ Without --port a start takes the port this state's last start had, and a new one only when that
57
+ port is taken; --port 0 always takes a new one.
27
58
  Opening that url on this machine needs no link, code or session credential; --pair makes a
28
59
  narrower, expiring link for one folder.
29
60
  PRIVATE_CONNECTION_FILE is the connectionFile the running service printed; it and every
30
61
  connection file are credentials.
31
62
  `;
32
- const options = { example: { type: 'string' }, 'verify-evidence': { type: 'string' }, acp: { type: 'string' }, 'acp-agent': { type: 'string' }, root: { type: 'string' }, 'state-dir': { type: 'string' }, core: { type: 'string' }, rscript: { type: 'string' }, python: { type: 'string' }, ssh: { type: 'string' }, serverctl: { type: 'string' }, claude: { type: 'string' }, codex: { type: 'string' }, 'agent-backend': { type: 'string' }, port: { type: 'string' }, limits: { type: 'string' }, plugins: { type: 'string' }, pair: { type: 'string' }, open: { type: 'boolean' },
33
- grant: { type: 'string' }, project: { type: 'string' }, session: { type: 'string' }, mcp: { type: 'string' }, help: { type: 'boolean', short: 'h' } };
34
- let parsed;
35
- try {
36
- parsed = parseArgs({ options });
37
- }
38
- catch (error) {
39
- process.stderr.write((error instanceof Error ? error.message : String(error)) + '\n\n' + usage);
40
- process.exit(2);
63
+ const options = { example: { type: 'string' }, 'verify-evidence': { type: 'string' }, acp: { type: 'string' }, 'acp-agent': { type: 'string' }, root: { type: 'string' }, 'state-dir': { type: 'string' }, core: { type: 'string' }, rscript: { type: 'string' }, python: { type: 'string' }, ssh: { type: 'string' }, serverctl: { type: 'string' }, claude: { type: 'string' }, codex: { type: 'string' }, 'agent-backend': { type: 'string' }, 'acp-command': { type: 'string' }, port: { type: 'string' }, limits: { type: 'string' }, plugins: { type: 'string' }, pair: { type: 'string' }, open: { type: 'boolean' },
64
+ grant: { type: 'string' }, project: { type: 'string' }, session: { type: 'string' }, agent: { type: 'string' }, mcp: { type: 'string' }, help: { type: 'boolean', short: 'h' } };
65
+ // A command that is refused says why and what to do. A service that is not running is the
66
+ // common case for --grant and --pair; anything without a known reason keeps its stack.
67
+ function refuse(error, url) {
68
+ const e = error;
69
+ if (e?.cause?.code === 'ECONNREFUSED' && url)
70
+ process.stderr.write(`Rho is not running at ${url}. Start it with the same --state-dir, then run this again.\n`);
71
+ else if (e?.error?.message || e?.kind || e?.syscall)
72
+ process.stderr.write((e.error?.message ?? e.message) + '\n');
73
+ else
74
+ throw error;
75
+ process.exit(1);
41
76
  }
42
- const { values } = parsed;
43
- if (values['verify-evidence']) {
77
+ if (process.argv[2] === 'plugin') {
44
78
  try {
45
- const { verifyEvidence } = await import('./evidence-export.js');
46
- process.stdout.write(JSON.stringify(await verifyEvidence(path.resolve(values['verify-evidence']))) + '\n');
79
+ await (await import('./plugin-cli.js')).pluginCli(process.argv.slice(3));
47
80
  }
48
- catch (e) {
49
- process.stderr.write((e instanceof Error ? e.message : String(e)) + '\n');
81
+ catch (error) {
82
+ process.stderr.write((error instanceof Error ? error.message : String(error)) + '\n');
50
83
  process.exitCode = 1;
51
84
  }
52
- process.exit(process.exitCode ?? 0);
53
85
  }
54
- if (values.help) {
55
- process.stdout.write(usage);
56
- process.exit(0);
57
- }
58
- // The Agent's side of a grant: its MCP server reads the connection file and speaks on stdio,
59
- // so nothing else may write to stdout in this mode.
60
- if (values.acp) {
61
- if (!['claude', 'codex'].includes(values['acp-agent'] ?? '')) {
62
- process.stderr.write('Select --acp-agent claude or codex.\n');
86
+ else {
87
+ let parsed;
88
+ try {
89
+ parsed = parseArgs({ options });
90
+ }
91
+ catch (error) {
92
+ process.stderr.write((error instanceof Error ? error.message : String(error)) + '\n\n' + usage);
63
93
  process.exit(2);
64
94
  }
65
- const { runExternalAcp } = await import('@rho-workspace/plugin-agent/acp-cli');
66
- runExternalAcp(path.resolve(values.acp), values['acp-agent']);
67
- }
68
- else if (values.mcp) {
69
- process.env.RHO_AGENT_CONNECTION = path.resolve(values.mcp);
70
- await import('@rho-workspace/plugin-agent/mcp');
71
- }
72
- else {
73
- // Access comes from whoever runs Rho: the operator's connection grants one project to one
74
- // Agent session, and the reply says how Claude Code starts that session's MCP server.
75
- if (values.grant) {
76
- if (!values.project) {
77
- process.stderr.write('--grant needs --project, the project folder relative to the service root.\n\n' + usage);
78
- process.exit(2);
95
+ const { values } = parsed;
96
+ if (values['verify-evidence']) {
97
+ try {
98
+ const { verifyEvidence } = await import('./evidence-export.js');
99
+ process.stdout.write(JSON.stringify(await verifyEvidence(path.resolve(values['verify-evidence']))) + '\n');
79
100
  }
80
- const { RhoClient } = await import('./client.js');
81
- const client = await RhoClient.fromFile(path.resolve(values.grant));
82
- const project = await client.call('project.open', { path: values.project });
83
- const session = values.session ?? 'claude-code';
84
- const granted = await client.call('session.grant', { projectId: project.id, externalSessionId: session });
85
- const { mcpCommand, mcpName } = await import('./agent-connect.js');
86
- const command = mcpCommand(granted.connectionFile), name = mcpName(project.root);
87
- const quote = (value) => /^[A-Za-z0-9_./:@=-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
88
- process.stdout.write(JSON.stringify({ projectId: project.id, session, connectionFile: granted.connectionFile, methods: granted.methods.length,
89
- mcp: { name, command: command[0], args: command.slice(1) }, claude: ['claude', 'mcp', 'add', name, '--', ...command].map(quote).join(' ') }) + '\n');
90
- process.exit(0);
101
+ catch (e) {
102
+ process.stderr.write((e instanceof Error ? e.message : String(e)) + '\n');
103
+ process.exitCode = 1;
104
+ }
105
+ process.exit(process.exitCode ?? 0);
91
106
  }
92
- if (values.pair) {
93
- const { RhoClient } = await import('./client.js');
94
- const client = await RhoClient.fromFile(path.resolve(values.pair));
95
- const pairing = await client.call('session.browser.pair', values.root ? { path: values.root } : {});
96
- const openUrl = link(client.connection.url, pairing.code);
97
- process.stdout.write(JSON.stringify({ url: client.connection.url, openUrl, pairingCode: pairing.code, pairingExpiresAt: pairing.expiresAt }) + '\n');
98
- if (values.open)
99
- openBrowser(openUrl);
107
+ if (values.help) {
108
+ process.stdout.write(usage);
100
109
  process.exit(0);
101
110
  }
102
- if (!values.root || !values['state-dir']) {
103
- process.stderr.write(usage);
104
- process.exit(2);
111
+ // The Agent's side of a grant: its MCP server reads the connection file and speaks on stdio,
112
+ // so nothing else may write to stdout in this mode.
113
+ if (values.acp) {
114
+ if (!['claude', 'codex'].includes(values['acp-agent'] ?? '')) {
115
+ process.stderr.write('Select --acp-agent claude or codex.\n');
116
+ process.exit(2);
117
+ }
118
+ const { runExternalAcp } = await import('@rho-workspace/plugin-agent/acp-cli');
119
+ runExternalAcp(path.resolve(values.acp), values['acp-agent']);
105
120
  }
106
- const limits = values.limits ? JSON.parse(await fs.readFile(values.limits, 'utf8')) : {};
107
- const allowed = new Set(['maxRequestBytes', 'maxEventBytes', 'eventRetention', 'maxSubscriptions', 'maxQueue', 'maxContexts', 'maxBindings', 'maxActivities', 'maxContextBytes', 'maxRetainedMetadataBytes', 'maxConcurrentActions', 'maxOperations', 'maxLogPageBytes', 'maxCodeBytes', 'maxReconciled', 'documents', 'resources', 'workspace', 'r', 'annotations', 'interactions', 'capabilityLimits', 'browser', 'nativeFolders', 'conversations', 'evidence', 'console']);
108
- if (Object.keys(limits).some(k => !allowed.has(k)))
109
- throw new Error('Unknown service budget in --limits file.');
110
- const { RhoService } = await import('./server.js'), { runtime } = await import('./runtime.js');
111
- const agentBackend = values['agent-backend'] ?? process.env.RHO_AGENT_BACKEND ?? 'claude-acp';
112
- if (!['claude-acp', 'codex-acp', 'claude-cli'].includes(agentBackend))
113
- throw new Error('Choose --agent-backend claude-acp, codex-acp or claude-cli.');
114
- // A Core that cannot be used (none for this platform, a changed digest) is read by a person.
115
- const native = await runtime(values.core, values.rscript).catch(e => { process.stderr.write((e instanceof Error ? e.message : String(e)) + '\n'); process.exit(1); });
116
- // A person starting Rho sees the project it ships with; a composition that starts a service
117
- // for its own work says so itself and is left alone.
118
- if (values.example !== undefined && !['on', 'off'].includes(values.example))
119
- throw new Error('Choose --example on or off.');
120
- const example = values.example !== 'off';
121
- const service = await new RhoService({ example, ...limits, root: path.resolve(values.root), stateDir: path.resolve(values['state-dir']), ...native, agentBackend: agentBackend, codex: values.codex ?? process.env.RHO_CODEX ?? 'codex', claude: values.claude ?? process.env.RHO_CLAUDE ?? 'claude', python: values.python ?? process.env.RHO_PYTHON, ssh: values.ssh ?? process.env.RHO_SSH, serverctl: values.serverctl ?? process.env.RHO_SERVERCTL, port: Number(values.port ?? 0), ...(values.plugins !== undefined ? { plugins: values.plugins ? values.plugins.split(',') : [] } : {}) }).start();
122
- // The narrow conclusion a person needs on one line: what was selected, where state
123
- // lives, and how to open it. It stays inside `ready` so a parser reading line one is
124
- // unaffected. Opening this url on this machine is the access; no link is minted.
125
- const rscript = values.rscript ?? process.env.RHO_RSCRIPT ?? 'Rscript';
126
- process.stdout.write(JSON.stringify({ ready: {
127
- url: service.url, connectionFile: path.join(service.state.directory, 'connection.json'), apiVersion: '2.0',
128
- openUrl: service.url,
129
- startup: {
130
- projects: path.resolve(values.root), state: path.resolve(values['state-dir']),
131
- core: service.bridge.binary, rscript: service.bridge.rscript,
132
- note: 'Open this url in a browser on this machine. Execution stays off until an Agent submits a run; project.doctor answers what is missing.'
121
+ else if (values.mcp) {
122
+ process.env.RHO_AGENT_CONNECTION = path.resolve(values.mcp);
123
+ await import('@rho-workspace/plugin-agent/mcp');
124
+ }
125
+ else {
126
+ // Access comes from whoever runs Rho: the operator's connection grants one project to one
127
+ // Agent session, and the reply says how Claude Code starts that session's MCP server.
128
+ if (values.grant) {
129
+ if (!values.project) {
130
+ process.stderr.write('--grant needs --project, the project folder relative to the service root.\n\n' + usage);
131
+ process.exit(2);
132
+ }
133
+ if (values.agent !== undefined && !['claude-code', 'codex'].includes(values.agent)) {
134
+ process.stderr.write('Choose --agent claude-code or codex.\n');
135
+ process.exit(2);
136
+ }
137
+ if (values.agent !== undefined && values.session !== undefined) {
138
+ process.stderr.write('--agent names the session itself; leave out --session.\n');
139
+ process.exit(2);
140
+ }
141
+ const { RhoClient } = await import('./client.js');
142
+ const client = await RhoClient.fromFile(path.resolve(values.grant)).catch(error => refuse(error));
143
+ try {
144
+ const project = await client.call('project.open', { path: values.project });
145
+ // The same as the project page's button: the grant that Agent keeps, then its own
146
+ // `mcp add`, recorded so the page shows it under that Agent.
147
+ if (values.agent) {
148
+ const connected = await client.call('session.connect', { projectId: project.id, agent: values.agent });
149
+ process.stdout.write(JSON.stringify({ projectId: project.id, root: project.root, agent: values.agent, methods: connected.methods, granted: connected.granted, added: connected.added }) + '\n');
150
+ process.exit(0);
151
+ }
152
+ const session = values.session ?? 'claude-code';
153
+ const granted = await client.call('session.grant', { projectId: project.id, externalSessionId: session });
154
+ const { mcpCommand, mcpName } = await import('./agent-connect.js');
155
+ const command = mcpCommand(granted.connectionFile), name = mcpName(project.root);
156
+ const quote = (value) => /^[A-Za-z0-9_./:@=-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
157
+ process.stdout.write(JSON.stringify({ projectId: project.id, session, connectionFile: granted.connectionFile, methods: granted.methods.length,
158
+ mcp: { name, command: command[0], args: command.slice(1) }, claude: ['claude', 'mcp', 'add', name, '--', ...command].map(quote).join(' ') }) + '\n');
159
+ process.exit(0);
160
+ }
161
+ catch (error) {
162
+ refuse(error, client.connection.url);
163
+ }
164
+ }
165
+ if (values.pair) {
166
+ const { RhoClient } = await import('./client.js');
167
+ const client = await RhoClient.fromFile(path.resolve(values.pair)).catch(error => refuse(error));
168
+ try {
169
+ const pairing = await client.call('session.browser.pair', values.root ? { path: values.root } : {});
170
+ const openUrl = link(client.connection.url, pairing.code);
171
+ process.stdout.write(JSON.stringify({ url: client.connection.url, openUrl, pairingCode: pairing.code, pairingExpiresAt: pairing.expiresAt }) + '\n');
172
+ if (values.open)
173
+ openBrowser(openUrl);
174
+ process.exit(0);
175
+ }
176
+ catch (error) {
177
+ refuse(error, client.connection.url);
178
+ }
179
+ }
180
+ // A person who runs `npx @rho-workspace/rho` with no arguments wants the application, not a
181
+ // usage page: start it on their own folders, say in plain words where it is, and open it when
182
+ // this is an interactive terminal. Every flagged start is unchanged, so scripts and
183
+ // acceptance runs keep the output they read.
184
+ const quickStart = !values.root && !values['state-dir'];
185
+ if (!quickStart && (!values.root || !values['state-dir'])) {
186
+ process.stderr.write(usage);
187
+ process.exit(2);
188
+ }
189
+ let quickRoot = '', quickState = '';
190
+ if (quickStart) {
191
+ const { homedir } = await import('node:os');
192
+ quickRoot = path.join(homedir(), 'Rho', 'projects');
193
+ quickState = path.join(homedir(), 'Rho', 'state');
194
+ await fs.mkdir(quickRoot, { recursive: true });
195
+ await fs.mkdir(quickState, { recursive: true });
196
+ }
197
+ const projectsRoot = values.root ?? quickRoot, stateDirectory = values['state-dir'] ?? quickState;
198
+ const limits = values.limits ? JSON.parse(await fs.readFile(values.limits, 'utf8')) : {};
199
+ const allowed = new Set(['maxRequestBytes', 'maxEventBytes', 'eventRetention', 'maxSubscriptions', 'maxQueue', 'maxContexts', 'maxBindings', 'maxActivities', 'maxContextBytes', 'maxRetainedMetadataBytes', 'maxConcurrentActions', 'maxOperations', 'maxLogPageBytes', 'maxCodeBytes', 'maxReconciled', 'documents', 'resources', 'workspace', 'r', 'annotations', 'interactions', 'capabilityLimits', 'browser', 'nativeFolders', 'conversations', 'evidence', 'console']);
200
+ if (Object.keys(limits).some(k => !allowed.has(k)))
201
+ throw new Error('Unknown service budget in --limits file.');
202
+ const { RhoService } = await import('./server.js'), { runtime } = await import('./runtime.js');
203
+ const agentBackend = values['agent-backend'] ?? process.env.RHO_AGENT_BACKEND ?? 'claude-acp';
204
+ if (!['claude-acp', 'codex-acp', 'claude-cli', 'acp'].includes(agentBackend))
205
+ throw new Error('Choose --agent-backend claude-acp, codex-acp, claude-cli or acp.');
206
+ const acpCommand = agentBackend === 'acp' ? parseAcpCommand(values['acp-command'] ?? process.env.RHO_ACP_COMMAND) : undefined;
207
+ if (values.port !== undefined && !(/^\d+$/.test(values.port) && Number(values.port) <= 65535)) {
208
+ process.stderr.write('Choose --port 0 to 65535, or leave it out.\n');
209
+ process.exit(2);
210
+ }
211
+ // A Core that cannot be used (none for this platform, a changed digest) is read by a person.
212
+ const native = await runtime(values.core, values.rscript).catch(e => { process.stderr.write((e instanceof Error ? e.message : String(e)) + '\n'); process.exit(1); });
213
+ // A person starting Rho sees the project it ships with; a composition that starts a service
214
+ // for its own work says so itself and is left alone.
215
+ if (values.example !== undefined && !['on', 'off'].includes(values.example))
216
+ throw new Error('Choose --example on or off.');
217
+ const example = values.example !== 'off';
218
+ const service = await new RhoService({ example, ...limits, root: path.resolve(projectsRoot), stateDir: path.resolve(stateDirectory), ...native, agentBackend: agentBackend, acpCommand, codex: values.codex ?? process.env.RHO_CODEX ?? 'codex', claude: values.claude ?? process.env.RHO_CLAUDE ?? 'claude', python: values.python ?? process.env.RHO_PYTHON, ssh: values.ssh ?? process.env.RHO_SSH, serverctl: values.serverctl ?? process.env.RHO_SERVERCTL, port: values.port === undefined ? undefined : Number(values.port), ...(values.plugins !== undefined ? { plugins: values.plugins ? values.plugins.split(',') : [] } : {}) }).start().catch(async (error) => {
219
+ // Starting Rho again on a state it already runs on is the common refusal: say where it runs.
220
+ const held = error?.details?.stateDirectory;
221
+ const running = typeof held === 'string' ? await fs.readFile(path.join(held, 'connection.json'), 'utf8').then(text => String(JSON.parse(text).url), () => '') : '';
222
+ return refuse(running ? Object.assign(error, { message: `Rho is already running for ${held} at ${running}. Open that url, or stop that service before starting it again.` }) : error);
223
+ });
224
+ // The narrow conclusion a person needs on one line: what was selected, where state
225
+ // lives, and how to open it. It stays inside `ready` so a parser reading line one is
226
+ // unaffected. Opening this url on this machine is the access; no link is minted.
227
+ const rscript = values.rscript ?? process.env.RHO_RSCRIPT ?? 'Rscript';
228
+ process.stdout.write(JSON.stringify({ ready: {
229
+ url: service.url, connectionFile: path.join(service.state.directory, 'connection.json'), apiVersion: '2.0',
230
+ openUrl: service.url,
231
+ startup: {
232
+ projects: path.resolve(projectsRoot), state: path.resolve(stateDirectory),
233
+ core: service.bridge.binary, rscript: service.bridge.rscript,
234
+ note: 'Open this url in a browser on this machine. Execution stays off until an Agent submits a run; project.doctor answers what is missing.'
235
+ }
236
+ } }) + '\n');
237
+ // What a person needs, after the machine line: where it is, and what is missing to work in it.
238
+ // A missing Agent or R does not stop the workspace; it decides what the person can do next.
239
+ if (quickStart) {
240
+ const say = (text) => process.stdout.write(text + '\n');
241
+ say('');
242
+ say('Rho is running. Open this address in a browser on this machine:');
243
+ say(' ' + service.url);
244
+ say('');
245
+ say('Projects folder: ' + quickRoot);
246
+ say('Service state: ' + quickState);
247
+ say('');
248
+ const agentCommand = await import('@rho-workspace/plugin-agent/acp-command').catch(() => null);
249
+ // The selection the service itself got: an explicit flag, then the environment, then the name
250
+ // on PATH. Reporting "installed" for a client the person pointed at a missing path is worse
251
+ // than saying nothing.
252
+ const claudePath = values.claude ?? process.env.RHO_CLAUDE, codexPath = values.codex ?? process.env.RHO_CODEX;
253
+ const env = { ...process.env, ...(claudePath ? { CLAUDE_CODE_EXECUTABLE: claudePath } : {}), ...(codexPath ? { CODEX_PATH: codexPath } : {}) };
254
+ // An ACP agent named with --acp-command is the operator's own command; it is named, not checked.
255
+ if (acpCommand)
256
+ say('Agent: ' + acpCommand.file + ', named with --acp-command.');
257
+ else {
258
+ const missing = agentCommand ? agentCommand.missingClient(agentBackend === 'codex-acp' ? 'codex' : 'claude', env) : null;
259
+ say(missing
260
+ ? 'Agent: ' + missing + ' Until then folders can be opened and files read here; the Agent cannot run in them.'
261
+ : 'Agent: ' + (agentBackend === 'codex-acp' ? 'Codex' : 'Claude Code') + ' is installed; a conversation can start in any project.');
133
262
  }
134
- } }) + '\n');
135
- if (values.open)
136
- openBrowser(service.url);
137
- let closing = false;
138
- // Windows has no SIGTERM another process can send, and a console may pass on Ctrl+C turned off;
139
- // there Ctrl+Break on Rho's console (SIGBREAK) stops it in order too.
140
- for (const signal of ['SIGTERM', 'SIGINT', ...(process.platform === 'win32' ? ['SIGBREAK'] : [])])
141
- process.on(signal, async () => { if (closing)
142
- return; closing = true; const reports = await service.close(); process.stdout.write(JSON.stringify({ shutdown: reports }) + '\n'); });
263
+ const hasR = await fs.access(service.bridge.rscript).then(() => true, () => false);
264
+ say(hasR
265
+ ? 'R: ' + service.bridge.rscript
266
+ : 'R: Rscript was not found. Reading, editing and viewing work; running R needs R installed (or --rscript).');
267
+ say('');
268
+ say('Stop it with Ctrl+C. Start it again with: npx @rho-workspace/rho');
269
+ if (process.stdout.isTTY)
270
+ openBrowser(service.url);
271
+ }
272
+ if (values.open)
273
+ openBrowser(service.url);
274
+ let closing = false;
275
+ // Windows has no SIGTERM another process can send, and a console may pass on Ctrl+C turned off;
276
+ // there Ctrl+Break on Rho's console (SIGBREAK) stops it in order too.
277
+ for (const signal of ['SIGTERM', 'SIGINT', ...(process.platform === 'win32' ? ['SIGBREAK'] : [])])
278
+ process.on(signal, async () => { if (closing)
279
+ return; closing = true; const reports = await service.close(); process.stdout.write(JSON.stringify({ shutdown: reports }) + '\n'); });
280
+ }
143
281
  }