@rho-workspace/rho 0.5.3 → 0.5.4
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/README.md +24 -5
- package/dist/capabilities.d.ts +27 -0
- package/dist/capabilities.js +73 -1
- package/dist/cli.js +150 -25
- package/dist/contract.generated.d.ts +112 -12
- package/dist/contracts/owners.generated.js +103 -8
- package/dist/conversation-presentation.d.ts +3 -1
- package/dist/conversation-presentation.js +16 -6
- package/dist/conversations.d.ts +10 -6
- package/dist/conversations.js +17 -13
- package/dist/installs.d.ts +11 -0
- package/dist/installs.js +179 -0
- package/dist/project-notes.js +2 -0
- package/dist/protocol.js +7 -5
- package/dist/screen-check-skill.d.ts +2 -0
- package/dist/screen-check-skill.js +39 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +50 -7
- package/dist/web/.vite/manifest.json +51 -51
- package/dist/web/assets/{choices-B-y2OYvE.js → choices-DU9kHlyQ.js} +1 -1
- package/dist/web/assets/{console-e1P5qYQ5.js → console-Dbdo-5oO.js} +1 -1
- package/dist/web/assets/{dist-Dd3iAGFy.js → dist-B2V0VDO0.js} +1 -1
- package/dist/web/assets/{dist-CH56yNDn.js → dist-BV3hWfn_.js} +1 -1
- package/dist/web/assets/{dist-CBM2oq6g.js → dist-CLNF2g-E.js} +1 -1
- package/dist/web/assets/{dist-DEorbOW0.js → dist-D25Vn17i.js} +1 -1
- package/dist/web/assets/{explore-in-muWXOGOY.js → explore-in-8Q5ftt-E.js} +1 -1
- package/dist/web/assets/{graph-CK-df5lh.js → graph-hS2kvHTI.js} +1 -1
- package/dist/web/assets/{index-BYuKpyoi.js → index-BHQIaDsx.js} +220 -121
- package/dist/web/assets/index-Cj1_iqyK.css +1 -0
- package/dist/web/assets/{notes-meaning-B3HFbh84.js → notes-meaning-DdPbtNzJ.js} +1 -1
- package/dist/web/assets/{overview-D1Ev2OC_.js → overview-BV6ZO5TO.js} +3 -3
- package/dist/web/assets/{preview-HbmQKUGY.js → preview-n5lOOdjN.js} +1 -1
- package/dist/web/assets/{review-BVh9jWMB.js → review-CmWou4Iv.js} +1 -1
- package/dist/web/assets/{skills-DPaEYkln.js → skills-Dy7n3L9d.js} +1 -1
- package/dist/web/assets/{skills-work-dvjfy_tu.js → skills-work-Cj0W6b-I.js} +1 -1
- package/dist/web/assets/{studio-process-BYm8mjRJ.js → studio-process-QOY_n2_8.js} +1 -1
- package/dist/web/assets/{timeline-events-CDZ7wiJX.js → timeline-events-DmX1BCh3.js} +1 -1
- package/dist/web/assets/{work-turns-BeCt303c.js → work-turns-Co0C71rJ.js} +1 -1
- package/dist/web/assets/{workspace-d5Hw_ClZ.js → workspace-BKFefRfJ.js} +2 -2
- package/dist/web/composition.json +73 -73
- package/dist/web/index.html +2 -2
- package/dist/web/renderer.json +1 -1
- package/docs/FRONTEND.md +30 -14
- package/docs/HEADLESS.md +59 -13
- package/example/pa14-qs/.rho/rho/conversations/c-67f77cfebaba2cea.json +1 -1
- package/example/pa14-qs/.rho/rho/conversations/c-91aa2e46b1ac1a6c.json +1 -1
- package/example/pa14-qs/.rho/rho/conversations/c-fa24dfd80dfe4bcb.json +1 -1
- package/node_modules/@hono/node-server/dist/index.js +11 -0
- package/node_modules/@hono/node-server/dist/index.mjs +11 -0
- package/node_modules/@hono/node-server/dist/listener.js +11 -0
- package/node_modules/@hono/node-server/dist/listener.mjs +11 -0
- package/node_modules/@hono/node-server/dist/request.js +11 -0
- package/node_modules/@hono/node-server/dist/request.mjs +11 -0
- package/node_modules/@hono/node-server/dist/server.js +11 -0
- package/node_modules/@hono/node-server/dist/server.mjs +11 -0
- package/node_modules/@hono/node-server/dist/vercel.js +11 -0
- package/node_modules/@hono/node-server/dist/vercel.mjs +11 -0
- package/node_modules/@hono/node-server/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-agent/README.md +3 -1
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +14 -3
- package/node_modules/@rho-workspace/plugin-agent/dist/conversation.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/conversation.js +1 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/generic-acp.d.ts +11 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/generic-acp.js +7 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +5 -1
- package/node_modules/@rho-workspace/plugin-agent/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-annotations/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-documents/README.md +4 -0
- package/node_modules/@rho-workspace/plugin-documents/dist/index.d.ts +3 -0
- package/node_modules/@rho-workspace/plugin-documents/dist/index.js +1 -1
- package/node_modules/@rho-workspace/plugin-documents/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/README.md +71 -10
- package/node_modules/@rho-workspace/plugin-kernel/dist/code.d.ts +2 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/code.js +4 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +5 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +7 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +55 -8
- package/node_modules/@rho-workspace/plugin-kernel/dist/lineage.d.ts +9 -6
- package/node_modules/@rho-workspace/plugin-kernel/dist/lineage.js +18 -41
- package/node_modules/@rho-workspace/plugin-kernel/dist/operations.js +5 -2
- package/node_modules/@rho-workspace/plugin-kernel/dist/script-flow.d.ts +39 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/script-flow.js +661 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +3 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.js +10 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-flow.d.ts +38 -4
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-flow.js +194 -97
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -2
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +10 -2
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +79 -18
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +115 -45
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui.d.ts +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui.js +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/value-graph.d.ts +63 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/value-graph.js +344 -0
- package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +6 -3
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +6 -3
- package/node_modules/express-rate-limit/dist/index.cjs +1 -1
- package/node_modules/express-rate-limit/dist/index.mjs +1 -1
- package/node_modules/express-rate-limit/package.json +1 -1
- package/node_modules/ip-address/dist/ipv6.js +1 -1
- package/node_modules/ip-address/package.json +1 -1
- package/node_modules/playwright-core/LICENSE +202 -0
- package/node_modules/playwright-core/NOTICE +5 -0
- package/node_modules/playwright-core/ThirdPartyNotices.txt +13 -0
- package/node_modules/playwright-core/bin/install_media_pack.ps1 +5 -0
- package/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +33 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +42 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +13 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +24 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +42 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +12 -0
- package/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +24 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +23 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +23 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +48 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +11 -0
- package/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +24 -0
- package/node_modules/playwright-core/browsers.json +52 -0
- package/node_modules/playwright-core/cli.js +22 -0
- package/node_modules/playwright-core/index.js +17 -0
- package/node_modules/playwright-core/index.mjs +28 -0
- package/node_modules/playwright-core/lib/bootstrap.js +149 -0
- package/node_modules/playwright-core/lib/coreBundle.js +78041 -0
- package/node_modules/playwright-core/lib/entry/cliDaemon.js +5 -0
- package/node_modules/playwright-core/lib/entry/dashboardApp.js +3 -0
- package/node_modules/playwright-core/lib/entry/mcp.js +10 -0
- package/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +3 -0
- package/node_modules/playwright-core/lib/package.js +50 -0
- package/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
- package/node_modules/playwright-core/lib/server/electron/loader.js +123 -0
- package/node_modules/playwright-core/lib/serverRegistry.js +1939 -0
- package/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +65 -0
- package/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +141 -0
- package/node_modules/playwright-core/lib/tools/cli-client/cli.js +7 -0
- package/node_modules/playwright-core/lib/tools/cli-client/help.json +808 -0
- package/node_modules/playwright-core/lib/tools/cli-client/minimist.js +128 -0
- package/node_modules/playwright-core/lib/tools/cli-client/output.js +352 -0
- package/node_modules/playwright-core/lib/tools/cli-client/program.js +422 -0
- package/node_modules/playwright-core/lib/tools/cli-client/registry.js +180 -0
- package/node_modules/playwright-core/lib/tools/cli-client/session.js +261 -0
- package/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
- package/node_modules/playwright-core/lib/tools/utils/extension.js +112 -0
- package/node_modules/playwright-core/lib/tools/utils/socketConnection.js +108 -0
- package/node_modules/playwright-core/lib/utilsBundle.js +86078 -0
- package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +1890 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule--QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule-CQ8RZGBm.js +32 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-AwgwcYie.css +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/index-hRvO4yU2.js +12 -0
- package/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
- package/node_modules/playwright-core/lib/vite/dashboard/index.html +29 -0
- package/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +24 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/index.html +16 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -0
- package/node_modules/playwright-core/lib/vite/htmlReport/report.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DK72OMRi.js +32 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-BZpYJZ-P.css +1 -0
- package/node_modules/playwright-core/lib/vite/recorder/assets/index-Cn0mmDuv.js +129 -0
- package/node_modules/playwright-core/lib/vite/recorder/index.html +29 -0
- package/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +9 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-BEB2e8M3.js +32 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-D-g2vrvf.js +184 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-B794rD2Z.js +7 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.C8BeHuxd.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.html +44 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/index.oNry3Hjv.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +16 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +9 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +10 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CYwC_gH1.js +5 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CyMfwkXJ.css +1 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +18 -0
- package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
- package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
- package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
- package/node_modules/playwright-core/lib/xdg-open +1267 -0
- package/node_modules/playwright-core/package.json +34 -0
- package/package.json +10 -6
- package/protocol/index.json +17 -17
- package/protocol/operations/conversation.settings.check.json +147 -4
- package/protocol/operations/conversation.settings.read.json +145 -3
- package/protocol/operations/conversation.settings.update.json +166 -4
- package/protocol/operations/conversation.start.json +2 -1
- package/protocol/operations/kernel.interrupt.json +20 -0
- package/protocol/operations/kernel.read.json +30 -0
- package/protocol/operations/kernel.run.json +36 -1
- package/protocol/operations/kernel.stop.json +10 -0
- package/scripts/browser-checks.mjs +53 -0
- package/scripts/screen-check.mjs +193 -0
- package/vendor/composition.json +102 -102
- package/vendor/rho-workspace-plugin-agent-0.4.28.tgz +0 -0
- package/vendor/rho-workspace-plugin-annotations-0.5.2.tgz +0 -0
- package/vendor/rho-workspace-plugin-documents-0.4.11.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
- package/web/theme.css +17 -2
- package/dist/web/assets/index-DBd77u4a.css +0 -1
- package/vendor/rho-workspace-plugin-agent-0.4.27.tgz +0 -0
- package/vendor/rho-workspace-plugin-documents-0.4.10.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.2.tgz +0 -0
package/README.md
CHANGED
|
@@ -39,8 +39,9 @@ contract. Electron is reserved for a later desktop client that reuses the WebUI
|
|
|
39
39
|
and component boundaries. Functional views use CodeMirror 6, AG Grid Community
|
|
40
40
|
and ECharts.
|
|
41
41
|
|
|
42
|
-
Install
|
|
43
|
-
`rho
|
|
42
|
+
Install it with `npm install -g @rho-workspace/rho` on Node 22.22 or later, below 27, start
|
|
43
|
+
`rho`, and connect an external Agent with `rho --grant`
|
|
44
|
+
([Install and start](docs/HEADLESS.md#install-and-start)). Projects,
|
|
44
45
|
files, execution records and workspaces are available through the local WebUI.
|
|
45
46
|
The Agent uses the same scoped capabilities; a browser request copied to an external
|
|
46
47
|
Agent is explicitly a handoff.
|
|
@@ -50,8 +51,25 @@ plans and visual rules. See [Frontend](docs/FRONTEND.md) for the current interfa
|
|
|
50
51
|
[Status](docs/STATUS.md) for executed checks and limits, and the
|
|
51
52
|
[documentation index](docs/README.md) for backend and release information.
|
|
52
53
|
|
|
54
|
+
## Set up with an Agent
|
|
55
|
+
|
|
56
|
+
A person can ask Claude Code or Codex, in any language:
|
|
57
|
+
|
|
58
|
+
> Read https://yulab-smu.top/Rho/doc/setup.md and follow its steps to install and configure Rho for me.
|
|
59
|
+
|
|
60
|
+
or: 请阅读 https://yulab-smu.top/Rho/doc/setup.md 文档,按照步骤为我安装并配置 Rho。
|
|
61
|
+
|
|
62
|
+
That page is [SETUP.md](SETUP.md). The Agent checks the machine, installs the npm package and
|
|
63
|
+
starts Rho on `~/Rho/projects` and `~/Rho/state`. It then installs the Agent Skill in
|
|
64
|
+
[skills/rho](skills/rho/SKILL.md) into its own skills folder, connects itself to the example
|
|
65
|
+
project and says what is missing. The Skill teaches an Agent to install and connect Rho, call
|
|
66
|
+
its tools without guessing, and run an analysis a person can follow. It is published beside the
|
|
67
|
+
page at <https://yulab-smu.top/Rho/skills/rho/SKILL.md>.
|
|
68
|
+
|
|
53
69
|
## Documentation
|
|
54
70
|
|
|
71
|
+
<https://yulab-smu.top/Rho/> runs the real interface on the shipped example project,
|
|
72
|
+
replaying a recorded session without a backend, and gives the sentence above for an Agent.
|
|
55
73
|
The documentation is published at <https://yulab-smu.top/Rho/docs/>. The site is
|
|
56
74
|
generated from this repository's `docs/` directory, which stays the only source of
|
|
57
75
|
truth: every page names the file it came from and links to it.
|
|
@@ -60,12 +78,13 @@ truth: every page names the file it came from and links to it.
|
|
|
60
78
|
npm run docs:dev # assemble and serve locally
|
|
61
79
|
npm run docs:build # assemble, then build the static site into site/.vitepress/dist
|
|
62
80
|
npm run docs:check # fail if a document in docs/ is neither published nor withheld
|
|
81
|
+
npm run pages:build # front page, demo and documentation into site/.pages (add -- --home for the front page alone)
|
|
82
|
+
npm run pages:check # walk the demo and the front page in a browser
|
|
83
|
+
npm run pages:capture # record the demo again from this checkout's service (after npm run build)
|
|
63
84
|
```
|
|
64
85
|
|
|
65
86
|
`@rho-workspace/rho` is published to npm. The registry entry for 0.5.2 carries no
|
|
66
|
-
README, so its package page shows no description; from 0.5.3 the packer ships this README.
|
|
67
|
-
[downloads page](https://yulab-smu.top/Rho/) are the earlier 0.4.0 line; this npm line
|
|
68
|
-
is the current delivery.
|
|
87
|
+
README, so its package page shows no description; from 0.5.3 the packer ships this README.
|
|
69
88
|
|
|
70
89
|
For a new component view, start with [Plugin authoring](docs/PLUGIN-AUTHORING.md):
|
|
71
90
|
generate a typed package, preview it independently and select its compiled entry
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -64,6 +64,33 @@ export declare const personFilesSkill: {
|
|
|
64
64
|
description: string;
|
|
65
65
|
};
|
|
66
66
|
};
|
|
67
|
+
export declare const analysisRunSkillDeclaration: {
|
|
68
|
+
id: string;
|
|
69
|
+
version: string;
|
|
70
|
+
content: string;
|
|
71
|
+
digest: string;
|
|
72
|
+
intent: string;
|
|
73
|
+
requires: string[];
|
|
74
|
+
example: {
|
|
75
|
+
method: string;
|
|
76
|
+
input: {
|
|
77
|
+
language: string;
|
|
78
|
+
code: string;
|
|
79
|
+
purpose: string;
|
|
80
|
+
changeId: string;
|
|
81
|
+
waitMs: number;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
failureRecipes: {
|
|
85
|
+
kind: string;
|
|
86
|
+
message: string;
|
|
87
|
+
next: string;
|
|
88
|
+
}[];
|
|
89
|
+
verify: {
|
|
90
|
+
command: string;
|
|
91
|
+
description: string;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
67
94
|
export declare const taskMapSkill: {
|
|
68
95
|
id: string;
|
|
69
96
|
version: string;
|
package/dist/capabilities.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import { atomicJson, canonical, digest, now, readJson, requireThat, stateDirectory } from '@rho-workspace/plugin-contracts';
|
|
3
3
|
import { operations } from './protocol.js';
|
|
4
|
+
import { screenCheckSkill } from './screen-check-skill.js';
|
|
4
5
|
export const capabilityLimits = { maxReceipts: 256 };
|
|
5
6
|
// The umbrella Skill: what this application is, the order to work in, and the rules
|
|
6
7
|
// that used to be discovered by failing. It names where the detail lives instead of
|
|
@@ -85,6 +86,77 @@ const personFiles = {
|
|
|
85
86
|
};
|
|
86
87
|
export const rhoWorkspaceSkill = { ...applicationSkill, digest: skillDigest };
|
|
87
88
|
export const personFilesSkill = personFiles;
|
|
89
|
+
// Written after five real analyses (bulk RNA-seq, snRNA-seq, GeoMx DSP, bulk plus a
|
|
90
|
+
// metabolome, and a multi-omics cohort) ran through this application. None of them
|
|
91
|
+
// finished unattended at the time: turns ended on a call limit or on a wait nothing
|
|
92
|
+
// wakes, calls went into looking for what may be done rather than doing it, and results
|
|
93
|
+
// were not always written before the turn ended. Several of those stops have since been
|
|
94
|
+
// fixed in this application, so what follows is written as the practice that survived
|
|
95
|
+
// them rather than as a workaround for one build's limits.
|
|
96
|
+
const analysisRunContent = [
|
|
97
|
+
'A whole analysis has to survive interruption. Rho keeps no step cap and no wall time',
|
|
98
|
+
'by default, and a long analysis may still be stopped by the person or by the Agent',
|
|
99
|
+
'client, so write results as you go instead of holding them until the end.',
|
|
100
|
+
'',
|
|
101
|
+
'1. Spend calls on the analysis, not on discovery. Resolve the operations you will use',
|
|
102
|
+
' once each (kernel.run, kernel.read, kernel.interrupt, resource.list) and reuse the',
|
|
103
|
+
' contractRef you were given. In the runs behind this Skill one Agent spent 36',
|
|
104
|
+
' capability.index calls in a single analysis and ended with only its quality-control',
|
|
105
|
+
' table written.',
|
|
106
|
+
'2. Do not guess what a result reference contains. A refused read means the shape is',
|
|
107
|
+
' wrong, not that the result is missing: resolve the operation that produced it and',
|
|
108
|
+
' read the page or range it names instead of trying sibling paths.',
|
|
109
|
+
'3. Nothing wakes you later. No monitor, timer or event resumes your turn: a cell still',
|
|
110
|
+
' running when you stop is left running for nobody, and ending your reply is not',
|
|
111
|
+
' proof the analysis finished. Pass a waitMs long enough for the slowest step (a',
|
|
112
|
+
' normalisation over 90,000 nuclei can take many minutes), or read the cell with',
|
|
113
|
+
' kernel.read in your own next call.',
|
|
114
|
+
'4. Load once, build on it. A session keeps what a cell defines, so read the data in one',
|
|
115
|
+
' cell and derive in the next. A service restart does not keep those objects: the code',
|
|
116
|
+
' and its outputs stay, the values in memory do not, so keep a script that rebuilds',
|
|
117
|
+
' what matters.',
|
|
118
|
+
'5. Prefer a few large cells to many small ones, and keep each short enough to finish. A',
|
|
119
|
+
' cell that hangs costs time: interrupt it with kernel.interrupt and continue with the',
|
|
120
|
+
' part that is cheap to redo.',
|
|
121
|
+
'6. Draw to an explicit device. In these runs a ggsave call in the shared session hung',
|
|
122
|
+
' and the figure had to be redrawn with png() and dev.off(): open the device, plot,',
|
|
123
|
+
' close it, and let the figure land in a file you name.',
|
|
124
|
+
'7. Write each file once, complete, and save it to disk before you rely on it. One script',
|
|
125
|
+
' written in full and then run costs less than the same script edited piece by piece,',
|
|
126
|
+
' and a file on disk is the only version that outlives a stop.',
|
|
127
|
+
'8. Check packages in one call, and do not install into a temporary library. Name what is',
|
|
128
|
+
' missing, and when you substitute a gene-set source or an annotation build, say so',
|
|
129
|
+
' beside the result, because that choice changes the numbers.',
|
|
130
|
+
'9. Say what did not reproduce, and why. When a published count cannot be matched, name',
|
|
131
|
+
' the undefined choice (a threshold, a direction, a model) and compute the cheap',
|
|
132
|
+
' alternatives beside your main one instead of tuning until it fits.',
|
|
133
|
+
'10. Report the state you actually reached. Say which outputs exist on disk, which cell',
|
|
134
|
+
' was still running, and what a stopped turn left behind; the person continues from',
|
|
135
|
+
' that record rather than from a claim that the work is done.',
|
|
136
|
+
].join('\n');
|
|
137
|
+
const analysisRunSkill = {
|
|
138
|
+
id: 'rho.analysis-run', version: '1.1', content: analysisRunContent,
|
|
139
|
+
digest: digest(analysisRunContent),
|
|
140
|
+
intent: 'Run a whole analysis through the live sessions so a stop never loses the work.',
|
|
141
|
+
requires: ['kernel.run', 'kernel.read', 'kernel.interrupt', 'resource.list'],
|
|
142
|
+
example: {
|
|
143
|
+
method: 'kernel.run',
|
|
144
|
+
input: {
|
|
145
|
+
language: 'r',
|
|
146
|
+
code: 'counts <- read.csv("data/counts.csv", row.names = 1); dim(counts)',
|
|
147
|
+
purpose: 'Load the count matrix once and keep it for later cells',
|
|
148
|
+
changeId: 'load-counts',
|
|
149
|
+
waitMs: 120000
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
failureRecipes: [
|
|
153
|
+
{ kind: 'not_found', message: 'Skill is not available in this project and session.', next: 'Read skill.list for the Skills this project and session expose; a Skill owned by a disabled plugin is not listed.' },
|
|
154
|
+
{ kind: 'stale', message: 'Skill content digest changed.', next: 'Read the Skill again and apply the current text; do not act on a cached copy.' },
|
|
155
|
+
{ kind: 'limit', message: 'Too many cells are waiting.', next: 'Wait for the running cell or interrupt it, then continue in a new cell with a new changeId.' }
|
|
156
|
+
],
|
|
157
|
+
verify: { command: 'npm run test:notebook', description: 'Runs R and Python cells, keeps their output, plots and variables, interrupts and stops sessions, and keeps identity across a restart.' }
|
|
158
|
+
};
|
|
159
|
+
export const analysisRunSkillDeclaration = analysisRunSkill;
|
|
88
160
|
const taskMapContent = [
|
|
89
161
|
'Describe the whole task yourself: Rho fixes no closed list of kinds or states.',
|
|
90
162
|
'',
|
|
@@ -240,7 +312,7 @@ export class Capabilities {
|
|
|
240
312
|
}
|
|
241
313
|
async skills(principal) {
|
|
242
314
|
const state = await this.state();
|
|
243
|
-
const result = [rhoWorkspaceSkill, personFilesSkill, taskMapSkill];
|
|
315
|
+
const result = [rhoWorkspaceSkill, personFilesSkill, taskMapSkill, analysisRunSkill, screenCheckSkill];
|
|
244
316
|
for (const descriptor of this.plugins.descriptors()) {
|
|
245
317
|
const status = this.pluginStatus(descriptor.id, state);
|
|
246
318
|
for (const skill of descriptor.skills ?? []) {
|
package/dist/cli.js
CHANGED
|
@@ -10,12 +10,39 @@ function openBrowser(target) {
|
|
|
10
10
|
const [command, args] = process.platform === 'darwin' ? ['open', [target]] : process.platform === 'win32' ? ['cmd', ['/c', 'start', '""', target]] : ['xdg-open', [target]];
|
|
11
11
|
spawn(command, args, { stdio: 'ignore', detached: true }).on('error', () => process.stderr.write('Could not open a browser. Open openUrl from the output instead.\n')).unref();
|
|
12
12
|
}
|
|
13
|
+
// A generic ACP agent is any program that speaks ACP v1 over stdio. Its command is either a
|
|
14
|
+
// JSON argv array (safest for paths with spaces) or a simple command line.
|
|
15
|
+
function parseAcpCommand(spec) {
|
|
16
|
+
const text = (spec ?? '').trim();
|
|
17
|
+
if (!text)
|
|
18
|
+
throw new Error('--agent-backend acp needs --acp-command: a JSON argv array such as \'["/usr/bin/node","/path/agent.js"]\', or a command line.');
|
|
19
|
+
if (text.startsWith('[')) {
|
|
20
|
+
let parts;
|
|
21
|
+
try {
|
|
22
|
+
parts = JSON.parse(text);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error('--acp-command is not valid JSON.');
|
|
26
|
+
}
|
|
27
|
+
if (!Array.isArray(parts) || !parts.length || parts.some(x => typeof x !== 'string'))
|
|
28
|
+
throw new Error('--acp-command JSON must be a non-empty array of strings.');
|
|
29
|
+
return { file: parts[0], args: parts.slice(1) };
|
|
30
|
+
}
|
|
31
|
+
const parts = (text.match(/"[^"]*"|'[^']*'|\S+/g) ?? []).map(p => p.replace(/^(["'])(.*)\1$/, '$2'));
|
|
32
|
+
if (!parts.length)
|
|
33
|
+
throw new Error('--acp-command is empty.');
|
|
34
|
+
return { file: parts[0], args: parts.slice(1) };
|
|
35
|
+
}
|
|
13
36
|
const usage = `Verify a read-only evidence package:
|
|
14
37
|
rho --verify-evidence PATH
|
|
38
|
+
Start Rho on ~/Rho/projects and ~/Rho/state, and say where it is and what is missing:
|
|
39
|
+
rho
|
|
15
40
|
Start Rho:
|
|
16
|
-
rho --root PROJECTS_DIRECTORY --state-dir SERVICE_STATE [--core ABSOLUTE_CORE_BINARY] [--rscript ABSOLUTE_RSCRIPT] [--python PYTHON] [--ssh SSH] [--serverctl SERVERCTL] [--claude CLAUDE_CODE_BINARY] [--codex CODEX_BINARY] [--agent-backend claude-acp|codex-acp|claude-cli] [--port
|
|
41
|
+
rho --root PROJECTS_DIRECTORY --state-dir SERVICE_STATE [--core ABSOLUTE_CORE_BINARY] [--rscript ABSOLUTE_RSCRIPT] [--python PYTHON] [--ssh SSH] [--serverctl SERVERCTL] [--claude CLAUDE_CODE_BINARY] [--codex CODEX_BINARY] [--agent-backend claude-acp|codex-acp|claude-cli|acp] [--acp-command JSON_ARGV] [--port PORT] [--limits JSON_FILE] [--plugins documents,files,...] [--example on|off] [--open]
|
|
17
42
|
A narrower browser link (optional):
|
|
18
43
|
rho --pair PRIVATE_CONNECTION_FILE [--root PROJECTS_RELATIVE_DIRECTORY] [--open]
|
|
44
|
+
Add Rho to your own Claude Code or Codex for one project, as the project page's button does:
|
|
45
|
+
rho --grant PRIVATE_CONNECTION_FILE --project PROJECTS_RELATIVE_DIRECTORY --agent claude-code|codex
|
|
19
46
|
Give an Agent access to one project, and the command that adds it to Claude Code:
|
|
20
47
|
rho --grant PRIVATE_CONNECTION_FILE --project PROJECTS_RELATIVE_DIRECTORY [--session NAME]
|
|
21
48
|
Serve that access to the Agent over MCP (stdio):
|
|
@@ -24,13 +51,27 @@ Observe an external ACP client (use this as its agent command):
|
|
|
24
51
|
rho --acp /absolute/project --acp-agent claude|codex
|
|
25
52
|
--example off starts Rho without the project it ships; a new state otherwise gets that project
|
|
26
53
|
once, copied into the projects directory. --open opens the printed url in the default browser.
|
|
54
|
+
Without --port a start takes the port this state's last start had, and a new one only when that
|
|
55
|
+
port is taken; --port 0 always takes a new one.
|
|
27
56
|
Opening that url on this machine needs no link, code or session credential; --pair makes a
|
|
28
57
|
narrower, expiring link for one folder.
|
|
29
58
|
PRIVATE_CONNECTION_FILE is the connectionFile the running service printed; it and every
|
|
30
59
|
connection file are credentials.
|
|
31
60
|
`;
|
|
32
|
-
const options = { example: { type: 'string' }, 'verify-evidence': { type: 'string' }, acp: { type: 'string' }, 'acp-agent': { type: 'string' }, root: { type: 'string' }, 'state-dir': { type: 'string' }, core: { type: 'string' }, rscript: { type: 'string' }, python: { type: 'string' }, ssh: { type: 'string' }, serverctl: { type: 'string' }, claude: { type: 'string' }, codex: { type: 'string' }, 'agent-backend': { type: 'string' }, port: { type: 'string' }, limits: { type: 'string' }, plugins: { type: 'string' }, pair: { type: 'string' }, open: { type: 'boolean' },
|
|
33
|
-
grant: { type: 'string' }, project: { type: 'string' }, session: { type: 'string' }, mcp: { type: 'string' }, help: { type: 'boolean', short: 'h' } };
|
|
61
|
+
const options = { example: { type: 'string' }, 'verify-evidence': { type: 'string' }, acp: { type: 'string' }, 'acp-agent': { type: 'string' }, root: { type: 'string' }, 'state-dir': { type: 'string' }, core: { type: 'string' }, rscript: { type: 'string' }, python: { type: 'string' }, ssh: { type: 'string' }, serverctl: { type: 'string' }, claude: { type: 'string' }, codex: { type: 'string' }, 'agent-backend': { type: 'string' }, 'acp-command': { type: 'string' }, port: { type: 'string' }, limits: { type: 'string' }, plugins: { type: 'string' }, pair: { type: 'string' }, open: { type: 'boolean' },
|
|
62
|
+
grant: { type: 'string' }, project: { type: 'string' }, session: { type: 'string' }, agent: { type: 'string' }, mcp: { type: 'string' }, help: { type: 'boolean', short: 'h' } };
|
|
63
|
+
// A command that is refused says why and what to do. A service that is not running is the
|
|
64
|
+
// common case for --grant and --pair; anything without a known reason keeps its stack.
|
|
65
|
+
function refuse(error, url) {
|
|
66
|
+
const e = error;
|
|
67
|
+
if (e?.cause?.code === 'ECONNREFUSED' && url)
|
|
68
|
+
process.stderr.write(`Rho is not running at ${url}. Start it with the same --state-dir, then run this again.\n`);
|
|
69
|
+
else if (e?.error?.message || e?.kind || e?.syscall)
|
|
70
|
+
process.stderr.write((e.error?.message ?? e.message) + '\n');
|
|
71
|
+
else
|
|
72
|
+
throw error;
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
34
75
|
let parsed;
|
|
35
76
|
try {
|
|
36
77
|
parsed = parseArgs({ options });
|
|
@@ -77,40 +118,84 @@ else {
|
|
|
77
118
|
process.stderr.write('--grant needs --project, the project folder relative to the service root.\n\n' + usage);
|
|
78
119
|
process.exit(2);
|
|
79
120
|
}
|
|
121
|
+
if (values.agent !== undefined && !['claude-code', 'codex'].includes(values.agent)) {
|
|
122
|
+
process.stderr.write('Choose --agent claude-code or codex.\n');
|
|
123
|
+
process.exit(2);
|
|
124
|
+
}
|
|
125
|
+
if (values.agent !== undefined && values.session !== undefined) {
|
|
126
|
+
process.stderr.write('--agent names the session itself; leave out --session.\n');
|
|
127
|
+
process.exit(2);
|
|
128
|
+
}
|
|
80
129
|
const { RhoClient } = await import('./client.js');
|
|
81
|
-
const client = await RhoClient.fromFile(path.resolve(values.grant));
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
130
|
+
const client = await RhoClient.fromFile(path.resolve(values.grant)).catch(error => refuse(error));
|
|
131
|
+
try {
|
|
132
|
+
const project = await client.call('project.open', { path: values.project });
|
|
133
|
+
// The same as the project page's button: the grant that Agent keeps, then its own
|
|
134
|
+
// `mcp add`, recorded so the page shows it under that Agent.
|
|
135
|
+
if (values.agent) {
|
|
136
|
+
const connected = await client.call('session.connect', { projectId: project.id, agent: values.agent });
|
|
137
|
+
process.stdout.write(JSON.stringify({ projectId: project.id, root: project.root, agent: values.agent, methods: connected.methods, granted: connected.granted, added: connected.added }) + '\n');
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
const session = values.session ?? 'claude-code';
|
|
141
|
+
const granted = await client.call('session.grant', { projectId: project.id, externalSessionId: session });
|
|
142
|
+
const { mcpCommand, mcpName } = await import('./agent-connect.js');
|
|
143
|
+
const command = mcpCommand(granted.connectionFile), name = mcpName(project.root);
|
|
144
|
+
const quote = (value) => /^[A-Za-z0-9_./:@=-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
145
|
+
process.stdout.write(JSON.stringify({ projectId: project.id, session, connectionFile: granted.connectionFile, methods: granted.methods.length,
|
|
146
|
+
mcp: { name, command: command[0], args: command.slice(1) }, claude: ['claude', 'mcp', 'add', name, '--', ...command].map(quote).join(' ') }) + '\n');
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
refuse(error, client.connection.url);
|
|
151
|
+
}
|
|
91
152
|
}
|
|
92
153
|
if (values.pair) {
|
|
93
154
|
const { RhoClient } = await import('./client.js');
|
|
94
|
-
const client = await RhoClient.fromFile(path.resolve(values.pair));
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
155
|
+
const client = await RhoClient.fromFile(path.resolve(values.pair)).catch(error => refuse(error));
|
|
156
|
+
try {
|
|
157
|
+
const pairing = await client.call('session.browser.pair', values.root ? { path: values.root } : {});
|
|
158
|
+
const openUrl = link(client.connection.url, pairing.code);
|
|
159
|
+
process.stdout.write(JSON.stringify({ url: client.connection.url, openUrl, pairingCode: pairing.code, pairingExpiresAt: pairing.expiresAt }) + '\n');
|
|
160
|
+
if (values.open)
|
|
161
|
+
openBrowser(openUrl);
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
refuse(error, client.connection.url);
|
|
166
|
+
}
|
|
101
167
|
}
|
|
102
|
-
|
|
168
|
+
// A person who runs `npx @rho-workspace/rho` with no arguments wants the application, not a
|
|
169
|
+
// usage page: start it on their own folders, say in plain words where it is, and open it when
|
|
170
|
+
// this is an interactive terminal. Every flagged start is unchanged, so scripts and
|
|
171
|
+
// acceptance runs keep the output they read.
|
|
172
|
+
const quickStart = !values.root && !values['state-dir'];
|
|
173
|
+
if (!quickStart && (!values.root || !values['state-dir'])) {
|
|
103
174
|
process.stderr.write(usage);
|
|
104
175
|
process.exit(2);
|
|
105
176
|
}
|
|
177
|
+
let quickRoot = '', quickState = '';
|
|
178
|
+
if (quickStart) {
|
|
179
|
+
const { homedir } = await import('node:os');
|
|
180
|
+
quickRoot = path.join(homedir(), 'Rho', 'projects');
|
|
181
|
+
quickState = path.join(homedir(), 'Rho', 'state');
|
|
182
|
+
await fs.mkdir(quickRoot, { recursive: true });
|
|
183
|
+
await fs.mkdir(quickState, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
const projectsRoot = values.root ?? quickRoot, stateDirectory = values['state-dir'] ?? quickState;
|
|
106
186
|
const limits = values.limits ? JSON.parse(await fs.readFile(values.limits, 'utf8')) : {};
|
|
107
187
|
const allowed = new Set(['maxRequestBytes', 'maxEventBytes', 'eventRetention', 'maxSubscriptions', 'maxQueue', 'maxContexts', 'maxBindings', 'maxActivities', 'maxContextBytes', 'maxRetainedMetadataBytes', 'maxConcurrentActions', 'maxOperations', 'maxLogPageBytes', 'maxCodeBytes', 'maxReconciled', 'documents', 'resources', 'workspace', 'r', 'annotations', 'interactions', 'capabilityLimits', 'browser', 'nativeFolders', 'conversations', 'evidence', 'console']);
|
|
108
188
|
if (Object.keys(limits).some(k => !allowed.has(k)))
|
|
109
189
|
throw new Error('Unknown service budget in --limits file.');
|
|
110
190
|
const { RhoService } = await import('./server.js'), { runtime } = await import('./runtime.js');
|
|
111
191
|
const agentBackend = values['agent-backend'] ?? process.env.RHO_AGENT_BACKEND ?? 'claude-acp';
|
|
112
|
-
if (!['claude-acp', 'codex-acp', 'claude-cli'].includes(agentBackend))
|
|
113
|
-
throw new Error('Choose --agent-backend claude-acp, codex-acp
|
|
192
|
+
if (!['claude-acp', 'codex-acp', 'claude-cli', 'acp'].includes(agentBackend))
|
|
193
|
+
throw new Error('Choose --agent-backend claude-acp, codex-acp, claude-cli or acp.');
|
|
194
|
+
const acpCommand = agentBackend === 'acp' ? parseAcpCommand(values['acp-command'] ?? process.env.RHO_ACP_COMMAND) : undefined;
|
|
195
|
+
if (values.port !== undefined && !(/^\d+$/.test(values.port) && Number(values.port) <= 65535)) {
|
|
196
|
+
process.stderr.write('Choose --port 0 to 65535, or leave it out.\n');
|
|
197
|
+
process.exit(2);
|
|
198
|
+
}
|
|
114
199
|
// A Core that cannot be used (none for this platform, a changed digest) is read by a person.
|
|
115
200
|
const native = await runtime(values.core, values.rscript).catch(e => { process.stderr.write((e instanceof Error ? e.message : String(e)) + '\n'); process.exit(1); });
|
|
116
201
|
// A person starting Rho sees the project it ships with; a composition that starts a service
|
|
@@ -118,7 +203,12 @@ else {
|
|
|
118
203
|
if (values.example !== undefined && !['on', 'off'].includes(values.example))
|
|
119
204
|
throw new Error('Choose --example on or off.');
|
|
120
205
|
const example = values.example !== 'off';
|
|
121
|
-
const service = await new RhoService({ example, ...limits, root: path.resolve(
|
|
206
|
+
const service = await new RhoService({ example, ...limits, root: path.resolve(projectsRoot), stateDir: path.resolve(stateDirectory), ...native, agentBackend: agentBackend, acpCommand, codex: values.codex ?? process.env.RHO_CODEX ?? 'codex', claude: values.claude ?? process.env.RHO_CLAUDE ?? 'claude', python: values.python ?? process.env.RHO_PYTHON, ssh: values.ssh ?? process.env.RHO_SSH, serverctl: values.serverctl ?? process.env.RHO_SERVERCTL, port: values.port === undefined ? undefined : Number(values.port), ...(values.plugins !== undefined ? { plugins: values.plugins ? values.plugins.split(',') : [] } : {}) }).start().catch(async (error) => {
|
|
207
|
+
// Starting Rho again on a state it already runs on is the common refusal: say where it runs.
|
|
208
|
+
const held = error?.details?.stateDirectory;
|
|
209
|
+
const running = typeof held === 'string' ? await fs.readFile(path.join(held, 'connection.json'), 'utf8').then(text => String(JSON.parse(text).url), () => '') : '';
|
|
210
|
+
return refuse(running ? Object.assign(error, { message: `Rho is already running for ${held} at ${running}. Open that url, or stop that service before starting it again.` }) : error);
|
|
211
|
+
});
|
|
122
212
|
// The narrow conclusion a person needs on one line: what was selected, where state
|
|
123
213
|
// lives, and how to open it. It stays inside `ready` so a parser reading line one is
|
|
124
214
|
// unaffected. Opening this url on this machine is the access; no link is minted.
|
|
@@ -127,11 +217,46 @@ else {
|
|
|
127
217
|
url: service.url, connectionFile: path.join(service.state.directory, 'connection.json'), apiVersion: '2.0',
|
|
128
218
|
openUrl: service.url,
|
|
129
219
|
startup: {
|
|
130
|
-
projects: path.resolve(
|
|
220
|
+
projects: path.resolve(projectsRoot), state: path.resolve(stateDirectory),
|
|
131
221
|
core: service.bridge.binary, rscript: service.bridge.rscript,
|
|
132
222
|
note: 'Open this url in a browser on this machine. Execution stays off until an Agent submits a run; project.doctor answers what is missing.'
|
|
133
223
|
}
|
|
134
224
|
} }) + '\n');
|
|
225
|
+
// What a person needs, after the machine line: where it is, and what is missing to work in it.
|
|
226
|
+
// A missing Agent or R does not stop the workspace; it decides what the person can do next.
|
|
227
|
+
if (quickStart) {
|
|
228
|
+
const say = (text) => process.stdout.write(text + '\n');
|
|
229
|
+
say('');
|
|
230
|
+
say('Rho is running. Open this address in a browser on this machine:');
|
|
231
|
+
say(' ' + service.url);
|
|
232
|
+
say('');
|
|
233
|
+
say('Projects folder: ' + quickRoot);
|
|
234
|
+
say('Service state: ' + quickState);
|
|
235
|
+
say('');
|
|
236
|
+
const agentCommand = await import('@rho-workspace/plugin-agent/acp-command').catch(() => null);
|
|
237
|
+
// The selection the service itself got: an explicit flag, then the environment, then the name
|
|
238
|
+
// on PATH. Reporting "installed" for a client the person pointed at a missing path is worse
|
|
239
|
+
// than saying nothing.
|
|
240
|
+
const claudePath = values.claude ?? process.env.RHO_CLAUDE, codexPath = values.codex ?? process.env.RHO_CODEX;
|
|
241
|
+
const env = { ...process.env, ...(claudePath ? { CLAUDE_CODE_EXECUTABLE: claudePath } : {}), ...(codexPath ? { CODEX_PATH: codexPath } : {}) };
|
|
242
|
+
// An ACP agent named with --acp-command is the operator's own command; it is named, not checked.
|
|
243
|
+
if (acpCommand)
|
|
244
|
+
say('Agent: ' + acpCommand.file + ', named with --acp-command.');
|
|
245
|
+
else {
|
|
246
|
+
const missing = agentCommand ? agentCommand.missingClient(agentBackend === 'codex-acp' ? 'codex' : 'claude', env) : null;
|
|
247
|
+
say(missing
|
|
248
|
+
? 'Agent: ' + missing + ' Until then folders can be opened and files read here; the Agent cannot run in them.'
|
|
249
|
+
: 'Agent: ' + (agentBackend === 'codex-acp' ? 'Codex' : 'Claude Code') + ' is installed; a conversation can start in any project.');
|
|
250
|
+
}
|
|
251
|
+
const hasR = await fs.access(service.bridge.rscript).then(() => true, () => false);
|
|
252
|
+
say(hasR
|
|
253
|
+
? 'R: ' + service.bridge.rscript
|
|
254
|
+
: 'R: Rscript was not found. Reading, editing and viewing work; running R needs R installed (or --rscript).');
|
|
255
|
+
say('');
|
|
256
|
+
say('Stop it with Ctrl+C. Start it again with: npx @rho-workspace/rho');
|
|
257
|
+
if (process.stdout.isTTY)
|
|
258
|
+
openBrowser(service.url);
|
|
259
|
+
}
|
|
135
260
|
if (values.open)
|
|
136
261
|
openBrowser(service.url);
|
|
137
262
|
let closing = false;
|