@rho-workspace/rho 0.5.4 → 0.5.5
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/browser-access.js +3 -2
- package/dist/capabilities.d.ts +2 -2
- package/dist/capabilities.js +6 -1
- package/dist/cli.js +186 -173
- package/dist/contract.generated.d.ts +329 -4
- package/dist/contract.generated.js +1 -1
- package/dist/contracts/owners.generated.js +360 -6
- package/dist/discovery.d.ts +1 -0
- package/dist/discovery.js +3 -3
- package/dist/extension-contract.d.ts +99 -0
- package/dist/extension-contract.js +52 -0
- package/dist/extension-directory.d.ts +9 -0
- package/dist/extension-directory.js +11 -0
- package/dist/extension-worker.d.ts +1 -0
- package/dist/extension-worker.js +36 -0
- package/dist/extensions.d.ts +157 -0
- package/dist/extensions.js +349 -0
- package/dist/plugin-cli.d.ts +1 -0
- package/dist/plugin-cli.js +139 -0
- package/dist/plugin-inventory.json +18 -0
- package/dist/plugins.d.ts +3 -3
- package/dist/project-work.d.ts +3 -1
- package/dist/project-work.js +15 -2
- package/dist/protocol.d.ts +7 -0
- package/dist/protocol.js +4 -2
- package/dist/server.d.ts +4 -1
- package/dist/server.js +55 -8
- package/dist/web/.vite/manifest.json +61 -61
- package/dist/web/assets/{canvas-BXhC8RDZ.js → canvas-D8rDdfDz.js} +1 -1
- package/dist/web/assets/choices-b0XhrGQV.js +1 -0
- package/dist/web/assets/{console-Dbdo-5oO.js → console-BSjLGe0T.js} +1 -1
- package/dist/web/assets/{dist-B2V0VDO0.js → dist-Buk0lwzI.js} +2 -2
- package/dist/web/assets/dist-Cw8hRIlT.js +1 -0
- package/dist/web/assets/dist-Tzoe_zm-.js +3 -0
- package/dist/web/assets/{dist-D25Vn17i.js → dist-foH9IKc6.js} +1 -1
- package/dist/web/assets/explore-in-BVUy3ZW_.js +1 -0
- package/dist/web/assets/graph-1OdIJ9l7.js +2 -0
- package/dist/web/assets/index-CsC3sTRq.css +1 -0
- package/dist/web/assets/{index-BHQIaDsx.js → index-CxmItXqa.js} +85 -85
- package/dist/web/assets/{notes-meaning-DdPbtNzJ.js → notes-meaning-pVbbQUGP.js} +1 -1
- package/dist/web/assets/overview-B9UtVJ32.js +6 -0
- package/dist/web/assets/preview-CksD_5KS.js +1 -0
- package/dist/web/assets/{review-CmWou4Iv.js → review-CMxQlQ-C.js} +1 -1
- package/dist/web/assets/{skills-Dy7n3L9d.js → skills-BFNSyrnD.js} +1 -1
- package/dist/web/assets/{skills-work-Cj0W6b-I.js → skills-work-BGPSnF_I.js} +1 -1
- package/dist/web/assets/{studio-process-QOY_n2_8.js → studio-process-CsRoTHOh.js} +2 -2
- package/dist/web/assets/{timeline-events-DmX1BCh3.js → timeline-events-BbuuFWHV.js} +1 -1
- package/dist/web/assets/{work-turns-Co0C71rJ.js → work-turns-BvJlIMmA.js} +1 -1
- package/dist/web/assets/workspace-CWlkjLNx.js +5 -0
- package/dist/web/composition.json +67 -67
- package/dist/web/index.html +3 -3
- package/dist/web/renderer.json +1 -1
- package/dist/web-assets.d.ts +2 -1
- package/dist/web-assets.js +3 -2
- package/dist/work-contract.js +2 -2
- package/docs/HEADLESS.md +1 -1
- package/docs/PLUGIN-AUTHORING.md +4 -0
- package/docs/THIRD-PARTY-PLUGINS.md +190 -0
- package/node_modules/@rho-workspace/plugin-agent/README.md +39 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/activity-normalize.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/activity-normalize.js +2 -2
- package/node_modules/@rho-workspace/plugin-agent/dist/dsh-session.d.ts +6 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/dsh-session.js +174 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.d.ts +82 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +15 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +73 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session.js +190 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +373 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/index.js +1 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/operations.d.ts +33 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/operations.js +3 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/presentation.d.ts +14 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/presentation.js +2 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/ui-external-session.d.ts +15 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/ui-external-session.js +37 -0
- package/node_modules/@rho-workspace/plugin-agent/package.json +5 -1
- package/package.json +5 -3
- package/protocol/index.json +58 -4
- package/protocol/operations/agent.sessions.list.json +100 -0
- package/protocol/operations/agent.sessions.read.json +266 -0
- package/protocol/operations/extension.call.json +132 -0
- package/protocol/operations/extension.content.json +78 -0
- package/protocol/operations/extension.describe.json +195 -0
- package/protocol/operations/extension.install.json +198 -0
- package/protocol/operations/extension.list.json +123 -0
- package/protocol/operations/extension.manage.json +55 -0
- package/protocol/operations/extension.read.json +112 -0
- package/protocol/operations/project.work.read.json +3 -2
- package/protocol/operations/session.grant.json +9 -0
- package/scripts/third-party-acceptance.mjs +77 -0
- package/vendor/composition.json +85 -85
- package/vendor/rho-workspace-plugin-agent-0.4.29.tgz +0 -0
- package/dist/web/assets/choices-DU9kHlyQ.js +0 -1
- package/dist/web/assets/dist-BV3hWfn_.js +0 -3
- package/dist/web/assets/dist-CLNF2g-E.js +0 -1
- package/dist/web/assets/explore-in-8Q5ftt-E.js +0 -1
- package/dist/web/assets/graph-hS2kvHTI.js +0 -2
- package/dist/web/assets/index-Cj1_iqyK.css +0 -1
- package/dist/web/assets/overview-BV6ZO5TO.js +0 -6
- package/dist/web/assets/preview-n5lOOdjN.js +0 -1
- package/dist/web/assets/workspace-BKFefRfJ.js +0 -5
- package/vendor/rho-workspace-plugin-agent-0.4.28.tgz +0 -0
|
@@ -768,6 +768,24 @@
|
|
|
768
768
|
},
|
|
769
769
|
"listing": {
|
|
770
770
|
"actions": [
|
|
771
|
+
{
|
|
772
|
+
"presentation": {
|
|
773
|
+
"order": 58,
|
|
774
|
+
"displayName": "External conversations",
|
|
775
|
+
"description": "Reads retained conversations observed in this project. Input remains in the original client.",
|
|
776
|
+
"readOnly": true,
|
|
777
|
+
"runsCode": false
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
"presentation": {
|
|
782
|
+
"order": 59,
|
|
783
|
+
"displayName": "External conversation",
|
|
784
|
+
"description": "Reads native turns, tool outcomes and result receipts without sending or resuming anything.",
|
|
785
|
+
"readOnly": true,
|
|
786
|
+
"runsCode": false
|
|
787
|
+
}
|
|
788
|
+
},
|
|
771
789
|
{
|
|
772
790
|
"presentation": {
|
|
773
791
|
"order": 62,
|
package/dist/plugins.d.ts
CHANGED
|
@@ -74,7 +74,7 @@ export declare class Plugins {
|
|
|
74
74
|
timeoutSeconds?: number;
|
|
75
75
|
}[];
|
|
76
76
|
};
|
|
77
|
-
ids(): ("media" | "r" | "agent" | "files" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel"
|
|
77
|
+
ids(): ("media" | "r" | "agent" | "files" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel")[];
|
|
78
78
|
isKnown(id: string): id is PluginId;
|
|
79
79
|
availabilityFor(id: PluginId): any;
|
|
80
80
|
need<K extends PluginId>(id: K): Modules[K];
|
|
@@ -161,10 +161,10 @@ export declare class Plugins {
|
|
|
161
161
|
reason?: undefined;
|
|
162
162
|
};
|
|
163
163
|
descriptors(): PluginDescriptor[];
|
|
164
|
-
availableIds(): ("media" | "r" | "agent" | "files" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel"
|
|
164
|
+
availableIds(): ("media" | "r" | "agent" | "files" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel")[];
|
|
165
165
|
descriptor(id: string): PluginDescriptor | undefined;
|
|
166
166
|
nativeAvailability(rscript: string): Promise<any>;
|
|
167
|
-
operationOwner(method: string): "media" | "r" | "agent" | "files" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel" |
|
|
167
|
+
operationOwner(method: string): "media" | "r" | "agent" | "files" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel" | undefined;
|
|
168
168
|
operationDeclarations(): {
|
|
169
169
|
owner: string;
|
|
170
170
|
method: string;
|
package/dist/project-work.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Conversations } from './conversations.js';
|
|
2
2
|
import type { Project } from './state.js';
|
|
3
|
-
import type { observeSessionFiles, readAgentActivity, readExternalAcp } from '@rho-workspace/plugin-agent';
|
|
3
|
+
import type { observeSessionFiles, readAgentActivity, readExternalAcp, listExternalSessions, readExternalSession } from '@rho-workspace/plugin-agent';
|
|
4
4
|
import type { RhoOutput } from './contract.generated.js';
|
|
5
5
|
export declare function projectWork(input: {
|
|
6
6
|
project: Project;
|
|
@@ -24,4 +24,6 @@ export declare function projectWork(input: {
|
|
|
24
24
|
native: Awaited<ReturnType<typeof readAgentActivity>> | null;
|
|
25
25
|
external?: ReturnType<typeof readExternalAcp>;
|
|
26
26
|
inspect?: typeof observeSessionFiles;
|
|
27
|
+
observed?: ReturnType<typeof listExternalSessions>;
|
|
28
|
+
readObserved?: typeof readExternalSession;
|
|
27
29
|
}): Promise<RhoOutput<'project.work.read'>>;
|
package/dist/project-work.js
CHANGED
|
@@ -20,10 +20,23 @@ export async function projectWork(input) {
|
|
|
20
20
|
lastMessage: s.lastMessage, omittedMessageChars: s.omittedMessageChars, pendingPermissions: s.pendingPermissions,
|
|
21
21
|
operationIds: [], resourceRefs: [] }));
|
|
22
22
|
const refs = [...owned, ...external].flatMap(s => [...(s.observation?.tools ?? [])].reverse().flatMap(t => t.locations.map(l => ({ path: l.path, session: s.id, tool: t.id }))));
|
|
23
|
+
const observed = (input.observed?.sessions ?? []).slice(0, 8).map(s => {
|
|
24
|
+
let turns = [];
|
|
25
|
+
try {
|
|
26
|
+
turns = input.readObserved?.(input.project.root, { session: s.id, limit: 2 }).turns ?? [];
|
|
27
|
+
}
|
|
28
|
+
catch { /* Summary remains available when detail cannot be read. */ }
|
|
29
|
+
for (const turn of turns)
|
|
30
|
+
for (const tool of turn.tools)
|
|
31
|
+
for (const path of tool.paths)
|
|
32
|
+
refs.push({ path, session: s.id, tool: tool.id });
|
|
33
|
+
const resources = turns.flatMap(t => t.results.filter(r => r.id && r.version && r.projectId === input.project.id).map(r => ({ id: r.id, version: r.version })));
|
|
34
|
+
return { id: s.id, source: 'external-session', sessionId: s.sessionId, title: s.title, agent: s.producer, state: s.state, version: s.sourceCursor, observation: null, operationIds: [], resourceRefs: [...new Map(resources.map(r => [r.id + '@' + r.version, r])).values()] };
|
|
35
|
+
});
|
|
23
36
|
const inspected = input.inspect ? await input.inspect(input.project.root, refs) : { files: [], omitted: refs.length };
|
|
24
37
|
return { projectId: input.project.id, cursor: input.cursor, observedAt: new Date().toISOString(),
|
|
25
38
|
notes: { path: notesPath, ...(note ? { documentId: note.id, version: note.version, dirty: note.dirty } : {}) },
|
|
26
|
-
sessions: [...owned, ...external, ...native], files: inspected.files,
|
|
39
|
+
sessions: [...owned, ...observed, ...external, ...native], files: inspected.files,
|
|
27
40
|
native: { state: input.native ? 'available' : 'unavailable', message: input.native ? 'Retained native sources; input stays in their original window.' : 'Native activity is not available from the current Host. Reading this view does not start it.' },
|
|
28
|
-
omitted: { conversations: input.conversations.omitted, fileReferences: inspected.omitted, external: (input.external?.omitted ?? 0) + Math.max(0, externalSessions.length - external.length) } };
|
|
41
|
+
omitted: { conversations: input.conversations.omitted, fileReferences: inspected.omitted, external: (input.external?.omitted ?? 0) + (input.observed?.omitted ?? 0) + Math.max(0, (input.observed?.sessions.length ?? 0) - observed.length) + Math.max(0, externalSessions.length - external.length) } };
|
|
29
42
|
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -192,6 +192,13 @@ export declare const descriptions: {
|
|
|
192
192
|
'workspace.enhance': string;
|
|
193
193
|
'capability.index': string;
|
|
194
194
|
'capability.resolve': string;
|
|
195
|
+
'extension.list': string;
|
|
196
|
+
'extension.describe': string;
|
|
197
|
+
'extension.content': string;
|
|
198
|
+
'extension.call': string;
|
|
199
|
+
'extension.read': string;
|
|
200
|
+
'extension.install': string;
|
|
201
|
+
'extension.manage': string;
|
|
195
202
|
};
|
|
196
203
|
export declare const errorKinds: string[];
|
|
197
204
|
export declare const errorSchema: {
|
package/dist/protocol.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { extensionOperations, extensionResponses, extensionDescriptions } from './extension-contract.js';
|
|
1
2
|
import { evidenceSchema } from './evidence-capture.js';
|
|
2
3
|
import { ownerContracts } from './contracts/owners.generated.js';
|
|
3
4
|
import { enhancementSchemas } from './enhancement-contracts.js';
|
|
@@ -42,7 +43,7 @@ for (const owner of ['documents', 'files', 'r', 'requirements', 'tables', 'media
|
|
|
42
43
|
ownerOperations[method] = contract;
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
|
-
export const operations = { ...base.operations,
|
|
46
|
+
export const operations = { ...base.operations, ...extensionOperations,
|
|
46
47
|
'protocol.describe': obj({ ...project, method: text, cursor: { type: ['string', 'null'], maxLength: 128 }, limit: { type: 'integer', minimum: 1, maximum: 100 }, maxBytes: { type: 'integer', minimum: 1024, maximum: 65536 } }, []),
|
|
47
48
|
'project.pick': obj({}),
|
|
48
49
|
'project.source.pick': obj({}),
|
|
@@ -274,7 +275,7 @@ observationSnapshot.required = observationSnapshot.required.filter(key => key !=
|
|
|
274
275
|
const structuredComposition = contract.structuredCompositionSchema;
|
|
275
276
|
const composition = { anyOf: [contract.legacyCompositionSchema, structuredComposition] };
|
|
276
277
|
const taskMap = { ...contract.taskMapStateSchema, properties: { ...contract.taskMapStateSchema.properties, receipt: obj({ changeId: identity, requestDigest: digest, kind: { enum: ['declare', 'present', 'respond'] }, author: text, version: positive, at: { type: 'string', format: 'date-time' }, outcome: contract.taskMapReceiptField }, ['changeId', 'kind', 'author', 'version', 'at']) } };
|
|
277
|
-
export const responses = { ...base.responses, ...ownerResponses,
|
|
278
|
+
export const responses = { ...base.responses, ...ownerResponses, ...extensionResponses,
|
|
278
279
|
'project.work.read': projectWorkSchema(sessionObservation),
|
|
279
280
|
'project.open': { ...contract.projectRecordSchema, properties: { ...contract.projectRecordSchema.properties, host: contract.cliSchema }, required: [...contract.projectRecordSchema.required, 'host'] },
|
|
280
281
|
'project.pick': response({ cancelled: { type: 'boolean' }, project: { anyOf: [contract.projectSummarySchema, { type: 'null' }] } }),
|
|
@@ -374,6 +375,7 @@ export const responses = { ...base.responses, ...ownerResponses,
|
|
|
374
375
|
};
|
|
375
376
|
responses['session.grant'].properties.profile = operations['session.grant'].properties.profile;
|
|
376
377
|
export const descriptions = {
|
|
378
|
+
...extensionDescriptions,
|
|
377
379
|
'workspace.observe': 'Assigned presenter only: bounded layout/basis/owner summary/visibility projection. No feedback, full documents or native trace. Reading starts no model or renderer.',
|
|
378
380
|
'workspace.enhance': 'Assigned presenter only: conditional display emphasis/title/plain Agent note using exact current generation, witness and observation. Does not modify business resources, feedback or protection.',
|
|
379
381
|
'capability.index': 'Read a small role-narrowed index of currently authorized, enabled operations and immutable Skill/contract references.',
|
package/dist/server.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Extensions, extensionLimits } from './extensions.js';
|
|
1
2
|
import { evidenceExportLimits } from './evidence-export.js';
|
|
2
3
|
import * as http from 'node:http';
|
|
3
4
|
import { Plugins, type PluginConfig } from './plugins.js';
|
|
@@ -49,6 +50,7 @@ export declare const serviceLimits: {
|
|
|
49
50
|
export interface ServiceConfig extends Partial<typeof serviceLimits>, Partial<typeof requestLimits>, PluginConfig {
|
|
50
51
|
root: string;
|
|
51
52
|
stateDir: string;
|
|
53
|
+
extensions?: Partial<typeof extensionLimits>;
|
|
52
54
|
coreBinary?: string;
|
|
53
55
|
rscript: string;
|
|
54
56
|
python?: string;
|
|
@@ -93,6 +95,7 @@ export declare const layeredGrantLimits: {
|
|
|
93
95
|
};
|
|
94
96
|
export declare class RhoService {
|
|
95
97
|
config: ServiceConfig;
|
|
98
|
+
extensions: Extensions;
|
|
96
99
|
plugins: Plugins;
|
|
97
100
|
state: State;
|
|
98
101
|
bridge: CoreBridge;
|
|
@@ -167,7 +170,7 @@ export declare class RhoService {
|
|
|
167
170
|
conversationId?: string;
|
|
168
171
|
}): Promise<{
|
|
169
172
|
apiVersion: "2.0";
|
|
170
|
-
pluginFilter?: ("media" | "r" | "agent" | "files" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel"
|
|
173
|
+
pluginFilter?: ("media" | "r" | "agent" | "files" | "skills" | "documents" | "requirements" | "tables" | "charts" | "annotations" | "console" | "kernel")[] | undefined;
|
|
171
174
|
profile?: "layered-business" | "layered-presentation" | undefined;
|
|
172
175
|
url: string;
|
|
173
176
|
token: string;
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { Extensions } from './extensions.js';
|
|
2
|
+
import { extensionManagement } from './extension-contract.js';
|
|
1
3
|
import { EvidenceExporter, evidenceExportLimits } from './evidence-export.js';
|
|
2
|
-
import { Discovery } from './discovery.js';
|
|
4
|
+
import { Discovery, applicationEffects } from './discovery.js';
|
|
3
5
|
import * as http from 'node:http';
|
|
4
6
|
import * as fs from 'node:fs/promises';
|
|
5
7
|
import path from 'node:path';
|
|
@@ -20,7 +22,7 @@ import { BrowserAccess, localBrowserId, localBrowserPrincipal } from './browser-
|
|
|
20
22
|
import { addToAgent, connectLimits, mcpCommand, mcpName, outsideLabel } from './agent-connect.js';
|
|
21
23
|
import { NativeFolderPicker, nativeFolderLimits } from './native-folders.js';
|
|
22
24
|
import { homedir } from 'node:os';
|
|
23
|
-
import { serveWeb, servePageFrame, pageFramePath, webRoot, installedWebEntries } from './web-assets.js';
|
|
25
|
+
import { serveWeb, servePageFrame, pageFramePath, extensionFramePath, webRoot, installedWebEntries } from './web-assets.js';
|
|
24
26
|
import { Presentation } from './presentation.js';
|
|
25
27
|
import { presentationSchemas } from './presentation-contracts.js';
|
|
26
28
|
import { isNativeTime } from './native-time.js';
|
|
@@ -178,6 +180,7 @@ function explainFailure(e) {
|
|
|
178
180
|
}
|
|
179
181
|
export class RhoService {
|
|
180
182
|
config;
|
|
183
|
+
extensions;
|
|
181
184
|
plugins;
|
|
182
185
|
state;
|
|
183
186
|
bridge;
|
|
@@ -377,6 +380,7 @@ export class RhoService {
|
|
|
377
380
|
if (typeof value === 'number')
|
|
378
381
|
requireThat(Number.isSafeInteger(value) && (key === 'port' ? value >= 0 && value <= 65535 : value > 0), 'invalid', 'Invalid service budget.', { field: key });
|
|
379
382
|
}
|
|
383
|
+
this.extensions = new Extensions(path.join(config.stateDir, 'plugins'), config.extensions);
|
|
380
384
|
this.plugins = new Plugins(config);
|
|
381
385
|
this.state = new State(config.root, config.stateDir);
|
|
382
386
|
this.bridge = new CoreBridge(config.coreBinary, config.rscript);
|
|
@@ -484,6 +488,7 @@ export class RhoService {
|
|
|
484
488
|
await this.plugins.load();
|
|
485
489
|
this.plugins.installUi(await installedWebEntries(this.config.webRoot), Object.keys(operations));
|
|
486
490
|
await this.state.init();
|
|
491
|
+
await this.extensions.init();
|
|
487
492
|
// A person who starts Rho on a fresh state sees one finished study beside their own work.
|
|
488
493
|
// It is copied into the projects folder once and is otherwise an ordinary project.
|
|
489
494
|
this.example = await seedExampleProject(this.state, { enabled: this.config.example === true }).catch(() => null);
|
|
@@ -558,11 +563,11 @@ export class RhoService {
|
|
|
558
563
|
const capabilities = new Capabilities(this.plugins, project);
|
|
559
564
|
const state = await capabilities.state();
|
|
560
565
|
// The shared project listing remains available alongside an Agent's native tools.
|
|
561
|
-
const available = Object.keys(operations).filter(m => !m.startsWith('agent.activity.') && !m.startsWith('session.') && !m.startsWith('conversation.') && (!m.startsWith('project.') || m === 'project.entries') && !m.startsWith('requirements.prepare') && !m.startsWith('workspace.restore') && !['task.routing.put', 'binding.activity.append', 'feedback.submit', 'view.observe', 'view.observations.read', 'task.map.respond', 'annotation.decide', 'evidence.export', 'evidence.preview', 'console.run', 'console.stop', 'kernel.stop'].includes(m));
|
|
566
|
+
const available = Object.keys(operations).filter(m => !extensionManagement.includes(m) && !m.startsWith('agent.activity.') && !m.startsWith('agent.sessions.') && !m.startsWith('session.') && !m.startsWith('conversation.') && (!m.startsWith('project.') || m === 'project.entries') && !m.startsWith('requirements.prepare') && !m.startsWith('workspace.restore') && !['task.routing.put', 'binding.activity.append', 'feedback.submit', 'view.observe', 'view.observations.read', 'task.map.respond', 'annotation.decide', 'evidence.export', 'evidence.preview', 'console.run', 'console.stop', 'kernel.stop'].includes(m));
|
|
562
567
|
const profileMethods = profile === 'layered-presentation' ? ['binding.open', 'capability.index', 'capability.resolve', 'workspace.observe', 'workspace.enhance'] : available.filter(m => !['workspace.observe', 'workspace.enhance', ...(profile ? ['skill.list', 'skill.read', 'protocol.describe'] : [])].includes(m));
|
|
563
568
|
const selected = methods ?? (profile === 'layered-presentation' ? profileMethods : (await Promise.all(profileMethods.map(async (method) => [method, await capabilities.isAvailable(method, state)]))).filter(([, enabled]) => enabled).map(([method]) => method));
|
|
564
569
|
requireThat(profile !== 'layered-presentation' || selected.every(m => profileMethods.includes(m)), 'scope', 'The selected role cannot receive broader methods.');
|
|
565
|
-
requireThat(selected.every(m => !!operations[m] && !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.');
|
|
570
|
+
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.');
|
|
566
571
|
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.');
|
|
567
572
|
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 });
|
|
568
573
|
const token = randomBytes(32).toString('hex');
|
|
@@ -656,8 +661,8 @@ export class RhoService {
|
|
|
656
661
|
}
|
|
657
662
|
if (!url.pathname.startsWith('/api/')) {
|
|
658
663
|
requireThat((req.method === 'GET' || req.method === 'HEAD') && !url.search, 'not_found', 'Use the same-origin browser entry without query credentials.');
|
|
659
|
-
if (url.pathname === pageFramePath) {
|
|
660
|
-
servePageFrame(res, req.method === 'HEAD');
|
|
664
|
+
if (url.pathname === pageFramePath || url.pathname === extensionFramePath) {
|
|
665
|
+
servePageFrame(res, req.method === 'HEAD', url.pathname === extensionFramePath);
|
|
661
666
|
return;
|
|
662
667
|
}
|
|
663
668
|
await serveWeb(url.pathname, res, this.config.webRoot, req.method === 'HEAD');
|
|
@@ -688,7 +693,7 @@ export class RhoService {
|
|
|
688
693
|
requireThat(read.ok, read.ok ? 'invalid' : read.kind, read.ok ? '' : (read.problems[0]?.fix ? `Request does not match the versioned schema: ${read.problems[0].fix}` : 'Request does not match the versioned schema.'), read.ok ? {} : { problems: read.problems, remedy: 'Fix the named fields and resend the same request identity.' });
|
|
689
694
|
const request = read.request, contractRef = typeof req.headers['x-rho-contract-ref'] === 'string' ? req.headers['x-rho-contract-ref'] : undefined;
|
|
690
695
|
let value;
|
|
691
|
-
if (body.projectId && method.startsWith('kernel.')) {
|
|
696
|
+
if (body.projectId && (method.startsWith('kernel.') || method === 'extension.call')) {
|
|
692
697
|
// A cell may run for minutes and a run may wait for it. The live sessions keep their own
|
|
693
698
|
// order, so waiting holds no other request of the project, an interrupt among them.
|
|
694
699
|
const projectId = body.projectId;
|
|
@@ -862,6 +867,14 @@ export class RhoService {
|
|
|
862
867
|
requireThat(this.state.principals.some(current => current.id === principal.id && current.tokenHash === principal.tokenHash && current.methods.includes(method) && (!current.expiresAt || Date.parse(current.expiresAt) > Date.now())), 'unauthorized', 'Session authorization expired or was revoked before this operation.');
|
|
863
868
|
if (method === 'protocol.describe')
|
|
864
869
|
return describeProtocol(protocol, principal.methods, b);
|
|
870
|
+
if (extensionManagement.includes(method)) {
|
|
871
|
+
requireThat(principal.id === 'operator' || principal.id === localBrowserId, 'scope', 'Plugin installation and management belong to the person at this machine.');
|
|
872
|
+
return method === 'extension.install' ? this.extensions.install(b.source, b.replace, b.expectedDigest) : this.extensions.manage(b.pluginId, b.expectedDigest, b.action);
|
|
873
|
+
}
|
|
874
|
+
if (method === 'extension.list' && !b.projectId) {
|
|
875
|
+
requireThat(principal.id === 'operator' || principal.id === localBrowserId, 'scope', 'A project-scoped caller must supply its project.');
|
|
876
|
+
return this.extensions.list();
|
|
877
|
+
}
|
|
865
878
|
// A dry run of any operation body: the same schema the server enforces, with the
|
|
866
879
|
// edit each problem needs. Nothing is executed and no project is touched.
|
|
867
880
|
if (method === 'protocol.validate') {
|
|
@@ -936,6 +949,32 @@ export class RhoService {
|
|
|
936
949
|
const workspace = new Workspace(p, owners, this.config.workspace, this.config.interactions);
|
|
937
950
|
const interactions = new Interactions(p, owners, this.config.interactions);
|
|
938
951
|
const routing = new TaskRouting(p, this.config.capabilityLimits?.maxReceipts, this.config.maxRetainedMetadataBytes);
|
|
952
|
+
if (method.startsWith('extension.')) {
|
|
953
|
+
if (contractRef) {
|
|
954
|
+
const reader = new Discovery(this.plugins, p, principal, await routing.read(), await readJson(path.join(await this.dataDir(p), 'bindings.json'), []));
|
|
955
|
+
await reader.assertContract(method, contractRef);
|
|
956
|
+
}
|
|
957
|
+
if (method === 'extension.list')
|
|
958
|
+
return this.extensions.list();
|
|
959
|
+
if (method === 'extension.describe')
|
|
960
|
+
return this.extensions.describe(b.pluginId);
|
|
961
|
+
if (method === 'extension.content')
|
|
962
|
+
return this.extensions.content(b.pluginId, b.expectedDigest, b.kind, b.entryId);
|
|
963
|
+
if (method === 'extension.read')
|
|
964
|
+
return this.extensions.read(b.invocationId, principal.id, p.id);
|
|
965
|
+
if (method === 'extension.call')
|
|
966
|
+
return this.extensions.call(b, principal.id, p.root, async (target, body, effect) => {
|
|
967
|
+
requireThat(principal.methods.includes(target), 'scope', 'The caller has not been granted this Rho method.');
|
|
968
|
+
requireThat(!body?.projectId || body.projectId === p.id, 'scope', 'A plugin cannot delegate into another project.');
|
|
969
|
+
const request = readOperation(target, { ...body, projectId: p.id });
|
|
970
|
+
requireThat(request.ok, 'invalid', 'Plugin delegated an invalid Rho request.');
|
|
971
|
+
const capabilities = new Capabilities(this.plugins, p);
|
|
972
|
+
const status = (await capabilities.operationStatus(principal, await capabilities.state())).find(op => op.method === target);
|
|
973
|
+
requireThat(effect !== 'read' || (status?.effect ?? applicationEffects[target]) === 'read', 'scope', 'A read operation cannot delegate a mutating Rho method.');
|
|
974
|
+
const run = () => this.dispatch(request.request, principal);
|
|
975
|
+
return target.startsWith('kernel.') ? run() : this.serial(p.id, run);
|
|
976
|
+
});
|
|
977
|
+
}
|
|
939
978
|
// A person's own view of the folder they selected. The tab is confined to this one
|
|
940
979
|
// project, so it may list that folder and open a text file from it; registering
|
|
941
980
|
// resources and execution stay Agent/operator work.
|
|
@@ -1150,6 +1189,13 @@ export class RhoService {
|
|
|
1150
1189
|
}
|
|
1151
1190
|
// The person at this machine talks to a conversation Rho started; Agents and narrower links
|
|
1152
1191
|
// cannot start one, and an Agent started this way cannot start another.
|
|
1192
|
+
if (method.startsWith('agent.sessions.')) {
|
|
1193
|
+
requireThat(principal.id === localBrowserId, 'unauthorized', 'External conversation history belongs to the person at this machine.');
|
|
1194
|
+
const agent = this.plugins.need('agent');
|
|
1195
|
+
if (method === 'agent.sessions.list')
|
|
1196
|
+
return agent.listExternalSessions(p.root);
|
|
1197
|
+
return agent.readExternalSession(p.root, b);
|
|
1198
|
+
}
|
|
1153
1199
|
if (method === 'project.work.read') {
|
|
1154
1200
|
requireThat(principal.id === localBrowserId, 'scope', 'Project work is available to the person at this machine.');
|
|
1155
1201
|
const agent = (await new Capabilities(this.plugins, p).state()).enabled.includes('agent') ? this.plugins.optional('agent') : null;
|
|
@@ -1166,7 +1212,7 @@ export class RhoService {
|
|
|
1166
1212
|
}
|
|
1167
1213
|
return projectWork({ project: p, cursor: (await this.events(p)).cursor, conversations: await this.conversations.work(p, b.limit),
|
|
1168
1214
|
documents: await owners.documentSummaries(), activity: (await this.projectActivity(p)).entries,
|
|
1169
|
-
native, external: agent?.readExternalAcp(p.root), inspect: agent?.observeSessionFiles });
|
|
1215
|
+
native, external: agent?.readExternalAcp(p.root), observed: agent?.listExternalSessions(p.root), readObserved: agent?.readExternalSession, inspect: agent?.observeSessionFiles });
|
|
1170
1216
|
}
|
|
1171
1217
|
if (method.startsWith('conversation.')) {
|
|
1172
1218
|
requireThat(principal.id === localBrowserId, 'unauthorized', 'Conversations started in Rho belong to the person at this machine. Open Rho in a browser on this machine.');
|
|
@@ -1715,6 +1761,7 @@ export class RhoService {
|
|
|
1715
1761
|
return this.state.principals.some(p => p.id === id && p.tokenHash === tokenHash && p.methods.includes('observation.snapshot') && (!p.expiresAt || Date.parse(p.expiresAt) > Date.now()));
|
|
1716
1762
|
}
|
|
1717
1763
|
async close() {
|
|
1764
|
+
this.extensions.close();
|
|
1718
1765
|
this.closing = true;
|
|
1719
1766
|
this.folderPicker.close();
|
|
1720
1767
|
for (const s of this.subscribers)
|