@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.
- package/dist/agent-connect.d.ts +42 -0
- package/dist/agent-connect.js +68 -0
- package/dist/answer-board.js +3 -3
- package/dist/bridge.js +6 -1
- package/dist/browser-access.d.ts +1 -0
- package/dist/browser-access.js +5 -3
- package/dist/capabilities.d.ts +21 -5
- package/dist/capabilities.js +6 -4
- package/dist/cli.js +113 -18
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +13 -3
- package/dist/connect-cli.d.ts +50 -0
- package/dist/connect-cli.js +128 -0
- package/dist/contract.generated.d.ts +1437 -381
- package/dist/contract.generated.js +1 -1
- package/dist/contracts/owners.generated.js +1160 -267
- package/dist/conversation-presentation.d.ts +9 -2
- package/dist/conversation-presentation.js +3 -1
- package/dist/conversations.d.ts +306 -7
- package/dist/conversations.js +208 -22
- package/dist/discovery.js +3 -3
- package/dist/extension-contract.d.ts +19 -0
- package/dist/extension-contract.js +15 -6
- package/dist/extension-markets.d.ts +74 -0
- package/dist/extension-markets.js +166 -0
- package/dist/extensions.d.ts +29 -1
- package/dist/extensions.js +13 -4
- package/dist/external-work.d.ts +22 -0
- package/dist/external-work.js +91 -0
- package/dist/plugin-cli.js +75 -3
- package/dist/plugin-inventory.d.ts +56 -0
- package/dist/plugin-inventory.js +41 -0
- package/dist/plugin-inventory.json +1581 -102
- package/dist/plugin-presentation.d.ts +105 -2
- package/dist/plugin-presentation.js +59 -5
- package/dist/plugins.d.ts +14 -3
- package/dist/plugins.js +12 -6
- package/dist/project-notes.js +1 -1
- package/dist/protocol.d.ts +7 -0
- package/dist/protocol.js +40 -15
- package/dist/replay.js +4 -0
- package/dist/response-contracts.js +26 -11
- package/dist/running.d.ts +10 -0
- package/dist/running.js +45 -0
- package/dist/server.d.ts +4 -10
- package/dist/server.js +140 -14
- package/dist/state.d.ts +8 -0
- package/dist/state.js +8 -0
- package/dist/studio-layout.js +3 -3
- package/dist/task-frame.d.ts +1 -1
- package/dist/timeline-events.d.ts +1 -0
- package/dist/timeline-events.js +8 -1
- package/dist/ui-context.d.ts +73 -0
- package/dist/ui-context.js +98 -0
- package/dist/ui-contract.d.ts +23 -0
- package/dist/web/.vite/manifest.json +135 -76
- package/dist/web/assets/answer-board-CeYS8k8y.js +1 -0
- package/dist/web/assets/choices-D8vGwsl2.js +1 -0
- package/dist/web/assets/{console-BSjLGe0T.js → console-Dyuo1q4N.js} +4 -4
- package/dist/web/assets/{dist-Tzoe_zm-.js → dist-CSiCyBml.js} +1 -1
- package/dist/web/assets/{dist-Cw8hRIlT.js → dist-D9FLb9Hm.js} +1 -1
- package/dist/web/assets/{dist-foH9IKc6.js → dist-FXbTBwML.js} +1 -1
- package/dist/web/assets/{dist-Buk0lwzI.js → dist-l3DUhbw5.js} +1 -1
- package/dist/web/assets/{explore-in-BVUy3ZW_.js → explore-in-BfSFGRh2.js} +1 -1
- package/dist/web/assets/graph-BU2_o5uA.js +2 -0
- package/dist/web/assets/index-C16mXVvk.css +1 -0
- package/dist/web/assets/index-CWg9XYXx.js +952 -0
- package/dist/web/assets/markdown.reader-Ct2-e2Zt.png +0 -0
- package/dist/web/assets/{notes-meaning-pVbbQUGP.js → notes-meaning-T2m5zQfv.js} +1 -1
- package/dist/web/assets/overview-PzfLJCo-.js +6 -0
- package/dist/web/assets/preview-BguEdsza.js +1 -0
- package/dist/web/assets/previews-pWNik-2g.js +3 -0
- package/dist/web/assets/{review-CMxQlQ-C.js → review-Dy_85lcH.js} +1 -1
- package/dist/web/assets/rho.editor-CCJeyHma.png +0 -0
- package/dist/web/assets/rho.flow-WkMj6fRS.png +0 -0
- package/dist/web/assets/rho.media-Cc6amodO.png +0 -0
- package/dist/web/assets/rho.notebook-6vsZKGDq.png +0 -0
- package/dist/web/assets/rho.object-BGoeXjte.png +0 -0
- package/dist/web/assets/rho.plots-yIiISCkb.png +0 -0
- package/dist/web/assets/rho.scatter-DwHxesCs.png +0 -0
- package/dist/web/assets/rho.servers-DKf_mnNq.png +0 -0
- package/dist/web/assets/rho.table-DWOGaWDa.png +0 -0
- package/dist/web/assets/rho.variables-CKcWczhQ.png +0 -0
- package/dist/web/assets/{skills-BFNSyrnD.js → skills-Dyz7mBlc.js} +1 -1
- package/dist/web/assets/{skills-work-BGPSnF_I.js → skills-work-BEGqlCkx.js} +1 -1
- package/dist/web/assets/studio-process-D-ykbC4D.js +2 -0
- package/dist/web/assets/timeline-events-DKTho9fU.js +1 -0
- package/dist/web/assets/work-turns-DSabCogR.js +1 -0
- package/dist/web/assets/{workspace-CWlkjLNx.js → workspace-DfjBL--r.js} +2 -2
- package/dist/web/composition.json +135 -80
- package/dist/web/index.html +3 -3
- package/dist/web/renderer.json +1 -1
- package/dist/web-assets.js +2 -2
- package/dist/work-graph.d.ts +11 -1
- package/dist/work-graph.js +16 -5
- package/dist/workspace.d.ts +2 -2
- package/dist/workspace.js +8 -7
- package/docs/FRONTEND.md +71 -21
- package/docs/HEADLESS.md +127 -24
- package/docs/THIRD-PARTY-PLUGINS.md +75 -3
- package/node_modules/@rho-workspace/plugin-agent/README.md +34 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.d.ts +32 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.js +54 -11
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +29 -3
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +11 -5
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +12 -1
- package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +8 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/index.js +3 -2
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.d.ts +13 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.js +13 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +16 -8
- package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.d.ts +70 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.js +538 -0
- package/node_modules/@rho-workspace/plugin-agent/package.json +2 -1
- package/node_modules/@rho-workspace/plugin-figures/LICENSE +661 -0
- package/node_modules/@rho-workspace/plugin-figures/README.md +191 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/index.d.ts +236 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/index.js +165 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/operations.d.ts +44 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/operations.js +39 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/presentation.d.ts +19 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/presentation.js +26 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/record.d.ts +188 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/record.js +143 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.js +68 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui.d.ts +56 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui.js +363 -0
- package/node_modules/@rho-workspace/plugin-figures/package.json +41 -0
- package/node_modules/@rho-workspace/plugin-figures/py/figures.py +856 -0
- package/node_modules/@rho-workspace/plugin-figures/r/figures.R +775 -0
- package/node_modules/@rho-workspace/plugin-kernel/README.md +27 -3
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.d.ts +12 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +8 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +61 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +125 -9
- package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.d.ts +14 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.js +9 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +23 -5
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +6 -4
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +44 -17
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +6 -0
- package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +144 -4
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +106 -1
- package/node_modules/@rho-workspace/plugin-tables/README.md +5 -0
- package/node_modules/@rho-workspace/plugin-tables/dist/index.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-tables/dist/index.js +19 -3
- package/node_modules/@rho-workspace/plugin-tables/dist/ui.js +1 -1
- package/node_modules/@rho-workspace/plugin-tables/package.json +1 -1
- package/node_modules/hono/dist/adapter/aws-lambda/handler.js +1 -1
- package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +1 -1
- package/node_modules/hono/dist/cjs/adapter/bun/serve-static.js +1 -1
- package/node_modules/hono/dist/cjs/adapter/bun/websocket.js +1 -1
- package/node_modules/hono/dist/cjs/client/fetch-result-please.js +1 -1
- package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +2 -1
- package/node_modules/hono/dist/cjs/helper/ssg/ssg.js +5 -3
- package/node_modules/hono/dist/cjs/jsx/base.js +49 -30
- package/node_modules/hono/dist/cjs/jsx/components.js +2 -2
- package/node_modules/hono/dist/cjs/jsx/constants.js +2 -0
- package/node_modules/hono/dist/cjs/jsx/jsx-runtime.js +13 -17
- package/node_modules/hono/dist/cjs/middleware/etag/index.js +3 -1
- package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +1 -0
- package/node_modules/hono/dist/cjs/middleware/timing/timing.js +4 -3
- package/node_modules/hono/dist/cjs/utils/cookie.js +2 -2
- package/node_modules/hono/dist/cjs/utils/mime.js +1 -1
- package/node_modules/hono/dist/cjs/utils/url.js +1 -1
- package/node_modules/hono/dist/client/fetch-result-please.js +1 -1
- package/node_modules/hono/dist/helper/accepts/accepts.js +2 -1
- package/node_modules/hono/dist/helper/ssg/ssg.js +5 -3
- package/node_modules/hono/dist/jsx/base.js +49 -32
- package/node_modules/hono/dist/jsx/components.js +2 -2
- package/node_modules/hono/dist/jsx/constants.js +2 -1
- package/node_modules/hono/dist/jsx/jsx-runtime.js +15 -19
- package/node_modules/hono/dist/middleware/etag/index.js +3 -1
- package/node_modules/hono/dist/middleware/language/language.js +1 -1
- package/node_modules/hono/dist/middleware/pretty-json/index.js +1 -0
- package/node_modules/hono/dist/middleware/timing/timing.js +4 -3
- package/node_modules/hono/dist/utils/cookie.js +2 -2
- package/node_modules/hono/dist/utils/mime.js +1 -1
- package/node_modules/hono/dist/utils/url.js +1 -1
- package/node_modules/hono/package.json +1 -1
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +3 -2
- package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
- package/node_modules/jose/dist/webapi/jws/general/verify.js +2 -8
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +11 -8
- package/node_modules/jose/dist/webapi/lib/key_management.js +0 -1
- package/node_modules/jose/package.json +1 -1
- package/package.json +6 -4
- package/protocol/index.json +106 -64
- package/protocol/operations/agent.sessions.list.json +57 -3
- package/protocol/operations/agent.sessions.read.json +61 -4
- package/protocol/operations/capability.list.json +382 -6
- package/protocol/operations/composition.apply.json +10 -2
- package/protocol/operations/composition.delete.json +845 -0
- package/protocol/operations/composition.read.json +10 -2
- package/protocol/operations/composition.undo.json +10 -2
- package/protocol/operations/conversation.agents.add.json +346 -0
- package/protocol/operations/conversation.agents.remove.json +340 -0
- package/protocol/operations/conversation.configure.json +29 -0
- package/protocol/operations/conversation.decide.json +225 -0
- package/protocol/operations/conversation.list.json +29 -0
- package/protocol/operations/conversation.permission.json +225 -0
- package/protocol/operations/conversation.read.json +225 -0
- package/protocol/operations/conversation.restore.json +29 -0
- package/protocol/operations/conversation.send.json +294 -0
- package/protocol/operations/conversation.settings.check.json +226 -416
- package/protocol/operations/conversation.settings.read.json +224 -411
- package/protocol/operations/conversation.settings.update.json +248 -475
- package/protocol/operations/conversation.start.json +313 -6
- package/protocol/operations/conversation.stop.json +225 -0
- package/protocol/operations/conversation.trash.json +29 -0
- package/protocol/operations/extension.describe.json +39 -0
- package/protocol/operations/extension.install.json +62 -0
- package/protocol/operations/extension.list.json +164 -1
- package/protocol/operations/extension.market.change.json +210 -0
- package/protocol/operations/figure.read.json +265 -0
- package/protocol/operations/figure.select.json +157 -0
- package/protocol/operations/kernel.interrupt.json +119 -0
- package/protocol/operations/kernel.read.json +119 -0
- package/protocol/operations/kernel.run.json +119 -0
- package/protocol/operations/observation.snapshot.json +445 -8
- package/protocol/operations/project.openFile.json +10 -2
- package/protocol/operations/session.grant.json +4 -0
- package/protocol/operations/session.instructions.json +43 -0
- package/protocol/operations/shared.json +1 -0
- package/protocol/operations/view.close.json +10 -2
- package/protocol/operations/view.protection.set.json +10 -2
- package/vendor/composition.json +174 -109
- package/vendor/rho-workspace-plugin-agent-0.4.33.tgz +0 -0
- package/vendor/rho-workspace-plugin-figures-0.5.1.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.13.tgz +0 -0
- package/vendor/rho-workspace-plugin-tables-0.5.2.tgz +0 -0
- package/web/theme.css +17 -0
- package/dist/web/assets/answer-board-CBkGbSLD.js +0 -1
- package/dist/web/assets/canvas-D8rDdfDz.js +0 -1
- package/dist/web/assets/choices-b0XhrGQV.js +0 -1
- package/dist/web/assets/graph-1OdIJ9l7.js +0 -2
- package/dist/web/assets/index-CsC3sTRq.css +0 -1
- package/dist/web/assets/index-CxmItXqa.js +0 -891
- package/dist/web/assets/overview-B9UtVJ32.js +0 -6
- package/dist/web/assets/preview-CksD_5KS.js +0 -1
- package/dist/web/assets/studio-process-CsRoTHOh.js +0 -2
- package/dist/web/assets/timeline-events-BbuuFWHV.js +0 -1
- package/dist/web/assets/work-turns-BvJlIMmA.js +0 -1
- package/vendor/rho-workspace-plugin-agent-0.4.29.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
- package/vendor/rho-workspace-plugin-tables-0.5.0.tgz +0 -0
|
@@ -164,29 +164,44 @@ export const pluginPresentationSchema = { ...entryPresentationSchema, properties
|
|
|
164
164
|
examplePrompts: { type: 'array', items: text, maxItems: 16 }, viewNote: text, developer: text, license: text,
|
|
165
165
|
links: { type: 'array', maxItems: 16, items: obj({ label: text, url: text }, ['label', 'url']) },
|
|
166
166
|
dependencies: { type: 'array', maxItems: 16, items: obj({ pluginId: text, purpose: text }, ['pluginId', 'purpose']) }, uses: text } };
|
|
167
|
+
const skillListingSchema = { ...obj({
|
|
168
|
+
id: text, version: text, digest, presentation: entryPresentationSchema, intent: text,
|
|
169
|
+
requires: { type: 'array', items: text, maxItems: 64 },
|
|
170
|
+
example: obj({ method: text, input: { type: 'object' } }),
|
|
171
|
+
failureRecipes: { type: 'array', maxItems: 32, items: obj({ kind: text, message: text, next: text }) },
|
|
172
|
+
verify: obj({ command: text, description: text })
|
|
173
|
+
}, ['id', 'version', 'digest']), additionalProperties: true };
|
|
167
174
|
export const capabilityCatalogSchema = { ...obj({
|
|
168
175
|
version: text,
|
|
169
176
|
configuration: obj({ version: integer, enabled: { type: 'array', items: text, maxItems: 32 }, retainedReceipts: integer, retainedFromVersion: positive, maxReceipts: integer }, ['version', 'enabled']),
|
|
170
|
-
plugins: { type: 'array', maxItems: 16, items: { ...obj({ id: text, package: text, version: text, requires: { type: 'array', items: text }, selected: { type: 'boolean' }, packagePresent: { type: 'boolean' }, projectEnabled: { type: 'boolean' }, dependenciesSatisfied: { type: 'boolean' }, uiRenderable: { type: 'boolean' }, state: { enum: ['available', 'unavailable'] }, reason: text, missing: { type: 'array', items: text },
|
|
177
|
+
plugins: { type: 'array', maxItems: 16, items: { ...obj({ id: text, package: text, version: text, packageVersion: text, requires: { type: 'array', items: text }, selected: { type: 'boolean' }, packagePresent: { type: 'boolean' }, projectEnabled: { type: 'boolean' }, dependenciesSatisfied: { type: 'boolean' }, uiRenderable: { type: 'boolean' }, state: { enum: ['available', 'unavailable'] }, reason: text, missing: { type: 'array', items: text },
|
|
171
178
|
// The plugin's read whose events (who did what, when) the timeline draws.
|
|
172
179
|
timeline: text,
|
|
173
180
|
// The catalog also carries the owner's own presentation/listing hints and its
|
|
174
181
|
// bounded native availability; those are owner vocabulary, so they stay open.
|
|
175
182
|
// A reduced installation lists fewer entries, so each listing section is optional.
|
|
176
183
|
listing: obj({
|
|
177
|
-
skills: { type: 'array', maxItems: 64, items:
|
|
178
|
-
|
|
179
|
-
|
|
184
|
+
skills: { type: 'array', maxItems: 64, items: skillListingSchema },
|
|
185
|
+
operations: { type: 'array', maxItems: 64, items: obj({ method: text, version: text, effect: { enum: ['read', 'metadata', 'document', 'file', 'native'] }, requires: { type: 'array', items: text }, presentation: entryPresentationSchema }, ['method', 'version', 'effect', 'requires']) },
|
|
186
|
+
actions: { type: 'array', maxItems: 64, items: { ...obj({ id: text, version: text, requires: { type: 'array', items: text }, presentation: entryPresentationSchema }, []), additionalProperties: true } },
|
|
187
|
+
views: { type: 'array', maxItems: 64, items: { ...obj({ id: text, version: text, ui: text, bindings: { type: 'object', additionalProperties: text }, presentation: entryPresentationSchema }, ['id', 'version', 'ui']), additionalProperties: true } },
|
|
188
|
+
// Command-line entries the installed package links, read from its own manifest.
|
|
189
|
+
cli: { type: 'array', maxItems: 16, items: text },
|
|
190
|
+
mcp: obj({ entry: text, transport: text, tools: { type: 'array', maxItems: 32, items: obj({ name: text, description: text }) } }),
|
|
191
|
+
claudeCode: obj({
|
|
192
|
+
skills: { type: 'array', maxItems: 64, items: obj({ skill: text, name: text, description: text }) },
|
|
193
|
+
hooks: { type: 'array', maxItems: 64, items: obj({ id: text, event: text, matcher: text, entry: text, timeoutSeconds: positive }, ['event', 'matcher', 'entry']) }
|
|
194
|
+
}, [])
|
|
180
195
|
}, []),
|
|
181
196
|
presentation: pluginPresentationSchema,
|
|
182
197
|
nativeAvailability: nativeAvailabilitySchema,
|
|
183
198
|
}, ['id', 'package', 'selected', 'packagePresent', 'state']), additionalProperties: true } },
|
|
184
|
-
operations: { type: 'array', maxItems: 256, items: { ...obj({ method: text, owner: text, packagePresent: { type: 'boolean' }, projectEnabled: { type: 'boolean' }, dependenciesSatisfied: { type: 'boolean' }, authorized: { type: 'boolean' }, state: { enum: ['available', 'unavailable'] }, presentation: entryPresentationSchema }, ['method', 'authorized', 'state']), additionalProperties: true } },
|
|
199
|
+
operations: { type: 'array', maxItems: 256, items: { ...obj({ method: text, owner: text, effect: text, inputSchema: { type: 'object' }, outputSchema: { type: 'object' }, packagePresent: { type: 'boolean' }, projectEnabled: { type: 'boolean' }, dependenciesSatisfied: { type: 'boolean' }, authorized: { type: 'boolean' }, state: { enum: ['available', 'unavailable'] }, presentation: entryPresentationSchema }, ['method', 'authorized', 'state']), additionalProperties: true } },
|
|
185
200
|
actions: { type: 'array', maxItems: 64, items: { ...obj({ id: text, version: text, owner: text, pluginId: text, requires: { type: 'array', items: text }, inputSchema: { type: 'object' }, presentation: entryPresentationSchema }, ['id', 'version']), additionalProperties: true } },
|
|
186
201
|
unavailableActions: { type: 'array', maxItems: 64, items: { ...obj({ id: text, version: text, owner: text, pluginId: text, requires: { type: 'array', items: text }, inputSchema: { type: 'object' }, presentation: entryPresentationSchema, state: { const: 'unavailable' }, missing: { type: 'array', items: text } }, ['id', 'version', 'missing']), additionalProperties: true } },
|
|
187
|
-
components: { type: 'array', maxItems: 64, items: { ...obj({ id: text, version: text, owner: text, pluginId: text, ui: text, bindings: { type: 'object', additionalProperties: text }, propsSchema: { type: 'object' }, presentation: entryPresentationSchema }, ['id', 'version', 'ui', 'bindings']), additionalProperties: true } },
|
|
202
|
+
components: { type: 'array', maxItems: 64, items: { ...obj({ id: text, version: text, owner: text, pluginId: text, ui: text, uiPackage: text, uiPackageVersion: text, bindings: { type: 'object', additionalProperties: text }, propsSchema: { type: 'object' }, presentation: entryPresentationSchema }, ['id', 'version', 'ui', 'bindings']), additionalProperties: true } },
|
|
188
203
|
capabilities: { type: 'array', items: text },
|
|
189
|
-
skills: { type: 'array', maxItems: 64, items: { ...
|
|
204
|
+
skills: { type: 'array', maxItems: 64, items: { ...skillListingSchema, properties: { ...skillListingSchema.properties, owner: text, content: { type: 'string' } } } },
|
|
190
205
|
// The Agent owner contributes its own context description only when it is loaded.
|
|
191
206
|
agent: { type: 'object' }
|
|
192
207
|
}, ['version', 'configuration', 'plugins', 'operations', 'actions', 'components']), additionalProperties: true };
|
|
@@ -198,8 +213,8 @@ export const nodeSchema = { oneOf: [
|
|
|
198
213
|
obj({ id: identity, kind: { const: 'group' }, children: { type: 'array', items: identity, maxItems: 256 }, presentation: groupPresentation }, ['id', 'kind', 'children']),
|
|
199
214
|
{ ...viewSchema, properties: { ...viewSchema.properties, kind: { const: 'view' } }, required: [...viewSchema.required, 'kind'] }
|
|
200
215
|
] };
|
|
201
|
-
export const compositionReceiptSchema = obj({ changeId: identity, author: text, version: positive, requestDigest: digest, at: timestamp, kind: { enum: ['apply', 'undo', 'protect'] } });
|
|
202
|
-
const historyEntry = obj({ version: positive, snapshot: obj({ rootId: identity, nodes: { type: 'array', items: nodeSchema, maxItems: 256 } }) }, ['version', 'snapshot']);
|
|
216
|
+
export const compositionReceiptSchema = obj({ changeId: identity, author: text, version: positive, requestDigest: digest, at: timestamp, kind: { enum: ['apply', 'undo', 'protect', 'delete'] } });
|
|
217
|
+
const historyEntry = obj({ version: positive, snapshot: obj({ rootId: identity, nodes: { type: 'array', items: nodeSchema, maxItems: 256 }, deleted: { type: 'boolean' } }, ['rootId', 'nodes']) }, ['version', 'snapshot']);
|
|
203
218
|
export const witnessSchema = { ...obj({
|
|
204
219
|
witnessId: digest, projectId: identity, taskId: identity, compositionId: identity, compositionVersion: positive,
|
|
205
220
|
sourceEvent: { type: 'object', additionalProperties: true }, policyRef: digest,
|
|
@@ -208,13 +223,13 @@ export const witnessSchema = { ...obj({
|
|
|
208
223
|
at: timestamp
|
|
209
224
|
}, ['witnessId', 'projectId', 'taskId', 'compositionId', 'compositionVersion', 'views']) };
|
|
210
225
|
export const structuredCompositionSchema = { ...obj({
|
|
211
|
-
format: { const: 'rho.composition/1' }, compositionId: identity, rootId: identity,
|
|
226
|
+
format: { const: 'rho.composition/1' }, compositionId: identity, rootId: identity, deleted: { type: 'boolean' },
|
|
212
227
|
nodes: { type: 'array', items: nodeSchema, maxItems: 256 },
|
|
213
228
|
views: { type: 'array', items: viewSchema, maxItems: 32 },
|
|
214
229
|
availability: { type: 'object', additionalProperties: availabilitySchema },
|
|
215
230
|
version: positive, author: text, acknowledgedAt: timestamp,
|
|
216
231
|
history: { type: 'array', items: historyEntry, maxItems: 256 },
|
|
217
|
-
receipts: { type: 'array', items: { ...obj({ changeId: identity, requestDigest: digest, version: positive, author: text, at: timestamp, kind: { enum: ['apply', 'undo', 'protect', 'close'] } }, ['changeId', 'author', 'version', 'at']) }, maxItems: 256 },
|
|
232
|
+
receipts: { type: 'array', items: { ...obj({ changeId: identity, requestDigest: digest, version: positive, author: text, at: timestamp, kind: { enum: ['apply', 'undo', 'protect', 'close', 'delete'] } }, ['changeId', 'author', 'version', 'at']) }, maxItems: 256 },
|
|
218
233
|
retainedFromVersion: positive, undoRetainedFromVersion: positive,
|
|
219
234
|
receipt: compositionReceiptSchema,
|
|
220
235
|
witness: { anyOf: [witnessSchema, { type: 'null' }] }, witnessState: { enum: ['retained', 'expired', 'pending'] },
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Running = {
|
|
2
|
+
stateDirectory: string;
|
|
3
|
+
url: string;
|
|
4
|
+
pid: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const runningDirectory: (env?: NodeJS.ProcessEnv) => string;
|
|
7
|
+
export declare function announce(note: Running, directory?: string): Promise<void>;
|
|
8
|
+
export declare function withdraw(stateDirectory: string, pid?: number, directory?: string): Promise<void>;
|
|
9
|
+
export declare function running(directory?: string): Promise<Running[]>;
|
|
10
|
+
export declare function announced(stateDirectory: string, directory?: string): Promise<boolean>;
|
package/dist/running.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
export const runningDirectory = (env = process.env) => env.RHO_RUNNING_DIR || path.join(os.homedir(), 'Rho', 'running');
|
|
6
|
+
const noteFile = (stateDirectory, directory) => path.join(directory, createHash('sha256').update(path.resolve(stateDirectory)).digest('hex').slice(0, 16) + '.json');
|
|
7
|
+
async function read(file) {
|
|
8
|
+
try {
|
|
9
|
+
const note = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
10
|
+
return typeof note?.stateDirectory === 'string' && typeof note?.url === 'string' && Number.isInteger(note?.pid) ? note : null;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const alive = (pid) => {
|
|
17
|
+
try {
|
|
18
|
+
process.kill(pid, 0);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return error.code === 'EPERM';
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
export async function announce(note, directory = runningDirectory()) {
|
|
26
|
+
await fs.mkdir(directory, { recursive: true, mode: 0o700 });
|
|
27
|
+
const file = noteFile(note.stateDirectory, directory), partial = `${file}.${process.pid}.tmp`;
|
|
28
|
+
await fs.writeFile(partial, JSON.stringify({ ...note, stateDirectory: path.resolve(note.stateDirectory) }) + '\n', { mode: 0o600 });
|
|
29
|
+
await fs.rename(partial, file);
|
|
30
|
+
}
|
|
31
|
+
// Only the Rho that wrote a note removes it, so a later start on the same state keeps its own.
|
|
32
|
+
export async function withdraw(stateDirectory, pid = process.pid, directory = runningDirectory()) {
|
|
33
|
+
const file = noteFile(stateDirectory, directory);
|
|
34
|
+
if ((await read(file))?.pid === pid)
|
|
35
|
+
await fs.rm(file, { force: true });
|
|
36
|
+
}
|
|
37
|
+
export async function running(directory = runningDirectory()) {
|
|
38
|
+
const names = (await fs.readdir(directory).catch(() => [])).filter(name => name.endsWith('.json'));
|
|
39
|
+
const notes = await Promise.all(names.map(name => read(path.join(directory, name))));
|
|
40
|
+
return notes.filter((note) => !!note && alive(note.pid));
|
|
41
|
+
}
|
|
42
|
+
export async function announced(stateDirectory, directory = runningDirectory()) {
|
|
43
|
+
const note = await read(noteFile(stateDirectory, directory));
|
|
44
|
+
return !!note && note.stateDirectory === path.resolve(stateDirectory) && alive(note.pid);
|
|
45
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -116,6 +116,7 @@ export declare class RhoService {
|
|
|
116
116
|
conversations: Conversations;
|
|
117
117
|
private consoles;
|
|
118
118
|
private kernels;
|
|
119
|
+
private figures;
|
|
119
120
|
private skillHosts;
|
|
120
121
|
private feedback;
|
|
121
122
|
configureFeedback(projectId: string, options?: {
|
|
@@ -168,27 +169,20 @@ export declare class RhoService {
|
|
|
168
169
|
agentConnection(projectId: string, externalSessionId: string, file: string, methods?: string[], pluginFilter?: string[], profile?: 'layered-business' | 'layered-presentation', who?: {
|
|
169
170
|
label?: string;
|
|
170
171
|
conversationId?: string;
|
|
171
|
-
}): Promise<
|
|
172
|
-
apiVersion: "2.0";
|
|
173
|
-
pluginFilter?: ("media" | "r" | "agent" | "files" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel")[] | undefined;
|
|
174
|
-
profile?: "layered-business" | "layered-presentation" | undefined;
|
|
175
|
-
url: string;
|
|
176
|
-
token: string;
|
|
177
|
-
projectId: string;
|
|
178
|
-
externalSessionId: string;
|
|
179
|
-
methods: string[];
|
|
180
|
-
}>;
|
|
172
|
+
}): Promise<any>;
|
|
181
173
|
private authenticate;
|
|
182
174
|
private handle;
|
|
183
175
|
private readBody;
|
|
184
176
|
private json;
|
|
185
177
|
private serial;
|
|
186
178
|
private recordActivity;
|
|
179
|
+
private agentSessions;
|
|
187
180
|
private projectActivity;
|
|
188
181
|
private agentSummaries;
|
|
189
182
|
private dataDir;
|
|
190
183
|
private consoleFor;
|
|
191
184
|
private kernelFor;
|
|
185
|
+
private figuresFor;
|
|
192
186
|
private skillsFor;
|
|
193
187
|
dispatch(request: OperationRequest, principal: Principal, contractRef?: string): Promise<unknown>;
|
|
194
188
|
private events;
|
package/dist/server.js
CHANGED
|
@@ -19,7 +19,7 @@ import { Interactions } from './interactions.js';
|
|
|
19
19
|
import { TaskMap } from './task-map.js';
|
|
20
20
|
import { Capabilities } from './capabilities.js';
|
|
21
21
|
import { BrowserAccess, localBrowserId, localBrowserPrincipal } from './browser-access.js';
|
|
22
|
-
import { addToAgent, connectLimits, mcpCommand, mcpName, outsideLabel } from './agent-connect.js';
|
|
22
|
+
import { addToAgent, connectionInstructions, connectLimits, mcpCommand, mcpName, outsideLabel } from './agent-connect.js';
|
|
23
23
|
import { NativeFolderPicker, nativeFolderLimits } from './native-folders.js';
|
|
24
24
|
import { homedir } from 'node:os';
|
|
25
25
|
import { serveWeb, servePageFrame, pageFramePath, extensionFramePath, webRoot, installedWebEntries } from './web-assets.js';
|
|
@@ -34,6 +34,7 @@ import { exitWords, stopState } from './execution-facts.js';
|
|
|
34
34
|
import { exportRun } from './run-export.js';
|
|
35
35
|
import { ownerContracts } from './contracts/owners.generated.js';
|
|
36
36
|
import { Conversations } from './conversations.js';
|
|
37
|
+
import { captureUiContext } from './ui-context.js';
|
|
37
38
|
import { describeLimits } from './file-describer.js';
|
|
38
39
|
import { projectWork } from './project-work.js';
|
|
39
40
|
export const serviceLimits = { maxRequestBytes: 1024 * 1024, maxEventBytes: 65536, eventRetention: 256, maxSubscriptions: 8, maxQueue: 32, maxContexts: 128, maxBindings: 64, maxActivities: 256, maxContextBytes: 1024 * 1024, maxRetainedMetadataBytes: 32 * 1024 * 1024 };
|
|
@@ -178,6 +179,20 @@ function explainFailure(e) {
|
|
|
178
179
|
default: return ['storage', 'The operation could not be confirmed; retain the original identity and inspect its state.', { code }];
|
|
179
180
|
}
|
|
180
181
|
}
|
|
182
|
+
// `x-rho-client: name/version`, each part URI-encoded: the MCP client's own name for itself.
|
|
183
|
+
// Printable text only, bounded; anything else is ignored rather than shown.
|
|
184
|
+
function clientOf(header) {
|
|
185
|
+
if (typeof header !== 'string' || header.length > 300)
|
|
186
|
+
return null;
|
|
187
|
+
try {
|
|
188
|
+
const [name, version] = header.split('/').map(decodeURIComponent);
|
|
189
|
+
const ok = (value, max) => value !== undefined && value.length > 0 && value.length <= max && /^[\x20-\x7e]+$/.test(value);
|
|
190
|
+
return ok(name, 128) ? { name, ...(ok(version, 64) ? { version } : {}) } : null;
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
181
196
|
export class RhoService {
|
|
182
197
|
config;
|
|
183
198
|
extensions;
|
|
@@ -203,6 +218,7 @@ export class RhoService {
|
|
|
203
218
|
conversations;
|
|
204
219
|
consoles = new Map();
|
|
205
220
|
kernels = new Map();
|
|
221
|
+
figures = new Map();
|
|
206
222
|
skillHosts = new Map();
|
|
207
223
|
feedback(project, owners = this.plugins.project(project.root, project.id)) {
|
|
208
224
|
const agent = owners.need('agent');
|
|
@@ -394,6 +410,9 @@ export class RhoService {
|
|
|
394
410
|
},
|
|
395
411
|
directory: project => this.dataDir(project),
|
|
396
412
|
notebook: () => this.plugins.availableIds().includes('kernel'),
|
|
413
|
+
// start/send already holds the service's project serializer while this
|
|
414
|
+
// observation is frozen, before the conversation is recorded or launched.
|
|
415
|
+
uiContext: (project, location, limits) => captureUiContext(project, location, id => new Workspace(project, this.plugins.project(project.root, project.id), this.config.workspace).read(id), limits, this.plugins.optional('figures') ? (ref, selectionId, rows) => this.figuresFor(project).brief(ref, selectionId, rows) : undefined),
|
|
397
416
|
context: async (project, id) => {
|
|
398
417
|
const context = await this.plugins.project(project.root, project.id).need('agent').peekContext(id);
|
|
399
418
|
requireThat(context.projectId === project.id, 'scope', 'The context belongs to another project.');
|
|
@@ -452,6 +471,7 @@ export class RhoService {
|
|
|
452
471
|
return [await writeClaudePlugin(path.join(this.state.directory, 'claude-plugins'), { name: 'rho', version: selectedPluginVersions.agent, description: 'Skills and hooks of the plugins selected in Rho.', ...declared })];
|
|
453
472
|
},
|
|
454
473
|
changed: async (project, conversationId) => { void this.serial(project.id, () => this.emit(project, 'conversation.changed', conversationId, localBrowserId)).catch(() => { }); },
|
|
474
|
+
nativeSession: async (project, producer, sessionId) => (await import('@rho-workspace/plugin-agent/native-sessions')).nativeSession(project.root, producer, sessionId),
|
|
455
475
|
// The person's own Claude Code when one is named, with no tools and in an empty folder of
|
|
456
476
|
// Rho's state, so it reads only what it is given and loads no project instructions. What
|
|
457
477
|
// a call cost comes from Claude Code when it knows the model's price, else from models.dev.
|
|
@@ -527,7 +547,8 @@ export class RhoService {
|
|
|
527
547
|
await listen(0);
|
|
528
548
|
}
|
|
529
549
|
this.url = 'http://127.0.0.1:' + this.server.address().port;
|
|
530
|
-
|
|
550
|
+
// The process that serves it, so rho --stop can say when it has ended and not only closed.
|
|
551
|
+
await atomicJson(connectionFile, { url: this.url, token: this.adminToken, apiVersion: '2.0', pid: process.pid });
|
|
531
552
|
// An Agent's connection holds the address it was given. When the address changed, each one
|
|
532
553
|
// that named the earlier address follows, so the Agent's next session reaches this service
|
|
533
554
|
// with the grant it already has. Its token and methods are unchanged.
|
|
@@ -570,11 +591,22 @@ export class RhoService {
|
|
|
570
591
|
requireThat(selected.every(m => !extensionManagement.includes(m) && !!operations[m] && !m.startsWith('agent.sessions.') && !m.startsWith('session.') && !m.startsWith('conversation.') && (!m.startsWith('project.') || m === 'project.entries') && !['task.routing.put', 'console.run', 'console.stop', 'kernel.stop'].includes(m)), 'invalid', 'Unknown or operator-only Agent capability.');
|
|
571
592
|
requireThat(await Promise.all(selected.map(method => capabilities.isAvailable(method, state))).then(values => values.every(Boolean)), 'unavailable', 'A requested Agent operation has no selected provider.');
|
|
572
593
|
requireThat(!profile || new Set(['capability.index', 'capability.resolve', ...selected]).size <= layeredGrantLimits.maxMethods, 'limit', `Layered connection methods exceed the ${layeredGrantLimits.maxMethods}-method profile; choose an explicit narrower grant.`, { limit: 'maxMethods', maximum: layeredGrantLimits.maxMethods });
|
|
573
|
-
const token = randomBytes(32).toString('hex');
|
|
574
594
|
const id = 'agent:' + digest(projectId + ':' + externalSessionId).slice(0, 32);
|
|
595
|
+
const grantedMethods = [...new Set([...(profile ? ['capability.index', 'capability.resolve'] : ['protocol.describe', 'capability.list']), ...selected])];
|
|
596
|
+
const existing = this.state.principals.find(p => p.id === id);
|
|
597
|
+
const kept = await fs.readFile(file, 'utf8').then(text => JSON.parse(text), () => null);
|
|
598
|
+
// Repeating the same setup must not disconnect a client already using its grant.
|
|
599
|
+
// Changed scope, expired access or a stale address still requires a fresh credential.
|
|
600
|
+
if (existing && !(existing.expiresAt && Date.parse(existing.expiresAt) <= Date.now())
|
|
601
|
+
&& kept?.url === this.url && kept.projectId === projectId && kept.externalSessionId === externalSessionId
|
|
602
|
+
&& typeof kept.token === 'string' && digest(kept.token) === existing.tokenHash
|
|
603
|
+
&& canonical([...existing.methods].sort()) === canonical([...grantedMethods].sort())
|
|
604
|
+
&& kept.profile === profile && canonical(existing.pluginFilter ?? []) === canonical(pluginFilter ?? []))
|
|
605
|
+
return kept;
|
|
606
|
+
const token = randomBytes(32).toString('hex');
|
|
575
607
|
this.state.principals = this.state.principals.filter(p => p.id !== id);
|
|
576
608
|
requireThat(!pluginFilter || pluginFilter.every(id => this.plugins.isKnown(id)), 'invalid', 'Unknown MCP plugin filter.');
|
|
577
|
-
await this.state.addPrincipal({ id, tokenHash: digest(token), projectId, label: who.label ?? externalSessionId, ...(who.conversationId ? { conversationId: who.conversationId } : {}), methods:
|
|
609
|
+
await this.state.addPrincipal({ id, tokenHash: digest(token), projectId, label: who.label ?? externalSessionId, ...(who.conversationId ? { conversationId: who.conversationId } : {}), methods: grantedMethods, ...(profile ? { profile } : {}), ...(pluginFilter ? { pluginFilter } : {}) });
|
|
578
610
|
const connection = { url: this.url, token, projectId, externalSessionId, methods: [...new Set([...(profile ? ['capability.index', 'capability.resolve'] : []), ...selected])], ...(profile ? { profile } : {}), ...(pluginFilter ? { pluginFilter } : {}), apiVersion: '2.0' };
|
|
579
611
|
await atomicJson(file, connection);
|
|
580
612
|
return connection;
|
|
@@ -671,6 +703,10 @@ export class RhoService {
|
|
|
671
703
|
const method = url.pathname.startsWith('/api/v2/') ? url.pathname.slice(8) : '';
|
|
672
704
|
requireThat(method === 'events' || !!operations[method], 'not_found', 'Unknown API operation.');
|
|
673
705
|
const principal = this.authenticate(req, method);
|
|
706
|
+
// An Agent's MCP bridge names the client that started it; the person's screen shows it.
|
|
707
|
+
const named = principal.projectId && principal.kind !== 'browser' ? clientOf(req.headers['x-rho-client']) : null;
|
|
708
|
+
if (named)
|
|
709
|
+
void this.state.markClient(principal.id, named).catch(() => { });
|
|
674
710
|
if (method === 'events') {
|
|
675
711
|
requireThat(req.method === 'GET', 'invalid', 'Events use GET.');
|
|
676
712
|
await this.subscribe(url, principal, res);
|
|
@@ -785,6 +821,33 @@ export class RhoService {
|
|
|
785
821
|
// What the project's Agents did, for the person's screen:
|
|
786
822
|
// only principals whose grant names this project, only the fields the activity record
|
|
787
823
|
// keeps. A grant is the scope; an Agent need not have opened a binding.
|
|
824
|
+
// Every Agent session in the project folder, wherever it was started: Harness conversations Rho
|
|
825
|
+
// observes and the sessions Claude Code and Codex keep in their own files. Each names the Agent
|
|
826
|
+
// Rho continues it with and the Rho conversation that already continues it. Reads files only.
|
|
827
|
+
async agentSessions(p) {
|
|
828
|
+
const agent = this.plugins.optional('agent');
|
|
829
|
+
const external = agent ? agent.listExternalSessions(p.root) : { sessions: [], omitted: 0 };
|
|
830
|
+
const native = (await import('@rho-workspace/plugin-agent/native-sessions')).listNativeSessions(p.root);
|
|
831
|
+
const conversations = [...await this.conversations.list(p, {}), ...await this.conversations.list(p, { trashed: true })];
|
|
832
|
+
const bySession = new Map(conversations.filter(c => c.sessionId).map(c => [c.sessionId, c]));
|
|
833
|
+
const harness = await this.conversations.harness();
|
|
834
|
+
const sessions = [...external.sessions, ...native.sessions].flatMap(s => {
|
|
835
|
+
const conversation = bySession.get(s.sessionId);
|
|
836
|
+
if (conversation?.trashedAt)
|
|
837
|
+
return [];
|
|
838
|
+
const continueWith = s.producer === 'claude-code' ? 'claude-acp' : s.producer === 'codex' ? 'codex-acp' : s.producer === 'dsh' ? harness : null;
|
|
839
|
+
// Harness reports a turn in progress, not whether a window holds the session idle; a turn
|
|
840
|
+
// Rho itself is running is Rho's.
|
|
841
|
+
const open = s.producer === 'dsh' && s.state === 'running' && conversation?.state !== 'running' ? { where: 'DeepSeek Harness' } : s.open;
|
|
842
|
+
return [{ ...s, continueWith, ...(open !== undefined ? { open } : {}), ...(conversation ? { conversationId: conversation.conversationId } : {}) }];
|
|
843
|
+
});
|
|
844
|
+
// A session held only by Rho's own connection is not open elsewhere.
|
|
845
|
+
for (const s of sessions)
|
|
846
|
+
if (s.open && s.conversationId && await this.conversations.owns(p, s.conversationId, s.open.pid))
|
|
847
|
+
s.open = null;
|
|
848
|
+
sessions.sort((a, b) => b.lastAt.localeCompare(a.lastAt));
|
|
849
|
+
return { sessions: sessions.slice(0, 128), omitted: external.omitted + native.omitted + Math.max(0, sessions.length - 128) };
|
|
850
|
+
}
|
|
788
851
|
async projectActivity(p) {
|
|
789
852
|
const agent = this.plugins.project(p.root, p.id).agent;
|
|
790
853
|
if (!agent)
|
|
@@ -799,7 +862,7 @@ export class RhoService {
|
|
|
799
862
|
const activity = await this.projectActivity(p);
|
|
800
863
|
return this.state.principals.filter(x => x.projectId === p.id && x.kind !== 'browser' && x.id !== 'operator').map(x => {
|
|
801
864
|
const last = activity.entries.filter((e) => e.principal === x.id).at(-1);
|
|
802
|
-
return { principal: x.id, label: x.label ?? null, profile: x.profile ?? null, methods: x.methods, expiresAt: x.expiresAt ?? null, ended: !!x.expiresAt && Date.parse(x.expiresAt) <= Date.now(), conversationId: x.conversationId ?? null, added: x.added ?? null,
|
|
865
|
+
return { principal: x.id, label: x.label ?? null, profile: x.profile ?? null, methods: x.methods, expiresAt: x.expiresAt ?? null, ended: !!x.expiresAt && Date.parse(x.expiresAt) <= Date.now(), conversationId: x.conversationId ?? null, client: x.client ?? null, added: x.added ?? null,
|
|
803
866
|
last: last ? { at: String(last.at), method: String(last.method), outcome: last.outcome === 'rejected' ? 'rejected' : 'returned', ...(last.operationId ? { operationId: String(last.operationId) } : {}) } : null };
|
|
804
867
|
});
|
|
805
868
|
}
|
|
@@ -833,11 +896,24 @@ export class RhoService {
|
|
|
833
896
|
return;
|
|
834
897
|
pending = setTimeout(() => { pending = undefined; void this.serial(p.id, () => this.emit(p, 'kernel.changed', p.id, localBrowserId)).catch(() => { }); }, 250);
|
|
835
898
|
};
|
|
836
|
-
|
|
899
|
+
// With the figures plugin, each local R and Python session describes the plots it draws.
|
|
900
|
+
const figures = this.plugins.optional('figures');
|
|
901
|
+
host = new KernelHost(p.root, { limits: this.config.kernel, rscript: this.bridge.rscript, python: this.config.python, ssh: this.config.ssh, serverctl: this.config.serverctl, onChange: changed,
|
|
902
|
+
...(figures ? { extensions: { r: [figures.rExtension], python: [figures.pythonExtension] } } : {}) });
|
|
837
903
|
this.kernels.set(p.id, host);
|
|
838
904
|
}
|
|
839
905
|
return host;
|
|
840
906
|
}
|
|
907
|
+
// The figures the project's live R session drew, read from the records its cells kept.
|
|
908
|
+
figuresFor(p) {
|
|
909
|
+
let host = this.figures.get(p.id);
|
|
910
|
+
if (!host) {
|
|
911
|
+
const { Figures } = this.plugins.need('figures');
|
|
912
|
+
host = new Figures(p.root, { record: (cellId, plot) => this.kernelFor(p).figureRecord(cellId, plot), saved: sha256 => this.kernelFor(p).savedFigure(sha256) });
|
|
913
|
+
this.figures.set(p.id, host);
|
|
914
|
+
}
|
|
915
|
+
return host;
|
|
916
|
+
}
|
|
841
917
|
// The project's skills, written by the person and their Agent. A change reaches open pages
|
|
842
918
|
// once the request that made it has finished.
|
|
843
919
|
skillsFor(p) {
|
|
@@ -869,7 +945,9 @@ export class RhoService {
|
|
|
869
945
|
return describeProtocol(protocol, principal.methods, b);
|
|
870
946
|
if (extensionManagement.includes(method)) {
|
|
871
947
|
requireThat(principal.id === 'operator' || principal.id === localBrowserId, 'scope', 'Plugin installation and management belong to the person at this machine.');
|
|
872
|
-
|
|
948
|
+
if (method === 'extension.market.change')
|
|
949
|
+
return this.extensions.changeMarket(b);
|
|
950
|
+
return method === 'extension.install' ? this.extensions.install(b.source, b.replace, b.expectedDigest, b.market) : this.extensions.manage(b.pluginId, b.expectedDigest, b.action);
|
|
873
951
|
}
|
|
874
952
|
if (method === 'extension.list' && !b.projectId) {
|
|
875
953
|
requireThat(principal.id === 'operator' || principal.id === localBrowserId, 'scope', 'A project-scoped caller must supply its project.');
|
|
@@ -1156,10 +1234,15 @@ export class RhoService {
|
|
|
1156
1234
|
await this.emit(p, 'composition.changed', b.compositionId);
|
|
1157
1235
|
return { document, viewId, composition };
|
|
1158
1236
|
}
|
|
1237
|
+
if (method === 'session.instructions') {
|
|
1238
|
+
requireThat(principal.id === localBrowserId || principal.id === 'operator', 'unauthorized', 'Only the person at this machine can read Agent setup instructions.');
|
|
1239
|
+
return connectionInstructions(p, this.state.directory);
|
|
1240
|
+
}
|
|
1159
1241
|
if (method === 'session.grant') {
|
|
1160
1242
|
requireThat(principal.id === 'operator', 'unauthorized', 'Only the local operator may provision a project-scoped session.');
|
|
1161
1243
|
const file = path.join(this.state.directory, 'agent-' + digest(p.id + ':' + b.externalSessionId) + '.json');
|
|
1162
1244
|
const connection = await this.agentConnection(p.id, b.externalSessionId, file, b.methods, undefined, b.profile);
|
|
1245
|
+
await this.emit(p, 'activity.changed', method);
|
|
1163
1246
|
return { profile: connection.profile, connectionFile: file, projectId: p.id, methods: connection.methods, transport: 'stdio-mcp', externalSessionId: b.externalSessionId };
|
|
1164
1247
|
}
|
|
1165
1248
|
// The person's own Claude Code or Codex, outside Rho: the same grant the operator gives, kept
|
|
@@ -1190,11 +1273,16 @@ export class RhoService {
|
|
|
1190
1273
|
// The person at this machine talks to a conversation Rho started; Agents and narrower links
|
|
1191
1274
|
// cannot start one, and an Agent started this way cannot start another.
|
|
1192
1275
|
if (method.startsWith('agent.sessions.')) {
|
|
1193
|
-
requireThat(principal.id === localBrowserId, 'unauthorized', '
|
|
1194
|
-
const agent = this.plugins.need('agent');
|
|
1276
|
+
requireThat(principal.id === localBrowserId, 'unauthorized', 'Agent session history belongs to the person at this machine.');
|
|
1195
1277
|
if (method === 'agent.sessions.list')
|
|
1196
|
-
return
|
|
1197
|
-
|
|
1278
|
+
return this.agentSessions(p);
|
|
1279
|
+
const listed = (await this.agentSessions(p)).sessions.find(s => s.id === b.session);
|
|
1280
|
+
requireThat(listed, 'not_found', 'That Agent session is not in this project.');
|
|
1281
|
+
const read = listed.producer === 'claude-code' || listed.producer === 'codex'
|
|
1282
|
+
? await (await import('@rho-workspace/plugin-agent/native-sessions')).readNativeSession(p.root, b)
|
|
1283
|
+
: this.plugins.need('agent').readExternalSession(p.root, b);
|
|
1284
|
+
requireThat(read, 'not_found', 'That Agent session is no longer in this project.');
|
|
1285
|
+
return { ...read, session: { ...read.session, ...listed } };
|
|
1198
1286
|
}
|
|
1199
1287
|
if (method === 'project.work.read') {
|
|
1200
1288
|
requireThat(principal.id === localBrowserId, 'scope', 'Project work is available to the person at this machine.');
|
|
@@ -1235,16 +1323,35 @@ export class RhoService {
|
|
|
1235
1323
|
if (method === 'conversation.settings.read')
|
|
1236
1324
|
return this.conversations.settings();
|
|
1237
1325
|
this.plugins.need('agent');
|
|
1326
|
+
if (method === 'conversation.start' && b.continue) {
|
|
1327
|
+
const listed = (await this.agentSessions(p)).sessions.find(s => s.id === b.continue.session);
|
|
1328
|
+
requireThat(listed, 'not_found', 'That Agent session is not in this project. Nothing was sent.');
|
|
1329
|
+
requireThat(listed.continueWith, 'condition', listed.producer === 'dsh'
|
|
1330
|
+
? 'Rho continues DeepSeek Harness sessions through Harness\'s ACP command. Add DeepSeek Harness under Agents in Rho, or continue in Harness. Nothing was sent.'
|
|
1331
|
+
: 'Rho cannot continue this Agent\'s sessions. Nothing was sent.');
|
|
1332
|
+
requireThat(!listed.conversationId, 'conflict', 'This session already continues as a conversation here. Open it to send your message.');
|
|
1333
|
+
requireThat(!b.continue.copy || listed.producer !== 'dsh', 'condition', 'DeepSeek Harness cannot copy a session. Continue it in place once it is not answering. Nothing was sent.');
|
|
1334
|
+
requireThat(!listed.open || listed.producer !== 'dsh', 'condition', 'DeepSeek Harness is answering in this session now. Continue it once that turn ends. Nothing was sent.', { heldBy: 'DeepSeek Harness' });
|
|
1335
|
+
return this.conversations.start(p, principal.id, { ...b, continue: { producer: listed.producer, sessionId: listed.sessionId, title: listed.title, where: listed.where ?? (listed.producer === 'dsh' ? 'DeepSeek Harness' : 'another window'), copy: !!b.continue.copy, backend: listed.continueWith } });
|
|
1336
|
+
}
|
|
1238
1337
|
if (method === 'conversation.start')
|
|
1239
1338
|
return this.conversations.start(p, principal.id, b);
|
|
1240
|
-
if (method === 'conversation.send')
|
|
1339
|
+
if (method === 'conversation.send') {
|
|
1340
|
+
// A Harness session reports its own turns through its adapter; Rho does not write into one it is answering.
|
|
1341
|
+
const listed = (await this.agentSessions(p)).sessions.find(s => s.conversationId === b.conversationId && s.producer === 'dsh');
|
|
1342
|
+
requireThat(!listed?.open, 'condition', 'DeepSeek Harness is answering in this session now. Send once that turn ends. Nothing was sent.', { heldBy: 'DeepSeek Harness' });
|
|
1241
1343
|
return this.conversations.send(p, principal.id, b);
|
|
1344
|
+
}
|
|
1242
1345
|
if (method === 'conversation.configure')
|
|
1243
1346
|
return this.conversations.configure(p, principal.id, b);
|
|
1244
1347
|
if (method === 'conversation.settings.update')
|
|
1245
1348
|
return this.conversations.updateSettings(b);
|
|
1246
1349
|
if (method === 'conversation.settings.check')
|
|
1247
1350
|
return this.conversations.check(p, b);
|
|
1351
|
+
if (method === 'conversation.agents.add')
|
|
1352
|
+
return this.conversations.addAgent(p, b);
|
|
1353
|
+
if (method === 'conversation.agents.remove')
|
|
1354
|
+
return this.conversations.removeAgent(b);
|
|
1248
1355
|
}
|
|
1249
1356
|
const capabilities = new Capabilities(this.plugins, p);
|
|
1250
1357
|
// The person runs and stops Console commands with their own shell; anyone holding the read
|
|
@@ -1304,6 +1411,21 @@ export class RhoService {
|
|
|
1304
1411
|
if (method === 'kernel.server.skill')
|
|
1305
1412
|
return host.serverSkill(b.server, b.name);
|
|
1306
1413
|
}
|
|
1414
|
+
// A plot the live R session drew, with its marks bound to their rows. The person and their
|
|
1415
|
+
// Agent read it and keep the marks they point to; a kept selection reaches open pages.
|
|
1416
|
+
if (method.startsWith('figure.')) {
|
|
1417
|
+
await capabilities.assertAvailable(method, principal);
|
|
1418
|
+
const figures = this.figuresFor(p);
|
|
1419
|
+
const ref = { cellId: b.cellId, plot: b.plot, path: b.path, sha256: b.sha256 };
|
|
1420
|
+
if (method === 'figure.read')
|
|
1421
|
+
return figures.read({ ...ref, form: b.form, marks: b.marks, selection: b.selection, where: b.where, limit: b.limit });
|
|
1422
|
+
if (method === 'figure.select') {
|
|
1423
|
+
const result = await figures.select({ ...ref, marks: b.marks, note: b.note, changeId: b.changeId }, principal.id);
|
|
1424
|
+
const kept = result.selection;
|
|
1425
|
+
await this.emit(p, 'figure.changed', kept.sha256 ? 'file:' + kept.sha256 : kept.cellId + ':' + kept.plot, principal.id);
|
|
1426
|
+
return result;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1307
1429
|
// How work is done in the project: the person and their Agent read its skills, and record and
|
|
1308
1430
|
// undo changes to them, each kept with who made it, why and from what.
|
|
1309
1431
|
if (method.startsWith('skills.')) {
|
|
@@ -1487,8 +1609,12 @@ export class RhoService {
|
|
|
1487
1609
|
return owners.need('charts').plot(b.resource, b.x, b.y, b.maxPoints);
|
|
1488
1610
|
if (method === 'data.compare')
|
|
1489
1611
|
return owners.need('tables').compare(b.left, b.right, b.options);
|
|
1490
|
-
if (['composition.apply', 'composition.undo', 'view.protection.set'].includes(method)) {
|
|
1491
|
-
|
|
1612
|
+
if (['composition.apply', 'composition.undo', 'composition.delete', 'view.protection.set'].includes(method)) {
|
|
1613
|
+
if (method === 'composition.delete' && (await routing.read())?.compositionId === b.compositionId) {
|
|
1614
|
+
const retained = await workspace.read(b.compositionId);
|
|
1615
|
+
requireThat(retained.receipts?.some((r) => r.changeId === b.changeId), 'condition', 'An active task uses this layout.');
|
|
1616
|
+
}
|
|
1617
|
+
const result = await workspace.apply(b, principal.id, method === 'composition.undo' ? 'undo' : method === 'composition.delete' ? 'delete' : method === 'view.protection.set' ? 'protect' : 'apply');
|
|
1492
1618
|
const route = await routing.read();
|
|
1493
1619
|
if (route) {
|
|
1494
1620
|
try {
|
package/dist/state.d.ts
CHANGED
|
@@ -40,6 +40,10 @@ export interface Principal {
|
|
|
40
40
|
label?: string;
|
|
41
41
|
conversationId?: string;
|
|
42
42
|
added?: AddedTo;
|
|
43
|
+
client?: {
|
|
44
|
+
name: string;
|
|
45
|
+
version?: string;
|
|
46
|
+
};
|
|
43
47
|
}
|
|
44
48
|
export declare function lease(directory: string): Promise<() => Promise<void>>;
|
|
45
49
|
export declare class State {
|
|
@@ -55,6 +59,10 @@ export declare class State {
|
|
|
55
59
|
private persistPrincipals;
|
|
56
60
|
addPrincipal(principal: Principal): Promise<void>;
|
|
57
61
|
removePrincipal(id: string): Promise<void>;
|
|
62
|
+
markClient(id: string, client: {
|
|
63
|
+
name: string;
|
|
64
|
+
version?: string;
|
|
65
|
+
}): Promise<void>;
|
|
58
66
|
markAdded(id: string, added: AddedTo): Promise<void>;
|
|
59
67
|
revokeBrowser(id: string): Promise<{
|
|
60
68
|
sessionId: string;
|
package/dist/state.js
CHANGED
|
@@ -90,6 +90,14 @@ export class State {
|
|
|
90
90
|
async addPrincipal(principal) { this.principals.push(principal); await this.persistPrincipals(); }
|
|
91
91
|
async removePrincipal(id) { this.principals = this.principals.filter(p => p.id !== id); await this.persistPrincipals(); }
|
|
92
92
|
// Which Agent outside Rho a grant was added to, kept with the grant it serves.
|
|
93
|
+
// The MCP client that last used a grant, as it named itself; kept with the grant it used.
|
|
94
|
+
async markClient(id, client) {
|
|
95
|
+
const principal = this.principals.find(p => p.id === id);
|
|
96
|
+
if (!principal || (principal.client?.name === client.name && principal.client?.version === client.version))
|
|
97
|
+
return;
|
|
98
|
+
principal.client = client;
|
|
99
|
+
await this.persistPrincipals();
|
|
100
|
+
}
|
|
93
101
|
async markAdded(id, added) { const principal = this.principals.find(p => p.id === id); if (principal) {
|
|
94
102
|
principal.added = added;
|
|
95
103
|
await this.persistPrincipals();
|
package/dist/studio-layout.js
CHANGED
|
@@ -49,7 +49,7 @@ export function studioFiles(graph) {
|
|
|
49
49
|
take(item.node, role, item.role === 'primary');
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
return [...found.values()].sort((a, b) => rank[a.role] - rank[b.role] || Number(b.lead) - Number(a.lead) || a.first - b.first || a.path.localeCompare(b.path));
|
|
52
|
+
return [...found.values()].sort((a, b) => rank[a.role] - rank[b.role] || (a.role === 'input' && b.role === 'input' ? Number(b.category === 'data') - Number(a.category === 'data') : 0) || Number(b.lead) - Number(a.lead) || a.first - b.first || a.path.localeCompare(b.path));
|
|
53
53
|
}
|
|
54
54
|
// The files as they stood at a moment: those the work had by then, each with the versions kept
|
|
55
55
|
// by then. Null is now.
|
|
@@ -76,10 +76,10 @@ export function studioTree(views, commands, session = null) {
|
|
|
76
76
|
return id;
|
|
77
77
|
};
|
|
78
78
|
const input = region(studioIds.input, of('input').map(v => v.node)), code = region(studioIds.code, of('code').map(v => v.node));
|
|
79
|
-
const run = region(studioIds.commands, [
|
|
79
|
+
const run = region(studioIds.commands, [session, commands].filter((n) => !!n));
|
|
80
80
|
const made = [...of('result'), ...of('report')];
|
|
81
81
|
const figures = region(studioIds.figures, made.filter(v => v.figure).map(v => v.node)), result = region(studioIds.result, made.filter(v => !v.figure).map(v => v.node));
|
|
82
|
-
const leftId = column(studioIds.left, [input ? { id: input, weight: 1 } : null, code ? { id: code, weight: 1.
|
|
82
|
+
const leftId = column(studioIds.left, [input ? { id: input, weight: 1.4 } : null, code ? { id: code, weight: 1.2 } : null, run ? { id: run, weight: 1.4 } : null]);
|
|
83
83
|
const rightId = column(studioIds.right, [figures ? { id: figures, weight: 1.25 } : null, result ? { id: result, weight: 1 } : null]);
|
|
84
84
|
if (leftId && rightId) {
|
|
85
85
|
nodes.push(split(studioIds.root, 'horizontal', [{ id: leftId, weight: 1 }, { id: rightId, weight: 1.3 }]));
|
package/dist/task-frame.d.ts
CHANGED
|
@@ -98,4 +98,4 @@ export declare function counts(map: TaskMapState): {
|
|
|
98
98
|
waiting: number;
|
|
99
99
|
done: number;
|
|
100
100
|
};
|
|
101
|
-
export declare function authorLabel(author: string, principalId?: string): "You" | "Someone in another tab" | "
|
|
101
|
+
export declare function authorLabel(author: string, principalId?: string): "Rho" | "You" | "Someone in another tab" | "The Agent";
|
package/dist/timeline-events.js
CHANGED
|
@@ -6,13 +6,20 @@ export const personPrincipal = 'browser:local';
|
|
|
6
6
|
export const personLane = 'person', otherLane = 'other';
|
|
7
7
|
export function eventMarks(events, owners, now) {
|
|
8
8
|
const byPrincipal = new Map(owners.flatMap(o => o.principal ? [[o.principal, o]] : []));
|
|
9
|
+
const byEvent = new Map();
|
|
10
|
+
for (const owner of owners)
|
|
11
|
+
for (const key of owner.eventKeys ?? []) {
|
|
12
|
+
const previous = byEvent.get(key);
|
|
13
|
+
byEvent.set(key, previous === undefined || previous?.conversationId === owner.conversationId ? owner : null);
|
|
14
|
+
}
|
|
9
15
|
return events.flatMap(e => {
|
|
10
16
|
const at = Date.parse(e.startedAt);
|
|
11
17
|
if (!Number.isFinite(at))
|
|
12
18
|
return [];
|
|
13
19
|
const ended = e.endedAt ? Date.parse(e.endedAt) : NaN;
|
|
14
20
|
const end = Math.max(at, Number.isFinite(ended) ? ended : e.outcome === 'running' ? now : at);
|
|
15
|
-
const
|
|
21
|
+
const key = e.source + ':' + e.id;
|
|
22
|
+
const owner = e.by === personPrincipal ? null : byEvent.has(key) ? byEvent.get(key) : byPrincipal.get(e.by);
|
|
16
23
|
const who = e.by === personPrincipal ? 'person' : owner ? 'conversation' : 'other';
|
|
17
24
|
return [{ key: e.source + ':' + e.id, at, end, lane: who === 'person' ? personLane : owner ? owner.conversationId : otherLane,
|
|
18
25
|
laneTitle: who === 'person' ? 'You' : owner ? owner.title : 'Outside Rho', who, ...(owner ? { agent: owner.agent } : {}),
|