@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
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Develop and install third-party plugins
|
|
2
|
+
|
|
3
|
+
Rho can install independent npm packages while it is running. A package supplies
|
|
4
|
+
operations, Skills and standalone HTML views; it does not require rebuilding Rho
|
|
5
|
+
or becoming a base dependency. The first consumer is the optional
|
|
6
|
+
[sclet adapter](https://github.com/YuLab-SMU/Rho-plugins/tree/main/examples/sclet-plugin).
|
|
7
|
+
Its scientific implementation remains in sclet and its objects remain in the
|
|
8
|
+
project's shared R session. See the [observed episode](THIRD-PARTY-PLUGINS-EPISODE.md).
|
|
9
|
+
|
|
10
|
+
This is source functionality after Rho 0.5.4, not a new npm release. Build the
|
|
11
|
+
current checkout with `npm run build` and use `node dist/cli.js` in place of
|
|
12
|
+
`rho` until a distribution containing it is installed.
|
|
13
|
+
|
|
14
|
+
## Install and manage
|
|
15
|
+
|
|
16
|
+
Open a project, then **Plugins → Third-party plugins**. Install a directory entry
|
|
17
|
+
or enter a package source. The installed card shows its version, source, enabled
|
|
18
|
+
state and content digest. Open its view, read its guide, update, disable or remove
|
|
19
|
+
it there. An invalid source or failed update leaves the previous selection intact.
|
|
20
|
+
|
|
21
|
+
Only install packages you trust. Hosts run native code with the Rho process user's
|
|
22
|
+
OS permissions. Disabling npm lifecycle scripts prevents install hooks; it does
|
|
23
|
+
not make plugin execution a sandbox. No approval is added to already authorized
|
|
24
|
+
analysis. Paired browsers and Agent connections cannot install or manage packages.
|
|
25
|
+
|
|
26
|
+
The equivalent commands use the running service's private connection file:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
rho plugin add /absolute/path/my-plugin
|
|
30
|
+
rho plugin add /absolute/path/my-plugin-0.1.0.tgz
|
|
31
|
+
rho plugin add @your-org/rho-plugin@0.1.0
|
|
32
|
+
rho plugin add 'git+https://github.com/your-org/repository.git#COMMIT:packages/plugin'
|
|
33
|
+
rho plugin list
|
|
34
|
+
rho plugin update my-plugin
|
|
35
|
+
rho plugin update my-plugin --source @your-org/rho-plugin@0.2.0
|
|
36
|
+
rho plugin disable my-plugin
|
|
37
|
+
rho plugin enable my-plugin
|
|
38
|
+
rho plugin remove my-plugin
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Add `--connection /absolute/service-state/connection.json` for a nondefault
|
|
42
|
+
service. The default is `~/Rho/state/connection.json`. These commands do not start
|
|
43
|
+
a service. Never share the connection file. Public HTTPS `.tgz` URLs are also
|
|
44
|
+
accepted. Git sources require Git; package staging requires npm. Git submodules
|
|
45
|
+
and build hooks are not run. Build and include runtime files before distribution.
|
|
46
|
+
|
|
47
|
+
Installation stages an isolated dependency tree using npm with `--ignore-scripts`
|
|
48
|
+
and `--install-links`, validates the manifest and contained files, hashes the tree,
|
|
49
|
+
then atomically selects it. The source selector, resolved Git commit when present,
|
|
50
|
+
package version and runtime-tree digest remain visible. Listing does not import
|
|
51
|
+
plugin code. Content and calls verify the installed tree before use.
|
|
52
|
+
|
|
53
|
+
Disable blocks new calls and views. Remove takes the package out of the installed
|
|
54
|
+
selection. Both preserve native tasks, R sessions, project files and receipts.
|
|
55
|
+
Package revisions are retained in the service's `plugins/versions` directory;
|
|
56
|
+
remove is not a disk-purge command. Closing a view only releases its renderer.
|
|
57
|
+
|
|
58
|
+
## Create a package
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
rho plugin create my-plugin --id lab.my-plugin
|
|
62
|
+
rho plugin check ./my-plugin
|
|
63
|
+
rho plugin dev ./my-plugin
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`create` refuses to overwrite a directory. The generated package has a working
|
|
67
|
+
greeting operation, Skill and view. `dev` installs checked snapshots on source
|
|
68
|
+
changes, ignoring `.git` and `node_modules`. Reopen a view to load a new snapshot.
|
|
69
|
+
Invalid edits retain the last working installation. No Rho rebuild is needed.
|
|
70
|
+
|
|
71
|
+
| File | Responsibility |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| `package.json` | npm name/version, runtime files and `rhoPlugin: "rho-plugin.json"` |
|
|
74
|
+
| `rho-plugin.json` | API version 1, unique plugin id, title, description and entries |
|
|
75
|
+
| `host.mjs` | `run(operation, input, context)` over the domain's public capabilities |
|
|
76
|
+
| `SKILL.md` | When/how to use the package, prerequisites, failures and result limits |
|
|
77
|
+
| `view.html` | Prebuilt HTML with inline scripts/styles; may bundle a framework |
|
|
78
|
+
|
|
79
|
+
Each operation declares `id`, `title`, `description`, `effect` (`read` or
|
|
80
|
+
`native`), the Rho `methods` it delegates, and an object `inputSchema`. Each Skill
|
|
81
|
+
or view declares `id`, `title` and a package-relative `file`. The manifest's
|
|
82
|
+
`operations`, `skills` and `views` arrays are required, but may be empty. A host
|
|
83
|
+
is required only when operations exist. The checker validates entry containment,
|
|
84
|
+
API compatibility, identifiers, duplicate entries, schemas and entry sizes.
|
|
85
|
+
|
|
86
|
+
```js
|
|
87
|
+
export async function run(operation, input, context) {
|
|
88
|
+
if (operation === 'read-sessions') {
|
|
89
|
+
const value = await context.call('kernel.read', { limit: 1 });
|
|
90
|
+
return { sessions: value.sessions };
|
|
91
|
+
}
|
|
92
|
+
throw new Error('Unknown operation');
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`context.projectRoot` is the selected project, `context.invocationId` identifies
|
|
97
|
+
this call, and `context.changeId(label)` supplies a stable delegated write id.
|
|
98
|
+
`context.call(method, body)` validates the ordinary Rho schema, original caller's
|
|
99
|
+
grant, current project and owner availability. Declare every delegated method.
|
|
100
|
+
A read operation cannot delegate a mutating method. It must also keep its own
|
|
101
|
+
native work read-only; native host code is trusted, not OS-confined.
|
|
102
|
+
|
|
103
|
+
The host runs in a disposable child process for each call, without the service
|
|
104
|
+
token. Keep long work in its actual native owner and return its handle promptly.
|
|
105
|
+
For example sclet submits `kernel.run` with `waitMs: 0`, then observes the same
|
|
106
|
+
cell with `kernel.read`. Do not create a second scientific state store in Rho.
|
|
107
|
+
|
|
108
|
+
## Agent and view entries
|
|
109
|
+
|
|
110
|
+
Agents discover installed packages through `extension.list`, read their schema
|
|
111
|
+
with `extension.describe`, and read a Skill through `extension.content` with
|
|
112
|
+
`kind: "skill"`. These operations are available through the existing layered MCP
|
|
113
|
+
discovery. They then call:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"pluginId": "lab.my-plugin",
|
|
118
|
+
"expectedDigest": "64-character digest from extension.describe",
|
|
119
|
+
"operation": "greet",
|
|
120
|
+
"input": { "name": "World" },
|
|
121
|
+
"changeId": "original-request-1"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This is the body of `extension.call`; direct HTTP clients also supply `projectId`.
|
|
126
|
+
The returned receipt separates plugin completion from the native result in
|
|
127
|
+
`value`. Native invocations retain pending/completed/unknown receipts and delegated
|
|
128
|
+
requests/replies. Repeat the **exact** request to observe the same receipt, even
|
|
129
|
+
after update or removal. `extension.read` reads it by `invocationId` for the same
|
|
130
|
+
caller/project. A restart converts pending receipts to unknown; it never replays
|
|
131
|
+
them. Read-only calls do not accumulate durable receipts. No plugin receipt makes
|
|
132
|
+
native work Core-managed or establishes scientific validity.
|
|
133
|
+
|
|
134
|
+
A view runs in an iframe with an opaque origin, no service credential or storage
|
|
135
|
+
access, and no network loading. Bundle assets inline or as data URLs. Its bridge
|
|
136
|
+
is restricted to the selected package and digest:
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
const receipt = await rho.call('greet', { name: 'World' });
|
|
140
|
+
output.textContent = receipt.state === 'completed'
|
|
141
|
+
? receipt.value.greeting : receipt.error;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Pass a third argument to `rho.call` to retain an explicit original changeId.
|
|
145
|
+
The default generates one per deliberate call. A stale digest is refused; reopen
|
|
146
|
+
the view after an update. Several views may be declared and selected in the plugin
|
|
147
|
+
panel. These runtime views currently live on Plugins, not in Studio's resource
|
|
148
|
+
binding/layout contract. Existing compiled React extensions still use
|
|
149
|
+
[UI extension authoring](PLUGIN-AUTHORING.md).
|
|
150
|
+
|
|
151
|
+
## Contribute and distribute
|
|
152
|
+
|
|
153
|
+
Keep your repository, license, dependencies and release history independent. Check
|
|
154
|
+
the packed artifact, not just source: `npm pack --ignore-scripts`, then install
|
|
155
|
+
that tarball in a disposable Rho project. Exercise missing prerequisites, an input
|
|
156
|
+
error, recovery, update and removal. Record the Rho/API version, package digest,
|
|
157
|
+
native versions and actual result. Preserve user data and avoid credentials in
|
|
158
|
+
examples or diagnostics. Publish to your own npm namespace or share a public Git
|
|
159
|
+
source/tarball; Rho does not publish packages on your behalf.
|
|
160
|
+
|
|
161
|
+
No central registration is needed to install a package. To be discoverable in the
|
|
162
|
+
bundled directory, submit a PR editing
|
|
163
|
+
[`src/extension-directory.ts`](../src/extension-directory.ts), linking the public
|
|
164
|
+
source, license, author guide and a reproducible acceptance record. Propose an
|
|
165
|
+
immutable Git commit or exact npm version. Maintainers check the declared scope;
|
|
166
|
+
directory inclusion is not a security audit, release signature or scientific
|
|
167
|
+
endorsement. Future releases of the package can update that entry independently.
|
|
168
|
+
|
|
169
|
+
## Resource limits and current scope
|
|
170
|
+
|
|
171
|
+
Use the service's `--limits` JSON file with an `extensions` object to override
|
|
172
|
+
limits. Exceeding a limit refuses new work, retains the previous selected package
|
|
173
|
+
and obtained receipts, or records an unknown result after an interrupted call.
|
|
174
|
+
|
|
175
|
+
| Protected resource | Default configuration |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| Installed selections / retained revisions | `maxPlugins: 32`, `maxVersions: 128` |
|
|
178
|
+
| Admitted runtime tree | `maxFiles: 20000`, `maxPackageBytes: 134217728` |
|
|
179
|
+
| Skill/view bytes | `maxContentBytes: 524288` |
|
|
180
|
+
| Durable invocations / receipt and result bytes | `maxReceipts: 2000`, `maxResultBytes: 1048576` |
|
|
181
|
+
| Concurrent calls / delegated calls per invocation | `maxRunning: 4`, `maxDelegations: 32` |
|
|
182
|
+
| Worker / each install subprocess | `timeoutMs: 30000`, `installTimeoutMs: 180000` |
|
|
183
|
+
|
|
184
|
+
The tree-size check applies after npm staging; it is not a disk quota on npm/Git.
|
|
185
|
+
A timed-out worker does not prove a delegated native task stopped. Observe its
|
|
186
|
+
original identity. Retention limits refuse rather than silently evict identities;
|
|
187
|
+
raise them deliberately when appropriate. Manifests are capped at 64 KiB, with
|
|
188
|
+
32 operations and 16 entries of each content kind. The browser permits four
|
|
189
|
+
pending calls per view. macOS with Node 22 is observed in this episode; Windows,
|
|
190
|
+
Linux and independent developer usability have not been established for this path.
|
|
@@ -390,3 +390,42 @@ fetched; an explicit response excludes replies already present in that context.
|
|
|
390
390
|
ACP retains Codex's native MCP spelling while identifying its operation, and names
|
|
391
391
|
an otherwise unnamed permission from the original tool update. These observations
|
|
392
392
|
do not expand native permission or grant access to arbitrary session history.
|
|
393
|
+
|
|
394
|
+
## External conversations
|
|
395
|
+
|
|
396
|
+
Agent 0.4.29 exports `dsh-session` for a Harness adapter to retain a read-only
|
|
397
|
+
observation of a native version-4 session. `observeDshSession(projectRoot, header,
|
|
398
|
+
events)` requires the real native working folder to equal the selected project.
|
|
399
|
+
It preserves session, turn and tool-call identities. Repeated cuts replace the
|
|
400
|
+
same record; a changed prefix or backwards cursor is refused. It never creates
|
|
401
|
+
an Agent, executes a tool, changes a grant or resumes a runtime.
|
|
402
|
+
|
|
403
|
+
`external-session` supplies bounded `listExternalSessions` and
|
|
404
|
+
`readExternalSession` reads. The operation contracts are `agent.sessions.list`
|
|
405
|
+
and `agent.sessions.read`; an embedding application supplies authorization.
|
|
406
|
+
`ui-external-session` renders prompts, visible replies, operations and results,
|
|
407
|
+
with input explicitly retained by the native client. Reasoning, injected context
|
|
408
|
+
and system messages are excluded; credential-shaped strings are redacted.
|
|
409
|
+
This is an observation of the client's reports, not an independent execution proof.
|
|
410
|
+
|
|
411
|
+
A successful resource-registration reply carries its exact resource identity and
|
|
412
|
+
version. The application must verify that tuple against its own project resources.
|
|
413
|
+
A native deliverable without a receipt is only a reported path, with no historical
|
|
414
|
+
bytes asserted. Tool return, native exit code and turn completion remain separate.
|
|
415
|
+
An unfinished turn with no observation for 10 seconds has an unknown outcome.
|
|
416
|
+
|
|
417
|
+
`externalSessionLimits` names the fixed local observation budgets: 64 sessions
|
|
418
|
+
per project, 200 retained turns per session, 256 tools per turn, 32,768 source
|
|
419
|
+
events / 32 MiB per cut, 32,768 characters per prompt/reply and earlier-reply
|
|
420
|
+
collection, 8,192 characters per tool field, and 4 MiB per stored record.
|
|
421
|
+
Text/tools/earlier turns beyond their budgets are omitted with counts. An
|
|
422
|
+
oversized source, conflicting cut, invalid record or busy writer retains the
|
|
423
|
+
previous record and reports failure. Storage is `.rho/agent/external`, contained
|
|
424
|
+
in the project without symlink directories; atomic replacement leaves readers a
|
|
425
|
+
complete record. A positively dead PID lock can be reclaimed; an unknown/live
|
|
426
|
+
lock cannot. Reads create no directories or runtime state. These limits protect
|
|
427
|
+
local storage and observation work; they do not limit the native Agent.
|
|
428
|
+
|
|
429
|
+
`test/external-session.test.mjs` checks identity, prefix conflicts, omissions,
|
|
430
|
+
missing outcomes, redaction, project containment, retained failure and pagination.
|
|
431
|
+
The Rho application records its separate real Harness/browser acceptance.
|
|
@@ -10,5 +10,6 @@ export interface CaptureOptions {
|
|
|
10
10
|
maxEventBytes?: number;
|
|
11
11
|
}
|
|
12
12
|
export declare function canonicalActivity(value: any): string;
|
|
13
|
+
export declare function activityPayload(value: any, options: CaptureOptions): any;
|
|
13
14
|
export declare function normalizeClaude(records: any[], sessionId: string, project: string, options?: CaptureOptions): ObservedEvent[];
|
|
14
15
|
export declare function normalizeDsh(records: any[], options?: CaptureOptions): ObservedEvent[];
|
|
@@ -9,7 +9,7 @@ export function canonicalActivity(value) {
|
|
|
9
9
|
const sensitive = /(?:^|[_-])(?:authorization|cookie|password|passwd|secret|api[_-]?key|access[_-]?token|auth[_-]?token|token)$/i;
|
|
10
10
|
const hidden = /^(?:thinking|reasoning|reasoning_content|signature)$/i;
|
|
11
11
|
const txt = (v) => typeof v === 'string' ? v : Array.isArray(v) ? v.filter(b => b?.type === 'text').map(b => b.text).join('\n') : '';
|
|
12
|
-
function
|
|
12
|
+
export function activityPayload(value, options) {
|
|
13
13
|
let redacted = false, truncated = false;
|
|
14
14
|
const cap = options.maxFieldBytes ?? 8192;
|
|
15
15
|
const identities = new Set(['native_id', 'call_id', 'root_call_id', 'job_id', 'parent_message_id']);
|
|
@@ -76,7 +76,7 @@ function inputSummary(input) {
|
|
|
76
76
|
function chained(items, options) {
|
|
77
77
|
let prefix = hash(activityNormalizer);
|
|
78
78
|
return items.map(({ kind, data }, sequence) => {
|
|
79
|
-
const safe =
|
|
79
|
+
const safe = activityPayload(data, options);
|
|
80
80
|
prefix = hash(prefix + canonicalActivity({ kind, data: safe }));
|
|
81
81
|
return { sequence, kind, data: { ...safe, prefix_sha256: prefix } };
|
|
82
82
|
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ExternalSessionRecord } from './external-session.js';
|
|
2
|
+
export declare function externalProjectPath(root: string, input: unknown): string | null;
|
|
3
|
+
/** Project the native committed log. System prompts, injected context and reasoning
|
|
4
|
+
* are excluded. Original session, turn and call identities survive every backfill. */
|
|
5
|
+
export declare function projectDshSession(root: string, header: any, events: readonly any[], observedAt?: string): ExternalSessionRecord;
|
|
6
|
+
export declare function observeDshSession(root: string, header: any, events: readonly any[]): import("./external-session.js").ExternalSessionSummary;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { activityPayload, canonicalActivity } from './activity-normalize.js';
|
|
5
|
+
import { externalSessionId, retainedExternalSession, saveExternalSession } from './external-session.js';
|
|
6
|
+
import { externalSessionLimits as limits } from './external-session-contract.js';
|
|
7
|
+
const text = (v) => typeof v === 'string' ? v : Array.isArray(v) ? v.filter(b => b?.type === 'text' && typeof b.text === 'string').map(b => b.text).join('\n') : '';
|
|
8
|
+
const time = (v) => { const n = typeof v === 'number' ? v : Date.parse(v); if (!Number.isFinite(n))
|
|
9
|
+
throw new Error('Native event has no valid timestamp.'); return new Date(n).toISOString(); };
|
|
10
|
+
const hash = (v) => createHash('sha256').update(canonicalActivity(v)).digest('hex');
|
|
11
|
+
const safe = (value, cap = limits.maxTextChars) => (activityPayload({ text: value.replace(/\b([\w-]*(?:token|cookie|authorization)["']?\s*[=:]\s*)(?:"[^"]*"|'[^']*'|[^\s'";]+)/gi, '$1[redacted]') }, { content: true, maxFieldBytes: cap * 4, maxEventBytes: cap * 8 }).text ?? '').slice(0, cap).replace(/[\uD800-\uDBFF]$/, '');
|
|
12
|
+
export function externalProjectPath(root, input) {
|
|
13
|
+
if (typeof input !== 'string' || input.length > 1024 || input.includes('\0'))
|
|
14
|
+
return null;
|
|
15
|
+
const relative = path.relative(root, path.resolve(root, input)).split(path.sep).join('/');
|
|
16
|
+
return relative && !relative.startsWith('../') && !path.isAbsolute(relative) && !relative.split('/').some(p => ['..', '.rho', '.git'].includes(p)) ? relative : null;
|
|
17
|
+
}
|
|
18
|
+
function paths(root, args, content) {
|
|
19
|
+
const candidates = [args?.path, args?.file_path, args?.filename, args?.declaration?.path];
|
|
20
|
+
// These are explicitly labelled mentions, never inferred file writes or historical snapshots.
|
|
21
|
+
for (const m of content.matchAll(/(?<![\w/.:\-])\/?(?:[\w.\-\u0080-\uFFFF]+\/)*[\w.\-\u0080-\uFFFF]+\.(?:R|r|py|md|csv|tsv|png|svg|pdf|json|txt|qmd|Rmd)\b/g))
|
|
22
|
+
candidates.push(m[0]);
|
|
23
|
+
return [...new Set(candidates.map(p => externalProjectPath(root, p)).filter((p) => !!p))].slice(0, 32);
|
|
24
|
+
}
|
|
25
|
+
/** Project the native committed log. System prompts, injected context and reasoning
|
|
26
|
+
* are excluded. Original session, turn and call identities survive every backfill. */
|
|
27
|
+
export function projectDshSession(root, header, events, observedAt = new Date().toISOString()) {
|
|
28
|
+
root = fs.realpathSync(root);
|
|
29
|
+
if (!header || header.version !== 4 || typeof header.id !== 'string' || !/^[A-Za-z0-9._:@-]{1,128}$/.test(header.id) || typeof header.cwd !== 'string' || fs.realpathSync(header.cwd) !== root)
|
|
30
|
+
throw new Error('Harness session does not match the selected project.');
|
|
31
|
+
if (events.length > limits.maxEvents || Buffer.byteLength(JSON.stringify(events)) > limits.maxSourceBytes)
|
|
32
|
+
throw new Error('Harness observation exceeds source limits; previous observation retained.');
|
|
33
|
+
const turns = [], byTurn = new Map(), tools = new Map();
|
|
34
|
+
let current, title = '', lastAt = time(header.createdAt), omittedTurns = 0;
|
|
35
|
+
const makeTurn = (id, at) => {
|
|
36
|
+
let t = byTurn.get(id);
|
|
37
|
+
if (t)
|
|
38
|
+
return t;
|
|
39
|
+
t = { id: 'turn:' + id, at, state: 'unknown', prompt: '', reply: '', messages: [], tools: [], results: [], omittedTools: 0, omittedChars: 0 };
|
|
40
|
+
byTurn.set(id, t);
|
|
41
|
+
turns.push(t);
|
|
42
|
+
if (turns.length > limits.maxTurns) {
|
|
43
|
+
const gone = turns.shift();
|
|
44
|
+
omittedTurns++;
|
|
45
|
+
for (const [k, v] of tools)
|
|
46
|
+
if (v.turn === gone)
|
|
47
|
+
tools.delete(k);
|
|
48
|
+
for (const [k, v] of byTurn)
|
|
49
|
+
if (v === gone)
|
|
50
|
+
byTurn.delete(k);
|
|
51
|
+
}
|
|
52
|
+
return t;
|
|
53
|
+
};
|
|
54
|
+
const appendText = (t, field, value, replace = false) => {
|
|
55
|
+
const full = replace ? value : t[field] + (t[field] ? '\n\n' : '') + value;
|
|
56
|
+
t[field] = safe(full);
|
|
57
|
+
t.omittedChars += Math.max(0, full.length - t[field].length);
|
|
58
|
+
};
|
|
59
|
+
for (let i = 0; i < events.length; i++) {
|
|
60
|
+
const e = events[i];
|
|
61
|
+
if (e.seq !== i)
|
|
62
|
+
throw new Error('Harness source has a missing or reordered native event.');
|
|
63
|
+
const d = e.data ?? {}, at = time(e.time);
|
|
64
|
+
lastAt = at;
|
|
65
|
+
if (e.type === 'session/title' && typeof d.title === 'string')
|
|
66
|
+
title = safe(d.title, 512);
|
|
67
|
+
if (e.type === 'turn/start') {
|
|
68
|
+
current = makeTurn(d.turn, at);
|
|
69
|
+
current.state = 'running';
|
|
70
|
+
}
|
|
71
|
+
if (e.type === 'user/message' && d.source?.kind === 'user') {
|
|
72
|
+
if (!current)
|
|
73
|
+
current = makeTurn(e.seq, at);
|
|
74
|
+
appendText(current, 'prompt', text(d.content));
|
|
75
|
+
}
|
|
76
|
+
if (e.type === 'assistant/message') {
|
|
77
|
+
const t = byTurn.get(d.turn) ?? current;
|
|
78
|
+
if (!t)
|
|
79
|
+
continue;
|
|
80
|
+
const message = text(d.message?.content);
|
|
81
|
+
if (message) {
|
|
82
|
+
if (t.reply)
|
|
83
|
+
t.messages.push({ at, text: t.reply });
|
|
84
|
+
while (t.messages.length > 200 || t.messages.reduce((n, m) => n + m.text.length, 0) > limits.maxTextChars)
|
|
85
|
+
t.omittedChars += t.messages.shift().text.length;
|
|
86
|
+
appendText(t, 'reply', message, true);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (e.type === 'tool/call') {
|
|
90
|
+
const t = byTurn.get(d.turn) ?? current;
|
|
91
|
+
if (!t || typeof d.callId !== 'string' || typeof d.name !== 'string')
|
|
92
|
+
continue;
|
|
93
|
+
if (tools.has(d.callId))
|
|
94
|
+
throw new Error('Harness call identity was reused.');
|
|
95
|
+
if (t.tools.length >= limits.maxTools) {
|
|
96
|
+
t.omittedTools++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
let args = d.arguments;
|
|
100
|
+
if (typeof args === 'string') {
|
|
101
|
+
try {
|
|
102
|
+
args = JSON.parse(args);
|
|
103
|
+
}
|
|
104
|
+
catch { }
|
|
105
|
+
}
|
|
106
|
+
const name = d.name === 'mcp__rho__rho_call' && typeof args?.operation === 'string' ? args.operation : d.name;
|
|
107
|
+
const input = args?.input ?? args;
|
|
108
|
+
const detail = safe(typeof input?.command === 'string' ? input.command : JSON.stringify(input) ?? '', limits.maxDetailChars);
|
|
109
|
+
const tool = { id: d.callId, at, name, detail, output: '', outcome: 'requested', paths: paths(root, input, detail) };
|
|
110
|
+
t.tools.push(tool);
|
|
111
|
+
tools.set(d.callId, { turn: t, tool });
|
|
112
|
+
}
|
|
113
|
+
if (e.type === 'tool/result') {
|
|
114
|
+
const message = d.message, item = tools.get(message?.toolCallId ?? message?.source?.callId);
|
|
115
|
+
if (!item)
|
|
116
|
+
continue;
|
|
117
|
+
const output = text(message?.content);
|
|
118
|
+
item.tool.output = safe(output, limits.maxDetailChars);
|
|
119
|
+
item.tool.outcome = message?.isError ? 'failed' : 'returned';
|
|
120
|
+
item.turn.omittedChars += Math.max(0, output.length - item.tool.output.length);
|
|
121
|
+
// Only a successful Rho registration/capture reply asserts an exact resource version.
|
|
122
|
+
if (['resource.register', 'resource.capture', 'agent.activity.capture'].includes(item.tool.name) && !message?.isError) {
|
|
123
|
+
try {
|
|
124
|
+
const response = JSON.parse(output), r = response.value?.resource ?? response.value;
|
|
125
|
+
const relative = externalProjectPath(root, r?.path);
|
|
126
|
+
if (response.ok === true && relative && /^[a-f0-9]{64}$/.test(r.id) && /^[a-f0-9]{64}$/.test(r.version) && typeof r.projectId === 'string' && item.turn.results.length < 64)
|
|
127
|
+
item.turn.results.push({ path: relative, id: r.id, version: r.version, projectId: r.projectId, toolId: item.tool.id });
|
|
128
|
+
}
|
|
129
|
+
catch { /* The bounded receipt can be incomplete; never fabricate a resource. */ }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (e.type === 'job/settled') {
|
|
133
|
+
const item = tools.get(d.toolCall?.rootCallId ?? d.toolCall?.callId), exit = typeof d.detail === 'string' ? /^exit code: (-?\d+)$/.exec(d.detail) : null;
|
|
134
|
+
if (item && exit)
|
|
135
|
+
item.tool.exitCode = Number(exit[1]);
|
|
136
|
+
}
|
|
137
|
+
if (e.type === 'deliverables/presented') {
|
|
138
|
+
const t = byTurn.get(d.turn) ?? current;
|
|
139
|
+
if (!t)
|
|
140
|
+
continue;
|
|
141
|
+
for (const file of d.files ?? []) {
|
|
142
|
+
const relative = externalProjectPath(root, file.path);
|
|
143
|
+
if (relative && !t.results.some(r => r.path === relative) && t.results.length < 64)
|
|
144
|
+
t.results.push({ path: relative, toolId: d.callId ?? 'event:' + e.seq });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (e.type === 'turn/end') {
|
|
148
|
+
const t = byTurn.get(d.turn);
|
|
149
|
+
if (!t)
|
|
150
|
+
continue;
|
|
151
|
+
t.state = d.reason?.kind === 'completed' ? 'completed' : d.reason?.kind === 'cancelled' ? 'stopped' : d.reason?.kind === 'error' ? 'failed' : 'unknown';
|
|
152
|
+
t.endedAt = at;
|
|
153
|
+
for (const tool of t.tools)
|
|
154
|
+
if (tool.outcome === 'requested')
|
|
155
|
+
tool.outcome = 'unknown';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const summary = { id: externalSessionId('dsh', header.id), producer: 'dsh', sessionId: header.id, title: title || turns.find(t => t.prompt)?.prompt.slice(0, 160) || 'Harness conversation', lastAt, observedAt,
|
|
159
|
+
state: turns.at(-1)?.state ?? 'unknown', turns: turns.length + omittedTurns, sourceCursor: events.length, omittedTurns, inputOwner: 'external' };
|
|
160
|
+
const result = { format: 1, projectRoot: root, summary, turns, sourceDigest: hash(events) };
|
|
161
|
+
// A suffix is retained at the record budget; gaps are explicit, earlier files stay untouched.
|
|
162
|
+
while (Buffer.byteLength(JSON.stringify(result)) > limits.maxRecordBytes && result.turns.length > 1) {
|
|
163
|
+
result.turns.shift();
|
|
164
|
+
result.summary.omittedTurns++;
|
|
165
|
+
}
|
|
166
|
+
if (Buffer.byteLength(JSON.stringify(result)) > limits.maxRecordBytes)
|
|
167
|
+
throw new Error('Harness turn exceeds maxRecordBytes; previous observation retained.');
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
export function observeDshSession(root, header, events) {
|
|
171
|
+
const old = retainedExternalSession(root, 'dsh', header.id);
|
|
172
|
+
const value = projectDshSession(root, header, events);
|
|
173
|
+
return saveExternalSession(root, value, old ? hash(events.slice(0, old.summary.sourceCursor)) : undefined);
|
|
174
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export declare const externalSessionLimits: {
|
|
2
|
+
maxSessions: number;
|
|
3
|
+
maxTurns: number;
|
|
4
|
+
maxTools: number;
|
|
5
|
+
maxRecordBytes: number;
|
|
6
|
+
maxEvents: number;
|
|
7
|
+
maxSourceBytes: number;
|
|
8
|
+
maxTextChars: number;
|
|
9
|
+
maxDetailChars: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const externalResultSchema: {
|
|
12
|
+
type: string;
|
|
13
|
+
properties: Record<string, any>;
|
|
14
|
+
required: string[];
|
|
15
|
+
additionalProperties: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare const externalToolSchema: {
|
|
18
|
+
type: string;
|
|
19
|
+
properties: Record<string, any>;
|
|
20
|
+
required: string[];
|
|
21
|
+
additionalProperties: boolean;
|
|
22
|
+
};
|
|
23
|
+
export declare const externalTurnSchema: {
|
|
24
|
+
type: string;
|
|
25
|
+
properties: Record<string, any>;
|
|
26
|
+
required: string[];
|
|
27
|
+
additionalProperties: boolean;
|
|
28
|
+
};
|
|
29
|
+
export declare const externalSessionSummarySchema: {
|
|
30
|
+
type: string;
|
|
31
|
+
properties: Record<string, any>;
|
|
32
|
+
required: string[];
|
|
33
|
+
additionalProperties: boolean;
|
|
34
|
+
};
|
|
35
|
+
export declare const externalSessionSchemas: {
|
|
36
|
+
summary: {
|
|
37
|
+
type: string;
|
|
38
|
+
properties: Record<string, any>;
|
|
39
|
+
required: string[];
|
|
40
|
+
additionalProperties: boolean;
|
|
41
|
+
};
|
|
42
|
+
turn: {
|
|
43
|
+
type: string;
|
|
44
|
+
properties: Record<string, any>;
|
|
45
|
+
required: string[];
|
|
46
|
+
additionalProperties: boolean;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export declare const externalSessionOperations: {
|
|
50
|
+
'agent.sessions.list': {
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: string;
|
|
53
|
+
properties: Record<string, any>;
|
|
54
|
+
required: string[];
|
|
55
|
+
additionalProperties: boolean;
|
|
56
|
+
};
|
|
57
|
+
outputSchema: {
|
|
58
|
+
type: string;
|
|
59
|
+
properties: Record<string, any>;
|
|
60
|
+
required: string[];
|
|
61
|
+
additionalProperties: boolean;
|
|
62
|
+
};
|
|
63
|
+
requires: never[];
|
|
64
|
+
effect: "read";
|
|
65
|
+
};
|
|
66
|
+
'agent.sessions.read': {
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: string;
|
|
69
|
+
properties: Record<string, any>;
|
|
70
|
+
required: string[];
|
|
71
|
+
additionalProperties: boolean;
|
|
72
|
+
};
|
|
73
|
+
outputSchema: {
|
|
74
|
+
type: string;
|
|
75
|
+
properties: Record<string, any>;
|
|
76
|
+
required: string[];
|
|
77
|
+
additionalProperties: boolean;
|
|
78
|
+
};
|
|
79
|
+
requires: never[];
|
|
80
|
+
effect: "read";
|
|
81
|
+
};
|
|
82
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// A retained observation of another client's conversation. It has no input operation.
|
|
2
|
+
const text = { type: 'string' }, count = { type: 'integer', minimum: 0 };
|
|
3
|
+
const obj = (properties, required = Object.keys(properties)) => ({ type: 'object', properties, required, additionalProperties: false });
|
|
4
|
+
const array = (items, maxItems) => ({ type: 'array', items, maxItems });
|
|
5
|
+
export const externalSessionLimits = { maxSessions: 64, maxTurns: 200, maxTools: 256, maxRecordBytes: 4194304, maxEvents: 32768, maxSourceBytes: 33554432, maxTextChars: 32768, maxDetailChars: 8192 };
|
|
6
|
+
const state = { enum: ['running', 'completed', 'failed', 'stopped', 'unknown'] };
|
|
7
|
+
export const externalResultSchema = obj({ path: text, id: text, version: text, projectId: text, toolId: text }, ['path', 'toolId']);
|
|
8
|
+
export const externalToolSchema = obj({ id: text, at: text, name: text, detail: text, output: text, outcome: { enum: ['requested', 'returned', 'failed', 'unknown'] }, exitCode: { type: 'integer' }, paths: array(text, 32) }, ['id', 'at', 'name', 'detail', 'output', 'outcome', 'paths']);
|
|
9
|
+
export const externalTurnSchema = obj({ id: text, at: text, endedAt: text, state, prompt: text, reply: text, messages: array(obj({ at: text, text }), 200), tools: array(externalToolSchema, externalSessionLimits.maxTools), results: array(externalResultSchema, 64), omittedTools: count, omittedChars: count }, ['id', 'at', 'state', 'prompt', 'reply', 'messages', 'tools', 'results', 'omittedTools', 'omittedChars']);
|
|
10
|
+
export const externalSessionSummarySchema = obj({ id: { type: 'string', pattern: '^x-[a-f0-9]{64}$' }, producer: text, sessionId: text, title: text, lastAt: text, observedAt: text, state, turns: count, sourceCursor: count, omittedTurns: count, problem: text, inputOwner: { const: 'external' } }, ['id', 'producer', 'sessionId', 'title', 'lastAt', 'observedAt', 'state', 'turns', 'sourceCursor', 'omittedTurns', 'inputOwner']);
|
|
11
|
+
export const externalSessionSchemas = { summary: externalSessionSummarySchema, turn: externalTurnSchema };
|
|
12
|
+
export const externalSessionOperations = {
|
|
13
|
+
'agent.sessions.list': { inputSchema: obj({}), outputSchema: obj({ sessions: array(externalSessionSummarySchema, 64), omitted: count }), requires: [], effect: 'read' },
|
|
14
|
+
'agent.sessions.read': { inputSchema: obj({ session: { type: 'string', pattern: '^x-[a-f0-9]{64}$' }, before: count, limit: { type: 'integer', minimum: 1, maximum: 20 } }, ['session']), outputSchema: obj({ session: externalSessionSummarySchema, first: count, turns: array(externalTurnSchema, 20) }), requires: [], effect: 'read' },
|
|
15
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export { externalSessionLimits } from './external-session-contract.js';
|
|
2
|
+
export interface ExternalTool {
|
|
3
|
+
id: string;
|
|
4
|
+
at: string;
|
|
5
|
+
name: string;
|
|
6
|
+
detail: string;
|
|
7
|
+
output: string;
|
|
8
|
+
outcome: 'requested' | 'returned' | 'failed' | 'unknown';
|
|
9
|
+
exitCode?: number;
|
|
10
|
+
paths: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface ExternalResult {
|
|
13
|
+
path: string;
|
|
14
|
+
toolId: string;
|
|
15
|
+
id?: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
projectId?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ExternalTurn {
|
|
20
|
+
id: string;
|
|
21
|
+
at: string;
|
|
22
|
+
endedAt?: string;
|
|
23
|
+
state: 'running' | 'completed' | 'failed' | 'stopped' | 'unknown';
|
|
24
|
+
prompt: string;
|
|
25
|
+
reply: string;
|
|
26
|
+
messages: Array<{
|
|
27
|
+
at: string;
|
|
28
|
+
text: string;
|
|
29
|
+
}>;
|
|
30
|
+
tools: ExternalTool[];
|
|
31
|
+
results: ExternalResult[];
|
|
32
|
+
omittedTools: number;
|
|
33
|
+
omittedChars: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ExternalSessionSummary {
|
|
36
|
+
id: string;
|
|
37
|
+
producer: string;
|
|
38
|
+
sessionId: string;
|
|
39
|
+
title: string;
|
|
40
|
+
lastAt: string;
|
|
41
|
+
observedAt: string;
|
|
42
|
+
state: ExternalTurn['state'];
|
|
43
|
+
turns: number;
|
|
44
|
+
sourceCursor: number;
|
|
45
|
+
omittedTurns: number;
|
|
46
|
+
problem?: string;
|
|
47
|
+
inputOwner: 'external';
|
|
48
|
+
}
|
|
49
|
+
export interface ExternalSessionRecord {
|
|
50
|
+
format: 1;
|
|
51
|
+
projectRoot: string;
|
|
52
|
+
summary: ExternalSessionSummary;
|
|
53
|
+
turns: ExternalTurn[];
|
|
54
|
+
sourceDigest: string;
|
|
55
|
+
}
|
|
56
|
+
export declare const externalSessionId: (producer: string, session: string) => string;
|
|
57
|
+
export declare function listExternalSessions(root: string): {
|
|
58
|
+
sessions: ExternalSessionSummary[];
|
|
59
|
+
omitted: number;
|
|
60
|
+
};
|
|
61
|
+
export declare function readExternalSession(root: string, input: {
|
|
62
|
+
session: string;
|
|
63
|
+
before?: number;
|
|
64
|
+
limit?: number;
|
|
65
|
+
}): {
|
|
66
|
+
session: ExternalSessionSummary;
|
|
67
|
+
first: number;
|
|
68
|
+
turns: ExternalTurn[];
|
|
69
|
+
};
|
|
70
|
+
/** Native adapters retain an observation under their existing filesystem authority.
|
|
71
|
+
* No managed execution, Agent input, grant change or runtime startup is involved. */
|
|
72
|
+
export declare function saveExternalSession(root: string, value: ExternalSessionRecord, previousDigest?: string): ExternalSessionSummary;
|
|
73
|
+
export declare function retainedExternalSession(root: string, producer: string, sessionId: string): ExternalSessionRecord | undefined;
|