@rho-workspace/rho 0.5.6 → 0.5.7

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 (211) hide show
  1. package/dist/agent-connect.d.ts +36 -2
  2. package/dist/agent-connect.js +64 -11
  3. package/dist/answer-board.js +3 -3
  4. package/dist/bridge.js +6 -1
  5. package/dist/browser-access.d.ts +1 -0
  6. package/dist/browser-access.js +5 -3
  7. package/dist/capabilities.d.ts +21 -5
  8. package/dist/capabilities.js +6 -4
  9. package/dist/cli.js +76 -11
  10. package/dist/composition.d.ts +1 -1
  11. package/dist/composition.js +13 -3
  12. package/dist/connect-cli.d.ts +50 -0
  13. package/dist/connect-cli.js +128 -0
  14. package/dist/contract.generated.d.ts +1191 -278
  15. package/dist/contract.generated.js +1 -1
  16. package/dist/contracts/owners.generated.js +845 -47
  17. package/dist/conversation-presentation.d.ts +9 -2
  18. package/dist/conversation-presentation.js +3 -1
  19. package/dist/conversations.d.ts +154 -8
  20. package/dist/conversations.js +147 -24
  21. package/dist/discovery.js +2 -2
  22. package/dist/extension-contract.d.ts +19 -0
  23. package/dist/extension-contract.js +15 -6
  24. package/dist/extension-markets.d.ts +74 -0
  25. package/dist/extension-markets.js +166 -0
  26. package/dist/extensions.d.ts +29 -1
  27. package/dist/extensions.js +13 -4
  28. package/dist/external-work.d.ts +22 -0
  29. package/dist/external-work.js +91 -0
  30. package/dist/plugin-cli.js +75 -3
  31. package/dist/plugin-inventory.d.ts +56 -0
  32. package/dist/plugin-inventory.js +41 -0
  33. package/dist/plugin-inventory.json +1581 -102
  34. package/dist/plugin-presentation.d.ts +105 -2
  35. package/dist/plugin-presentation.js +59 -5
  36. package/dist/plugins.d.ts +14 -3
  37. package/dist/plugins.js +12 -6
  38. package/dist/project-notes.js +1 -1
  39. package/dist/protocol.d.ts +6 -0
  40. package/dist/protocol.js +30 -12
  41. package/dist/replay.js +4 -0
  42. package/dist/response-contracts.js +26 -11
  43. package/dist/running.d.ts +10 -0
  44. package/dist/running.js +45 -0
  45. package/dist/server.d.ts +2 -0
  46. package/dist/server.js +71 -8
  47. package/dist/state.d.ts +8 -0
  48. package/dist/state.js +8 -0
  49. package/dist/studio-layout.js +3 -3
  50. package/dist/task-frame.d.ts +1 -1
  51. package/dist/timeline-events.d.ts +1 -0
  52. package/dist/timeline-events.js +8 -1
  53. package/dist/ui-context.d.ts +73 -0
  54. package/dist/ui-context.js +98 -0
  55. package/dist/ui-contract.d.ts +23 -0
  56. package/dist/web/.vite/manifest.json +133 -74
  57. package/dist/web/assets/answer-board-CeYS8k8y.js +1 -0
  58. package/dist/web/assets/choices-D8vGwsl2.js +1 -0
  59. package/dist/web/assets/{console-Trqc0swD.js → console-Dyuo1q4N.js} +4 -4
  60. package/dist/web/assets/{dist-pF902jHA.js → dist-CSiCyBml.js} +1 -1
  61. package/dist/web/assets/{dist-DpIHRk3B.js → dist-D9FLb9Hm.js} +1 -1
  62. package/dist/web/assets/{dist-eMx6b8N1.js → dist-FXbTBwML.js} +1 -1
  63. package/dist/web/assets/{dist-DZmszrjG.js → dist-l3DUhbw5.js} +1 -1
  64. package/dist/web/assets/{explore-in-BCHqjJ7B.js → explore-in-BfSFGRh2.js} +1 -1
  65. package/dist/web/assets/graph-BU2_o5uA.js +2 -0
  66. package/dist/web/assets/index-C16mXVvk.css +1 -0
  67. package/dist/web/assets/index-CWg9XYXx.js +952 -0
  68. package/dist/web/assets/markdown.reader-Ct2-e2Zt.png +0 -0
  69. package/dist/web/assets/{notes-meaning-DcVGUBBY.js → notes-meaning-T2m5zQfv.js} +1 -1
  70. package/dist/web/assets/overview-PzfLJCo-.js +6 -0
  71. package/dist/web/assets/preview-BguEdsza.js +1 -0
  72. package/dist/web/assets/previews-pWNik-2g.js +3 -0
  73. package/dist/web/assets/{review-DYP0aCXb.js → review-Dy_85lcH.js} +1 -1
  74. package/dist/web/assets/rho.editor-CCJeyHma.png +0 -0
  75. package/dist/web/assets/rho.flow-WkMj6fRS.png +0 -0
  76. package/dist/web/assets/rho.media-Cc6amodO.png +0 -0
  77. package/dist/web/assets/rho.notebook-6vsZKGDq.png +0 -0
  78. package/dist/web/assets/rho.object-BGoeXjte.png +0 -0
  79. package/dist/web/assets/rho.plots-yIiISCkb.png +0 -0
  80. package/dist/web/assets/rho.scatter-DwHxesCs.png +0 -0
  81. package/dist/web/assets/rho.servers-DKf_mnNq.png +0 -0
  82. package/dist/web/assets/rho.table-DWOGaWDa.png +0 -0
  83. package/dist/web/assets/rho.variables-CKcWczhQ.png +0 -0
  84. package/dist/web/assets/{skills-1ec0Z-jx.js → skills-Dyz7mBlc.js} +1 -1
  85. package/dist/web/assets/{skills-work-DNexV4OJ.js → skills-work-BEGqlCkx.js} +1 -1
  86. package/dist/web/assets/studio-process-D-ykbC4D.js +2 -0
  87. package/dist/web/assets/timeline-events-DKTho9fU.js +1 -0
  88. package/dist/web/assets/work-turns-DSabCogR.js +1 -0
  89. package/dist/web/assets/{workspace-COmCbGV3.js → workspace-DfjBL--r.js} +2 -2
  90. package/dist/web/composition.json +138 -83
  91. package/dist/web/index.html +3 -3
  92. package/dist/web/renderer.json +1 -1
  93. package/dist/web-assets.js +2 -2
  94. package/dist/work-graph.d.ts +11 -1
  95. package/dist/work-graph.js +16 -5
  96. package/dist/workspace.d.ts +2 -2
  97. package/dist/workspace.js +8 -7
  98. package/docs/FRONTEND.md +53 -11
  99. package/docs/HEADLESS.md +104 -11
  100. package/docs/THIRD-PARTY-PLUGINS.md +75 -3
  101. package/node_modules/@rho-workspace/plugin-agent/README.md +5 -0
  102. package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.d.ts +31 -0
  103. package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.js +45 -10
  104. package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +19 -3
  105. package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +3 -2
  106. package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +1 -1
  107. package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +8 -0
  108. package/node_modules/@rho-workspace/plugin-agent/dist/index.js +3 -2
  109. package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.d.ts +13 -0
  110. package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.js +13 -0
  111. package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +16 -8
  112. package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.d.ts +1 -1
  113. package/node_modules/@rho-workspace/plugin-agent/package.json +1 -1
  114. package/node_modules/@rho-workspace/plugin-figures/LICENSE +661 -0
  115. package/node_modules/@rho-workspace/plugin-figures/README.md +191 -0
  116. package/node_modules/@rho-workspace/plugin-figures/dist/index.d.ts +236 -0
  117. package/node_modules/@rho-workspace/plugin-figures/dist/index.js +165 -0
  118. package/node_modules/@rho-workspace/plugin-figures/dist/operations.d.ts +44 -0
  119. package/node_modules/@rho-workspace/plugin-figures/dist/operations.js +39 -0
  120. package/node_modules/@rho-workspace/plugin-figures/dist/presentation.d.ts +19 -0
  121. package/node_modules/@rho-workspace/plugin-figures/dist/presentation.js +26 -0
  122. package/node_modules/@rho-workspace/plugin-figures/dist/record.d.ts +188 -0
  123. package/node_modules/@rho-workspace/plugin-figures/dist/record.js +143 -0
  124. package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.d.ts +1 -0
  125. package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.js +68 -0
  126. package/node_modules/@rho-workspace/plugin-figures/dist/ui.d.ts +56 -0
  127. package/node_modules/@rho-workspace/plugin-figures/dist/ui.js +363 -0
  128. package/node_modules/@rho-workspace/plugin-figures/package.json +41 -0
  129. package/node_modules/@rho-workspace/plugin-figures/py/figures.py +856 -0
  130. package/node_modules/@rho-workspace/plugin-figures/r/figures.R +775 -0
  131. package/node_modules/@rho-workspace/plugin-kernel/README.md +27 -3
  132. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.d.ts +12 -0
  133. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +8 -1
  134. package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +61 -0
  135. package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +125 -9
  136. package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.d.ts +14 -0
  137. package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.js +9 -0
  138. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +23 -5
  139. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -1
  140. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +6 -4
  141. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +44 -17
  142. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +6 -0
  143. package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
  144. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +144 -4
  145. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +106 -1
  146. package/node_modules/@rho-workspace/plugin-tables/README.md +5 -0
  147. package/node_modules/@rho-workspace/plugin-tables/dist/index.d.ts +1 -0
  148. package/node_modules/@rho-workspace/plugin-tables/dist/index.js +19 -3
  149. package/node_modules/@rho-workspace/plugin-tables/dist/ui.js +1 -1
  150. package/node_modules/@rho-workspace/plugin-tables/package.json +1 -1
  151. package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +3 -2
  152. package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
  153. package/node_modules/jose/dist/webapi/jws/general/verify.js +2 -8
  154. package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +11 -8
  155. package/node_modules/jose/dist/webapi/lib/key_management.js +0 -1
  156. package/node_modules/jose/package.json +1 -1
  157. package/package.json +6 -4
  158. package/protocol/index.json +91 -55
  159. package/protocol/operations/agent.sessions.list.json +13 -5
  160. package/protocol/operations/agent.sessions.read.json +13 -5
  161. package/protocol/operations/capability.list.json +382 -6
  162. package/protocol/operations/composition.apply.json +10 -2
  163. package/protocol/operations/composition.delete.json +845 -0
  164. package/protocol/operations/composition.read.json +10 -2
  165. package/protocol/operations/composition.undo.json +10 -2
  166. package/protocol/operations/conversation.agents.add.json +346 -0
  167. package/protocol/operations/conversation.agents.remove.json +340 -0
  168. package/protocol/operations/conversation.decide.json +196 -0
  169. package/protocol/operations/conversation.permission.json +196 -0
  170. package/protocol/operations/conversation.read.json +196 -0
  171. package/protocol/operations/conversation.send.json +265 -0
  172. package/protocol/operations/conversation.settings.check.json +213 -495
  173. package/protocol/operations/conversation.settings.read.json +211 -490
  174. package/protocol/operations/conversation.settings.update.json +235 -554
  175. package/protocol/operations/conversation.start.json +267 -5
  176. package/protocol/operations/conversation.stop.json +196 -0
  177. package/protocol/operations/extension.describe.json +39 -0
  178. package/protocol/operations/extension.install.json +62 -0
  179. package/protocol/operations/extension.list.json +164 -1
  180. package/protocol/operations/extension.market.change.json +210 -0
  181. package/protocol/operations/figure.read.json +265 -0
  182. package/protocol/operations/figure.select.json +157 -0
  183. package/protocol/operations/kernel.interrupt.json +119 -0
  184. package/protocol/operations/kernel.read.json +119 -0
  185. package/protocol/operations/kernel.run.json +119 -0
  186. package/protocol/operations/observation.snapshot.json +416 -8
  187. package/protocol/operations/project.openFile.json +10 -2
  188. package/protocol/operations/session.grant.json +4 -0
  189. package/protocol/operations/shared.json +1 -0
  190. package/protocol/operations/view.close.json +10 -2
  191. package/protocol/operations/view.protection.set.json +10 -2
  192. package/vendor/composition.json +177 -112
  193. package/vendor/rho-workspace-plugin-agent-0.4.33.tgz +0 -0
  194. package/vendor/rho-workspace-plugin-figures-0.5.1.tgz +0 -0
  195. package/vendor/rho-workspace-plugin-kernel-0.5.13.tgz +0 -0
  196. package/vendor/rho-workspace-plugin-tables-0.5.2.tgz +0 -0
  197. package/web/theme.css +5 -0
  198. package/dist/web/assets/answer-board-CBkGbSLD.js +0 -1
  199. package/dist/web/assets/canvas-D8rDdfDz.js +0 -1
  200. package/dist/web/assets/choices-D-YfnFP6.js +0 -1
  201. package/dist/web/assets/graph-lENgfQNH.js +0 -2
  202. package/dist/web/assets/index-B3_OVy91.css +0 -1
  203. package/dist/web/assets/index-BpL_Kl_s.js +0 -892
  204. package/dist/web/assets/overview-CDnVKlOt.js +0 -6
  205. package/dist/web/assets/preview-BEC1E9MK.js +0 -1
  206. package/dist/web/assets/studio-process-EYQgikxq.js +0 -2
  207. package/dist/web/assets/timeline-events-Chj2Gbx7.js +0 -1
  208. package/dist/web/assets/work-turns-CT9RVcWY.js +0 -1
  209. package/vendor/rho-workspace-plugin-agent-0.4.30.tgz +0 -0
  210. package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
  211. package/vendor/rho-workspace-plugin-tables-0.5.0.tgz +0 -0
package/docs/FRONTEND.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Browser application
2
2
 
3
- Updated 2026-11-01. The frontend was reset at the user's request. Previous frontend
3
+ Updated 2026-11-07. The frontend was reset at the user's request. Previous frontend
4
4
  plans, visual specifications, Paper references and appearance rulers have been
5
5
  removed from the working repository. Git retains their history; they do not govern
6
6
  this implementation. This document describes the current application, not another
@@ -10,6 +10,19 @@ prescriptive design system. How it changes next is decided by reviewing real run
10
10
  screens do not yet show. Avoid unnecessary secondary labels and descriptions: let the
11
11
  layout do the explaining.
12
12
 
13
+ ## The Agent's view of the sending tab
14
+
15
+ Each message sent from the Agent panel carries that tab's page and, in Studio,
16
+ its workspace ID, observed layout version and replay position. The service freezes
17
+ a bounded view summary with the message; a retry keeps that original context.
18
+ Under a Studio message, the workspace name records where it was sent from.
19
+ An empty workspace has no views, even when the project contains files. An Agent can
20
+ delete that empty layout through `composition.delete` and restore it through
21
+ `composition.undo`. Deletion removes the sidebar entry and moves an observing tab
22
+ to another workspace, or Overview if none remains. Project files and sessions are
23
+ separate. The [Studio context episode](STUDIO-CONTEXT-EPISODE.md) records the original
24
+ misunderstanding, the real OpenCode run and the limits of this summary.
25
+
13
26
  ## Starting from the work
14
27
 
15
28
  A person needs to find a project, work on its contents, ask an Agent for help and
@@ -111,6 +124,20 @@ inspect what happened. The application gives those actions direct places:
111
124
  the reader is there, also when it is shown again or resized. Only the reader's own
112
125
  wheel, touch, keys or scroll bar take it off the end
113
126
  ([episode](SIMULATED-PROJECT-EPISODE.md#the-notebook)).
127
+ - **Figures.** An R plot built with ggplot2 (directly or by a package on it) is drawn in the
128
+ cell and in the Plots view from its figure record. Pointing at a mark shows its row;
129
+ click, Shift/⌘-click or drag selects, Esc clears, and the rows behind the selection are
130
+ listed by layer. **Ask the Agent** keeps the selection and opens the Agent panel with it
131
+ attached to the next message; the chip's × detaches it. Marks the Agent pointed at appear
132
+ as an amber chip above the plot with its note, shown on the plot when the person has
133
+ nothing selected. A plot nothing described stays a picture; one that failed says why
134
+ under it ([episode](FIGURES-EPISODE.md)). A ggplot a cell saved with `ggsave` is drawn the
135
+ same way wherever its file is shown with the bytes the cell saved: on the answer board, in
136
+ the Results & figures file view and Studio's media view, and under the cell, which names
137
+ the file ([episode](SAVED-FIGURES-EPISODE.md)). Layers that draw the same rows count the
138
+ selection once. Pages drawn with base graphics and ComplexHeatmaps are drawn and selected
139
+ the same way; heatmap cells keep their colour while others are selected, and a selection
140
+ outlines them ([episode](DRAWN-FIGURES-EPISODE.md)).
114
141
  - **Two forms.** Notebook | Console switches how the same sessions read: as a notebook (above)
115
142
  or as a console, one transcript as R or Python print it at a terminal. Each cell is its code
116
143
  after the prompt (`>`/`+` in R, `>>>`/`...` in Python, a new statement at the prompt) and what
@@ -255,22 +282,33 @@ inspect what happened. The application gives those actions direct places:
255
282
  conversations. Each row names where the session started (Terminal, VS Code, Codex
256
283
  app, Script…) and whether another window has it open now, and offers Continue,
257
284
  the terminal resume command, Markdown export and the trash
258
- ([session continuity](SESSION-CONTINUITY-EPISODE.md)). Agents lists the Agents
259
- this service can start, with the one new conversations use, its model and folded
260
- settings; an Agent not found on this computer is greyed out. Below it, one setup
261
- prompt is for the person's own local MCP client. The prompt names
262
- the running installation and exact existing project; the Agent configures its
263
- own client. Reading or copying grants no access. Connections are listed by their
264
- actual session labels, with access waiting for a call, a verified last call,
265
- refusal or ended access. Copy failure leaves selectable text; request failure
285
+ ([session continuity](SESSION-CONTINUITY-EPISODE.md)). Agents is one list of
286
+ the project's Agents in one row design, whatever way each works here: the Agents
287
+ this service can start (a radio for new conversations, the model and folded
288
+ settings; one not found on this computer is greyed out), and the Agents connected
289
+ from their own windows. A window sits under the Agent it belongs to, named by its
290
+ MCP client's own name (`clientInfo`) or else by the session name the Agent chose;
291
+ each window shows its name, a waiting, verified, refused or ended state, what its
292
+ grant allows and its last call. Marks and Agent names come from the Agent icons
293
+ plugin, by the words of the name an Agent gave; a name it does not know shows
294
+ its initial. Below the list, one setup prompt is for the person's own local
295
+ MCP client. The prompt names the project and gives one `rho connect` command,
296
+ whose reply tells the Agent how to configure its own client. Reading or copying grants no
297
+ access. Copy failure leaves selectable text; request failure
266
298
  offers retry. ACP observations and Host activity remain under a disclosure
267
299
  ([connection episode](AGENT-CONNECT-EPISODE.md)). Conversation rows retain title,
268
300
  model, effort, messages, state, usage, cost and their files, commands and answers;
269
301
  the trash restores or deletes. Runs follow when present
270
302
  ([Agents and conversations](AGENT-PAGE-EPISODE.md)).
271
303
  - **Project settings** manages source folders and explicitly checks available tools.
304
+ - **Work from other windows** feeds the same Graph, Console, answer board
305
+ and session Studio through `agent.sessions.read`. File links in a native answer are
306
+ reported results; current bytes are not labelled historical. A continued native session
307
+ is listed once. Notebook cells join its timeline by exact cell IDs in their receipts,
308
+ not by assuming every call on one connection belongs to that conversation. The
309
+ [external work episode](EXTERNAL-WORK-EPISODE.md) records the actual Harness check.
272
310
  - **Studio** shows how a conversation's work was done. The sidebar lists under Studio
273
- each session started in Rho, newest first (eight, then All sessions), and then the
311
+ each recorded Agent session, newest first (eight, then All sessions), and then the
274
312
  person's own workspaces; the Studio bar names the one shown. A session is titled by its
275
313
  section of the project notes, else by the Agent's title, else by its first message.
276
314
  The **project notes** (`project-notes.md` at the project root) are one Markdown file
@@ -571,7 +609,11 @@ four messages through `web/agent-work.tsx`, the same step model as the panel.
571
609
  `web/agent-settings.tsx` keeps one copy of `conversation.settings.read` per tab for
572
610
  the Agents tab and the panel's settings menu (`web/settings-menu.tsx`);
573
611
  asking an Agent what it offers (`conversation.settings.check`) is always the
574
- person's click.
612
+ person's click. The Agents tab adds an ACP Agent (`conversation.agents.add`) when the
613
+ person clicks Add on one found installed, types its command, or clicks Use in Rho on an
614
+ Agent connected from its own window whose command Rho knows; an added Agent's settings
615
+ show its command and Remove from Rho. An Agent that offers no settings says so in the
616
+ message box rather than offering to ask.
575
617
  Shared notes opens `project-notes.md` in the existing Documents owner. A failed work read keeps the previous
576
618
  view and offers Refresh. The cursor invalidates the read model; individual sources
577
619
  and resources retain their own versions. See the [observed loop](ACP-EPISODE.md#shared-workspace-loop).
package/docs/HEADLESS.md CHANGED
@@ -40,7 +40,7 @@ The selected baseline is macOS arm64, Node 22.22.3 (engine range
40
40
  `>=22.22.0 <27`; the suite and the installed check also passed on Node 24.22.2 and
41
41
  26.13.0), npm 10.9.8, Python 3 with `fcntl`, and Rscript 4.5.2. Windows x64
42
42
  was checked with Node 22.23.4 and Rscript 4.5.1; its writer lock needs no Python, and
43
- there `rho.cmd` starts Rho and Ctrl+Break on its console stops it in order.
43
+ there `rho.cmd` starts Rho, and Ctrl+Break on its console or `rho --stop` stops it in order.
44
44
  The unsigned npm package `@rho-workspace/rho` contains Plugins and one digest-checked Core
45
45
  binary for each of macOS arm64, Linux x64 and arm64 and Windows x64; CI installs it on each.
46
46
  R and any project-selected packages are external prerequisites, and so are the Agent
@@ -89,6 +89,17 @@ the state that named the old address is rewritten to the new one, keeping its to
89
89
  port named, never replaced. A second start on a state that is already running is
90
90
  refused with the running service's address.
91
91
 
92
+ `rho --stop [--state-dir DIR]` (`~/Rho/state` without the flag) stops the service that
93
+ holds that state as Ctrl+C does, from any program. On Windows no signal reaches a
94
+ console program from another process, so an Agent that started Rho in the background
95
+ had no orderly way to stop it ([episode](WINDOWS-SETUP-EPISODE.md)). The command leaves
96
+ `service.stop` in the state folder, which the running launcher takes within half a
97
+ second, then waits until the state's writer lock is free and the process that served
98
+ it, named in `connection.json`, has ended. It prints `{"stopped":{"state":…}}`, and the
99
+ service's own output ends with its `{"shutdown":…}` line. It exits 1 when nothing holds
100
+ that state, or when the service there is an earlier version that never takes the
101
+ request (after 10 s, removing it). A start removes a request no service took.
102
+
92
103
  `--port`, `--rscript`, `--core`, `--plugins` and `--limits` are explicit
93
104
  operator selections; `RHO_RSCRIPT`, `RHO_CORE_BINARY` and `RHO_PYTHON` select
94
105
  equivalent runtimes. A Core override is outside bundled digest verification.
@@ -135,15 +146,52 @@ service and reports confirmation separately. Existing Hosts keep their owner.
135
146
  ### Connect your own Agent
136
147
 
137
148
  The project's **Agent → Agents** tab offers one prompt for any Agent
138
- that can configure and run a local stdio MCP server. `session.instructions` is a
139
- read-only operation for the local browser or operator: it returns the current
140
- Node/launcher paths and existing project ID, without credential contents. Reading
141
- or copying starts no Agent, grants no access and edits no client configuration.
142
- The Agent uses its own supported configuration mechanism; Rho needs no additional
143
- brand-specific form. The [connection episode](AGENT-CONNECT-EPISODE.md) records the
144
- actual clients checked and the limits.
149
+ that can configure and run a local stdio MCP server. The prompt names the project
150
+ and gives one command:
151
+
152
+ ```sh
153
+ rho connect "/absolute/project folder" --as NAME
154
+ ```
155
+
156
+ The Agent puts a short, stable name for itself in place of `NAME`.
157
+
158
+ Each Rho started from the command line writes a note in `~/Rho/running/`: its state
159
+ folder, address and process ID, with no credential. It removes the note when it
160
+ stops, and a note whose process is gone is ignored. `RHO_RUNNING_DIR` moves that
161
+ folder.
162
+
163
+ Without `--state-dir`, `rho connect` asks each running Rho with a note, and the Rho
164
+ on `~/Rho/state`, which of them holds the folder. It refuses when more than one does.
165
+ The prompt adds `--state-dir` only for a Rho that has no note and does not use
166
+ `~/Rho/state`, such as one started by a program rather than the CLI.
167
+
168
+ When the first `rho` on the service's PATH is not this installation, or cannot run,
169
+ the prompt starts the command with this Node and `cli.js` instead. The build marks
170
+ `dist/cli.js` executable, so a source checkout linked onto PATH keeps working.
171
+
172
+ `rho connect` finds the innermost project holding the folder, grants it to the named
173
+ Agent and tells the Agent the rest in its reply:
145
174
 
146
- The running service's operator connection grants one project to a named Agent:
175
+ - the MCP server to add, and the ready entries below;
176
+ - to keep the entry private and out of version control;
177
+ - to call `rho_index` and check the project ID.
178
+
179
+ `--json` prints the `--grant` reply instead. The command refuses, without a grant:
180
+
181
+ - a missing or placeholder name (exit 2);
182
+ - a folder that is not a project, or a state folder no Rho has used (exit 1);
183
+ - a folder that more than one running Rho holds (exit 1);
184
+ - no running Rho, naming the last address it knows (exit 1).
185
+
186
+ `session.instructions` is a read-only operation for the local browser or operator.
187
+ It returns the prompt and the command, without credential contents. Reading or
188
+ copying starts no Agent, grants no access and edits no client configuration. The
189
+ Agent uses its own supported configuration mechanism and writes its own entry; the
190
+ ready entries only spare it the search. The [connection episode](AGENT-CONNECT-EPISODE.md)
191
+ records the actual clients checked and the limits.
192
+
193
+ The running service's operator connection also grants one project to a named Agent
194
+ directly, which setup prompts gave before `rho connect`:
147
195
 
148
196
  ```sh
149
197
  rho --grant /absolute/service-state/connection.json \
@@ -159,8 +207,13 @@ legacy default is `claude-code`.
159
207
  The JSON reply supplies `mcp.name`, `mcp.command` and `mcp.args` for the Agent to
160
208
  register in its own client. `rho --mcp FILE` serves that project grant on stdio with
161
209
  `rho_index`, `rho_resolve`, `rho_call` and `rho_read`. Connection files are credentials;
162
- keep their contents out of prompts and projects. The reply also retains the older
163
- `claude` command for compatibility.
210
+ keep their contents out of prompts and projects. The reply also holds ready entries
211
+ for the clients whose own mechanism has been used with Rho: `claude` and `codex`
212
+ (their `mcp add` commands), `opencode` (the project's `.opencode/opencode.json` and
213
+ the entry to merge) and `dsh` (the DeepSeek Harness profile patch and the entry to
214
+ append). The bridge sends its MCP client's `clientInfo` name and version as
215
+ `x-rho-client`; Rho keeps it with the grant and shows it as `client` in the
216
+ snapshot's `agents`, ignoring anything that is not short printable text.
164
217
 
165
218
  Repeating the same grant keeps a valid credential with the same project, methods
166
219
  and profile; changing scope, expired access or stale connection data requires a
@@ -213,6 +266,26 @@ settings name one; a model without Auto runs in Accept edits with the adapter's
213
266
  Codex's adapter already opens in its own auto-review mode. ACP cancellation confirms the
214
267
  turn, not every native job.
215
268
 
269
+ Any other Agent that speaks ACP v1 runs through the generic driver, with no adapter of its
270
+ own. `conversation.agents.add` adds one for new conversations in every project, given its
271
+ program (on `PATH` or a path) and arguments, such as `["hermes", "acp"]`. Rho starts it
272
+ once in the project, as `conversation.settings.check` does: a session is opened, what the
273
+ Agent offers is read and the session is removed; no message is sent. The Agent is kept as
274
+ `acp:<name>` only when it answered; otherwise the reply is the Agent's error, or the last
275
+ line it printed when it exited first. `conversation.settings.read` lists the added Agents
276
+ (`added`) and, from `PATH`, the Agents with a known ACP command that are installed and not
277
+ added (`installed`; Agent's `knownAcpAgents`). `conversation.agents.remove` takes one out
278
+ of new conversations. Each conversation records the command it started with, so it
279
+ continues with the same Agent. `--acp-command '<argv JSON>'` / `RHO_ACP_COMMAND` still
280
+ names one ACP Agent at start (`acp`); without it, `dsh acp` is used when Harness's `acp`
281
+ profile is installed. A Harness session in the folder continues only through Harness's own
282
+ ACP command, named at start or added. See the [episode](AGENTS-IN-RHO-EPISODE.md).
283
+
284
+ | Protected resource | Configuration and default | Exhaustion |
285
+ | --- | --- | --- |
286
+ | Added Agents, kept in the service's Agent settings | `conversations.maxAddedAgents` 16 | Adding another is refused until one is removed. |
287
+ | Starting an Agent to add it | `conversations.connectTimeoutMs` 60 s; one start per command at a time | The start ends; nothing is added and the reason is returned. |
288
+
216
289
  For an external ACP client, select this command as that client's Agent executable:
217
290
 
218
291
  ```sh
@@ -435,6 +508,7 @@ RHO_CORE_BINARY=/absolute/rho-core npm run recovery
435
508
  RHO_CORE_BINARY=/absolute/rho-core npm run agent
436
509
  npm run pack:local -- --core darwin-arm64=/absolute/mac/rho-core \
437
510
  --core linux-x64=/absolute/linux-x64/rho-core --core linux-arm64=/absolute/linux-arm64/rho-core \
511
+ --core win32-x64=/absolute/win32-x64/rho-core.exe \
438
512
  --core-source /absolute/Rho-core --plugins /absolute/Rho-plugins
439
513
  node scripts/install-acceptance.mjs /absolute/rho-workspace-rho-0.5.2.tgz \
440
514
  /absolute/ordinary-project /absolute/paired-project
@@ -470,6 +544,25 @@ records source commits/trees, build-input and lock hashes, binary/package digest
470
544
  and platform, plus each selected plugin tarball identity. It does not build Core, sign or publish to a registry. Direct
471
545
  packing of the development source-linked root is rejected.
472
546
 
547
+ Each Core is built as CI's `core` job in [`ci.yml`](../.github/workflows/ci.yml) builds it.
548
+ Check out the Core commit that `components.json` selects with LF line endings
549
+ (`git config --global core.autocrlf false` before cloning). Core compiles
550
+ `docs/ACTIVITY.md` and `docs/MANAGED-RUN.md` in byte for byte, and under Git for
551
+ Windows' default a fresh clone gave a Core whose contract text ended its lines in CRLF
552
+ where CI's has LF. Core keeps those two files LF itself from `8ef933a`, and the packer
553
+ records them among Core's build inputs. Then run
554
+ `CARGO_PROFILE_DEV_DEBUG=0 cargo build --locked --target TARGET`: on Windows, TARGET is
555
+ `x86_64-pc-windows-msvc` (MSVC Build Tools) and the binary is
556
+ `target/x86_64-pc-windows-msvc/debug/rho-core.exe`. A Core built on another machine is
557
+ not CI's: it names that machine's Cargo registry folder, under the builder's user
558
+ folder, 78 times. To carry the accepted Cores instead, take CI's `core-PLATFORM`
559
+ artifacts or the published package's `vendor/core/PLATFORM/` binaries, and add
560
+ `--core-composition` with the composition that records them (the package's
561
+ `vendor/composition.json`); the packer refuses a binary whose digest it does not record. On Windows, `npm ci` in Rho-plugins also
562
+ unpacks the Agent adapters' optional platform clients (Codex 462 MiB, the Claude Agent
563
+ SDK 284 MiB), which the package leaves out. In one run that took 14.5 of its 15
564
+ minutes ([#112](https://github.com/YuLab-SMU/Rho/issues/112)).
565
+
473
566
 
474
567
  ## Native artifact intake (phase 1)
475
568
 
@@ -7,9 +7,10 @@ or becoming a base dependency. The first consumer is the optional
7
7
  Its scientific implementation remains in sclet and its objects remain in the
8
8
  project's shared R session. See the [observed episode](THIRD-PARTY-PLUGINS-EPISODE.md).
9
9
 
10
- This is source functionality after Rho 0.5.4, not a new npm release. Build the
11
- current checkout with `npm run build` and use `node dist/cli.js` in place of
12
- `rho` until a distribution containing it is installed.
10
+ Runtime package installation is included in Rho 0.5.5. User-configured remote
11
+ markets are source functionality after 0.5.6. Build the current checkout with
12
+ `npm run build` and use `node dist/cli.js` in place of `rho` for that new path
13
+ until a distribution containing it is installed.
13
14
 
14
15
  ## Install and manage
15
16
 
@@ -55,6 +56,69 @@ selection. Both preserve native tasks, R sessions, project files and receipts.
55
56
  Package revisions are retained in the service's `plugins/versions` directory;
56
57
  remove is not a disk-purge command. Closing a view only releases its renderer.
57
58
 
59
+ ## Remote plugin markets
60
+
61
+ In **Plugins → Third-party plugins → Plugin markets**, add an HTTPS JSON index.
62
+ The plugin search above includes its titles, descriptions, package ids and
63
+ keywords. Rho retains the index locally; opening the page, searching and listing
64
+ do not fetch it again or install its packages. **Refresh** explicitly reads the
65
+ source again. A failed refresh displays its error and keeps the last successful
66
+ index available. **Remove** removes the source, keeping installed plugins and
67
+ their recorded market keywords searchable.
68
+
69
+ The [YuLab Rho index](https://raw.githubusercontent.com/YuLab-SMU/Rho/main/site/market/rho.json)
70
+ includes Semantic figures, discoverable by `ggtree`, `clusterProfiler`, `dotplot`
71
+ and `cnetplot`. It is an optional source; add it explicitly:
72
+
73
+ ```sh
74
+ rho plugin market add https://raw.githubusercontent.com/YuLab-SMU/Rho/main/site/market/rho.json
75
+ rho plugin market list
76
+ rho plugin market install MARKET_ID yulab.semantic-figures
77
+ rho plugin market refresh MARKET_ID
78
+ rho plugin market remove MARKET_ID
79
+ ```
80
+
81
+ Use the id returned by `add` or `list` for `MARKET_ID`. The ordinary
82
+ `--connection FILE` option selects a running service. The browser and CLI use
83
+ the same saved sources. Agents can read cached indexes with `extension.list`;
84
+ adding, refreshing, removing sources and installing packages remain local-person
85
+ operations through `extension.market.change` and `extension.install`.
86
+
87
+ Publishers can host a static file matching the [actual index](../site/market/rho.json):
88
+
89
+ ```json
90
+ {
91
+ "apiVersion": 1,
92
+ "title": "My lab's Rho plugins",
93
+ "plugins": [{
94
+ "id": "lab.my-plugin",
95
+ "title": "My plugin",
96
+ "version": "0.1.0",
97
+ "description": "What this plugin does",
98
+ "source": "@my-lab/rho-plugin@0.1.0",
99
+ "repository": "https://github.com/my-lab/rho-plugin",
100
+ "needs": "Required tools or data",
101
+ "keywords": ["analysis", "visualization"]
102
+ }]
103
+ }
104
+ ```
105
+
106
+ Each index selects one version per plugin id. `keywords` is optional; the other
107
+ fields shown are required. Package sources use an exact npm version matching
108
+ `version`, or `git+https://…#FULL_40_CHARACTER_COMMIT:package/path`. The package's
109
+ actual plugin id and version must match before installation selects it. Install
110
+ binds the source to the index digest the person selected; a changed or removed
111
+ index refuses a stale install. The installed package records its market URL,
112
+ title, index digest and keywords alongside its own source and runtime-tree digest.
113
+
114
+ Index URLs use HTTPS without credentials, queries or fragments. Explicit loopback
115
+ HTTP supports local development. Redirects, authenticated indexes, automatic
116
+ refresh/update and tarball entries in markets are not supported in this pass;
117
+ direct package installation still accepts tarballs. An index is publisher metadata,
118
+ not a signature or proof of package validity. This Rho `apiVersion: 1` index is
119
+ separate from the [Harness index](../site/market/index.json); the formats are not
120
+ interchangeable. See the [observed market episode](PLUGIN-MARKETS-EPISODE.md).
121
+
58
122
  ## Create a package
59
123
 
60
124
  ```sh
@@ -141,6 +205,10 @@ output.textContent = receipt.state === 'completed'
141
205
  ? receipt.value.greeting : receipt.error;
142
206
  ```
143
207
 
208
+ The frame follows the page's own height from 200 to 900 px, so let the page flow
209
+ rather than fill the viewport; a taller page scrolls inside the frame. The page
210
+ `create` generates shows a labelled form, a result and a failure state to start from.
211
+
144
212
  Pass a third argument to `rho.call` to retain an explicit original changeId.
145
213
  The default generates one per deliberate call. A stale digest is refused; reopen
146
214
  the view after an update. Several views may be declared and selected in the plugin
@@ -174,6 +242,8 @@ and obtained receipts, or records an unknown result after an interrupted call.
174
242
 
175
243
  | Protected resource | Default configuration |
176
244
  | --- | --- |
245
+ | Configured market sources / entries in each index | `maxMarkets: 16`, `maxMarketEntries: 128` |
246
+ | Each downloaded index / request duration | `maxMarketBytes: 262144`, `marketTimeoutMs: 10000` |
177
247
  | Installed selections / retained revisions | `maxPlugins: 32`, `maxVersions: 128` |
178
248
  | Admitted runtime tree | `maxFiles: 20000`, `maxPackageBytes: 134217728` |
179
249
  | Skill/view bytes | `maxContentBytes: 524288` |
@@ -181,6 +251,8 @@ and obtained receipts, or records an unknown result after an interrupted call.
181
251
  | Concurrent calls / delegated calls per invocation | `maxRunning: 4`, `maxDelegations: 32` |
182
252
  | Worker / each install subprocess | `timeoutMs: 30000`, `installTimeoutMs: 180000` |
183
253
 
254
+ Market limits refuse an initial add or retain the previous index after a failed
255
+ refresh. The byte limit also checks streamed responses without a Content-Length.
184
256
  The tree-size check applies after npm staging; it is not a disk quota on npm/Git.
185
257
  A timed-out worker does not prove a delegated native task stopped. Observe its
186
258
  original identity. Retention limits refuse rather than silently evict identities;
@@ -347,6 +347,11 @@ observation of the original identity, never replacement work. A refused connecti
347
347
  transport failure whose outcome is known: nothing reached the service, so the call
348
348
  returns `unavailable` naming the address and saying nothing ran (`delivered: false`).
349
349
 
350
+ Since 0.4.31 each call also carries `x-rho-client`: the name and version the MCP
351
+ client gave in its own `initialize` (`clientInfo`), each URL-encoded and at most 128
352
+ characters, joined by `/`. A client that gives no name sends no header. The
353
+ application uses it to say which Agent a connection belongs to; it grants nothing.
354
+
350
355
  The pure `./layered-contract` entry defines the four tool inputs and Agent note
351
356
  props. `./layered` provides the bounded complete-object cache and caller checks;
352
357
  it owns no second registry, native loop or task scheduler. Before execution it
@@ -8,6 +8,37 @@ export declare function acpCommand(agent: AcpAgent): {
8
8
  * executable, else the one already named in the environment, else the client on PATH. */
9
9
  export declare function acpEnvironment(agent: AcpAgent, env: NodeJS.ProcessEnv, executable?: string): NodeJS.ProcessEnv;
10
10
  export declare function withoutSessionMarkers(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
11
+ /** The file a shell runs for `program`: the program itself when it names a path, else the first
12
+ * runnable match on PATH. On Windows a copied environment keeps the variable's own case (Path),
13
+ * and a program is a file with an extension PATHEXT names (claude.exe, an npm codex.cmd). */
14
+ export declare function locate(program: string, env: NodeJS.ProcessEnv): string | null;
11
15
  /** Why the adapter could not start the person's client, or null when its executable is
12
16
  * there. The adapters report a missing client only as a closed connection. */
13
17
  export declare function missingClient(agent: AcpAgent, env: NodeJS.ProcessEnv): string | null;
18
+ /** Agents that serve ACP through a command of their own: the program a person installs and the
19
+ * arguments that make it an ACP Agent, as the ACP registry (github.com/agentclientprotocol/registry,
20
+ * read 2026-11-07) gives them or as Rho has run them. Rho starts any of these as it starts a
21
+ * command the person names; the list only lets it find them. */
22
+ export interface KnownAcpAgent {
23
+ id: string;
24
+ name: string;
25
+ program: string;
26
+ args: string[];
27
+ }
28
+ export declare const knownAcpAgents: KnownAcpAgent[];
29
+ /** The known Agents installed on this computer, each with the file its command starts. Finding
30
+ * them reads PATH (and Harness's profile folder) and starts nothing: whether one answers as an
31
+ * ACP Agent is known only once it is started. */
32
+ export declare function installedAcpAgents(env?: NodeJS.ProcessEnv): {
33
+ id: string;
34
+ name: string;
35
+ command: {
36
+ file: string;
37
+ args: string[];
38
+ };
39
+ }[];
40
+ /** The known Agent a command starts, when it starts one with its ACP arguments. */
41
+ export declare function knownAcpAgent(command: {
42
+ file: string;
43
+ args: string[];
44
+ }): KnownAcpAgent | null;
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import { fileURLToPath } from 'node:url';
4
5
  export function acpCommand(agent) {
@@ -24,17 +25,15 @@ const sessionMarkers = /^(CLAUDECODE|CLAUDE_CODE_(CHILD_SESSION|ENTRYPOINT|SESSI
24
25
  export function withoutSessionMarkers(env) {
25
26
  return Object.fromEntries(Object.entries(env).filter(([key]) => !sessionMarkers.test(key)));
26
27
  }
27
- /** Why the adapter could not start the person's client, or null when its executable is
28
- * there. The adapters report a missing client only as a closed connection. */
29
- export function missingClient(agent, env) {
30
- const executable = env[executableVariable[agent]] || agent;
31
- // On Windows a copied environment keeps the variable's own case (Path), and a client is a
32
- // file with an extension PATHEXT names (claude.exe, an npm codex.cmd), as the shell finds it.
28
+ /** The file a shell runs for `program`: the program itself when it names a path, else the first
29
+ * runnable match on PATH. On Windows a copied environment keeps the variable's own case (Path),
30
+ * and a program is a file with an extension PATHEXT names (claude.exe, an npm codex.cmd). */
31
+ export function locate(program, env) {
33
32
  const windows = process.platform === 'win32';
34
33
  const variable = (name) => windows ? Object.entries(env).find(([key]) => key.toUpperCase() === name)?.[1] : env[name];
35
- const named = executable.includes('/') || (windows && executable.includes('\\'));
36
- const extensions = windows && !path.extname(executable) ? (variable('PATHEXT') ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) : [''];
37
- const candidates = (named ? [executable] : (variable('PATH') ?? '').split(path.delimiter).filter(Boolean).map(directory => path.join(directory, executable))).flatMap(file => extensions.map(extension => file + extension));
34
+ const named = program.includes('/') || (windows && program.includes('\\'));
35
+ const extensions = windows && !path.extname(program) ? (variable('PATHEXT') ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) : [''];
36
+ const candidates = (named ? [program] : (variable('PATH') ?? '').split(path.delimiter).filter(Boolean).map(directory => path.join(directory, program))).flatMap(file => extensions.map(extension => file + extension));
38
37
  const runnable = (file) => { try {
39
38
  fs.accessSync(file, fs.constants.X_OK);
40
39
  return fs.statSync(file).isFile();
@@ -42,7 +41,43 @@ export function missingClient(agent, env) {
42
41
  catch {
43
42
  return false;
44
43
  } };
45
- if (candidates.some(runnable))
44
+ return candidates.find(runnable) ?? null;
45
+ }
46
+ /** Why the adapter could not start the person's client, or null when its executable is
47
+ * there. The adapters report a missing client only as a closed connection. */
48
+ export function missingClient(agent, env) {
49
+ const executable = env[executableVariable[agent]] || agent;
50
+ if (locate(executable, env))
46
51
  return null;
52
+ const named = executable.includes('/') || (process.platform === 'win32' && executable.includes('\\'));
47
53
  return `${clientName[agent]} was not found ${named ? 'at ' + executable : 'on PATH (' + executable + ')'}. Install it, or select its executable.`;
48
54
  }
55
+ export const knownAcpAgents = [
56
+ // DeepSeek Harness serves ACP from its `acp` profile, installed apart from the launcher.
57
+ { id: 'dsh', name: 'DeepSeek Harness', program: 'dsh', args: ['acp'] },
58
+ { id: 'hermes', name: 'Hermes Agent', program: 'hermes', args: ['acp'] },
59
+ { id: 'kimi', name: 'Kimi Code', program: 'kimi', args: ['acp'] },
60
+ { id: 'opencode', name: 'OpenCode', program: 'opencode', args: ['acp'] },
61
+ { id: 'gemini', name: 'Gemini CLI', program: 'gemini', args: ['--acp'] },
62
+ { id: 'qwen', name: 'Qwen Code', program: 'qwen', args: ['--acp'] },
63
+ { id: 'goose', name: 'goose', program: 'goose', args: ['acp'] },
64
+ { id: 'cursor', name: 'Cursor Agent', program: 'cursor-agent', args: ['acp'] },
65
+ { id: 'auggie', name: 'Auggie CLI', program: 'auggie', args: ['--acp'] },
66
+ { id: 'codebuddy', name: 'CodeBuddy Code', program: 'codebuddy', args: ['--acp'] },
67
+ { id: 'droid', name: 'Factory Droid', program: 'droid', args: ['exec', '--output-format', 'acp-daemon'] },
68
+ ];
69
+ const harnessProfile = (env) => fs.existsSync(path.join(env.DSH_HOME || path.join(os.homedir(), '.dsh'), 'profiles', 'acp', 'package.json'));
70
+ /** The known Agents installed on this computer, each with the file its command starts. Finding
71
+ * them reads PATH (and Harness's profile folder) and starts nothing: whether one answers as an
72
+ * ACP Agent is known only once it is started. */
73
+ export function installedAcpAgents(env = process.env) {
74
+ return knownAcpAgents.flatMap(agent => {
75
+ const file = locate(agent.program, env);
76
+ return file && (agent.id !== 'dsh' || harnessProfile(env)) ? [{ id: agent.id, name: agent.name, command: { file, args: agent.args } }] : [];
77
+ });
78
+ }
79
+ /** The known Agent a command starts, when it starts one with its ACP arguments. */
80
+ export function knownAcpAgent(command) {
81
+ const program = path.basename(command.file).replace(/\.(exe|cmd|bat)$/i, '').toLowerCase();
82
+ return knownAcpAgents.find(agent => agent.program === program && agent.args.length === command.args.length && agent.args.every((arg, i) => arg === command.args[i])) ?? null;
83
+ }
@@ -148,9 +148,16 @@ export class AcpConversation {
148
148
  } });
149
149
  bounded.on('error', () => { });
150
150
  child.stdout.pipe(bounded);
151
- // Drain diagnostics without storing credentials, URLs or native reasoning.
152
- child.stderr.on('data', (chunk) => { this.outputBytes += chunk.length; if (this.outputBytes > o.limits.maxOutputBytes)
153
- overflow('output-limit'); });
151
+ // Drain diagnostics without storing credentials, URLs or native reasoning. Its last few
152
+ // kilobytes stay in memory only, to say why an Agent that exits before it answers did not
153
+ // start (an unknown argument, a missing sign-in); only its last line is shown, redacted.
154
+ let printed = '';
155
+ child.stderr.on('data', (chunk) => {
156
+ this.outputBytes += chunk.length;
157
+ if (this.outputBytes > o.limits.maxOutputBytes)
158
+ overflow('output-limit');
159
+ printed = (printed + chunk.toString('utf8')).slice(-4096);
160
+ });
154
161
  this.connection = acp.client({ name: 'rho-agent' })
155
162
  .onNotification(acp.methods.client.session.update, ({ params }) => {
156
163
  try {
@@ -239,6 +246,15 @@ export class AcpConversation {
239
246
  ...(initialized.agentInfo ? { adapter: { name: initialized.agentInfo.name, version: initialized.agentInfo.version } } : {}), ...(model ? { model } : {}) };
240
247
  this.observationOrigin = 'live';
241
248
  }
249
+ catch (error) {
250
+ await Promise.race([this.childEnded, new Promise(resolve => setTimeout(resolve, 300))]);
251
+ const last = printed.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').split(/\r?\n/).map(line => line.trim()).filter(Boolean).at(-1);
252
+ // The process may have ended while this waited, which the type of exit does not follow.
253
+ const exit = this.exit;
254
+ if (exit && last && !this.fault)
255
+ throw new Error(`The Agent exited before it answered${exit.code !== null ? ' (exit ' + exit.code + ')' : ''}. It printed: ${last.slice(0, 300)}`);
256
+ throw error;
257
+ }
242
258
  finally {
243
259
  clearTimeout(timer);
244
260
  }
@@ -10,9 +10,10 @@ export const externalToolSchema = obj({ id: text, at: text, name: text, detail:
10
10
  export const externalTurnSchema = obj({ id: text, at: text, endedAt: text, state, prompt: text, reply: text, messages: array(obj({ at: text, text }), 200), tools: array(externalToolSchema, externalSessionLimits.maxTools), results: array(externalResultSchema, 64), omittedTools: count, omittedChars: count, where: text }, ['id', 'at', 'state', 'prompt', 'reply', 'messages', 'tools', 'results', 'omittedTools', 'omittedChars']);
11
11
  export const externalSessionSummarySchema = obj({ id: { type: 'string', pattern: '^x-[a-f0-9]{64}$' }, producer: text, sessionId: text, title: text, lastAt: text, observedAt: text, state, turns: count, sourceCursor: count, omittedTurns: count, problem: text, inputOwner: { const: 'external' },
12
12
  // Native sessions: where it was started, a live process holding it, the command that continues it in a terminal
13
- // and its size. The application adds the Agent it continues with in Rho and the Rho conversation that continued it.
13
+ // and its size. The application adds the Agent it continues with in Rho (acp:… is an ACP Agent the person
14
+ // added there) and the Rho conversation that continued it.
14
15
  where: text, open: { anyOf: [{ type: 'null' }, obj({ where: text, since: text, pid: count }, ['where'])] }, resume: text, bytes: count,
15
- continueWith: { enum: ['claude-acp', 'codex-acp', 'acp', null] }, conversationId: text }, ['id', 'producer', 'sessionId', 'title', 'lastAt', 'observedAt', 'state', 'turns', 'sourceCursor', 'omittedTurns']);
16
+ continueWith: { anyOf: [{ enum: ['claude-acp', 'codex-acp', 'acp', null] }, { type: 'string', pattern: '^acp:[a-z0-9][a-z0-9-]{0,39}$' }] }, conversationId: text }, ['id', 'producer', 'sessionId', 'title', 'lastAt', 'observedAt', 'state', 'turns', 'sourceCursor', 'omittedTurns']);
16
17
  export const externalSessionSchemas = { summary: externalSessionSummarySchema, turn: externalTurnSchema };
17
18
  export const externalSessionOperations = {
18
19
  'agent.sessions.list': { inputSchema: obj({}), outputSchema: obj({ sessions: array(externalSessionSummarySchema, 128), omitted: count }), requires: [], effect: 'read' },
@@ -54,7 +54,7 @@ export interface ExternalSessionSummary {
54
54
  } | null;
55
55
  resume?: string;
56
56
  bytes?: number;
57
- continueWith?: 'claude-acp' | 'codex-acp' | 'acp' | null;
57
+ continueWith?: 'claude-acp' | 'codex-acp' | 'acp' | `acp:${string}` | null;
58
58
  conversationId?: string;
59
59
  }
60
60
  export interface ExternalSessionRecord {
@@ -1971,6 +1971,14 @@ export declare const plugin: {
1971
1971
  modelLoop: string;
1972
1972
  contextDelivery: string;
1973
1973
  proactivePush: string;
1974
+ mcp: {
1975
+ entry: string;
1976
+ transport: string;
1977
+ tools: {
1978
+ name: string;
1979
+ description: string;
1980
+ }[];
1981
+ };
1974
1982
  };
1975
1983
  };
1976
1984
  export { listExternalSessions, readExternalSession } from './external-session.js';
@@ -1,4 +1,5 @@
1
1
  import { layeredSchemas } from './layered-contract.js';
2
+ import { mcpEntry } from './mcp-entry.js';
2
3
  import { skillPresentation, presentation, operationPresentation } from './presentation.js';
3
4
  import { operationContracts } from './operations.js';
4
5
  import * as fs from 'node:fs/promises';
@@ -302,9 +303,9 @@ const presentationSkill = {
302
303
  ],
303
304
  verify: { command: 'npm run test:presenter', description: 'Presentation enhances display within its witness and role, and refuses business feedback or document access.' }
304
305
  };
305
- export const plugin = { entryVersion: '1.0', id: 'agent', package: '@rho-workspace/plugin-agent', version: '0.4.19', requires: [], capabilities: ['agent-context'], presentation,
306
+ export const plugin = { entryVersion: '1.0', id: 'agent', package: '@rho-workspace/plugin-agent', version: '0.4.33', requires: [], capabilities: ['agent-context'], presentation,
306
307
  operations: Object.entries(operationContracts).map(([method, contract]) => ({ method, presentation: operationPresentation[method], version: '2.0', ...structuredClone(contract), requires: [...contract.requires] })),
307
308
  components: [{ id: 'rho.agent-note', version: '0.2.0', owner: 'rho-plugins.agent', bindings: {}, propsSchema: layeredSchemas.AgentNoteProps, ui: './ui', presentation: { displayName: 'Agent note', description: 'A plain presentation explanation with authenticated author and binding basis.', readOnly: true, runsCode: false } }],
308
309
  skills: [{ ...agentSkill, presentation: skillPresentation }, { ...feedbackSkill, presentation: { displayName: 'Business feedback', description: 'Reads original feedback, preserves current human edits and links an explicit reply.' } }, { ...presentationSkill, presentation: { displayName: 'Presentation', description: 'Enhances the assigned visible layout without business feedback or document access.' } }],
309
- agent: { id: 'claude-code-mcp', transport: 'stdio', modelLoop: 'external-client', contextDelivery: 'pull-tool-result', proactivePush: 'unsupported' } };
310
+ agent: { id: 'claude-code-mcp', transport: 'stdio', modelLoop: 'external-client', contextDelivery: 'pull-tool-result', proactivePush: 'unsupported', mcp: mcpEntry } };
310
311
  export { listExternalSessions, readExternalSession } from './external-session.js';
@@ -0,0 +1,13 @@
1
+ export declare const mcpTools: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: any;
5
+ }[];
6
+ export declare const mcpEntry: {
7
+ entry: string;
8
+ transport: string;
9
+ tools: {
10
+ name: string;
11
+ description: string;
12
+ }[];
13
+ };