@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,4 @@
1
- import type { CapabilityCatalog, CapabilityPlugin } from './browser.js';
1
+ import type { CapabilityCatalog, CapabilityPlugin, EntryPresentation } from './browser.js';
2
2
  export declare function pluginPresentation(plugin: CapabilityPlugin): {
3
3
  displayName: string;
4
4
  tagline: string;
@@ -35,6 +35,7 @@ export declare function pluginGroups(plugins: CapabilityPlugin[]): {
35
35
  id: string;
36
36
  package: string;
37
37
  version?: string;
38
+ packageVersion?: string;
38
39
  requires?: string[];
39
40
  selected: boolean;
40
41
  packagePresent: boolean;
@@ -57,9 +58,40 @@ export declare function pluginGroups(plugins: CapabilityPlugin[]): {
57
58
  readOnly?: boolean;
58
59
  runsCode?: boolean;
59
60
  };
61
+ intent?: string;
62
+ requires?: string[];
63
+ example?: {
64
+ method: string;
65
+ input: {};
66
+ };
67
+ failureRecipes?: {
68
+ kind: string;
69
+ message: string;
70
+ next: string;
71
+ }[];
72
+ verify?: {
73
+ command: string;
74
+ description: string;
75
+ };
76
+ }[];
77
+ operations?: {
78
+ method: string;
79
+ version: string;
80
+ effect: "read" | "metadata" | "document" | "file" | "native";
81
+ requires: string[];
82
+ presentation?: {
83
+ displayName?: string;
84
+ description?: string;
85
+ order?: number;
86
+ readOnly?: boolean;
87
+ runsCode?: boolean;
88
+ };
60
89
  }[];
61
90
  actions?: {
62
- presentation: {
91
+ id?: string;
92
+ version?: string;
93
+ requires?: string[];
94
+ presentation?: {
63
95
  displayName?: string;
64
96
  description?: string;
65
97
  order?: number;
@@ -71,6 +103,9 @@ export declare function pluginGroups(plugins: CapabilityPlugin[]): {
71
103
  id: string;
72
104
  version: string;
73
105
  ui: string;
106
+ bindings?: {
107
+ [k: string]: string;
108
+ };
74
109
  presentation?: {
75
110
  displayName?: string;
76
111
  description?: string;
@@ -79,6 +114,29 @@ export declare function pluginGroups(plugins: CapabilityPlugin[]): {
79
114
  runsCode?: boolean;
80
115
  };
81
116
  }[];
117
+ cli?: string[];
118
+ mcp?: {
119
+ entry: string;
120
+ transport: string;
121
+ tools: {
122
+ name: string;
123
+ description: string;
124
+ }[];
125
+ };
126
+ claudeCode?: {
127
+ skills?: {
128
+ skill: string;
129
+ name: string;
130
+ description: string;
131
+ }[];
132
+ hooks?: {
133
+ id?: string;
134
+ event: string;
135
+ matcher: string;
136
+ entry: string;
137
+ timeoutSeconds?: number;
138
+ }[];
139
+ };
82
140
  };
83
141
  presentation?: {
84
142
  displayName?: string;
@@ -122,8 +180,16 @@ export declare function pluginGroups(plugins: CapabilityPlugin[]): {
122
180
  }[];
123
181
  }[];
124
182
  export declare function pluginEntries(catalog: CapabilityCatalog, pluginId: string): {
183
+ operations: {
184
+ method: string;
185
+ effect?: string;
186
+ presentation?: EntryPresentation;
187
+ state?: string;
188
+ authorized?: boolean;
189
+ }[];
125
190
  actions: {
126
191
  displayName: string;
192
+ identities: string[];
127
193
  description: string;
128
194
  readOnly: boolean;
129
195
  runsCode: boolean;
@@ -132,10 +198,19 @@ export declare function pluginEntries(catalog: CapabilityCatalog, pluginId: stri
132
198
  id: string;
133
199
  version: string;
134
200
  ui: string;
201
+ package: string | undefined;
202
+ packageVersion: string | undefined;
203
+ bindings: {
204
+ [k: string]: string;
205
+ };
135
206
  displayName: string;
136
207
  description: string;
137
208
  readOnly: boolean | undefined;
138
209
  runsCode: boolean | undefined;
210
+ propsSchema: {
211
+ required?: string[];
212
+ properties?: Record<string, unknown>;
213
+ } | undefined;
139
214
  }[];
140
215
  skills: {
141
216
  id: string;
@@ -148,5 +223,33 @@ export declare function pluginEntries(catalog: CapabilityCatalog, pluginId: stri
148
223
  readOnly?: boolean;
149
224
  runsCode?: boolean;
150
225
  };
226
+ intent?: string;
227
+ requires?: string[];
228
+ example?: {
229
+ method: string;
230
+ input: {};
231
+ };
232
+ failureRecipes?: {
233
+ kind: string;
234
+ message: string;
235
+ next: string;
236
+ }[];
237
+ verify?: {
238
+ command: string;
239
+ description: string;
240
+ };
151
241
  }[];
152
242
  };
243
+ export declare function pluginMatches(catalog: CapabilityCatalog, plugin: CapabilityPlugin, query: string): boolean;
244
+ export type PluginKind = {
245
+ kind: 'ui' | 'page' | 'mcp' | 'skills' | 'claude-code' | 'mcp-server' | 'cli';
246
+ label: string;
247
+ count?: number;
248
+ detail: string;
249
+ };
250
+ export declare function pluginKinds(plugin: CapabilityPlugin, entries: ReturnType<typeof pluginEntries>): PluginKind[];
251
+ export type ViewSubject = 'document' | 'markdown' | 'html' | 'table' | 'media' | 'object' | 'draft' | 'none';
252
+ export declare function viewSubject(view: {
253
+ id: string;
254
+ bindings: Record<string, string>;
255
+ }): ViewSubject;
@@ -23,33 +23,87 @@ export function pluginGroups(plugins) {
23
23
  }
24
24
  export function pluginEntries(catalog, pluginId) {
25
25
  const plugin = catalog.plugins.find(p => p.id === pluginId);
26
- const operations = catalog.operations.filter(o => o.owner === pluginId);
26
+ // Keep retained descriptions when an owner is absent, and overlay live state
27
+ // only for the exact method. Display copy is optional; identity never is.
28
+ const methods = new Map();
29
+ for (const operation of [...(plugin?.listing?.operations ?? []), ...catalog.operations.filter(o => o.owner === pluginId)]) {
30
+ methods.set(operation.method, { ...methods.get(operation.method), ...operation });
31
+ }
32
+ const operations = [...methods.values()];
27
33
  const actions = [...(catalog.actions ?? []), ...(catalog.unavailableActions ?? [])].filter(a => a.pluginId === pluginId);
28
34
  const groups = new Map();
29
35
  for (const entry of [...operations, ...actions, ...(plugin?.listing?.actions ?? [])]) {
30
- const displayName = entry.presentation?.displayName;
36
+ const identity = 'method' in entry ? entry.method : entry.id;
37
+ const displayName = entry.presentation?.displayName || identity;
31
38
  if (!displayName)
32
39
  continue;
33
40
  // Several methods can expose one owner-authored human action. A group is
34
41
  // read-only only when every declared member is explicitly read-only.
35
42
  if (!groups.has(displayName))
36
- groups.set(displayName, { displayName, presentations: [] });
43
+ groups.set(displayName, { displayName, identities: [], presentations: [] });
44
+ if (identity && !groups.get(displayName).identities.includes(identity))
45
+ groups.get(displayName).identities.push(identity);
37
46
  groups.get(displayName).presentations.push(entry.presentation);
38
47
  }
48
+ const components = new Map();
49
+ for (const component of [...(plugin?.listing?.views ?? []), ...catalog.components.filter(c => c.pluginId === pluginId)]) {
50
+ components.set(component.id + '@' + component.version, component);
51
+ }
39
52
  return {
53
+ operations,
40
54
  actions: [...groups.values()].sort((a, b) => Math.min(...a.presentations.map(p => p?.order ?? Infinity)) - Math.min(...b.presentations.map(p => p?.order ?? Infinity))).map(group => ({
41
55
  displayName: group.displayName,
56
+ identities: group.identities,
42
57
  description: [...new Set(group.presentations.flatMap(p => p?.description ? [p.description] : []))].join(' '),
43
58
  readOnly: group.presentations.every(p => p?.readOnly === true),
44
59
  runsCode: group.presentations.some(p => p?.runsCode === true),
45
60
  })),
46
- views: [...catalog.components.filter(c => c.pluginId === pluginId), ...(plugin?.listing?.views ?? [])].map(c => ({
61
+ views: [...components.values()].map(c => ({
47
62
  id: c.id, version: c.version, ui: c.ui,
48
- displayName: c.presentation?.displayName ?? '',
63
+ package: ('uiPackage' in c && c.uiPackage) || plugin?.package,
64
+ packageVersion: ('uiPackageVersion' in c && c.uiPackageVersion) || plugin?.packageVersion,
65
+ bindings: c.bindings ?? {},
66
+ displayName: c.presentation?.displayName || c.id,
49
67
  description: c.presentation?.description ?? '',
50
68
  readOnly: c.presentation?.readOnly,
51
69
  runsCode: c.presentation?.runsCode,
70
+ // The live component's props contract, when the owner is loaded; retained listings carry none.
71
+ propsSchema: 'propsSchema' in c ? c.propsSchema : undefined,
52
72
  })),
53
73
  skills: plugin?.listing?.skills ?? [],
54
74
  };
55
75
  }
76
+ export function pluginMatches(catalog, plugin, query) {
77
+ const entries = pluginEntries(catalog, plugin.id);
78
+ return JSON.stringify([plugin.id, plugin.package, plugin.presentation, entries, plugin.listing?.claudeCode, plugin.listing?.mcp]).toLowerCase().includes(query.trim().toLowerCase());
79
+ }
80
+ export function pluginKinds(plugin, entries) {
81
+ const listing = plugin.listing, note = pluginPresentation(plugin).viewNote, kinds = [];
82
+ if (entries.views.length)
83
+ kinds.push({ kind: 'ui', label: 'UI', count: entries.views.length, detail: entries.views.length === 1 ? 'One view in the browser' : entries.views.length + ' views in the browser' });
84
+ else
85
+ kinds.push({ kind: 'page', label: note === 'Its own page in each project' ? 'Rho page' : note === 'Inside other views' ? 'In other views' : 'No view', detail: note || 'No view of its own; your Agent uses it' });
86
+ if (entries.operations.length)
87
+ kinds.push({ kind: 'mcp', label: 'MCP tools', count: entries.operations.length, detail: 'Operations an Agent calls over the shared Rho MCP connection' });
88
+ if (entries.skills.length)
89
+ kinds.push({ kind: 'skills', label: 'Skills', count: entries.skills.length, detail: 'Instructions an Agent reads before using it' });
90
+ if (listing?.claudeCode) {
91
+ const parts = [...(listing.claudeCode.skills ?? []).map(s => 'Skill /' + s.name), ...(listing.claudeCode.hooks ?? []).map(h => h.event + ' hook')];
92
+ kinds.push({ kind: 'claude-code', label: 'Claude Code', detail: parts.join(', ') || 'Native entries for Claude Code sessions Rho starts' });
93
+ }
94
+ if (listing?.mcp)
95
+ kinds.push({ kind: 'mcp-server', label: 'MCP server', count: listing.mcp.tools.length, detail: 'Serves Rho to Agents over ' + listing.mcp.transport });
96
+ if (listing?.cli?.length)
97
+ kinds.push({ kind: 'cli', label: 'CLI', count: listing.cli.length, detail: listing.cli.join(', ') });
98
+ return kinds;
99
+ }
100
+ export function viewSubject(view) {
101
+ const [key, kind] = Object.entries(view.bindings)[0] ?? [];
102
+ if (!key)
103
+ return 'none';
104
+ if (key === 'draft')
105
+ return 'draft';
106
+ if (kind === 'document')
107
+ return view.id === 'markdown.reader' ? 'markdown' : view.id === 'html.reader' ? 'html' : 'document';
108
+ return kind === 'table' || kind === 'media' || kind === 'object' ? kind : 'none';
109
+ }
package/dist/plugins.d.ts CHANGED
@@ -23,6 +23,7 @@ declare const entries: {
23
23
  agent: () => Promise<typeof import("@rho-workspace/plugin-agent")>;
24
24
  console: () => Promise<typeof import("@rho-workspace/plugin-console")>;
25
25
  kernel: () => Promise<typeof import("@rho-workspace/plugin-kernel")>;
26
+ figures: () => Promise<typeof import("@rho-workspace/plugin-figures")>;
26
27
  skills: () => Promise<typeof import("@rho-workspace/plugin-skills")>;
27
28
  };
28
29
  export type PluginId = keyof typeof entries;
@@ -74,7 +75,7 @@ export declare class Plugins {
74
75
  timeoutSeconds?: number;
75
76
  }[];
76
77
  };
77
- ids(): ("media" | "r" | "agent" | "files" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel")[];
78
+ ids(): ("media" | "r" | "files" | "agent" | "figures" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel")[];
78
79
  isKnown(id: string): id is PluginId;
79
80
  availabilityFor(id: PluginId): any;
80
81
  need<K extends PluginId>(id: K): Modules[K];
@@ -85,6 +86,14 @@ export declare class Plugins {
85
86
  modelLoop: string;
86
87
  contextDelivery: string;
87
88
  proactivePush: string;
89
+ mcp: {
90
+ entry: string;
91
+ transport: string;
92
+ tools: {
93
+ name: string;
94
+ description: string;
95
+ }[];
96
+ };
88
97
  } | undefined;
89
98
  version: string;
90
99
  plugins: any[];
@@ -125,6 +134,8 @@ export declare class Plugins {
125
134
  presentation?: import("@rho-workspace/plugin-contracts").EntryPresentation;
126
135
  }[];
127
136
  components: ({
137
+ uiPackage?: string | undefined;
138
+ uiPackageVersion?: string | undefined;
128
139
  pluginId: string;
129
140
  id: string;
130
141
  version: string;
@@ -161,10 +172,10 @@ export declare class Plugins {
161
172
  reason?: undefined;
162
173
  };
163
174
  descriptors(): PluginDescriptor[];
164
- availableIds(): ("media" | "r" | "agent" | "files" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel")[];
175
+ availableIds(): ("media" | "r" | "files" | "agent" | "figures" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel")[];
165
176
  descriptor(id: string): PluginDescriptor | undefined;
166
177
  nativeAvailability(rscript: string): Promise<any>;
167
- operationOwner(method: string): "media" | "r" | "agent" | "files" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel" | undefined;
178
+ operationOwner(method: string): "media" | "r" | "files" | "agent" | "figures" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel" | undefined;
168
179
  operationDeclarations(): {
169
180
  owner: string;
170
181
  method: string;
package/dist/plugins.js CHANGED
@@ -2,6 +2,7 @@ import * as fs from 'node:fs/promises';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { confined, readJson, requireThat } from '@rho-workspace/plugin-contracts';
4
4
  import { ownerContracts } from './contracts/owners.generated.js';
5
+ import { packageFacts, pluginListing } from './plugin-inventory.js';
5
6
  import { validateUiExtension, uiExtensionLimits } from './ui-extension.js';
6
7
  const executionInputSchema = ownerContracts.selected.execution.input;
7
8
  // A finite application selection, not a discovery service or an arbitrary code
@@ -18,9 +19,10 @@ const entries = {
18
19
  agent: () => import('@rho-workspace/plugin-agent'),
19
20
  console: () => import('@rho-workspace/plugin-console'),
20
21
  kernel: () => import('@rho-workspace/plugin-kernel'),
22
+ figures: () => import('@rho-workspace/plugin-figures'),
21
23
  skills: () => import('@rho-workspace/plugin-skills')
22
24
  };
23
- export const selectedPluginVersions = { documents: '0.4.7', files: '0.5.0', r: '0.4.1', requirements: '0.4.0', tables: '0.5.0', media: '0.4.0', charts: '0.4.0', annotations: '0.5.2', agent: '0.4.19', console: '0.3.0', kernel: '0.5.1', skills: '0.1.0' };
25
+ export const selectedPluginVersions = { documents: '0.4.7', files: '0.5.0', r: '0.4.1', requirements: '0.4.0', tables: '0.5.2', media: '0.4.0', charts: '0.4.0', annotations: '0.5.2', agent: '0.4.33', console: '0.3.0', kernel: '0.5.1', figures: '0.5.1', skills: '0.1.0' };
24
26
  export const pluginIds = Object.keys(entries);
25
27
  const only = (defaults, values = {}) => Object.fromEntries(Object.entries(values).filter(([k, v]) => k in defaults && v !== undefined));
26
28
  export class Plugins {
@@ -75,9 +77,10 @@ export class Plugins {
75
77
  if (timeline && module.plugin.operations.some(o => o.method === timeline && o.effect === 'read'))
76
78
  status.timeline = timeline;
77
79
  status.requires = [...module.plugin.requires];
78
- status.listing = {
79
- skills: module.plugin.skills.map(({ content, ...skill }) => skill),
80
- };
80
+ const facts = await packageFacts(status.package);
81
+ if (facts.version)
82
+ status.packageVersion = facts.version;
83
+ status.listing = pluginListing(module.plugin, facts);
81
84
  status.packagePresent = true;
82
85
  status.state = 'available';
83
86
  delete status.reason;
@@ -143,7 +146,10 @@ export class Plugins {
143
146
  packagePresent: p.packagePresent === true,
144
147
  dependenciesSatisfied: p.packagePresent === true && p.state === 'available' && !(p.missing?.length),
145
148
  uiRenderable: p.state === 'available' && this.installedUi.some(entry => entry.plugin === p.id && (entry.extension || entry.packageVersion === p.version))
146
- })), operations: this.operationDeclarations(), actions, unavailableActions, components: [...descriptors.flatMap(p => (p.components ?? []).map(component => ({ ...component, pluginId: p.id }))),
149
+ })), operations: this.operationDeclarations(), actions, unavailableActions, components: [...descriptors.flatMap(p => (p.components ?? []).map(component => {
150
+ const installed = this.installedUi.find(e => e.component === component.id && e.version === component.version && e.plugin === p.id);
151
+ return { ...component, pluginId: p.id, ...(installed ? { uiPackage: installed.package, uiPackageVersion: installed.packageVersion } : {}) };
152
+ })),
147
153
  ...this.installedUi.filter(e => e.extension && this.modules[e.plugin]).map(e => ({ id: e.component, version: e.version, owner: e.package, pluginId: e.plugin, ui: e.entrySha256, bindings: e.extension.bindings, propsSchema: e.extension.propsSchema,
148
154
  presentation: { displayName: e.extension.displayName, description: e.extension.description }, uiPackage: e.package, uiPackageVersion: e.packageVersion }))], capabilities: [...descriptors.flatMap(p => p.capabilities), ...actions.flatMap(a => a.capabilities ?? [])], skills: descriptors.flatMap(p => p.skills ?? []), ...(this.modules.agent ? { agent: this.modules.agent.plugin.agent } : {}) };
149
155
  }
@@ -164,7 +170,7 @@ export class Plugins {
164
170
  const descriptor = this.descriptors().find(p => p.operations?.some(o => o.method === method));
165
171
  if (descriptor)
166
172
  return descriptor.id;
167
- const owner = method.startsWith('annotation.') ? 'annotations' : method.startsWith('console.') ? 'console' : method.startsWith('kernel.') ? 'kernel' : method.startsWith('skills.') ? 'skills' : method.startsWith('document.') ? 'documents' : method.startsWith('requirements.') ? 'requirements' : method === 'action.submit' || method === 'object.read' ? 'r' : method === 'data.plot' ? 'charts' : method.startsWith('data.') || method === 'selection.set' ? 'tables' : method.startsWith('media.') ? 'media' : method.startsWith('feedback.') || method.startsWith('context.') || method.startsWith('binding.') || method === 'session.grant' ? 'agent' : method.startsWith('resource.') || method === 'artifact.check' || method === 'action.export' ? 'files' : undefined;
173
+ const owner = method.startsWith('annotation.') ? 'annotations' : method.startsWith('console.') ? 'console' : method.startsWith('kernel.') ? 'kernel' : method.startsWith('figure.') ? 'figures' : method.startsWith('skills.') ? 'skills' : method.startsWith('document.') ? 'documents' : method.startsWith('requirements.') ? 'requirements' : method === 'action.submit' || method === 'object.read' ? 'r' : method === 'data.plot' ? 'charts' : method.startsWith('data.') || method === 'selection.set' ? 'tables' : method.startsWith('media.') ? 'media' : method.startsWith('feedback.') || method.startsWith('context.') || method.startsWith('binding.') || method === 'session.grant' ? 'agent' : method.startsWith('resource.') || method === 'artifact.check' || method === 'action.export' ? 'files' : undefined;
168
174
  return owner;
169
175
  }
170
176
  operationDeclarations() { return this.descriptors().flatMap(p => (p.operations ?? []).map(operation => ({ ...operation, owner: p.id }))); }
@@ -33,7 +33,7 @@ export function notesInstructions(conversationId, options = {}) {
33
33
  '- Run exploratory code there instead of Rscript -e, python3 -c or a throwaway script: find kernel.run with rho_index and rho_resolve once, then call it with rho_call, giving language "r" or "python", the code, a short purpose that says what the cell is for, and waitMs (for example 120000) to wait for it to finish.',
34
34
  '- What a cell defines stays for your next cells, so load data once and build on it. kernel.read shows the cells, including any the person ran, and the variables.',
35
35
  '- To look at a value without adding a cell, call kernel.inspect with its language and name: it gives a page of a table, a model\'s fit and coefficients, or a list\'s parts, with the code that reaches each part.',
36
- '- Keep the code your results rest on in script files as well, so they can be run again without the session.',
36
+ '- Keep the code your results rest on in script files as well, so the results can be rebuilt without the session. Run such a script in the session with kernel.run (source("analysis.R") in R, exec(open("analysis.py").read()) in Python), not with Rscript or python3 from a shell: a saved figure is readable mark by mark only while its file holds the bytes a Notebook cell saved, so a shell run that writes it again leaves a picture.',
37
37
  '- Before a multi-step analysis, read the Skill "rho.analysis-run" with skill.list and skill.read: it holds the practice that keeps a long analysis from losing work when a turn stops, written after five real reproductions ran here.',
38
38
  ] : []),
39
39
  ].join('\n');
@@ -122,6 +122,7 @@ export declare const runSchema: {
122
122
  export declare const responses: Record<string, any>;
123
123
  export declare const descriptions: {
124
124
  'document.read': string;
125
+ 'composition.delete': string;
125
126
  'document.edit': string;
126
127
  'feedback.submit': string;
127
128
  'feedback.read': string;
@@ -166,13 +167,18 @@ export declare const descriptions: {
166
167
  'kernel.events': string;
167
168
  'kernel.inspect': string;
168
169
  'kernel.plot': string;
170
+ 'figure.read': string;
171
+ 'figure.select': string;
169
172
  'kernel.interrupt': string;
170
173
  'kernel.stop': string;
171
174
  'conversation.delete': string;
172
175
  'conversation.settings.read': string;
173
176
  'conversation.settings.update': string;
174
177
  'conversation.settings.check': string;
178
+ 'conversation.agents.add': string;
179
+ 'conversation.agents.remove': string;
175
180
  'session.connect': string;
181
+ 'session.instructions': string;
176
182
  'conversation.read': string;
177
183
  'project.work.read': string;
178
184
  'resource.register': string;
@@ -193,6 +199,7 @@ export declare const descriptions: {
193
199
  'capability.index': string;
194
200
  'capability.resolve': string;
195
201
  'extension.list': string;
202
+ 'extension.market.change': string;
196
203
  'extension.describe': string;
197
204
  'extension.content': string;
198
205
  'extension.call': string;
package/dist/protocol.js CHANGED
@@ -9,6 +9,7 @@ import { objectSchema as obj, text, refSchema as ref } from '@rho-workspace/plug
9
9
  import { routeSchemas } from './task-routing-contract.js';
10
10
  import { baseProtocol } from './protocol-base.js';
11
11
  import { projectWorkSchema } from './work-contract.js';
12
+ import { uiLocationSchema, uiContextSchema } from './ui-context.js';
12
13
  const base = structuredClone(baseProtocol);
13
14
  const ownerSchema = (id, name) => ownerContracts.selected[id][name];
14
15
  const ownerSchemaRecord = (id, name) => ownerContracts.selected[id][name];
@@ -21,9 +22,10 @@ const array = (items) => ({ type: 'array', items });
21
22
  const response = (properties, required = Object.keys(properties)) => ({ type: 'object', properties, required, additionalProperties: true });
22
23
  const changeId = { type: 'string', pattern: '^[A-Za-z0-9._:@-]{1,128}$' };
23
24
  const identity = changeId;
24
- // 'acp' is a generic ACP agent: the operator names its command (--acp-command), so Rho
25
- // supports any ACP v1 implementation without a per-agent adapter.
26
- const agentBackend = { enum: ['claude-acp', 'codex-acp', 'acp'] };
25
+ // Claude Code and Codex through their adapters, and any other ACP v1 Agent without a per-agent
26
+ // adapter: 'acp' is the command named when the service started (--acp-command), 'acp:' and a name
27
+ // an Agent the person added in Rho (conversation.agents.add).
28
+ const agentBackend = { type: 'string', pattern: '^(claude-acp|codex-acp|acp|acp:[a-z0-9][a-z0-9-]{0,39})$' };
27
29
  const outsideAgent = { enum: ['claude-code', 'codex'] };
28
30
  const addedTo = response({ agent: outsideAgent, name: text, scope: { enum: ['folder', 'user'] }, at: text });
29
31
  const choiceText = { type: 'string', minLength: 1, maxLength: 512 }, choiceValue = { anyOf: [choiceText, { type: 'boolean' }] };
@@ -38,7 +40,7 @@ function scoped(input) {
38
40
  return { ...input, properties: { ...project, ...properties }, required: ['projectId', ...required.filter(name => name !== 'projectId')] };
39
41
  }
40
42
  const ownerOperations = {};
41
- for (const owner of ['documents', 'files', 'r', 'requirements', 'tables', 'media', 'charts', 'agent', 'console', 'kernel', 'skills']) {
43
+ for (const owner of ['documents', 'files', 'r', 'requirements', 'tables', 'media', 'charts', 'agent', 'console', 'kernel', 'figures', 'skills']) {
42
44
  for (const [method, contract] of Object.entries(ownerSchemaRecord(owner, 'operationContracts'))) {
43
45
  ownerOperations[method] = contract;
44
46
  }
@@ -71,8 +73,9 @@ export const operations = { ...base.operations, ...extensionOperations,
71
73
  'data.compare': obj({ ...project, left: ref, right: ref, options: obj({ leftKey: text, rightKey: text, leftNamespace: text, rightNamespace: text, mapping: { const: 'equal-string' }, leftField: text, rightField: text, join: { enum: ['left', 'inner', 'full'] } }) }),
72
74
  'media.describe': obj({ ...project, resource: ref }),
73
75
  // A conversation the person starts in Rho: their words, under a request identity they hold.
74
- 'conversation.start': obj({ ...project, changeId, text: { type: 'string', minLength: 1, maxLength: 16384 }, backend: agentBackend, config: choices, contexts: { ...array(digest), maxItems: 8 } }, ['projectId', 'changeId', 'text']),
75
- 'conversation.send': obj({ ...project, conversationId: changeId, changeId, text: { type: 'string', minLength: 1, maxLength: 16384 }, contexts: { ...array(digest), maxItems: 8 }, continuationOf: changeId }, ['projectId', 'conversationId', 'changeId', 'text']),
76
+ 'conversation.start': obj({ ...project, changeId, text: { type: 'string', minLength: 1, maxLength: 16384 }, backend: agentBackend, config: choices, contexts: { ...array(digest), maxItems: 8 }, uiContext: uiLocationSchema,
77
+ continue: obj({ session: { type: 'string', pattern: '^x-[a-f0-9]{64}$' }, copy: { type: 'boolean' } }, ['session']) }, ['projectId', 'changeId', 'text']),
78
+ 'conversation.send': obj({ ...project, conversationId: changeId, changeId, text: { type: 'string', minLength: 1, maxLength: 16384 }, contexts: { ...array(digest), maxItems: 8 }, uiContext: uiLocationSchema, continuationOf: changeId }, ['projectId', 'conversationId', 'changeId', 'text']),
76
79
  'conversation.permission': obj({ ...project, conversationId: changeId, turnId: changeId, permissionId: changeId, optionId: { type: 'string', minLength: 1, maxLength: 256 } }),
77
80
  'conversation.stop': obj({ ...project, conversationId: changeId, turnId: changeId }),
78
81
  'conversation.decide': obj({ ...project, conversationId: changeId, turnId: changeId, changeId, decision: { enum: ['accepted', 'needs-changes'] }, note: { type: 'string', maxLength: 4096 } }, ['projectId', 'conversationId', 'turnId', 'changeId', 'decision']),
@@ -84,10 +87,14 @@ export const operations = { ...base.operations, ...extensionOperations,
84
87
  // The person's Agent and its settings (model, effort, mode…) among those the Agent offers.
85
88
  'conversation.configure': obj({ ...project, conversationId: changeId, configId: choiceText, value: choiceValue }),
86
89
  'conversation.settings.read': obj(project),
87
- 'conversation.settings.update': obj({ ...project, expectedVersion: integer, backend: { anyOf: [agentBackend, { type: 'null' }] }, choices: obj({ 'claude-acp': choices, 'codex-acp': choices, 'acp': choices }, []), describe: obj({ enabled: { type: 'boolean' } }, ['enabled']) }, ['projectId', 'expectedVersion']),
90
+ 'conversation.settings.update': obj({ ...project, expectedVersion: integer, backend: { anyOf: [agentBackend, { type: 'null' }] }, choices: { type: 'object', maxProperties: 20, propertyNames: agentBackend, additionalProperties: choices }, describe: obj({ enabled: { type: 'boolean' } }, ['enabled']) }, ['projectId', 'expectedVersion']),
88
91
  'conversation.settings.check': obj({ ...project, backend: agentBackend }),
92
+ // An ACP Agent the person adds or removes for new conversations: the program (on PATH or a path) and its arguments.
93
+ 'conversation.agents.add': obj({ ...project, command: { type: 'array', minItems: 1, maxItems: 32, items: { type: 'string', minLength: 1, maxLength: 4096 } } }),
94
+ 'conversation.agents.remove': obj({ ...project, backend: { type: 'string', pattern: '^acp:[a-z0-9][a-z0-9-]{0,39}$' } }),
89
95
  // Claude Code or Codex running outside Rho, given this project and told how to start Rho's MCP server.
90
- 'session.connect': obj({ ...project, agent: outsideAgent })
96
+ 'session.connect': obj({ ...project, agent: outsideAgent }),
97
+ 'session.instructions': obj(project)
91
98
  };
92
99
  // Every owner-declared operation and response comes from the snapshot, scoped once.
93
100
  for (const [method, contract] of Object.entries(ownerOperations))
@@ -110,6 +117,7 @@ const applyOperation = { oneOf: [
110
117
  ] };
111
118
  operations['composition.apply'] = obj({ ...condition, operations: { ...array(applyOperation), minItems: 1, maxItems: 256 } });
112
119
  operations['composition.undo'] = obj(condition);
120
+ operations['composition.delete'] = obj(condition);
113
121
  operations['view.protection.set'] = obj({ ...condition, viewId: changeId, protected: { type: 'boolean' } });
114
122
  operations['view.close'] = obj({ ...operations['view.close'].properties, changeId }, operations['view.close'].required);
115
123
  const selectionScope = { oneOf: [obj({ kind: { const: 'caller' } }), obj({ kind: { enum: ['view', 'shared'] }, id: changeId })] };
@@ -200,14 +208,20 @@ const catalog = { ...contract.capabilityCatalogSchema };
200
208
  const conversationSummary = response({ conversationId: text, title: text, client: text, principal: { anyOf: [text, { type: 'null' }] }, createdAt: text, updatedAt: text, version: integer, turns: integer, state: { enum: ['idle', 'running', 'completed', 'failed', 'stopped', 'unknown'] }, lastAt: text,
201
209
  backend: text, agentTitle: { anyOf: [text, { type: 'null' }] }, adapter: { anyOf: [response({ name: text, version: text }, ['name']), { type: 'null' }] }, model: { anyOf: [text, { type: 'null' }] }, config: choices, turnLimit: { type: 'boolean' }, waitingPermission: { type: 'boolean' },
202
210
  usage: { anyOf: [response({ used: { type: 'number', minimum: 0 }, size: { type: 'number', minimum: 0 }, cost: { anyOf: [response({ amount: { type: 'number' }, currency: text }), { type: 'null' }] } }), { type: 'null' }] },
203
- costUsd: { anyOf: [{ type: 'number' }, { type: 'null' }] }, sessionId: { anyOf: [text, { type: 'null' }] }, trashedAt: { anyOf: [text, { type: 'null' }] } }, ['conversationId', 'title', 'client', 'principal', 'createdAt', 'updatedAt', 'version', 'turns', 'state', 'lastAt']);
211
+ costUsd: { anyOf: [{ type: 'number' }, { type: 'null' }] }, sessionId: { anyOf: [text, { type: 'null' }] }, trashedAt: { anyOf: [text, { type: 'null' }] },
212
+ origin: { anyOf: [response({ producer: text, where: text, copyOf: text }, ['producer', 'where']), { type: 'null' }] } }, ['conversationId', 'title', 'client', 'principal', 'createdAt', 'updatedAt', 'version', 'turns', 'state', 'lastAt']);
204
213
  const sessionObservation = ownerSchema('agent-session', 'sessionObservationSchema');
205
214
  // What one Agent offered, from a check or a conversation's first session, and the person's choices.
206
215
  const offered = response({ at: text, source: { enum: ['check', 'conversation'] }, adapter: { anyOf: [response({ name: text, version: text }, ['name']), { type: 'null' }] }, options: array(ownerSchema('agent-session', 'configOptionSchema')), omitted: integer });
207
216
  const agentSettings = response({ version: integer, updatedAt: { anyOf: [text, { type: 'null' }] }, backend: { anyOf: [agentBackend, { type: 'null' }] }, serviceBackend: { enum: ['claude-acp', 'codex-acp', 'claude-cli', 'acp'] },
208
- choices: response({ 'claude-acp': choices, 'codex-acp': choices, 'acp': choices }, []), offered: response({ 'claude-acp': offered, 'codex-acp': offered, 'acp': offered }, []), checking: array(agentBackend),
217
+ choices: { type: 'object', propertyNames: agentBackend, additionalProperties: choices }, offered: { type: 'object', propertyNames: agentBackend, additionalProperties: offered }, checking: array(agentBackend),
209
218
  // Whether a model describes the files a message writes; the facts are read either way.
210
- describe: response({ enabled: { type: 'boolean' } }) }, ['version', 'updatedAt', 'backend', 'serviceBackend', 'choices', 'offered', 'checking']);
219
+ describe: response({ enabled: { type: 'boolean' } }),
220
+ // Which Agents this service can start, without starting them.
221
+ agents: { type: 'object', propertyNames: agentBackend, additionalProperties: response({ found: { type: 'boolean' }, problem: text, name: { anyOf: [text, { type: 'null' }] } }, ['found']) },
222
+ // The ACP Agents the person added, and those Rho knows that are installed here and not added yet.
223
+ added: array(response({ backend: agentBackend, name: text, command: array(text), addedAt: text })),
224
+ installed: array(response({ id: text, name: text, command: array(text) })) }, ['version', 'updatedAt', 'backend', 'serviceBackend', 'choices', 'offered', 'checking']);
211
225
  // A tool step keeps its original identity, the Agent's bounded human title, its ACP
212
226
  // category and one identifying input (command, path, pattern or query), for a command the
213
227
  // Agent's own few words on what it is for (purpose), when it was
@@ -219,7 +233,7 @@ const conversationStep = response({ kind: { enum: ['text', 'tool', 'error'] }, a
219
233
  output: response({ text: { type: 'string' }, lines: integer, bytes: integer, cut: { type: 'boolean' } }, ['text', 'lines', 'bytes', 'cut']) }, ['kind', 'at']);
220
234
  const conversationTurn = response({ id: text, by: text, text: { type: 'string' }, sentAt: text, state: { enum: ['running', 'completed', 'failed', 'stopped', 'unknown'] }, startedAt: { anyOf: [text, { type: 'null' }] }, endedAt: { anyOf: [text, { type: 'null' }] }, model: { anyOf: [text, { type: 'null' }] },
221
235
  stop: { anyOf: [response({ requestedAt: text, by: text, reason: { enum: ['person', 'closing', 'wall-limit', 'output-limit', 'line-limit', 'identity'] } }), { type: 'null' }] },
222
- steps: array(conversationStep), omitted: integer, evidence: evidenceSchema, contexts: array(digest),
236
+ steps: array(conversationStep), omitted: integer, evidence: evidenceSchema, contexts: array(digest), uiContext: uiContextSchema,
223
237
  // What the files it wrote hold: facts read from each file, then a model's sentence naming the
224
238
  // version it read, the model, how it was given the file (its first lines, the image, or what
225
239
  // made it), and what the call cost and on what basis (Rho's describeLimits). The spend sums
@@ -261,6 +275,8 @@ const observationSnapshot = response({
261
275
  conversationId: { anyOf: [text, { type: 'null' }] },
262
276
  // The Agent outside Rho this grant was added to, when the person added it from Rho.
263
277
  added: { anyOf: [addedTo, { type: 'null' }] },
278
+ // The MCP client that used this grant, as it named itself (its initialize clientInfo).
279
+ client: { anyOf: [{ type: 'null' }, response({ name: text, version: text }, ['name'])] },
264
280
  last: { anyOf: [{ type: 'null' }, response({ at: text, method: text, outcome: { enum: ['returned', 'rejected'] }, operationId: text }, ['at', 'method', 'outcome'])] } }, ['principal', 'label', 'profile', 'methods', 'expiresAt', 'ended', 'last']) },
265
281
  // Conversations started in Rho, for the person at this machine; empty for anyone else.
266
282
  conversations: { type: 'array', maxItems: 64, items: conversationSummary },
@@ -332,7 +348,10 @@ export const responses = { ...base.responses, ...ownerResponses, ...extensionRes
332
348
  'conversation.settings.read': agentSettings,
333
349
  'conversation.settings.update': agentSettings,
334
350
  'conversation.settings.check': agentSettings,
351
+ 'conversation.agents.add': agentSettings,
352
+ 'conversation.agents.remove': agentSettings,
335
353
  'session.connect': response({ principal: text, label: text, methods: integer, granted: { type: 'boolean' }, added: addedTo }),
354
+ 'session.instructions': response({ request: text, prompt: text, command: array(text) }),
336
355
  'protocol.describe': response({ apiVersion: { const: '2.0' }, schemaVersion: { const: 2 }, revision: digest, entries: array(response({ method: text, summary: text })), next: { type: ['string', 'null'] }, complete: { type: 'boolean' }, contract: response({ method: text, inputSchema: { type: 'object' }, outputSchema: { type: 'object' }, definitions: { type: 'object' }, description: text }) }, ['apiVersion', 'schemaVersion', 'revision', 'entries', 'next', 'complete']),
337
356
  'capability.list': { ...catalog, properties: { ...catalog.properties, host: contract.cliSchema }, required: [...(catalog.required ?? []), 'host'] },
338
357
  'capability.configure': response({ version: positive, enabled: array(text), receipt: response({ changeId: text, author: text, version: positive, enabled: array(text), at: text }) }),
@@ -353,7 +372,7 @@ export const responses = { ...base.responses, ...ownerResponses, ...extensionRes
353
372
  'workspace.save': contract.workspaceSaveSchema,
354
373
  'workspace.restore': contract.workspaceRestoreSchema,
355
374
  'observation.snapshot': observationSnapshot,
356
- 'composition.apply': structuredComposition, 'composition.undo': structuredComposition, 'view.protection.set': structuredComposition,
375
+ 'composition.apply': structuredComposition, 'composition.undo': structuredComposition, 'composition.delete': structuredComposition, 'view.protection.set': structuredComposition,
357
376
  'composition.read': composition, 'view.close': composition,
358
377
  'project.openFile': response({ document: ownerResponses['document.open'], viewId: text, composition: structuredComposition }),
359
378
  'interaction.selection.read': contract.selectionChannelSchema,
@@ -381,6 +400,7 @@ export const descriptions = {
381
400
  'capability.index': 'Read a small role-narrowed index of currently authorized, enabled operations and immutable Skill/contract references.',
382
401
  'capability.resolve': 'Fetch selected relevant Skills with complete owner contracts, whole objects only. Pages pin catalog/bundle/caller; knownRefs only reuse currently available immutable bodies.', ...base.descriptions,
383
402
  'document.read': 'Read the shared acknowledged buffer, including unsaved text and its version.',
403
+ 'composition.delete': 'Delete an empty Studio workspace layout only, never the project directory, documents, resources, conversations or running sessions. Requires its observed expectedVersion and a stable changeId. The same request returns its original receipt without deleting a recreated workspace. Nonempty, conversation and active-task layouts are refused. The deleted layout and its history remain readable with composition.read and recoverable with composition.undo; it is omitted from active workspace lists. Recreating it uses a conditional composition.apply replacement at its retained version.',
384
404
  'document.edit': 'Apply conditional UTF-16 text edits to the shared buffer; keep the returned version.',
385
405
  'feedback.submit': 'Teacher browser only: freeze the original draft/anchor, prepare the committed message and retain one original-session delivery. Preserve submissionId across missing acknowledgements; stages are separate durable facts.',
386
406
  'feedback.read': 'Original business binding only: fetch one complete frozen feedback object using its original delivery/context and a stable fetchId. relatedDocumentId is a pointer: read its CURRENT version before conditional edit/save and preserve human changes. This owner-served receipt does not prove client return or understanding.',
@@ -395,7 +415,7 @@ export const descriptions = {
395
415
  'action.submit': 'Execute the explicitly selected project R script snapshot and requirements. Persist operationId before delivery; identical retries retain the original Core identity. Output directory is the first R argument, followed by explicit args or frozen resource paths. Artifact paths are output-relative; completion is run-directory-relative, returned as plan.completion, so an inventory can remain beside immutable producer artifacts.',
396
416
  'binding.activity.append': 'Registered native adapter only: retain original CLI tool and artifact-ready observations. This does not execute, validate, or present artifacts.',
397
417
  'task.routing.put': 'Control plane only: conditionally assign one task and business binding to this project without starting a native session.',
398
- 'conversation.start': 'Person at this machine only: start a conversation with the selected Claude Code or Codex Agent in this project. Rho selects the Agent plugin backend, attaches this project\'s MCP grant and records the message before sending it. Without backend or config, the person\'s saved Agent and settings are used and recorded with the conversation. A repeated changeId with the same text returns the original conversation and starts nothing.',
418
+ 'conversation.start': 'Person at this machine only: start a conversation with the selected Claude Code or Codex Agent in this project. Rho selects the Agent plugin backend, attaches this project\'s MCP grant and records the message before sending it. Without backend or config, the person\'s saved Agent and settings are used and recorded with the conversation. A repeated changeId with the same text returns the original conversation and starts nothing. With continue, the message continues an Agent session listed by agent.sessions.list (one started in a terminal, an editor or the Agent\'s own app) through that same session, or through a copy of it with copy; a session another live window holds open is refused unless copied.',
399
419
  'conversation.send': 'Person at this machine only: send the next message to a conversation started in Rho. A continuationOf links a new message to the latest confirmed Agent turn-limit stop; the original request is not resent. Refused while the previous message is being answered. A repeated changeId with the same input returns the original message.',
400
420
  'conversation.decide': 'Person at this machine only: say to use an answer, or that it needs changes and why. The decision holds for the file versions that message kept when it was given and says nothing of later versions. A new decision supersedes the one before; none is removed. Refused while the Agent is still answering.',
401
421
  'conversation.stop': 'Person at this machine only: ask the original Agent to stop answering one message. ACP cancellation is confirmed by the original prompt response; a lost connection keeps the outcome unknown. Legacy CLI conversations confirm process exit. Steps already taken stay done.',
@@ -425,13 +445,18 @@ export const descriptions = {
425
445
  'kernel.events': 'Who ran each cell of the live sessions and when, newest first, for the timeline: its language and place, how it ended and its purpose or first line. No code or output.',
426
446
  'kernel.inspect': 'One value of a live session, as its session describes it: what kind of thing it is (a table, a model, a vector, a list, a function or a value), short facts, and one page of its rows, elements or parts, each part with the code that reaches it. Never starts a session or waits for a cell: while a cell runs or waits the answer is busy. A reading that takes too long is interrupted.',
427
447
  'kernel.plot': 'One plot a cell kept, as PNG bytes.',
448
+ 'figure.read': 'What a plot a cell drew shows: its layers, mappings, panels and data, the rows behind marks, and the marks people and Agents selected.',
449
+ 'figure.select': 'Keeps a set of a figure\'s marks with a note, shown highlighted in the plot; the plot and its data do not change.',
428
450
  'kernel.interrupt': 'Interrupt one cell: a running cell stops and its session and values stay; a waiting cell is not run. A cell that has ended is returned unchanged.',
429
451
  'kernel.stop': 'Person at this machine only: end one live session. Its values are gone, the running cell ends with an error and waiting cells are not run. A session that has ended is returned unchanged.',
430
452
  'conversation.delete': 'Person at this machine only: delete Rho\'s record of a conversation in the trash. The Agent\'s own session files are not touched. Repeating the request that started it is then refused.',
431
453
  'conversation.settings.read': 'Person at this machine only: read the Agent and settings chosen for new conversations and what each Agent last offered. Reading starts nothing.',
432
454
  'conversation.settings.update': 'Person at this machine only: choose the Agent and its settings for new conversations, among the values that Agent last offered, and whether a model describes the files a message writes. A stale expectedVersion is refused.',
433
455
  'conversation.settings.check': 'Person at this machine only: start the chosen Agent in this project to read the settings it offers, then remove that session and end the process. No message is sent; one check per Agent runs at a time.',
456
+ 'conversation.agents.add': 'Person at this machine only: add an ACP Agent for new conversations in every project, given its program (on PATH or a path) and arguments, such as ["hermes", "acp"]. Rho starts it once in this project to read what it offers, then removes that session and ends the process; no message is sent. It is kept only when it answered. Adding the same command again checks it again.',
457
+ 'conversation.agents.remove': 'Person at this machine only: remove an added ACP Agent from the Agents new conversations can use. Its conversations stay and continue with the command they started with.',
434
458
  'session.connect': 'Person at this machine only: give Claude Code or Codex running outside Rho access to this project, then add Rho\'s MCP server with that Agent\'s own `mcp add` (Claude Code: for this project folder; Codex: in its user configuration). A grant whose connection still reaches this service is kept, so a connected session keeps working; a new grant the Agent refuses is removed. No message is sent and no Agent session starts.',
459
+ 'session.instructions': 'Person at this machine or operator only: read a prompt for the person\'s own MCP client to connect itself to this existing project. Returns local launcher paths, never credential contents. Reading or copying grants no access, edits no client configuration and starts no runtime.',
435
460
  'conversation.read': 'Read a conversation started in Rho: the person\'s messages, the Agent\'s visible text and tool steps, and how each message ended. Reading starts and resumes nothing.',
436
461
  'project.work.read': 'Person at this machine only: read linked Agent sessions, retained native activity identities, current referenced files and the shared notes document. Source cursors and resource versions remain independent. Does not start an Agent, collector or Host.',
437
462
  'resource.register': 'Declare scoped existing bytes and their type/schema at the supplied digest. This read and registration starts no runtime.',
@@ -456,4 +481,4 @@ export const errorKinds = base.errorKinds;
456
481
  export const errorSchema = base.definitions.error;
457
482
  export const errorEnvelope = obj({ apiVersion: { const: '2.0' }, ok: { const: false }, error: errorSchema });
458
483
  export const responseEnvelope = (method) => obj({ apiVersion: { const: '2.0' }, ok: { const: true }, value: responses[method] });
459
- export const protocol = { ...base, apiVersion: '2.0', schemaVersion: 2, transport: 'HTTP JSON POST /api/v2/{operation}; GET /api/v2/events', eventTypes: [...new Set([...base.eventTypes, 'project.sources.changed', 'capability.changed', 'view.closed', 'interaction.selection.changed', 'annotation.changed', 'context.changed', 'workspace.restored', 'task.routing.changed', 'binding.native.changed', 'feedback.changed', 'task.map.changed', 'feedback.changed', 'activity.changed', 'resource.changed', 'conversation.changed', 'console.changed', 'kernel.changed', 'skills.changed'])], operations, responses, descriptions: orderedDescriptions, definitions, errorEnvelope, status: 'One versioned contract. Retained operations stay observable through the current API; requests this contract does not accept receive a typed refusal rather than a second dialect.' };
484
+ export const protocol = { ...base, apiVersion: '2.0', schemaVersion: 2, transport: 'HTTP JSON POST /api/v2/{operation}; GET /api/v2/events', eventTypes: [...new Set([...base.eventTypes, 'project.sources.changed', 'capability.changed', 'view.closed', 'interaction.selection.changed', 'annotation.changed', 'context.changed', 'workspace.restored', 'task.routing.changed', 'binding.native.changed', 'feedback.changed', 'task.map.changed', 'feedback.changed', 'activity.changed', 'resource.changed', 'conversation.changed', 'console.changed', 'kernel.changed', 'figure.changed', 'skills.changed'])], operations, responses, descriptions: orderedDescriptions, definitions, errorEnvelope, status: 'One versioned contract. Retained operations stay observable through the current API; requests this contract does not accept receive a typed refusal rather than a second dialect.' };
package/dist/replay.js CHANGED
@@ -12,6 +12,10 @@ export function turnAt(turn, at) {
12
12
  const began = time(step.startedAt ?? step.at), last = time(step.at);
13
13
  if (!(began <= at))
14
14
  return [];
15
+ if (step.kind === 'tool' && step.outcomeAt && time(step.outcomeAt) > at && step.outcome !== 'requested') {
16
+ const { exitCode: _, output: __, failed: ___, ...rest } = step;
17
+ return [{ ...rest, outcome: 'unknown' }];
18
+ }
15
19
  if (last <= at)
16
20
  return [step];
17
21
  // A tool reported before the moment and finished after it was still going.