@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.
- package/dist/agent-connect.d.ts +36 -2
- package/dist/agent-connect.js +64 -11
- package/dist/answer-board.js +3 -3
- package/dist/bridge.js +6 -1
- package/dist/browser-access.d.ts +1 -0
- package/dist/browser-access.js +5 -3
- package/dist/capabilities.d.ts +21 -5
- package/dist/capabilities.js +6 -4
- package/dist/cli.js +76 -11
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +13 -3
- package/dist/connect-cli.d.ts +50 -0
- package/dist/connect-cli.js +128 -0
- package/dist/contract.generated.d.ts +1191 -278
- package/dist/contract.generated.js +1 -1
- package/dist/contracts/owners.generated.js +845 -47
- package/dist/conversation-presentation.d.ts +9 -2
- package/dist/conversation-presentation.js +3 -1
- package/dist/conversations.d.ts +154 -8
- package/dist/conversations.js +147 -24
- package/dist/discovery.js +2 -2
- package/dist/extension-contract.d.ts +19 -0
- package/dist/extension-contract.js +15 -6
- package/dist/extension-markets.d.ts +74 -0
- package/dist/extension-markets.js +166 -0
- package/dist/extensions.d.ts +29 -1
- package/dist/extensions.js +13 -4
- package/dist/external-work.d.ts +22 -0
- package/dist/external-work.js +91 -0
- package/dist/plugin-cli.js +75 -3
- package/dist/plugin-inventory.d.ts +56 -0
- package/dist/plugin-inventory.js +41 -0
- package/dist/plugin-inventory.json +1581 -102
- package/dist/plugin-presentation.d.ts +105 -2
- package/dist/plugin-presentation.js +59 -5
- package/dist/plugins.d.ts +14 -3
- package/dist/plugins.js +12 -6
- package/dist/project-notes.js +1 -1
- package/dist/protocol.d.ts +6 -0
- package/dist/protocol.js +30 -12
- package/dist/replay.js +4 -0
- package/dist/response-contracts.js +26 -11
- package/dist/running.d.ts +10 -0
- package/dist/running.js +45 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +71 -8
- package/dist/state.d.ts +8 -0
- package/dist/state.js +8 -0
- package/dist/studio-layout.js +3 -3
- package/dist/task-frame.d.ts +1 -1
- package/dist/timeline-events.d.ts +1 -0
- package/dist/timeline-events.js +8 -1
- package/dist/ui-context.d.ts +73 -0
- package/dist/ui-context.js +98 -0
- package/dist/ui-contract.d.ts +23 -0
- package/dist/web/.vite/manifest.json +133 -74
- package/dist/web/assets/answer-board-CeYS8k8y.js +1 -0
- package/dist/web/assets/choices-D8vGwsl2.js +1 -0
- package/dist/web/assets/{console-Trqc0swD.js → console-Dyuo1q4N.js} +4 -4
- package/dist/web/assets/{dist-pF902jHA.js → dist-CSiCyBml.js} +1 -1
- package/dist/web/assets/{dist-DpIHRk3B.js → dist-D9FLb9Hm.js} +1 -1
- package/dist/web/assets/{dist-eMx6b8N1.js → dist-FXbTBwML.js} +1 -1
- package/dist/web/assets/{dist-DZmszrjG.js → dist-l3DUhbw5.js} +1 -1
- package/dist/web/assets/{explore-in-BCHqjJ7B.js → explore-in-BfSFGRh2.js} +1 -1
- package/dist/web/assets/graph-BU2_o5uA.js +2 -0
- package/dist/web/assets/index-C16mXVvk.css +1 -0
- package/dist/web/assets/index-CWg9XYXx.js +952 -0
- package/dist/web/assets/markdown.reader-Ct2-e2Zt.png +0 -0
- package/dist/web/assets/{notes-meaning-DcVGUBBY.js → notes-meaning-T2m5zQfv.js} +1 -1
- package/dist/web/assets/overview-PzfLJCo-.js +6 -0
- package/dist/web/assets/preview-BguEdsza.js +1 -0
- package/dist/web/assets/previews-pWNik-2g.js +3 -0
- package/dist/web/assets/{review-DYP0aCXb.js → review-Dy_85lcH.js} +1 -1
- package/dist/web/assets/rho.editor-CCJeyHma.png +0 -0
- package/dist/web/assets/rho.flow-WkMj6fRS.png +0 -0
- package/dist/web/assets/rho.media-Cc6amodO.png +0 -0
- package/dist/web/assets/rho.notebook-6vsZKGDq.png +0 -0
- package/dist/web/assets/rho.object-BGoeXjte.png +0 -0
- package/dist/web/assets/rho.plots-yIiISCkb.png +0 -0
- package/dist/web/assets/rho.scatter-DwHxesCs.png +0 -0
- package/dist/web/assets/rho.servers-DKf_mnNq.png +0 -0
- package/dist/web/assets/rho.table-DWOGaWDa.png +0 -0
- package/dist/web/assets/rho.variables-CKcWczhQ.png +0 -0
- package/dist/web/assets/{skills-1ec0Z-jx.js → skills-Dyz7mBlc.js} +1 -1
- package/dist/web/assets/{skills-work-DNexV4OJ.js → skills-work-BEGqlCkx.js} +1 -1
- package/dist/web/assets/studio-process-D-ykbC4D.js +2 -0
- package/dist/web/assets/timeline-events-DKTho9fU.js +1 -0
- package/dist/web/assets/work-turns-DSabCogR.js +1 -0
- package/dist/web/assets/{workspace-COmCbGV3.js → workspace-DfjBL--r.js} +2 -2
- package/dist/web/composition.json +138 -83
- package/dist/web/index.html +3 -3
- package/dist/web/renderer.json +1 -1
- package/dist/web-assets.js +2 -2
- package/dist/work-graph.d.ts +11 -1
- package/dist/work-graph.js +16 -5
- package/dist/workspace.d.ts +2 -2
- package/dist/workspace.js +8 -7
- package/docs/FRONTEND.md +53 -11
- package/docs/HEADLESS.md +104 -11
- package/docs/THIRD-PARTY-PLUGINS.md +75 -3
- package/node_modules/@rho-workspace/plugin-agent/README.md +5 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.d.ts +31 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.js +45 -10
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +19 -3
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +3 -2
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +1 -1
- package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +8 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/index.js +3 -2
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.d.ts +13 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.js +13 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +16 -8
- package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.d.ts +1 -1
- package/node_modules/@rho-workspace/plugin-agent/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-figures/LICENSE +661 -0
- package/node_modules/@rho-workspace/plugin-figures/README.md +191 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/index.d.ts +236 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/index.js +165 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/operations.d.ts +44 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/operations.js +39 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/presentation.d.ts +19 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/presentation.js +26 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/record.d.ts +188 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/record.js +143 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.js +68 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui.d.ts +56 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui.js +363 -0
- package/node_modules/@rho-workspace/plugin-figures/package.json +41 -0
- package/node_modules/@rho-workspace/plugin-figures/py/figures.py +856 -0
- package/node_modules/@rho-workspace/plugin-figures/r/figures.R +775 -0
- package/node_modules/@rho-workspace/plugin-kernel/README.md +27 -3
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.d.ts +12 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +8 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +61 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +125 -9
- package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.d.ts +14 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.js +9 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +23 -5
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +6 -4
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +44 -17
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +6 -0
- package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +144 -4
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +106 -1
- package/node_modules/@rho-workspace/plugin-tables/README.md +5 -0
- package/node_modules/@rho-workspace/plugin-tables/dist/index.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-tables/dist/index.js +19 -3
- package/node_modules/@rho-workspace/plugin-tables/dist/ui.js +1 -1
- package/node_modules/@rho-workspace/plugin-tables/package.json +1 -1
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +3 -2
- package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
- package/node_modules/jose/dist/webapi/jws/general/verify.js +2 -8
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +11 -8
- package/node_modules/jose/dist/webapi/lib/key_management.js +0 -1
- package/node_modules/jose/package.json +1 -1
- package/package.json +6 -4
- package/protocol/index.json +91 -55
- package/protocol/operations/agent.sessions.list.json +13 -5
- package/protocol/operations/agent.sessions.read.json +13 -5
- package/protocol/operations/capability.list.json +382 -6
- package/protocol/operations/composition.apply.json +10 -2
- package/protocol/operations/composition.delete.json +845 -0
- package/protocol/operations/composition.read.json +10 -2
- package/protocol/operations/composition.undo.json +10 -2
- package/protocol/operations/conversation.agents.add.json +346 -0
- package/protocol/operations/conversation.agents.remove.json +340 -0
- package/protocol/operations/conversation.decide.json +196 -0
- package/protocol/operations/conversation.permission.json +196 -0
- package/protocol/operations/conversation.read.json +196 -0
- package/protocol/operations/conversation.send.json +265 -0
- package/protocol/operations/conversation.settings.check.json +213 -495
- package/protocol/operations/conversation.settings.read.json +211 -490
- package/protocol/operations/conversation.settings.update.json +235 -554
- package/protocol/operations/conversation.start.json +267 -5
- package/protocol/operations/conversation.stop.json +196 -0
- package/protocol/operations/extension.describe.json +39 -0
- package/protocol/operations/extension.install.json +62 -0
- package/protocol/operations/extension.list.json +164 -1
- package/protocol/operations/extension.market.change.json +210 -0
- package/protocol/operations/figure.read.json +265 -0
- package/protocol/operations/figure.select.json +157 -0
- package/protocol/operations/kernel.interrupt.json +119 -0
- package/protocol/operations/kernel.read.json +119 -0
- package/protocol/operations/kernel.run.json +119 -0
- package/protocol/operations/observation.snapshot.json +416 -8
- package/protocol/operations/project.openFile.json +10 -2
- package/protocol/operations/session.grant.json +4 -0
- package/protocol/operations/shared.json +1 -0
- package/protocol/operations/view.close.json +10 -2
- package/protocol/operations/view.protection.set.json +10 -2
- package/vendor/composition.json +177 -112
- package/vendor/rho-workspace-plugin-agent-0.4.33.tgz +0 -0
- package/vendor/rho-workspace-plugin-figures-0.5.1.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.13.tgz +0 -0
- package/vendor/rho-workspace-plugin-tables-0.5.2.tgz +0 -0
- package/web/theme.css +5 -0
- package/dist/web/assets/answer-board-CBkGbSLD.js +0 -1
- package/dist/web/assets/canvas-D8rDdfDz.js +0 -1
- package/dist/web/assets/choices-D-YfnFP6.js +0 -1
- package/dist/web/assets/graph-lENgfQNH.js +0 -2
- package/dist/web/assets/index-B3_OVy91.css +0 -1
- package/dist/web/assets/index-BpL_Kl_s.js +0 -892
- package/dist/web/assets/overview-CDnVKlOt.js +0 -6
- package/dist/web/assets/preview-BEC1E9MK.js +0 -1
- package/dist/web/assets/studio-process-EYQgikxq.js +0 -2
- package/dist/web/assets/timeline-events-Chj2Gbx7.js +0 -1
- package/dist/web/assets/work-turns-CT9RVcWY.js +0 -1
- package/vendor/rho-workspace-plugin-agent-0.4.30.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
- 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-
|
|
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
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
|
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.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
163
|
-
`claude`
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
`
|
|
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
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
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 =
|
|
36
|
-
const extensions = windows && !path.extname(
|
|
37
|
-
const candidates = (named ? [
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
|
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.
|
|
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';
|