@rho-workspace/rho 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/dist/agent-connect.d.ts +42 -0
  2. package/dist/agent-connect.js +68 -0
  3. package/dist/answer-board.js +3 -3
  4. package/dist/bridge.js +6 -1
  5. package/dist/browser-access.d.ts +1 -0
  6. package/dist/browser-access.js +5 -3
  7. package/dist/capabilities.d.ts +21 -5
  8. package/dist/capabilities.js +6 -4
  9. package/dist/cli.js +113 -18
  10. package/dist/composition.d.ts +1 -1
  11. package/dist/composition.js +13 -3
  12. package/dist/connect-cli.d.ts +50 -0
  13. package/dist/connect-cli.js +128 -0
  14. package/dist/contract.generated.d.ts +1437 -381
  15. package/dist/contract.generated.js +1 -1
  16. package/dist/contracts/owners.generated.js +1160 -267
  17. package/dist/conversation-presentation.d.ts +9 -2
  18. package/dist/conversation-presentation.js +3 -1
  19. package/dist/conversations.d.ts +306 -7
  20. package/dist/conversations.js +208 -22
  21. package/dist/discovery.js +3 -3
  22. package/dist/extension-contract.d.ts +19 -0
  23. package/dist/extension-contract.js +15 -6
  24. package/dist/extension-markets.d.ts +74 -0
  25. package/dist/extension-markets.js +166 -0
  26. package/dist/extensions.d.ts +29 -1
  27. package/dist/extensions.js +13 -4
  28. package/dist/external-work.d.ts +22 -0
  29. package/dist/external-work.js +91 -0
  30. package/dist/plugin-cli.js +75 -3
  31. package/dist/plugin-inventory.d.ts +56 -0
  32. package/dist/plugin-inventory.js +41 -0
  33. package/dist/plugin-inventory.json +1581 -102
  34. package/dist/plugin-presentation.d.ts +105 -2
  35. package/dist/plugin-presentation.js +59 -5
  36. package/dist/plugins.d.ts +14 -3
  37. package/dist/plugins.js +12 -6
  38. package/dist/project-notes.js +1 -1
  39. package/dist/protocol.d.ts +7 -0
  40. package/dist/protocol.js +40 -15
  41. package/dist/replay.js +4 -0
  42. package/dist/response-contracts.js +26 -11
  43. package/dist/running.d.ts +10 -0
  44. package/dist/running.js +45 -0
  45. package/dist/server.d.ts +4 -10
  46. package/dist/server.js +140 -14
  47. package/dist/state.d.ts +8 -0
  48. package/dist/state.js +8 -0
  49. package/dist/studio-layout.js +3 -3
  50. package/dist/task-frame.d.ts +1 -1
  51. package/dist/timeline-events.d.ts +1 -0
  52. package/dist/timeline-events.js +8 -1
  53. package/dist/ui-context.d.ts +73 -0
  54. package/dist/ui-context.js +98 -0
  55. package/dist/ui-contract.d.ts +23 -0
  56. package/dist/web/.vite/manifest.json +135 -76
  57. package/dist/web/assets/answer-board-CeYS8k8y.js +1 -0
  58. package/dist/web/assets/choices-D8vGwsl2.js +1 -0
  59. package/dist/web/assets/{console-BSjLGe0T.js → console-Dyuo1q4N.js} +4 -4
  60. package/dist/web/assets/{dist-Tzoe_zm-.js → dist-CSiCyBml.js} +1 -1
  61. package/dist/web/assets/{dist-Cw8hRIlT.js → dist-D9FLb9Hm.js} +1 -1
  62. package/dist/web/assets/{dist-foH9IKc6.js → dist-FXbTBwML.js} +1 -1
  63. package/dist/web/assets/{dist-Buk0lwzI.js → dist-l3DUhbw5.js} +1 -1
  64. package/dist/web/assets/{explore-in-BVUy3ZW_.js → explore-in-BfSFGRh2.js} +1 -1
  65. package/dist/web/assets/graph-BU2_o5uA.js +2 -0
  66. package/dist/web/assets/index-C16mXVvk.css +1 -0
  67. package/dist/web/assets/index-CWg9XYXx.js +952 -0
  68. package/dist/web/assets/markdown.reader-Ct2-e2Zt.png +0 -0
  69. package/dist/web/assets/{notes-meaning-pVbbQUGP.js → notes-meaning-T2m5zQfv.js} +1 -1
  70. package/dist/web/assets/overview-PzfLJCo-.js +6 -0
  71. package/dist/web/assets/preview-BguEdsza.js +1 -0
  72. package/dist/web/assets/previews-pWNik-2g.js +3 -0
  73. package/dist/web/assets/{review-CMxQlQ-C.js → review-Dy_85lcH.js} +1 -1
  74. package/dist/web/assets/rho.editor-CCJeyHma.png +0 -0
  75. package/dist/web/assets/rho.flow-WkMj6fRS.png +0 -0
  76. package/dist/web/assets/rho.media-Cc6amodO.png +0 -0
  77. package/dist/web/assets/rho.notebook-6vsZKGDq.png +0 -0
  78. package/dist/web/assets/rho.object-BGoeXjte.png +0 -0
  79. package/dist/web/assets/rho.plots-yIiISCkb.png +0 -0
  80. package/dist/web/assets/rho.scatter-DwHxesCs.png +0 -0
  81. package/dist/web/assets/rho.servers-DKf_mnNq.png +0 -0
  82. package/dist/web/assets/rho.table-DWOGaWDa.png +0 -0
  83. package/dist/web/assets/rho.variables-CKcWczhQ.png +0 -0
  84. package/dist/web/assets/{skills-BFNSyrnD.js → skills-Dyz7mBlc.js} +1 -1
  85. package/dist/web/assets/{skills-work-BGPSnF_I.js → skills-work-BEGqlCkx.js} +1 -1
  86. package/dist/web/assets/studio-process-D-ykbC4D.js +2 -0
  87. package/dist/web/assets/timeline-events-DKTho9fU.js +1 -0
  88. package/dist/web/assets/work-turns-DSabCogR.js +1 -0
  89. package/dist/web/assets/{workspace-CWlkjLNx.js → workspace-DfjBL--r.js} +2 -2
  90. package/dist/web/composition.json +135 -80
  91. package/dist/web/index.html +3 -3
  92. package/dist/web/renderer.json +1 -1
  93. package/dist/web-assets.js +2 -2
  94. package/dist/work-graph.d.ts +11 -1
  95. package/dist/work-graph.js +16 -5
  96. package/dist/workspace.d.ts +2 -2
  97. package/dist/workspace.js +8 -7
  98. package/docs/FRONTEND.md +71 -21
  99. package/docs/HEADLESS.md +127 -24
  100. package/docs/THIRD-PARTY-PLUGINS.md +75 -3
  101. package/node_modules/@rho-workspace/plugin-agent/README.md +34 -0
  102. package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.d.ts +32 -0
  103. package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.js +54 -11
  104. package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.d.ts +1 -0
  105. package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +29 -3
  106. package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +11 -5
  107. package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +12 -1
  108. package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +8 -0
  109. package/node_modules/@rho-workspace/plugin-agent/dist/index.js +3 -2
  110. package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.d.ts +13 -0
  111. package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.js +13 -0
  112. package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +16 -8
  113. package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.d.ts +70 -0
  114. package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.js +538 -0
  115. package/node_modules/@rho-workspace/plugin-agent/package.json +2 -1
  116. package/node_modules/@rho-workspace/plugin-figures/LICENSE +661 -0
  117. package/node_modules/@rho-workspace/plugin-figures/README.md +191 -0
  118. package/node_modules/@rho-workspace/plugin-figures/dist/index.d.ts +236 -0
  119. package/node_modules/@rho-workspace/plugin-figures/dist/index.js +165 -0
  120. package/node_modules/@rho-workspace/plugin-figures/dist/operations.d.ts +44 -0
  121. package/node_modules/@rho-workspace/plugin-figures/dist/operations.js +39 -0
  122. package/node_modules/@rho-workspace/plugin-figures/dist/presentation.d.ts +19 -0
  123. package/node_modules/@rho-workspace/plugin-figures/dist/presentation.js +26 -0
  124. package/node_modules/@rho-workspace/plugin-figures/dist/record.d.ts +188 -0
  125. package/node_modules/@rho-workspace/plugin-figures/dist/record.js +143 -0
  126. package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.d.ts +1 -0
  127. package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.js +68 -0
  128. package/node_modules/@rho-workspace/plugin-figures/dist/ui.d.ts +56 -0
  129. package/node_modules/@rho-workspace/plugin-figures/dist/ui.js +363 -0
  130. package/node_modules/@rho-workspace/plugin-figures/package.json +41 -0
  131. package/node_modules/@rho-workspace/plugin-figures/py/figures.py +856 -0
  132. package/node_modules/@rho-workspace/plugin-figures/r/figures.R +775 -0
  133. package/node_modules/@rho-workspace/plugin-kernel/README.md +27 -3
  134. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.d.ts +12 -0
  135. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +8 -1
  136. package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +61 -0
  137. package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +125 -9
  138. package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.d.ts +14 -0
  139. package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.js +9 -0
  140. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +23 -5
  141. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -1
  142. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +6 -4
  143. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +44 -17
  144. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +6 -0
  145. package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
  146. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +144 -4
  147. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +106 -1
  148. package/node_modules/@rho-workspace/plugin-tables/README.md +5 -0
  149. package/node_modules/@rho-workspace/plugin-tables/dist/index.d.ts +1 -0
  150. package/node_modules/@rho-workspace/plugin-tables/dist/index.js +19 -3
  151. package/node_modules/@rho-workspace/plugin-tables/dist/ui.js +1 -1
  152. package/node_modules/@rho-workspace/plugin-tables/package.json +1 -1
  153. package/node_modules/hono/dist/adapter/aws-lambda/handler.js +1 -1
  154. package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +1 -1
  155. package/node_modules/hono/dist/cjs/adapter/bun/serve-static.js +1 -1
  156. package/node_modules/hono/dist/cjs/adapter/bun/websocket.js +1 -1
  157. package/node_modules/hono/dist/cjs/client/fetch-result-please.js +1 -1
  158. package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +2 -1
  159. package/node_modules/hono/dist/cjs/helper/ssg/ssg.js +5 -3
  160. package/node_modules/hono/dist/cjs/jsx/base.js +49 -30
  161. package/node_modules/hono/dist/cjs/jsx/components.js +2 -2
  162. package/node_modules/hono/dist/cjs/jsx/constants.js +2 -0
  163. package/node_modules/hono/dist/cjs/jsx/jsx-runtime.js +13 -17
  164. package/node_modules/hono/dist/cjs/middleware/etag/index.js +3 -1
  165. package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +1 -0
  166. package/node_modules/hono/dist/cjs/middleware/timing/timing.js +4 -3
  167. package/node_modules/hono/dist/cjs/utils/cookie.js +2 -2
  168. package/node_modules/hono/dist/cjs/utils/mime.js +1 -1
  169. package/node_modules/hono/dist/cjs/utils/url.js +1 -1
  170. package/node_modules/hono/dist/client/fetch-result-please.js +1 -1
  171. package/node_modules/hono/dist/helper/accepts/accepts.js +2 -1
  172. package/node_modules/hono/dist/helper/ssg/ssg.js +5 -3
  173. package/node_modules/hono/dist/jsx/base.js +49 -32
  174. package/node_modules/hono/dist/jsx/components.js +2 -2
  175. package/node_modules/hono/dist/jsx/constants.js +2 -1
  176. package/node_modules/hono/dist/jsx/jsx-runtime.js +15 -19
  177. package/node_modules/hono/dist/middleware/etag/index.js +3 -1
  178. package/node_modules/hono/dist/middleware/language/language.js +1 -1
  179. package/node_modules/hono/dist/middleware/pretty-json/index.js +1 -0
  180. package/node_modules/hono/dist/middleware/timing/timing.js +4 -3
  181. package/node_modules/hono/dist/utils/cookie.js +2 -2
  182. package/node_modules/hono/dist/utils/mime.js +1 -1
  183. package/node_modules/hono/dist/utils/url.js +1 -1
  184. package/node_modules/hono/package.json +1 -1
  185. package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +3 -2
  186. package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
  187. package/node_modules/jose/dist/webapi/jws/general/verify.js +2 -8
  188. package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +11 -8
  189. package/node_modules/jose/dist/webapi/lib/key_management.js +0 -1
  190. package/node_modules/jose/package.json +1 -1
  191. package/package.json +6 -4
  192. package/protocol/index.json +106 -64
  193. package/protocol/operations/agent.sessions.list.json +57 -3
  194. package/protocol/operations/agent.sessions.read.json +61 -4
  195. package/protocol/operations/capability.list.json +382 -6
  196. package/protocol/operations/composition.apply.json +10 -2
  197. package/protocol/operations/composition.delete.json +845 -0
  198. package/protocol/operations/composition.read.json +10 -2
  199. package/protocol/operations/composition.undo.json +10 -2
  200. package/protocol/operations/conversation.agents.add.json +346 -0
  201. package/protocol/operations/conversation.agents.remove.json +340 -0
  202. package/protocol/operations/conversation.configure.json +29 -0
  203. package/protocol/operations/conversation.decide.json +225 -0
  204. package/protocol/operations/conversation.list.json +29 -0
  205. package/protocol/operations/conversation.permission.json +225 -0
  206. package/protocol/operations/conversation.read.json +225 -0
  207. package/protocol/operations/conversation.restore.json +29 -0
  208. package/protocol/operations/conversation.send.json +294 -0
  209. package/protocol/operations/conversation.settings.check.json +226 -416
  210. package/protocol/operations/conversation.settings.read.json +224 -411
  211. package/protocol/operations/conversation.settings.update.json +248 -475
  212. package/protocol/operations/conversation.start.json +313 -6
  213. package/protocol/operations/conversation.stop.json +225 -0
  214. package/protocol/operations/conversation.trash.json +29 -0
  215. package/protocol/operations/extension.describe.json +39 -0
  216. package/protocol/operations/extension.install.json +62 -0
  217. package/protocol/operations/extension.list.json +164 -1
  218. package/protocol/operations/extension.market.change.json +210 -0
  219. package/protocol/operations/figure.read.json +265 -0
  220. package/protocol/operations/figure.select.json +157 -0
  221. package/protocol/operations/kernel.interrupt.json +119 -0
  222. package/protocol/operations/kernel.read.json +119 -0
  223. package/protocol/operations/kernel.run.json +119 -0
  224. package/protocol/operations/observation.snapshot.json +445 -8
  225. package/protocol/operations/project.openFile.json +10 -2
  226. package/protocol/operations/session.grant.json +4 -0
  227. package/protocol/operations/session.instructions.json +43 -0
  228. package/protocol/operations/shared.json +1 -0
  229. package/protocol/operations/view.close.json +10 -2
  230. package/protocol/operations/view.protection.set.json +10 -2
  231. package/vendor/composition.json +174 -109
  232. package/vendor/rho-workspace-plugin-agent-0.4.33.tgz +0 -0
  233. package/vendor/rho-workspace-plugin-figures-0.5.1.tgz +0 -0
  234. package/vendor/rho-workspace-plugin-kernel-0.5.13.tgz +0 -0
  235. package/vendor/rho-workspace-plugin-tables-0.5.2.tgz +0 -0
  236. package/web/theme.css +17 -0
  237. package/dist/web/assets/answer-board-CBkGbSLD.js +0 -1
  238. package/dist/web/assets/canvas-D8rDdfDz.js +0 -1
  239. package/dist/web/assets/choices-b0XhrGQV.js +0 -1
  240. package/dist/web/assets/graph-1OdIJ9l7.js +0 -2
  241. package/dist/web/assets/index-CsC3sTRq.css +0 -1
  242. package/dist/web/assets/index-CxmItXqa.js +0 -891
  243. package/dist/web/assets/overview-B9UtVJ32.js +0 -6
  244. package/dist/web/assets/preview-CksD_5KS.js +0 -1
  245. package/dist/web/assets/studio-process-CsRoTHOh.js +0 -2
  246. package/dist/web/assets/timeline-events-BbuuFWHV.js +0 -1
  247. package/dist/web/assets/work-turns-BvJlIMmA.js +0 -1
  248. package/vendor/rho-workspace-plugin-agent-0.4.29.tgz +0 -0
  249. package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
  250. package/vendor/rho-workspace-plugin-tables-0.5.0.tgz +0 -0
@@ -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) {
@@ -14,19 +15,25 @@ const clientName = { claude: 'Claude Code', codex: 'Codex' };
14
15
  * executable, else the one already named in the environment, else the client on PATH. */
15
16
  export function acpEnvironment(agent, env, executable) {
16
17
  const name = executableVariable[agent];
17
- return { ...env, [name]: executable || env[name] || agent };
18
+ return { ...withoutSessionMarkers(env), [name]: executable || env[name] || agent };
18
19
  }
19
- /** Why the adapter could not start the person's client, or null when its executable is
20
- * there. The adapters report a missing client only as a closed connection. */
21
- export function missingClient(agent, env) {
22
- const executable = env[executableVariable[agent]] || agent;
23
- // On Windows a copied environment keeps the variable's own case (Path), and a client is a
24
- // file with an extension PATHEXT names (claude.exe, an npm codex.cmd), as the shell finds it.
20
+ // When Rho itself was started from inside a Claude Code session (an Agent ran the setup), the
21
+ // environment carries that session's markers. A Claude Code started with them takes itself for
22
+ // its child: it saves no transcript (so its session cannot be continued anywhere) and reports to
23
+ // the parent's socket. Rho's sessions are the person's own, so those markers are not passed on.
24
+ const sessionMarkers = /^(CLAUDECODE|CLAUDE_CODE_(CHILD_SESSION|ENTRYPOINT|SESSION_ID|SESSION_ATTENDED|MESSAGING_SOCKET|MESSAGING_TOKEN|EXECPATH|SSE_PORT)|CLAUDE_PID)$/;
25
+ export function withoutSessionMarkers(env) {
26
+ return Object.fromEntries(Object.entries(env).filter(([key]) => !sessionMarkers.test(key)));
27
+ }
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) {
25
32
  const windows = process.platform === 'win32';
26
33
  const variable = (name) => windows ? Object.entries(env).find(([key]) => key.toUpperCase() === name)?.[1] : env[name];
27
- const named = executable.includes('/') || (windows && executable.includes('\\'));
28
- const extensions = windows && !path.extname(executable) ? (variable('PATHEXT') ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) : [''];
29
- const candidates = (named ? [executable] : (variable('PATH') ?? '').split(path.delimiter).filter(Boolean).map(directory => path.join(directory, executable))).flatMap(file => extensions.map(extension => file + extension));
34
+ const named = program.includes('/') || (windows && program.includes('\\'));
35
+ const extensions = windows && !path.extname(program) ? (variable('PATHEXT') ?? '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean) : [''];
36
+ const candidates = (named ? [program] : (variable('PATH') ?? '').split(path.delimiter).filter(Boolean).map(directory => path.join(directory, program))).flatMap(file => extensions.map(extension => file + extension));
30
37
  const runnable = (file) => { try {
31
38
  fs.accessSync(file, fs.constants.X_OK);
32
39
  return fs.statSync(file).isFile();
@@ -34,7 +41,43 @@ export function missingClient(agent, env) {
34
41
  catch {
35
42
  return false;
36
43
  } };
37
- if (candidates.some(runnable))
44
+ return candidates.find(runnable) ?? null;
45
+ }
46
+ /** Why the adapter could not start the person's client, or null when its executable is
47
+ * there. The adapters report a missing client only as a closed connection. */
48
+ export function missingClient(agent, env) {
49
+ const executable = env[executableVariable[agent]] || agent;
50
+ if (locate(executable, env))
38
51
  return null;
52
+ const named = executable.includes('/') || (process.platform === 'win32' && executable.includes('\\'));
39
53
  return `${clientName[agent]} was not found ${named ? 'at ' + executable : 'on PATH (' + executable + ')'}. Install it, or select its executable.`;
40
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
+ }
@@ -8,6 +8,7 @@ export interface AcpConversationOptions {
8
8
  mcpServers: AgentMcpServer[];
9
9
  limits: AgentProcessLimits;
10
10
  sessionId?: string;
11
+ forkFrom?: string;
11
12
  command: {
12
13
  file: string;
13
14
  args: string[];
@@ -148,9 +148,16 @@ export class AcpConversation {
148
148
  } });
149
149
  bounded.on('error', () => { });
150
150
  child.stdout.pipe(bounded);
151
- // Drain diagnostics without storing credentials, URLs or native reasoning.
152
- child.stderr.on('data', (chunk) => { this.outputBytes += chunk.length; if (this.outputBytes > o.limits.maxOutputBytes)
153
- overflow('output-limit'); });
151
+ // Drain diagnostics without storing credentials, URLs or native reasoning. Its last few
152
+ // kilobytes stay in memory only, to say why an Agent that exits before it answers did not
153
+ // start (an unknown argument, a missing sign-in); only its last line is shown, redacted.
154
+ let printed = '';
155
+ child.stderr.on('data', (chunk) => {
156
+ this.outputBytes += chunk.length;
157
+ if (this.outputBytes > o.limits.maxOutputBytes)
158
+ overflow('output-limit');
159
+ printed = (printed + chunk.toString('utf8')).slice(-4096);
160
+ });
154
161
  this.connection = acp.client({ name: 'rho-agent' })
155
162
  .onNotification(acp.methods.client.session.update, ({ params }) => {
156
163
  try {
@@ -211,6 +218,16 @@ export class AcpConversation {
211
218
  throw new Error('This Agent cannot load the original session. Nothing was resent.');
212
219
  }
213
220
  }
221
+ else if (o.forkFrom) {
222
+ const caps = initialized.agentCapabilities;
223
+ if (!caps?.sessionCapabilities?.fork)
224
+ throw new Error('This Agent cannot continue a copy of a session. Nothing was sent.');
225
+ const forked = await connection.agent.request('session/fork', { ...params, sessionId: o.forkFrom });
226
+ if (typeof forked?.sessionId !== 'string' || forked.sessionId === o.forkFrom)
227
+ throw new Error('The Agent did not create a separate copy of the session. Nothing was sent.');
228
+ this.sessionId = forked.sessionId;
229
+ this.settings = forked.configOptions ?? [];
230
+ }
214
231
  else {
215
232
  const created = await connection.agent.request(acp.methods.agent.session.new, params);
216
233
  this.sessionId = created.sessionId;
@@ -229,6 +246,15 @@ export class AcpConversation {
229
246
  ...(initialized.agentInfo ? { adapter: { name: initialized.agentInfo.name, version: initialized.agentInfo.version } } : {}), ...(model ? { model } : {}) };
230
247
  this.observationOrigin = 'live';
231
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
+ }
232
258
  finally {
233
259
  clearTimeout(timer);
234
260
  }
@@ -1,4 +1,5 @@
1
- // A retained observation of another client's conversation. It has no input operation.
1
+ // A conversation an Agent holds in its own record: a retained observation of another client's
2
+ // conversation, or a session the Agent keeps in its own files. Reading it starts nothing.
2
3
  const text = { type: 'string' }, count = { type: 'integer', minimum: 0 };
3
4
  const obj = (properties, required = Object.keys(properties)) => ({ type: 'object', properties, required, additionalProperties: false });
4
5
  const array = (items, maxItems) => ({ type: 'array', items, maxItems });
@@ -6,10 +7,15 @@ export const externalSessionLimits = { maxSessions: 64, maxTurns: 200, maxTools:
6
7
  const state = { enum: ['running', 'completed', 'failed', 'stopped', 'unknown'] };
7
8
  export const externalResultSchema = obj({ path: text, id: text, version: text, projectId: text, toolId: text }, ['path', 'toolId']);
8
9
  export const externalToolSchema = obj({ id: text, at: text, name: text, detail: text, output: text, outcome: { enum: ['requested', 'returned', 'failed', 'unknown'] }, exitCode: { type: 'integer' }, paths: array(text, 32) }, ['id', 'at', 'name', 'detail', 'output', 'outcome', 'paths']);
9
- 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 }, ['id', 'at', 'state', 'prompt', 'reply', 'messages', 'tools', 'results', 'omittedTools', 'omittedChars']);
10
- 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' } }, ['id', 'producer', 'sessionId', 'title', 'lastAt', 'observedAt', 'state', 'turns', 'sourceCursor', 'omittedTurns', 'inputOwner']);
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
+ 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
+ // 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 (acp:… is an ACP Agent the person
14
+ // added there) and the Rho conversation that continued it.
15
+ where: text, open: { anyOf: [{ type: 'null' }, obj({ where: text, since: text, pid: count }, ['where'])] }, resume: text, bytes: count,
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']);
11
17
  export const externalSessionSchemas = { summary: externalSessionSummarySchema, turn: externalTurnSchema };
12
18
  export const externalSessionOperations = {
13
- 'agent.sessions.list': { inputSchema: obj({}), outputSchema: obj({ sessions: array(externalSessionSummarySchema, 64), omitted: count }), requires: [], effect: 'read' },
14
- 'agent.sessions.read': { inputSchema: obj({ session: { type: 'string', pattern: '^x-[a-f0-9]{64}$' }, before: count, limit: { type: 'integer', minimum: 1, maximum: 20 } }, ['session']), outputSchema: obj({ session: externalSessionSummarySchema, first: count, turns: array(externalTurnSchema, 20) }), requires: [], effect: 'read' },
19
+ 'agent.sessions.list': { inputSchema: obj({}), outputSchema: obj({ sessions: array(externalSessionSummarySchema, 128), omitted: count }), requires: [], effect: 'read' },
20
+ 'agent.sessions.read': { inputSchema: obj({ session: { type: 'string', pattern: '^x-[a-f0-9]{64}$' }, before: count, limit: { type: 'integer', minimum: 1, maximum: 50 } }, ['session']), outputSchema: obj({ session: externalSessionSummarySchema, first: count, turns: array(externalTurnSchema, 50) }), requires: [], effect: 'read' },
15
21
  };
@@ -31,6 +31,7 @@ export interface ExternalTurn {
31
31
  results: ExternalResult[];
32
32
  omittedTools: number;
33
33
  omittedChars: number;
34
+ where?: string;
34
35
  }
35
36
  export interface ExternalSessionSummary {
36
37
  id: string;
@@ -44,7 +45,17 @@ export interface ExternalSessionSummary {
44
45
  sourceCursor: number;
45
46
  omittedTurns: number;
46
47
  problem?: string;
47
- inputOwner: 'external';
48
+ inputOwner?: 'external';
49
+ where?: string;
50
+ open?: {
51
+ where: string;
52
+ since?: string;
53
+ pid?: number;
54
+ } | null;
55
+ resume?: string;
56
+ bytes?: number;
57
+ continueWith?: 'claude-acp' | 'codex-acp' | 'acp' | `acp:${string}` | null;
58
+ conversationId?: string;
48
59
  }
49
60
  export interface ExternalSessionRecord {
50
61
  format: 1;
@@ -1971,6 +1971,14 @@ export declare const plugin: {
1971
1971
  modelLoop: string;
1972
1972
  contextDelivery: string;
1973
1973
  proactivePush: string;
1974
+ mcp: {
1975
+ entry: string;
1976
+ transport: string;
1977
+ tools: {
1978
+ name: string;
1979
+ description: string;
1980
+ }[];
1981
+ };
1974
1982
  };
1975
1983
  };
1976
1984
  export { listExternalSessions, readExternalSession } from './external-session.js';
@@ -1,4 +1,5 @@
1
1
  import { layeredSchemas } from './layered-contract.js';
2
+ import { mcpEntry } from './mcp-entry.js';
2
3
  import { skillPresentation, presentation, operationPresentation } from './presentation.js';
3
4
  import { operationContracts } from './operations.js';
4
5
  import * as fs from 'node:fs/promises';
@@ -302,9 +303,9 @@ const presentationSkill = {
302
303
  ],
303
304
  verify: { command: 'npm run test:presenter', description: 'Presentation enhances display within its witness and role, and refuses business feedback or document access.' }
304
305
  };
305
- export const plugin = { entryVersion: '1.0', id: 'agent', package: '@rho-workspace/plugin-agent', version: '0.4.19', requires: [], capabilities: ['agent-context'], presentation,
306
+ export const plugin = { entryVersion: '1.0', id: 'agent', package: '@rho-workspace/plugin-agent', version: '0.4.33', requires: [], capabilities: ['agent-context'], presentation,
306
307
  operations: Object.entries(operationContracts).map(([method, contract]) => ({ method, presentation: operationPresentation[method], version: '2.0', ...structuredClone(contract), requires: [...contract.requires] })),
307
308
  components: [{ id: 'rho.agent-note', version: '0.2.0', owner: 'rho-plugins.agent', bindings: {}, propsSchema: layeredSchemas.AgentNoteProps, ui: './ui', presentation: { displayName: 'Agent note', description: 'A plain presentation explanation with authenticated author and binding basis.', readOnly: true, runsCode: false } }],
308
309
  skills: [{ ...agentSkill, presentation: skillPresentation }, { ...feedbackSkill, presentation: { displayName: 'Business feedback', description: 'Reads original feedback, preserves current human edits and links an explicit reply.' } }, { ...presentationSkill, presentation: { displayName: 'Presentation', description: 'Enhances the assigned visible layout without business feedback or document access.' } }],
309
- agent: { id: 'claude-code-mcp', transport: 'stdio', modelLoop: 'external-client', contextDelivery: 'pull-tool-result', proactivePush: 'unsupported' } };
310
+ agent: { id: 'claude-code-mcp', transport: 'stdio', modelLoop: 'external-client', contextDelivery: 'pull-tool-result', proactivePush: 'unsupported', mcp: mcpEntry } };
310
311
  export { listExternalSessions, readExternalSession } from './external-session.js';
@@ -0,0 +1,13 @@
1
+ export declare const mcpTools: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: any;
5
+ }[];
6
+ export declare const mcpEntry: {
7
+ entry: string;
8
+ transport: string;
9
+ tools: {
10
+ name: string;
11
+ description: string;
12
+ }[];
13
+ };
@@ -0,0 +1,13 @@
1
+ import { layeredSchemas } from './layered-contract.js';
2
+ // The public MCP entry and its actual tool list share one declaration. Reading
3
+ // this metadata imports no server, connection file or Agent process.
4
+ export const mcpTools = [
5
+ { name: 'rho_index', description: 'Discover optional Rho workspace capabilities alongside your native tools. Core managed execution is chosen only when needed. Select owner or search to page authorized operations. No full schemas are loaded.', inputSchema: layeredSchemas.McpIndexInput },
6
+ { name: 'rho_resolve', description: 'Fetch complete guidance and contracts only for selected operations. Follow whole-object pages; knownRefs reuse bodies you still hold.', inputSchema: layeredSchemas.McpResolveInput },
7
+ { name: 'rho_call', description: 'Call one resolved authorized operation. Large replies return an immutable result reference for rho_read; never repeat a write to retrieve its output.', inputSchema: layeredSchemas.McpCallInput },
8
+ { name: 'rho_read', description: 'Inspect a retained result by JSON pointer, page containers or text, or explicitly release it. Text offsets count UTF-16 units. Reading never invokes the original operation.', inputSchema: layeredSchemas.McpReadInput }
9
+ ];
10
+ export const mcpEntry = {
11
+ entry: './mcp', transport: 'stdio',
12
+ tools: mcpTools.map(({ name, description }) => ({ name, description }))
13
+ };
@@ -3,7 +3,7 @@ import * as fs from 'node:fs/promises';
3
3
  import http from 'node:http';
4
4
  import path from 'node:path';
5
5
  import { retainAgentIntent } from './agent-intents.js';
6
- import { layeredSchemas } from './layered-contract.js';
6
+ import { mcpTools as tools } from './mcp-entry.js';
7
7
  import { LayeredMcp } from './layered.js';
8
8
  import { ResultPages, resultLimits } from './result-pages.js';
9
9
  import { Ajv } from 'ajv';
@@ -31,11 +31,25 @@ requireThat(Number.isSafeInteger(transportMs) && transportMs > 0, 'invalid', 'In
31
31
  const allowance = (input) => transportMs + (Number.isSafeInteger(input.waitMs) && input.waitMs > 0 ? Math.min(input.waitMs, 600000) : 0);
32
32
  // node:http rather than fetch: fetch stops waiting for a response's headers after 300 s whatever
33
33
  // the signal allows, and the service answers a waited cell only when the cell ends (P3, run 2).
34
+ // The client that started this server, as it named itself in MCP initialize, so the person's
35
+ // screen can show whose window the connection is. A name and version only, each bounded.
36
+ function clientHeader() {
37
+ let info;
38
+ try {
39
+ info = server.getClientVersion();
40
+ }
41
+ catch {
42
+ return {};
43
+ }
44
+ if (!info?.name)
45
+ return {};
46
+ return { 'x-rho-client': [info.name, info.version].filter(Boolean).map(x => encodeURIComponent(String(x).slice(0, 128))).join('/') };
47
+ }
34
48
  async function invoke(method, input = {}, contractRef) {
35
49
  const body = JSON.stringify({ ...input, projectId: c.projectId });
36
50
  const response = await new Promise((resolve, reject) => {
37
51
  const request = http.request(new URL('/api/v2/' + method, endpoint), { method: 'POST', signal: AbortSignal.timeout(allowance(input)),
38
- headers: { authorization: 'Bearer ' + c.token, 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), ...(contractRef ? { 'x-rho-contract-ref': contractRef } : {}) } }, resolve);
52
+ headers: { authorization: 'Bearer ' + c.token, 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), ...clientHeader(), ...(contractRef ? { 'x-rho-contract-ref': contractRef } : {}) } }, resolve);
39
53
  // A refused connection is the one failure whose outcome is known: nothing reached the
40
54
  // service, so nothing ran. It is usually Rho not running, and the person can start it.
41
55
  request.once('error', error => reject(error.code === 'ECONNREFUSED'
@@ -125,12 +139,6 @@ const results = new ResultPages(path.dirname(descriptor), digest(c.projectId + '
125
139
  const index = await layer.index({ search: operation });
126
140
  requireThat(index.ok && index.value.entries.some((e) => e.id === operation), 'scope', 'The operation that produced this result is no longer available to this connection.');
127
141
  }, limits);
128
- const tools = [
129
- { name: 'rho_index', description: 'Discover optional Rho workspace capabilities alongside your native tools. Core managed execution is chosen only when needed. Select owner or search to page authorized operations. No full schemas are loaded.', inputSchema: layeredSchemas.McpIndexInput },
130
- { name: 'rho_resolve', description: 'Fetch complete guidance and contracts only for selected operations. Follow whole-object pages; knownRefs reuse bodies you still hold.', inputSchema: layeredSchemas.McpResolveInput },
131
- { name: 'rho_call', description: 'Call one resolved authorized operation. Large replies return an immutable result reference for rho_read; never repeat a write to retrieve its output.', inputSchema: layeredSchemas.McpCallInput },
132
- { name: 'rho_read', description: 'Inspect a retained result by JSON pointer, page containers or text, or explicitly release it. Text offsets count UTF-16 units. Reading never invokes the original operation.', inputSchema: layeredSchemas.McpReadInput }
133
- ];
134
142
  const ajv = new Ajv({ strict: false });
135
143
  const validators = Object.fromEntries(tools.map(t => [t.name, ajv.compile(t.inputSchema)]));
136
144
  const server = new Server({ name: 'rho-project', version: '0.4.0' }, { capabilities: { tools: {} } });
@@ -0,0 +1,70 @@
1
+ import { type ExternalSessionSummary, type ExternalTurn } from './external-session.js';
2
+ export declare const nativeSessionLimits: {
3
+ maxSessions: number;
4
+ maxScanFiles: number;
5
+ maxHeadBytes: number;
6
+ maxTailBytes: number;
7
+ maxReadBytes: number;
8
+ maxTurns: number;
9
+ maxTools: number;
10
+ maxTextChars: number;
11
+ maxDetailChars: number;
12
+ activeMs: number;
13
+ };
14
+ export type NativeProducer = 'claude-code' | 'codex';
15
+ export interface NativeSessionSources {
16
+ claudeConfigDir?: string;
17
+ codexHome?: string;
18
+ }
19
+ export interface NativeSessionSummary extends ExternalSessionSummary {
20
+ producer: NativeProducer;
21
+ where: string;
22
+ open: {
23
+ where: string;
24
+ since?: string;
25
+ pid?: number;
26
+ } | null;
27
+ resume: string;
28
+ bytes: number;
29
+ }
30
+ export declare function nativeSources(env?: NodeJS.ProcessEnv): Required<NativeSessionSources>;
31
+ /** The Agents' own sessions whose working folder is `root`, newest first. Reads files only. */
32
+ export declare function listNativeSessions(root: string, sources?: NativeSessionSources): {
33
+ sessions: NativeSessionSummary[];
34
+ omitted: number;
35
+ };
36
+ /** One session's turns, read through its file (its last maxReadBytes when larger). */
37
+ export declare function readNativeSession(root: string, input: {
38
+ session: string;
39
+ before?: number;
40
+ limit?: number;
41
+ }, sources?: NativeSessionSources): Promise<{
42
+ session: {
43
+ turns: number;
44
+ omittedTurns: number;
45
+ producer: NativeProducer;
46
+ where: string;
47
+ open: {
48
+ where: string;
49
+ since?: string;
50
+ pid?: number;
51
+ } | null;
52
+ resume: string;
53
+ bytes: number;
54
+ id: string;
55
+ sessionId: string;
56
+ title: string;
57
+ lastAt: string;
58
+ observedAt: string;
59
+ state: ExternalTurn["state"];
60
+ sourceCursor: number;
61
+ problem?: string;
62
+ inputOwner?: "external";
63
+ continueWith?: "claude-acp" | "codex-acp" | "acp" | `acp:${string}` | null;
64
+ conversationId?: string;
65
+ };
66
+ first: number;
67
+ turns: ExternalTurn[];
68
+ } | null>;
69
+ /** Whether `sessionId` names a session of this Agent in `root`, and its summary. */
70
+ export declare function nativeSession(root: string, producer: NativeProducer, sessionId: string, sources?: NativeSessionSources): NativeSessionSummary | null;