@rho-workspace/rho 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/dist/agent-connect.d.ts +42 -0
  2. package/dist/agent-connect.js +68 -0
  3. package/dist/answer-board.js +3 -3
  4. package/dist/bridge.js +6 -1
  5. package/dist/browser-access.d.ts +1 -0
  6. package/dist/browser-access.js +5 -3
  7. package/dist/capabilities.d.ts +21 -5
  8. package/dist/capabilities.js +6 -4
  9. package/dist/cli.js +113 -18
  10. package/dist/composition.d.ts +1 -1
  11. package/dist/composition.js +13 -3
  12. package/dist/connect-cli.d.ts +50 -0
  13. package/dist/connect-cli.js +128 -0
  14. package/dist/contract.generated.d.ts +1437 -381
  15. package/dist/contract.generated.js +1 -1
  16. package/dist/contracts/owners.generated.js +1160 -267
  17. package/dist/conversation-presentation.d.ts +9 -2
  18. package/dist/conversation-presentation.js +3 -1
  19. package/dist/conversations.d.ts +306 -7
  20. package/dist/conversations.js +208 -22
  21. package/dist/discovery.js +3 -3
  22. package/dist/extension-contract.d.ts +19 -0
  23. package/dist/extension-contract.js +15 -6
  24. package/dist/extension-markets.d.ts +74 -0
  25. package/dist/extension-markets.js +166 -0
  26. package/dist/extensions.d.ts +29 -1
  27. package/dist/extensions.js +13 -4
  28. package/dist/external-work.d.ts +22 -0
  29. package/dist/external-work.js +91 -0
  30. package/dist/plugin-cli.js +75 -3
  31. package/dist/plugin-inventory.d.ts +56 -0
  32. package/dist/plugin-inventory.js +41 -0
  33. package/dist/plugin-inventory.json +1581 -102
  34. package/dist/plugin-presentation.d.ts +105 -2
  35. package/dist/plugin-presentation.js +59 -5
  36. package/dist/plugins.d.ts +14 -3
  37. package/dist/plugins.js +12 -6
  38. package/dist/project-notes.js +1 -1
  39. package/dist/protocol.d.ts +7 -0
  40. package/dist/protocol.js +40 -15
  41. package/dist/replay.js +4 -0
  42. package/dist/response-contracts.js +26 -11
  43. package/dist/running.d.ts +10 -0
  44. package/dist/running.js +45 -0
  45. package/dist/server.d.ts +4 -10
  46. package/dist/server.js +140 -14
  47. package/dist/state.d.ts +8 -0
  48. package/dist/state.js +8 -0
  49. package/dist/studio-layout.js +3 -3
  50. package/dist/task-frame.d.ts +1 -1
  51. package/dist/timeline-events.d.ts +1 -0
  52. package/dist/timeline-events.js +8 -1
  53. package/dist/ui-context.d.ts +73 -0
  54. package/dist/ui-context.js +98 -0
  55. package/dist/ui-contract.d.ts +23 -0
  56. package/dist/web/.vite/manifest.json +135 -76
  57. package/dist/web/assets/answer-board-CeYS8k8y.js +1 -0
  58. package/dist/web/assets/choices-D8vGwsl2.js +1 -0
  59. package/dist/web/assets/{console-BSjLGe0T.js → console-Dyuo1q4N.js} +4 -4
  60. package/dist/web/assets/{dist-Tzoe_zm-.js → dist-CSiCyBml.js} +1 -1
  61. package/dist/web/assets/{dist-Cw8hRIlT.js → dist-D9FLb9Hm.js} +1 -1
  62. package/dist/web/assets/{dist-foH9IKc6.js → dist-FXbTBwML.js} +1 -1
  63. package/dist/web/assets/{dist-Buk0lwzI.js → dist-l3DUhbw5.js} +1 -1
  64. package/dist/web/assets/{explore-in-BVUy3ZW_.js → explore-in-BfSFGRh2.js} +1 -1
  65. package/dist/web/assets/graph-BU2_o5uA.js +2 -0
  66. package/dist/web/assets/index-C16mXVvk.css +1 -0
  67. package/dist/web/assets/index-CWg9XYXx.js +952 -0
  68. package/dist/web/assets/markdown.reader-Ct2-e2Zt.png +0 -0
  69. package/dist/web/assets/{notes-meaning-pVbbQUGP.js → notes-meaning-T2m5zQfv.js} +1 -1
  70. package/dist/web/assets/overview-PzfLJCo-.js +6 -0
  71. package/dist/web/assets/preview-BguEdsza.js +1 -0
  72. package/dist/web/assets/previews-pWNik-2g.js +3 -0
  73. package/dist/web/assets/{review-CMxQlQ-C.js → review-Dy_85lcH.js} +1 -1
  74. package/dist/web/assets/rho.editor-CCJeyHma.png +0 -0
  75. package/dist/web/assets/rho.flow-WkMj6fRS.png +0 -0
  76. package/dist/web/assets/rho.media-Cc6amodO.png +0 -0
  77. package/dist/web/assets/rho.notebook-6vsZKGDq.png +0 -0
  78. package/dist/web/assets/rho.object-BGoeXjte.png +0 -0
  79. package/dist/web/assets/rho.plots-yIiISCkb.png +0 -0
  80. package/dist/web/assets/rho.scatter-DwHxesCs.png +0 -0
  81. package/dist/web/assets/rho.servers-DKf_mnNq.png +0 -0
  82. package/dist/web/assets/rho.table-DWOGaWDa.png +0 -0
  83. package/dist/web/assets/rho.variables-CKcWczhQ.png +0 -0
  84. package/dist/web/assets/{skills-BFNSyrnD.js → skills-Dyz7mBlc.js} +1 -1
  85. package/dist/web/assets/{skills-work-BGPSnF_I.js → skills-work-BEGqlCkx.js} +1 -1
  86. package/dist/web/assets/studio-process-D-ykbC4D.js +2 -0
  87. package/dist/web/assets/timeline-events-DKTho9fU.js +1 -0
  88. package/dist/web/assets/work-turns-DSabCogR.js +1 -0
  89. package/dist/web/assets/{workspace-CWlkjLNx.js → workspace-DfjBL--r.js} +2 -2
  90. package/dist/web/composition.json +135 -80
  91. package/dist/web/index.html +3 -3
  92. package/dist/web/renderer.json +1 -1
  93. package/dist/web-assets.js +2 -2
  94. package/dist/work-graph.d.ts +11 -1
  95. package/dist/work-graph.js +16 -5
  96. package/dist/workspace.d.ts +2 -2
  97. package/dist/workspace.js +8 -7
  98. package/docs/FRONTEND.md +71 -21
  99. package/docs/HEADLESS.md +127 -24
  100. package/docs/THIRD-PARTY-PLUGINS.md +75 -3
  101. package/node_modules/@rho-workspace/plugin-agent/README.md +34 -0
  102. package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.d.ts +32 -0
  103. package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.js +54 -11
  104. package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.d.ts +1 -0
  105. package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +29 -3
  106. package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +11 -5
  107. package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +12 -1
  108. package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +8 -0
  109. package/node_modules/@rho-workspace/plugin-agent/dist/index.js +3 -2
  110. package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.d.ts +13 -0
  111. package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.js +13 -0
  112. package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +16 -8
  113. package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.d.ts +70 -0
  114. package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.js +538 -0
  115. package/node_modules/@rho-workspace/plugin-agent/package.json +2 -1
  116. package/node_modules/@rho-workspace/plugin-figures/LICENSE +661 -0
  117. package/node_modules/@rho-workspace/plugin-figures/README.md +191 -0
  118. package/node_modules/@rho-workspace/plugin-figures/dist/index.d.ts +236 -0
  119. package/node_modules/@rho-workspace/plugin-figures/dist/index.js +165 -0
  120. package/node_modules/@rho-workspace/plugin-figures/dist/operations.d.ts +44 -0
  121. package/node_modules/@rho-workspace/plugin-figures/dist/operations.js +39 -0
  122. package/node_modules/@rho-workspace/plugin-figures/dist/presentation.d.ts +19 -0
  123. package/node_modules/@rho-workspace/plugin-figures/dist/presentation.js +26 -0
  124. package/node_modules/@rho-workspace/plugin-figures/dist/record.d.ts +188 -0
  125. package/node_modules/@rho-workspace/plugin-figures/dist/record.js +143 -0
  126. package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.d.ts +1 -0
  127. package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.js +68 -0
  128. package/node_modules/@rho-workspace/plugin-figures/dist/ui.d.ts +56 -0
  129. package/node_modules/@rho-workspace/plugin-figures/dist/ui.js +363 -0
  130. package/node_modules/@rho-workspace/plugin-figures/package.json +41 -0
  131. package/node_modules/@rho-workspace/plugin-figures/py/figures.py +856 -0
  132. package/node_modules/@rho-workspace/plugin-figures/r/figures.R +775 -0
  133. package/node_modules/@rho-workspace/plugin-kernel/README.md +27 -3
  134. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.d.ts +12 -0
  135. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +8 -1
  136. package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +61 -0
  137. package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +125 -9
  138. package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.d.ts +14 -0
  139. package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.js +9 -0
  140. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +23 -5
  141. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -1
  142. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +6 -4
  143. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +44 -17
  144. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +6 -0
  145. package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
  146. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +144 -4
  147. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +106 -1
  148. package/node_modules/@rho-workspace/plugin-tables/README.md +5 -0
  149. package/node_modules/@rho-workspace/plugin-tables/dist/index.d.ts +1 -0
  150. package/node_modules/@rho-workspace/plugin-tables/dist/index.js +19 -3
  151. package/node_modules/@rho-workspace/plugin-tables/dist/ui.js +1 -1
  152. package/node_modules/@rho-workspace/plugin-tables/package.json +1 -1
  153. package/node_modules/hono/dist/adapter/aws-lambda/handler.js +1 -1
  154. package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +1 -1
  155. package/node_modules/hono/dist/cjs/adapter/bun/serve-static.js +1 -1
  156. package/node_modules/hono/dist/cjs/adapter/bun/websocket.js +1 -1
  157. package/node_modules/hono/dist/cjs/client/fetch-result-please.js +1 -1
  158. package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +2 -1
  159. package/node_modules/hono/dist/cjs/helper/ssg/ssg.js +5 -3
  160. package/node_modules/hono/dist/cjs/jsx/base.js +49 -30
  161. package/node_modules/hono/dist/cjs/jsx/components.js +2 -2
  162. package/node_modules/hono/dist/cjs/jsx/constants.js +2 -0
  163. package/node_modules/hono/dist/cjs/jsx/jsx-runtime.js +13 -17
  164. package/node_modules/hono/dist/cjs/middleware/etag/index.js +3 -1
  165. package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +1 -0
  166. package/node_modules/hono/dist/cjs/middleware/timing/timing.js +4 -3
  167. package/node_modules/hono/dist/cjs/utils/cookie.js +2 -2
  168. package/node_modules/hono/dist/cjs/utils/mime.js +1 -1
  169. package/node_modules/hono/dist/cjs/utils/url.js +1 -1
  170. package/node_modules/hono/dist/client/fetch-result-please.js +1 -1
  171. package/node_modules/hono/dist/helper/accepts/accepts.js +2 -1
  172. package/node_modules/hono/dist/helper/ssg/ssg.js +5 -3
  173. package/node_modules/hono/dist/jsx/base.js +49 -32
  174. package/node_modules/hono/dist/jsx/components.js +2 -2
  175. package/node_modules/hono/dist/jsx/constants.js +2 -1
  176. package/node_modules/hono/dist/jsx/jsx-runtime.js +15 -19
  177. package/node_modules/hono/dist/middleware/etag/index.js +3 -1
  178. package/node_modules/hono/dist/middleware/language/language.js +1 -1
  179. package/node_modules/hono/dist/middleware/pretty-json/index.js +1 -0
  180. package/node_modules/hono/dist/middleware/timing/timing.js +4 -3
  181. package/node_modules/hono/dist/utils/cookie.js +2 -2
  182. package/node_modules/hono/dist/utils/mime.js +1 -1
  183. package/node_modules/hono/dist/utils/url.js +1 -1
  184. package/node_modules/hono/package.json +1 -1
  185. package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +3 -2
  186. package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
  187. package/node_modules/jose/dist/webapi/jws/general/verify.js +2 -8
  188. package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +11 -8
  189. package/node_modules/jose/dist/webapi/lib/key_management.js +0 -1
  190. package/node_modules/jose/package.json +1 -1
  191. package/package.json +6 -4
  192. package/protocol/index.json +106 -64
  193. package/protocol/operations/agent.sessions.list.json +57 -3
  194. package/protocol/operations/agent.sessions.read.json +61 -4
  195. package/protocol/operations/capability.list.json +382 -6
  196. package/protocol/operations/composition.apply.json +10 -2
  197. package/protocol/operations/composition.delete.json +845 -0
  198. package/protocol/operations/composition.read.json +10 -2
  199. package/protocol/operations/composition.undo.json +10 -2
  200. package/protocol/operations/conversation.agents.add.json +346 -0
  201. package/protocol/operations/conversation.agents.remove.json +340 -0
  202. package/protocol/operations/conversation.configure.json +29 -0
  203. package/protocol/operations/conversation.decide.json +225 -0
  204. package/protocol/operations/conversation.list.json +29 -0
  205. package/protocol/operations/conversation.permission.json +225 -0
  206. package/protocol/operations/conversation.read.json +225 -0
  207. package/protocol/operations/conversation.restore.json +29 -0
  208. package/protocol/operations/conversation.send.json +294 -0
  209. package/protocol/operations/conversation.settings.check.json +226 -416
  210. package/protocol/operations/conversation.settings.read.json +224 -411
  211. package/protocol/operations/conversation.settings.update.json +248 -475
  212. package/protocol/operations/conversation.start.json +313 -6
  213. package/protocol/operations/conversation.stop.json +225 -0
  214. package/protocol/operations/conversation.trash.json +29 -0
  215. package/protocol/operations/extension.describe.json +39 -0
  216. package/protocol/operations/extension.install.json +62 -0
  217. package/protocol/operations/extension.list.json +164 -1
  218. package/protocol/operations/extension.market.change.json +210 -0
  219. package/protocol/operations/figure.read.json +265 -0
  220. package/protocol/operations/figure.select.json +157 -0
  221. package/protocol/operations/kernel.interrupt.json +119 -0
  222. package/protocol/operations/kernel.read.json +119 -0
  223. package/protocol/operations/kernel.run.json +119 -0
  224. package/protocol/operations/observation.snapshot.json +445 -8
  225. package/protocol/operations/project.openFile.json +10 -2
  226. package/protocol/operations/session.grant.json +4 -0
  227. package/protocol/operations/session.instructions.json +43 -0
  228. package/protocol/operations/shared.json +1 -0
  229. package/protocol/operations/view.close.json +10 -2
  230. package/protocol/operations/view.protection.set.json +10 -2
  231. package/vendor/composition.json +174 -109
  232. package/vendor/rho-workspace-plugin-agent-0.4.33.tgz +0 -0
  233. package/vendor/rho-workspace-plugin-figures-0.5.1.tgz +0 -0
  234. package/vendor/rho-workspace-plugin-kernel-0.5.13.tgz +0 -0
  235. package/vendor/rho-workspace-plugin-tables-0.5.2.tgz +0 -0
  236. package/web/theme.css +17 -0
  237. package/dist/web/assets/answer-board-CBkGbSLD.js +0 -1
  238. package/dist/web/assets/canvas-D8rDdfDz.js +0 -1
  239. package/dist/web/assets/choices-b0XhrGQV.js +0 -1
  240. package/dist/web/assets/graph-1OdIJ9l7.js +0 -2
  241. package/dist/web/assets/index-CsC3sTRq.css +0 -1
  242. package/dist/web/assets/index-CxmItXqa.js +0 -891
  243. package/dist/web/assets/overview-B9UtVJ32.js +0 -6
  244. package/dist/web/assets/preview-CksD_5KS.js +0 -1
  245. package/dist/web/assets/studio-process-CsRoTHOh.js +0 -2
  246. package/dist/web/assets/timeline-events-BbuuFWHV.js +0 -1
  247. package/dist/web/assets/work-turns-BvJlIMmA.js +0 -1
  248. package/vendor/rho-workspace-plugin-agent-0.4.29.tgz +0 -0
  249. package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
  250. package/vendor/rho-workspace-plugin-tables-0.5.0.tgz +0 -0
@@ -0,0 +1,538 @@
1
+ import fs, { constants } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import readline from 'node:readline';
5
+ import { externalSessionId } from './external-session.js';
6
+ // The sessions an Agent keeps in its own files for one folder, wherever they were started: a
7
+ // terminal, an editor, its own app or Rho. The Agent's own files and session id are the record;
8
+ // nothing is copied, started or changed by reading them. Each read is bounded.
9
+ export const nativeSessionLimits = {
10
+ maxSessions: 64, maxScanFiles: 4096, maxHeadBytes: 262144, maxTailBytes: 262144, maxReadBytes: 268435456,
11
+ maxTurns: 200, maxTools: 256, maxTextChars: 32768, maxDetailChars: 8192,
12
+ // A session written to this recently, with its last turn unfinished, is taken as still working.
13
+ activeMs: 60000
14
+ };
15
+ const home = () => os.homedir();
16
+ export function nativeSources(env = process.env) {
17
+ return { claudeConfigDir: env.CLAUDE_CONFIG_DIR || path.join(home(), '.claude'), codexHome: env.CODEX_HOME || path.join(home(), '.codex') };
18
+ }
19
+ const quote = (value) => process.platform === 'win32' ? '"' + value.replace(/"/g, '""') + '"' : "'" + value.replace(/'/g, "'\\''") + "'";
20
+ const resumeCommand = (producer, root, sessionId) => (process.platform === 'win32' ? 'cd /d ' : 'cd ') + quote(root) + ' && ' + (producer === 'claude-code' ? 'claude --resume ' : 'codex resume ') + sessionId;
21
+ const cut = (text, max) => text.length <= max ? text : text.slice(0, max - 1).replace(/[\uD800-\uDBFF]$/, '') + '…';
22
+ const realpath = (p) => { try {
23
+ return fs.realpathSync(p);
24
+ }
25
+ catch {
26
+ return path.resolve(p);
27
+ } };
28
+ // Up to `max` bytes from the start or the end of a file, as whole lines.
29
+ function readLines(file, from, max) {
30
+ const fd = fs.openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW);
31
+ try {
32
+ const size = fs.fstatSync(fd).size, length = Math.min(size, max), start = from === 'head' ? 0 : size - length;
33
+ const buffer = Buffer.alloc(length), n = fs.readSync(fd, buffer, 0, length, start);
34
+ let lines = buffer.subarray(0, n).toString('utf8').split('\n');
35
+ // A cut line at either end is dropped rather than misread.
36
+ if (from === 'tail' && start > 0)
37
+ lines = lines.slice(1);
38
+ if (from === 'head' && length < size)
39
+ lines = lines.slice(0, -1);
40
+ return { lines: lines.filter(Boolean), truncated: length < size, size };
41
+ }
42
+ finally {
43
+ fs.closeSync(fd);
44
+ }
45
+ }
46
+ function parse(line) { try {
47
+ return JSON.parse(line);
48
+ }
49
+ catch {
50
+ return null;
51
+ } }
52
+ // A file read as a stream into `reader`, so a long session neither holds the service nor its
53
+ // memory; only the last `max` bytes of a larger file.
54
+ async function streamInto(file, max, reader) {
55
+ const size = fs.statSync(file).size, start = Math.max(0, size - max);
56
+ const input = fs.createReadStream(file, { fd: fs.openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW), start, autoClose: true });
57
+ let skip = start > 0;
58
+ for await (const line of readline.createInterface({ input, crlfDelay: Infinity })) {
59
+ if (skip) {
60
+ skip = false;
61
+ continue;
62
+ }
63
+ if (line)
64
+ reader.push(line);
65
+ }
66
+ return { turns: reader.finish(), truncated: start > 0 };
67
+ }
68
+ // ——— Claude Code ———
69
+ // Claude Code keeps a folder's sessions under projects/<the folder path with every character
70
+ // other than a letter or digit replaced by '-'>. Long paths are shortened, and different folders
71
+ // can share a name, so each session's own cwd decides.
72
+ function claudeFolders(configDir, root) {
73
+ const projects = path.join(configDir, 'projects'), names = new Set();
74
+ for (const candidate of new Set([root, realpath(root)])) {
75
+ const name = candidate.replace(/[^a-zA-Z0-9]/g, '-');
76
+ if (name.length <= 200)
77
+ names.add(name);
78
+ else
79
+ try {
80
+ for (const entry of fs.readdirSync(projects))
81
+ if (entry.startsWith(name.slice(0, 200)))
82
+ names.add(entry);
83
+ }
84
+ catch { /* No projects folder yet. */ }
85
+ }
86
+ return [...names].map(name => path.join(projects, name)).filter(dir => { try {
87
+ return fs.statSync(dir).isDirectory();
88
+ }
89
+ catch {
90
+ return false;
91
+ } });
92
+ }
93
+ const claudeWhere = { cli: 'Terminal', 'sdk-ts': 'Agent app', 'sdk-py': 'Agent app', 'sdk-cli': 'Script', 'claude-vscode': 'VS Code', 'claude-desktop': 'Claude app', 'claude-jetbrains': 'JetBrains', remote: 'Claude on the web' };
94
+ // A human prompt, not a tool result, an injected reminder or a local command's output.
95
+ function claudePrompt(record) {
96
+ if (record.type !== 'user' || record.isMeta || record.isSidechain || record.isCompactSummary || record.isVisibleInTranscriptOnly)
97
+ return null;
98
+ const content = record.message?.content;
99
+ const text = typeof content === 'string' ? content : Array.isArray(content) && !content.some((b) => b?.type === 'tool_result')
100
+ ? content.filter((b) => b?.type === 'text' && typeof b.text === 'string').map((b) => b.text).join('\n') : null;
101
+ if (text === null)
102
+ return null;
103
+ const trimmed = text.trim();
104
+ if (!trimmed || /^<(local-command-stdout|local-command-stderr|local-command-caveat|system-reminder|task-notification)/.test(trimmed))
105
+ return null;
106
+ const command = /^<command-name>([^<]*)<\/command-name>/.exec(trimmed) ?? /<command-name>([^<]*)<\/command-name>/.exec(trimmed);
107
+ if (command) {
108
+ const args = /<command-args>([^<]*)<\/command-args>/.exec(trimmed)?.[1]?.trim();
109
+ return (command[1] ?? '').trim() + (args ? ' ' + args : '');
110
+ }
111
+ return trimmed;
112
+ }
113
+ const interrupted = (record) => record.type === 'user' && JSON.stringify(record.message?.content ?? '').includes('[Request interrupted by user');
114
+ function toolDetail(input, max) {
115
+ if (!input || typeof input !== 'object')
116
+ return '';
117
+ for (const key of ['command', 'file_path', 'notebook_path', 'path', 'pattern', 'query', 'url', 'operation', 'description', 'prompt']) {
118
+ const value = input[key];
119
+ if (typeof value === 'string' && value.trim())
120
+ return cut(value.trim(), max);
121
+ }
122
+ return cut(JSON.stringify(input), max);
123
+ }
124
+ function toolPaths(input) {
125
+ return ['file_path', 'notebook_path', 'path'].map(key => input?.[key]).filter((v) => typeof v === 'string' && v.length < 4096).slice(0, 32);
126
+ }
127
+ function resultText(content, max) {
128
+ if (typeof content === 'string')
129
+ return cut(content, max);
130
+ if (Array.isArray(content))
131
+ return cut(content.map(b => typeof b === 'string' ? b : b?.type === 'text' ? b.text : b?.type === 'image' ? '[image]' : '').filter(Boolean).join('\n'), max);
132
+ return '';
133
+ }
134
+ function newBuilder() { return { turns: [], texts: new Map(), omittedChars: 0, ended: new Set() }; }
135
+ function startTurn(b, id, at, prompt) {
136
+ const turn = { id, at, state: 'unknown', prompt: cut(prompt, nativeSessionLimits.maxTextChars), reply: '', messages: [], tools: [], results: [], omittedTools: 0, omittedChars: prompt.length > nativeSessionLimits.maxTextChars ? 1 : 0 };
137
+ b.turns.push(turn);
138
+ b.texts.set(turn, []);
139
+ if (b.turns.length > nativeSessionLimits.maxTurns) {
140
+ const dropped = b.turns.shift();
141
+ b.texts.delete(dropped);
142
+ b.ended.delete(dropped);
143
+ }
144
+ return turn;
145
+ }
146
+ function addText(b, turn, at, text) {
147
+ if (!text.trim())
148
+ return;
149
+ turn.endedAt = at;
150
+ b.texts.get(turn).push({ at, text, afterTool: false });
151
+ }
152
+ function addTool(turn, tool, b) {
153
+ turn.endedAt = tool.at;
154
+ for (const t of b.texts.get(turn))
155
+ t.afterTool = true;
156
+ if (turn.tools.length >= nativeSessionLimits.maxTools) {
157
+ turn.omittedTools++;
158
+ return;
159
+ }
160
+ turn.tools.push(tool);
161
+ }
162
+ // The words after the last tool are the answer; earlier words are what the Agent said on the way.
163
+ function finish(b, lastWrite, now) {
164
+ const recent = now - lastWrite < nativeSessionLimits.activeMs;
165
+ return b.turns.map((turn, index) => {
166
+ const texts = b.texts.get(turn), answer = texts.filter(t => !t.afterTool), along = texts.filter(t => t.afterTool);
167
+ const reply = answer.map(t => t.text).join('\n\n').trim();
168
+ const replyCut = cut(reply, nativeSessionLimits.maxTextChars);
169
+ const last = index === b.turns.length - 1, pending = turn.tools.some(t => t.outcome === 'requested');
170
+ let state = turn.state;
171
+ if (state === 'unknown')
172
+ state = b.ended.has(turn) || (!last && reply) || (last && reply && !pending && !recent) ? 'completed' : last && recent ? 'running' : reply && !pending ? 'completed' : 'unknown';
173
+ const tools = state === 'running' ? turn.tools : turn.tools.map(t => t.outcome === 'requested' ? { ...t, outcome: 'unknown' } : t);
174
+ return { ...turn, state, tools, reply: replyCut, messages: along.slice(-200).map(t => ({ at: t.at, text: cut(t.text, nativeSessionLimits.maxTextChars) })),
175
+ omittedChars: turn.omittedChars + (reply.length > replyCut.length ? 1 : 0) };
176
+ });
177
+ }
178
+ const readAll = (reader, lines) => { for (const line of lines)
179
+ reader.push(line); return reader.finish(); };
180
+ function claudeTurns(lines, lastWrite, now) { return readAll(claudeReader(lastWrite, now), lines); }
181
+ function claudeReader(lastWrite, now) {
182
+ const b = newBuilder(), tools = new Map();
183
+ let current = null;
184
+ return { finish: () => finish(b, lastWrite, now), push(line) {
185
+ const r = parse(line);
186
+ if (!r || (r.type !== 'user' && r.type !== 'assistant') || r.isSidechain)
187
+ return;
188
+ const at = typeof r.timestamp === 'string' ? r.timestamp : new Date(lastWrite).toISOString();
189
+ const prompt = claudePrompt(r);
190
+ if (prompt !== null) {
191
+ current = startTurn(b, typeof r.uuid === 'string' ? r.uuid : 'turn-' + b.turns.length, at, prompt);
192
+ if (typeof r.entrypoint === 'string')
193
+ current.where = claudeWhere[r.entrypoint] ?? r.entrypoint;
194
+ return;
195
+ }
196
+ if (!current)
197
+ return;
198
+ if (interrupted(r)) {
199
+ current.state = 'stopped';
200
+ current.endedAt = at;
201
+ return;
202
+ }
203
+ const content = Array.isArray(r.message?.content) ? r.message.content : [];
204
+ for (const block of content) {
205
+ if (r.type === 'assistant' && block?.type === 'text' && typeof block.text === 'string')
206
+ addText(b, current, at, block.text);
207
+ else if (r.type === 'assistant' && block?.type === 'tool_use' && typeof block.id === 'string') {
208
+ const tool = { id: block.id, at, name: String(block.name ?? 'tool').slice(0, 256), detail: toolDetail(block.input, nativeSessionLimits.maxDetailChars), output: '', outcome: 'requested', paths: toolPaths(block.input) };
209
+ tools.set(block.id, tool);
210
+ addTool(current, tool, b);
211
+ }
212
+ else if (r.type === 'user' && block?.type === 'tool_result' && typeof block.tool_use_id === 'string') {
213
+ const tool = tools.get(block.tool_use_id);
214
+ if (tool) {
215
+ tool.outcome = block.is_error ? 'failed' : 'returned';
216
+ tool.output = resultText(block.content, nativeSessionLimits.maxDetailChars);
217
+ current.endedAt = at;
218
+ }
219
+ }
220
+ }
221
+ if (r.type === 'assistant' && r.message?.stop_reason === 'end_turn')
222
+ b.ended.add(current);
223
+ } };
224
+ }
225
+ // Claude Code names each live process and the session it holds in sessions/<pid>.json.
226
+ function claudeLive(configDir) {
227
+ const live = new Map(), dir = path.join(configDir, 'sessions');
228
+ let names = [];
229
+ try {
230
+ names = fs.readdirSync(dir).filter(n => /^\d+\.json$/.test(n)).slice(0, 256);
231
+ }
232
+ catch {
233
+ return live;
234
+ }
235
+ for (const name of names) {
236
+ try {
237
+ const file = path.join(dir, name), stat = fs.statSync(file);
238
+ if (stat.size > 65536)
239
+ continue;
240
+ const v = JSON.parse(fs.readFileSync(file, 'utf8'));
241
+ if (!Number.isSafeInteger(v.pid) || v.pid <= 0 || typeof v.sessionId !== 'string')
242
+ continue;
243
+ try {
244
+ process.kill(v.pid, 0);
245
+ }
246
+ catch (e) {
247
+ if (e.code !== 'EPERM')
248
+ continue;
249
+ }
250
+ live.set(v.sessionId, { pid: v.pid, sessionId: v.sessionId, where: claudeWhere[v.entrypoint] ?? 'another window', ...(Number.isFinite(v.startedAt) ? { since: new Date(v.startedAt).toISOString() } : {}) });
251
+ }
252
+ catch { /* A file being written is read next time. */ }
253
+ }
254
+ return live;
255
+ }
256
+ // ——— Codex ———
257
+ const codexWhere = (originator) => {
258
+ const o = String(originator ?? '');
259
+ return /cli/i.test(o) ? 'Terminal' : /desktop/i.test(o) ? 'Codex app' : /vscode/i.test(o) ? 'VS Code' : /acp/i.test(o) ? 'Agent app' : o || 'Codex';
260
+ };
261
+ // The person's request inside a message that also lists attachments or context.
262
+ function codexRequest(text) {
263
+ const marker = text.lastIndexOf('## My request:');
264
+ const value = (marker >= 0 ? text.slice(marker + '## My request:'.length) : text).trim();
265
+ if (!value || /^<(environment_context|user_instructions|external_[a-z_]+|turn_aborted)/.test(value) || value.startsWith('# AGENTS.md instructions'))
266
+ return null;
267
+ return value;
268
+ }
269
+ function codexTurns(lines, lastWrite, now) { return readAll(codexReader(lastWrite, now), lines); }
270
+ function codexReader(lastWrite, now) {
271
+ const b = newBuilder(), calls = new Map();
272
+ let current = null, modern = false;
273
+ const text = (content) => Array.isArray(content) ? content.map((c) => typeof c?.text === 'string' ? c.text : '').join('\n') : typeof content === 'string' ? content : '';
274
+ return { finish: () => finish(b, lastWrite, now), push(line) {
275
+ const r = parse(line), p = r?.payload;
276
+ if (!r || !p)
277
+ return;
278
+ const at = typeof r.timestamp === 'string' ? r.timestamp : new Date(lastWrite).toISOString();
279
+ if (r.type === 'event_msg' && p.type === 'item_completed' && p.item) {
280
+ modern = true;
281
+ const item = p.item;
282
+ if (item.type === 'UserMessage') {
283
+ const request = codexRequest(text(item.content));
284
+ if (request)
285
+ current = startTurn(b, String(p.turn_id ?? item.id), at, request);
286
+ return;
287
+ }
288
+ if (!current)
289
+ return;
290
+ if (item.type === 'AgentMessage')
291
+ addText(b, current, at, text(item.content));
292
+ else if (item.type === 'CommandExecution') {
293
+ const command = Array.isArray(item.command) ? String(item.command.at(-1) ?? '') : String(item.command ?? '');
294
+ addTool(current, { id: String(item.id), at, name: 'Command', detail: cut(command, nativeSessionLimits.maxDetailChars), output: cut(String(item.aggregated_output ?? ''), nativeSessionLimits.maxDetailChars),
295
+ outcome: item.status === 'completed' ? (Number.isInteger(item.exit_code) && item.exit_code !== 0 ? 'failed' : 'returned') : item.status === 'failed' ? 'failed' : 'requested', ...(Number.isInteger(item.exit_code) ? { exitCode: item.exit_code } : {}), paths: [] }, b);
296
+ }
297
+ else if (item.type === 'FileChange') {
298
+ const paths = Object.keys(item.changes ?? {}).slice(0, 32);
299
+ addTool(current, { id: String(item.id), at, name: 'Edit', detail: cut(paths.join('\n'), nativeSessionLimits.maxDetailChars), output: '', outcome: item.status === 'failed' ? 'failed' : 'returned', paths }, b);
300
+ }
301
+ else if (item.type === 'McpToolCall') {
302
+ addTool(current, { id: String(item.id), at, name: [item.server, item.tool].filter(Boolean).join('.') || 'MCP tool', detail: toolDetail(item.arguments, nativeSessionLimits.maxDetailChars), output: '', outcome: item.status === 'failed' ? 'failed' : 'returned', paths: [] }, b);
303
+ }
304
+ else if (item.type === 'WebSearch' || item.type === 'ImageView') {
305
+ addTool(current, { id: String(item.id), at, name: item.type === 'WebSearch' ? 'Web search' : 'View image', detail: cut(String(item.query ?? item.path ?? ''), nativeSessionLimits.maxDetailChars), output: '', outcome: 'returned', paths: [] }, b);
306
+ }
307
+ return;
308
+ }
309
+ if (r.type === 'event_msg' && (p.type === 'task_complete' || p.type === 'turn_aborted')) {
310
+ if (current) {
311
+ if (p.type === 'turn_aborted')
312
+ current.state = 'stopped';
313
+ else
314
+ b.ended.add(current);
315
+ current.endedAt = at;
316
+ }
317
+ return;
318
+ }
319
+ if (modern)
320
+ return;
321
+ // The earlier rollout format: messages as events, tools as response items.
322
+ if (r.type === 'event_msg' && p.type === 'user_message' && typeof p.message === 'string') {
323
+ const request = codexRequest(p.message);
324
+ if (request)
325
+ current = startTurn(b, 'turn-' + b.turns.length + '-' + at, at, request);
326
+ }
327
+ else if (!current)
328
+ return;
329
+ else if (r.type === 'event_msg' && p.type === 'agent_message' && typeof p.message === 'string')
330
+ addText(b, current, at, p.message);
331
+ else if (r.type === 'response_item' && (p.type === 'function_call' || p.type === 'custom_tool_call') && typeof p.call_id === 'string') {
332
+ let input = p.input ?? p.arguments;
333
+ try {
334
+ if (typeof input === 'string' && input.startsWith('{'))
335
+ input = JSON.parse(input);
336
+ }
337
+ catch { /* Kept as text. */ }
338
+ const tool = { id: p.call_id, at, name: String(p.name ?? 'tool'), detail: typeof input === 'string' ? cut(input, nativeSessionLimits.maxDetailChars) : toolDetail(input, nativeSessionLimits.maxDetailChars), output: '', outcome: 'requested', paths: [] };
339
+ calls.set(p.call_id, tool);
340
+ addTool(current, tool, b);
341
+ }
342
+ else if (r.type === 'response_item' && (p.type === 'function_call_output' || p.type === 'custom_tool_call_output') && calls.has(p.call_id)) {
343
+ const tool = calls.get(p.call_id);
344
+ tool.outcome = 'returned';
345
+ tool.output = resultText(typeof p.output === 'string' ? p.output : p.output, nativeSessionLimits.maxDetailChars);
346
+ }
347
+ } };
348
+ }
349
+ // Codex holds thread-writer-locks/<id>.lock while a thread is open for writing.
350
+ function codexOpen(codexHome, sessionId) {
351
+ try {
352
+ return fs.statSync(path.join(codexHome, 'thread-writer-locks', sessionId.toLowerCase() + '.lock')).isFile();
353
+ }
354
+ catch {
355
+ return false;
356
+ }
357
+ }
358
+ function* codexFiles(codexHome) {
359
+ // Newest days first; each day's rollouts newest first.
360
+ const sessions = path.join(codexHome, 'sessions'), sorted = (dir) => { try {
361
+ return fs.readdirSync(dir).sort().reverse();
362
+ }
363
+ catch {
364
+ return [];
365
+ } };
366
+ for (const year of sorted(sessions))
367
+ for (const month of sorted(path.join(sessions, year)))
368
+ for (const day of sorted(path.join(sessions, year, month)))
369
+ for (const name of sorted(path.join(sessions, year, month, day)))
370
+ if (/^rollout-.*\.jsonl$/.test(name))
371
+ yield path.join(sessions, year, month, day, name);
372
+ }
373
+ const cache = new Map();
374
+ const titleOf = (...candidates) => cut((candidates.find(c => c && c.trim()) ?? 'Untitled session').replace(/\s+/g, ' ').trim(), 200);
375
+ function claudeSummary(file, root) {
376
+ const stat = fs.statSync(file), key = stat.size + ':' + stat.mtimeMs, kept = cache.get(file);
377
+ if (kept?.key === key)
378
+ return kept.value;
379
+ const head = readLines(file, 'head', nativeSessionLimits.maxHeadBytes), tail = head.truncated ? readLines(file, 'tail', nativeSessionLimits.maxTailBytes) : head;
380
+ let cwd = '', entrypoint = '', created = '', first = null, sessionId = path.basename(file, '.jsonl');
381
+ for (const line of head.lines) {
382
+ const r = parse(line);
383
+ if (!r)
384
+ continue;
385
+ if (!cwd && typeof r.cwd === 'string')
386
+ cwd = r.cwd;
387
+ if (!entrypoint && typeof r.entrypoint === 'string')
388
+ entrypoint = r.entrypoint;
389
+ if (!created && typeof r.timestamp === 'string')
390
+ created = r.timestamp;
391
+ if (first === null)
392
+ first = claudePrompt(r);
393
+ if (cwd && entrypoint && created && first !== null)
394
+ break;
395
+ }
396
+ let custom = '', ai = '', summaryText = '', lastPrompt = '';
397
+ for (const line of tail.lines) {
398
+ const r = parse(line);
399
+ if (!r)
400
+ continue;
401
+ if (r.type === 'custom-title' && typeof r.customTitle === 'string')
402
+ custom = r.customTitle;
403
+ else if (r.type === 'ai-title' && typeof r.aiTitle === 'string')
404
+ ai = r.aiTitle;
405
+ else if (r.type === 'summary' && typeof r.summary === 'string')
406
+ summaryText = r.summary;
407
+ else if (r.type === 'last-prompt' && typeof r.lastPrompt === 'string')
408
+ lastPrompt = r.lastPrompt;
409
+ }
410
+ // A transcript with no human prompt (a probe, or settings only) is not a conversation.
411
+ const turns = claudeTurns(tail.lines, stat.mtimeMs, Date.now());
412
+ // Unfinished: the last message is a prompt, a tool call or a tool result, not a final answer.
413
+ let unfinished = false;
414
+ for (let i = tail.lines.length - 1; i >= 0; i--) {
415
+ const r = parse(tail.lines[i]);
416
+ if (!r || (r.type !== 'user' && r.type !== 'assistant') || r.isSidechain || r.isMeta)
417
+ continue;
418
+ unfinished = r.type === 'user' ? !interrupted(r) : r.message?.stop_reason !== 'end_turn';
419
+ break;
420
+ }
421
+ const value = (!cwd || realpath(cwd) !== root || (first === null && !turns.length)) ? null : {
422
+ id: externalSessionId('claude-code', sessionId), producer: 'claude-code', sessionId, title: titleOf(custom, ai, summaryText, first, lastPrompt),
423
+ lastAt: new Date(stat.mtimeMs).toISOString(), turns: turns.length, sourceCursor: stat.size, omittedTurns: tail.truncated ? 1 : 0,
424
+ where: claudeWhere[entrypoint] ?? (entrypoint || 'Claude Code'), resume: resumeCommand('claude-code', root, sessionId), bytes: stat.size,
425
+ unfinished
426
+ };
427
+ cache.set(file, { key, value });
428
+ if (cache.size > 4096)
429
+ cache.delete(cache.keys().next().value);
430
+ return value;
431
+ }
432
+ function codexSummary(file, root) {
433
+ const stat = fs.statSync(file), key = stat.size + ':' + stat.mtimeMs, kept = cache.get(file);
434
+ if (kept?.key === key)
435
+ return kept.value;
436
+ const head = readLines(file, 'head', nativeSessionLimits.maxHeadBytes);
437
+ const meta = head.lines.length ? parse(head.lines[0]) : null, p = meta?.type === 'session_meta' ? meta.payload : null;
438
+ let value = null;
439
+ if (p && typeof p.id === 'string' && typeof p.cwd === 'string' && realpath(p.cwd) === root) {
440
+ const tail = head.truncated ? readLines(file, 'tail', nativeSessionLimits.maxTailBytes) : head;
441
+ const firstTurns = codexTurns(head.lines, stat.mtimeMs, Date.now()), turns = head.truncated ? codexTurns(tail.lines, stat.mtimeMs, Date.now()) : firstTurns;
442
+ let unfinished = false;
443
+ for (let i = tail.lines.length - 1; i >= 0; i--) {
444
+ const q = parse(tail.lines[i])?.payload;
445
+ if (q?.type === 'task_complete' || q?.type === 'turn_aborted')
446
+ break;
447
+ if (q?.type === 'task_started') {
448
+ unfinished = true;
449
+ break;
450
+ }
451
+ }
452
+ if (firstTurns.length || turns.length)
453
+ value = {
454
+ id: externalSessionId('codex', p.id), producer: 'codex', sessionId: p.id, title: titleOf(firstTurns[0]?.prompt, turns[0]?.prompt),
455
+ lastAt: new Date(stat.mtimeMs).toISOString(), turns: turns.length, sourceCursor: stat.size, omittedTurns: head.truncated ? 1 : 0,
456
+ where: codexWhere(p.originator), resume: resumeCommand('codex', root, p.id), bytes: stat.size, unfinished
457
+ };
458
+ }
459
+ cache.set(file, { key, value });
460
+ return value;
461
+ }
462
+ function find(root, sources) {
463
+ root = realpath(root);
464
+ const now = Date.now(), found = [];
465
+ let omitted = 0;
466
+ const live = claudeLive(sources.claudeConfigDir);
467
+ const add = (producer, file, value, open) => {
468
+ if (!value)
469
+ return;
470
+ const { unfinished, ...rest } = value;
471
+ const active = unfinished && now - Date.parse(value.lastAt) < nativeSessionLimits.activeMs;
472
+ found.push({ producer, sessionId: value.sessionId, file, summary: { ...rest, observedAt: new Date(now).toISOString(), open, state: active ? 'running' : 'completed' } });
473
+ };
474
+ for (const dir of claudeFolders(sources.claudeConfigDir, root)) {
475
+ let names = [];
476
+ try {
477
+ names = fs.readdirSync(dir).filter(n => /^[0-9a-f-]{36}\.jsonl$/i.test(n));
478
+ }
479
+ catch {
480
+ continue;
481
+ }
482
+ if (names.length > nativeSessionLimits.maxScanFiles) {
483
+ omitted += names.length - nativeSessionLimits.maxScanFiles;
484
+ names = names.slice(0, nativeSessionLimits.maxScanFiles);
485
+ }
486
+ for (const name of names) {
487
+ const file = path.join(dir, name);
488
+ try {
489
+ const value = claudeSummary(file, root);
490
+ const holder = value && live.get(value.sessionId);
491
+ add('claude-code', file, value, holder ? { where: holder.where, pid: holder.pid, ...(holder.since ? { since: holder.since } : {}) } : null);
492
+ }
493
+ catch {
494
+ omitted++;
495
+ }
496
+ }
497
+ }
498
+ let scanned = 0;
499
+ for (const file of codexFiles(sources.codexHome)) {
500
+ if (++scanned > nativeSessionLimits.maxScanFiles) {
501
+ omitted++;
502
+ break;
503
+ }
504
+ try {
505
+ const value = codexSummary(file, root);
506
+ add('codex', file, value, value && codexOpen(sources.codexHome, value.sessionId) ? { where: 'Codex' } : null);
507
+ }
508
+ catch {
509
+ omitted++;
510
+ }
511
+ }
512
+ found.sort((a, b) => b.summary.lastAt.localeCompare(a.summary.lastAt));
513
+ if (found.length > nativeSessionLimits.maxSessions) {
514
+ omitted += found.length - nativeSessionLimits.maxSessions;
515
+ found.length = nativeSessionLimits.maxSessions;
516
+ }
517
+ return { found, omitted };
518
+ }
519
+ /** The Agents' own sessions whose working folder is `root`, newest first. Reads files only. */
520
+ export function listNativeSessions(root, sources = {}) {
521
+ const { found, omitted } = find(root, { ...nativeSources(), ...sources });
522
+ return { sessions: found.map(f => f.summary), omitted };
523
+ }
524
+ /** One session's turns, read through its file (its last maxReadBytes when larger). */
525
+ export async function readNativeSession(root, input, sources = {}) {
526
+ const { found } = find(root, { ...nativeSources(), ...sources });
527
+ const hit = found.find(f => f.summary.id === input.session || f.sessionId === input.session);
528
+ if (!hit)
529
+ return null;
530
+ const stat = fs.statSync(hit.file), now = Date.now();
531
+ const body = await streamInto(hit.file, nativeSessionLimits.maxReadBytes, hit.producer === 'claude-code' ? claudeReader(stat.mtimeMs, now) : codexReader(stat.mtimeMs, now)), turns = body.turns;
532
+ const end = Math.min(turns.length, input.before ?? turns.length), first = Math.max(0, end - Math.min(50, Math.max(1, input.limit ?? 20)));
533
+ return { session: { ...hit.summary, turns: turns.length, omittedTurns: body.truncated ? 1 : 0 }, first, turns: turns.slice(first, end) };
534
+ }
535
+ /** Whether `sessionId` names a session of this Agent in `root`, and its summary. */
536
+ export function nativeSession(root, producer, sessionId, sources = {}) {
537
+ return find(root, { ...nativeSources(), ...sources }).found.find(f => f.producer === producer && f.sessionId === sessionId)?.summary ?? null;
538
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rho-workspace/plugin-agent",
3
- "version": "0.4.29",
3
+ "version": "0.4.33",
4
4
  "type": "module",
5
5
  "description": "Rho agent plugin",
6
6
  "license": "AGPL-3.0-only",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "exports": {
11
11
  "./external-session": "./dist/external-session.js",
12
+ "./native-sessions": "./dist/native-sessions.js",
12
13
  "./dsh-session": "./dist/dsh-session.js",
13
14
  "./external-session-contract": "./dist/external-session-contract.js",
14
15
  "./session-contract": "./dist/session-contract.js",