@rho-workspace/rho 0.5.3 → 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.
Files changed (277) hide show
  1. package/README.md +24 -5
  2. package/dist/browser-access.js +3 -2
  3. package/dist/capabilities.d.ts +29 -2
  4. package/dist/capabilities.js +79 -2
  5. package/dist/cli.js +237 -99
  6. package/dist/contract.generated.d.ts +441 -16
  7. package/dist/contract.generated.js +1 -1
  8. package/dist/contracts/owners.generated.js +459 -10
  9. package/dist/conversation-presentation.d.ts +3 -1
  10. package/dist/conversation-presentation.js +16 -6
  11. package/dist/conversations.d.ts +10 -6
  12. package/dist/conversations.js +17 -13
  13. package/dist/discovery.d.ts +1 -0
  14. package/dist/discovery.js +3 -3
  15. package/dist/extension-contract.d.ts +99 -0
  16. package/dist/extension-contract.js +52 -0
  17. package/dist/extension-directory.d.ts +9 -0
  18. package/dist/extension-directory.js +11 -0
  19. package/dist/extension-worker.d.ts +1 -0
  20. package/dist/extension-worker.js +36 -0
  21. package/dist/extensions.d.ts +157 -0
  22. package/dist/extensions.js +349 -0
  23. package/dist/installs.d.ts +11 -0
  24. package/dist/installs.js +179 -0
  25. package/dist/plugin-cli.d.ts +1 -0
  26. package/dist/plugin-cli.js +139 -0
  27. package/dist/plugin-inventory.json +18 -0
  28. package/dist/plugins.d.ts +3 -3
  29. package/dist/project-notes.js +2 -0
  30. package/dist/project-work.d.ts +3 -1
  31. package/dist/project-work.js +15 -2
  32. package/dist/protocol.d.ts +7 -0
  33. package/dist/protocol.js +11 -7
  34. package/dist/screen-check-skill.d.ts +2 -0
  35. package/dist/screen-check-skill.js +39 -0
  36. package/dist/server.d.ts +6 -1
  37. package/dist/server.js +105 -15
  38. package/dist/web/.vite/manifest.json +65 -65
  39. package/dist/web/assets/{canvas-BXhC8RDZ.js → canvas-D8rDdfDz.js} +1 -1
  40. package/dist/web/assets/choices-b0XhrGQV.js +1 -0
  41. package/dist/web/assets/{console-e1P5qYQ5.js → console-BSjLGe0T.js} +1 -1
  42. package/dist/web/assets/{dist-Dd3iAGFy.js → dist-Buk0lwzI.js} +2 -2
  43. package/dist/web/assets/dist-Cw8hRIlT.js +1 -0
  44. package/dist/web/assets/dist-Tzoe_zm-.js +3 -0
  45. package/dist/web/assets/{dist-DEorbOW0.js → dist-foH9IKc6.js} +1 -1
  46. package/dist/web/assets/explore-in-BVUy3ZW_.js +1 -0
  47. package/dist/web/assets/graph-1OdIJ9l7.js +2 -0
  48. package/dist/web/assets/index-CsC3sTRq.css +1 -0
  49. package/dist/web/assets/{index-BYuKpyoi.js → index-CxmItXqa.js} +220 -121
  50. package/dist/web/assets/{notes-meaning-B3HFbh84.js → notes-meaning-pVbbQUGP.js} +1 -1
  51. package/dist/web/assets/overview-B9UtVJ32.js +6 -0
  52. package/dist/web/assets/preview-CksD_5KS.js +1 -0
  53. package/dist/web/assets/{review-BVh9jWMB.js → review-CMxQlQ-C.js} +1 -1
  54. package/dist/web/assets/{skills-DPaEYkln.js → skills-BFNSyrnD.js} +1 -1
  55. package/dist/web/assets/{skills-work-dvjfy_tu.js → skills-work-BGPSnF_I.js} +1 -1
  56. package/dist/web/assets/{studio-process-BYm8mjRJ.js → studio-process-CsRoTHOh.js} +2 -2
  57. package/dist/web/assets/{timeline-events-CDZ7wiJX.js → timeline-events-BbuuFWHV.js} +1 -1
  58. package/dist/web/assets/{work-turns-BeCt303c.js → work-turns-BvJlIMmA.js} +1 -1
  59. package/dist/web/assets/workspace-CWlkjLNx.js +5 -0
  60. package/dist/web/composition.json +80 -80
  61. package/dist/web/index.html +3 -3
  62. package/dist/web/renderer.json +1 -1
  63. package/dist/web-assets.d.ts +2 -1
  64. package/dist/web-assets.js +3 -2
  65. package/dist/work-contract.js +2 -2
  66. package/docs/FRONTEND.md +30 -14
  67. package/docs/HEADLESS.md +60 -14
  68. package/docs/PLUGIN-AUTHORING.md +4 -0
  69. package/docs/THIRD-PARTY-PLUGINS.md +190 -0
  70. package/example/pa14-qs/.rho/rho/conversations/c-67f77cfebaba2cea.json +1 -1
  71. package/example/pa14-qs/.rho/rho/conversations/c-91aa2e46b1ac1a6c.json +1 -1
  72. package/example/pa14-qs/.rho/rho/conversations/c-fa24dfd80dfe4bcb.json +1 -1
  73. package/node_modules/@hono/node-server/dist/index.js +11 -0
  74. package/node_modules/@hono/node-server/dist/index.mjs +11 -0
  75. package/node_modules/@hono/node-server/dist/listener.js +11 -0
  76. package/node_modules/@hono/node-server/dist/listener.mjs +11 -0
  77. package/node_modules/@hono/node-server/dist/request.js +11 -0
  78. package/node_modules/@hono/node-server/dist/request.mjs +11 -0
  79. package/node_modules/@hono/node-server/dist/server.js +11 -0
  80. package/node_modules/@hono/node-server/dist/server.mjs +11 -0
  81. package/node_modules/@hono/node-server/dist/vercel.js +11 -0
  82. package/node_modules/@hono/node-server/dist/vercel.mjs +11 -0
  83. package/node_modules/@hono/node-server/package.json +1 -1
  84. package/node_modules/@rho-workspace/plugin-agent/README.md +42 -1
  85. package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +14 -3
  86. package/node_modules/@rho-workspace/plugin-agent/dist/activity-normalize.d.ts +1 -0
  87. package/node_modules/@rho-workspace/plugin-agent/dist/activity-normalize.js +2 -2
  88. package/node_modules/@rho-workspace/plugin-agent/dist/conversation.d.ts +1 -0
  89. package/node_modules/@rho-workspace/plugin-agent/dist/conversation.js +1 -0
  90. package/node_modules/@rho-workspace/plugin-agent/dist/dsh-session.d.ts +6 -0
  91. package/node_modules/@rho-workspace/plugin-agent/dist/dsh-session.js +174 -0
  92. package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.d.ts +82 -0
  93. package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +15 -0
  94. package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +73 -0
  95. package/node_modules/@rho-workspace/plugin-agent/dist/external-session.js +190 -0
  96. package/node_modules/@rho-workspace/plugin-agent/dist/generic-acp.d.ts +11 -0
  97. package/node_modules/@rho-workspace/plugin-agent/dist/generic-acp.js +7 -0
  98. package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +373 -0
  99. package/node_modules/@rho-workspace/plugin-agent/dist/index.js +1 -0
  100. package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +5 -1
  101. package/node_modules/@rho-workspace/plugin-agent/dist/operations.d.ts +33 -0
  102. package/node_modules/@rho-workspace/plugin-agent/dist/operations.js +3 -0
  103. package/node_modules/@rho-workspace/plugin-agent/dist/presentation.d.ts +14 -0
  104. package/node_modules/@rho-workspace/plugin-agent/dist/presentation.js +2 -0
  105. package/node_modules/@rho-workspace/plugin-agent/dist/ui-external-session.d.ts +15 -0
  106. package/node_modules/@rho-workspace/plugin-agent/dist/ui-external-session.js +37 -0
  107. package/node_modules/@rho-workspace/plugin-agent/package.json +5 -1
  108. package/node_modules/@rho-workspace/plugin-annotations/package.json +1 -1
  109. package/node_modules/@rho-workspace/plugin-documents/README.md +4 -0
  110. package/node_modules/@rho-workspace/plugin-documents/dist/index.d.ts +3 -0
  111. package/node_modules/@rho-workspace/plugin-documents/dist/index.js +1 -1
  112. package/node_modules/@rho-workspace/plugin-documents/package.json +1 -1
  113. package/node_modules/@rho-workspace/plugin-kernel/README.md +71 -10
  114. package/node_modules/@rho-workspace/plugin-kernel/dist/code.d.ts +2 -0
  115. package/node_modules/@rho-workspace/plugin-kernel/dist/code.js +4 -1
  116. package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +5 -0
  117. package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +7 -0
  118. package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +55 -8
  119. package/node_modules/@rho-workspace/plugin-kernel/dist/lineage.d.ts +9 -6
  120. package/node_modules/@rho-workspace/plugin-kernel/dist/lineage.js +18 -41
  121. package/node_modules/@rho-workspace/plugin-kernel/dist/operations.js +5 -2
  122. package/node_modules/@rho-workspace/plugin-kernel/dist/script-flow.d.ts +39 -0
  123. package/node_modules/@rho-workspace/plugin-kernel/dist/script-flow.js +661 -0
  124. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +3 -0
  125. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.js +10 -0
  126. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-flow.d.ts +38 -4
  127. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-flow.js +194 -97
  128. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -2
  129. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +10 -2
  130. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +79 -18
  131. package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +115 -45
  132. package/node_modules/@rho-workspace/plugin-kernel/dist/ui.d.ts +1 -1
  133. package/node_modules/@rho-workspace/plugin-kernel/dist/ui.js +1 -1
  134. package/node_modules/@rho-workspace/plugin-kernel/dist/value-graph.d.ts +63 -0
  135. package/node_modules/@rho-workspace/plugin-kernel/dist/value-graph.js +344 -0
  136. package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
  137. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +6 -3
  138. package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +6 -3
  139. package/node_modules/express-rate-limit/dist/index.cjs +1 -1
  140. package/node_modules/express-rate-limit/dist/index.mjs +1 -1
  141. package/node_modules/express-rate-limit/package.json +1 -1
  142. package/node_modules/ip-address/dist/ipv6.js +1 -1
  143. package/node_modules/ip-address/package.json +1 -1
  144. package/node_modules/playwright-core/LICENSE +202 -0
  145. package/node_modules/playwright-core/NOTICE +5 -0
  146. package/node_modules/playwright-core/ThirdPartyNotices.txt +13 -0
  147. package/node_modules/playwright-core/bin/install_media_pack.ps1 +5 -0
  148. package/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +33 -0
  149. package/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +42 -0
  150. package/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +13 -0
  151. package/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +24 -0
  152. package/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +42 -0
  153. package/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +12 -0
  154. package/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +24 -0
  155. package/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +48 -0
  156. package/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +11 -0
  157. package/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +23 -0
  158. package/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +48 -0
  159. package/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +11 -0
  160. package/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +23 -0
  161. package/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +48 -0
  162. package/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +11 -0
  163. package/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +24 -0
  164. package/node_modules/playwright-core/browsers.json +52 -0
  165. package/node_modules/playwright-core/cli.js +22 -0
  166. package/node_modules/playwright-core/index.js +17 -0
  167. package/node_modules/playwright-core/index.mjs +28 -0
  168. package/node_modules/playwright-core/lib/bootstrap.js +149 -0
  169. package/node_modules/playwright-core/lib/coreBundle.js +78041 -0
  170. package/node_modules/playwright-core/lib/entry/cliDaemon.js +5 -0
  171. package/node_modules/playwright-core/lib/entry/dashboardApp.js +3 -0
  172. package/node_modules/playwright-core/lib/entry/mcp.js +10 -0
  173. package/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +3 -0
  174. package/node_modules/playwright-core/lib/package.js +50 -0
  175. package/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
  176. package/node_modules/playwright-core/lib/server/electron/loader.js +123 -0
  177. package/node_modules/playwright-core/lib/serverRegistry.js +1939 -0
  178. package/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +65 -0
  179. package/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +141 -0
  180. package/node_modules/playwright-core/lib/tools/cli-client/cli.js +7 -0
  181. package/node_modules/playwright-core/lib/tools/cli-client/help.json +808 -0
  182. package/node_modules/playwright-core/lib/tools/cli-client/minimist.js +128 -0
  183. package/node_modules/playwright-core/lib/tools/cli-client/output.js +352 -0
  184. package/node_modules/playwright-core/lib/tools/cli-client/program.js +422 -0
  185. package/node_modules/playwright-core/lib/tools/cli-client/registry.js +180 -0
  186. package/node_modules/playwright-core/lib/tools/cli-client/session.js +261 -0
  187. package/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
  188. package/node_modules/playwright-core/lib/tools/utils/extension.js +112 -0
  189. package/node_modules/playwright-core/lib/tools/utils/socketConnection.js +108 -0
  190. package/node_modules/playwright-core/lib/utilsBundle.js +86078 -0
  191. package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +1890 -0
  192. package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule--QdMvsKi.css +1 -0
  193. package/node_modules/playwright-core/lib/vite/dashboard/assets/codeMirrorModule-CQ8RZGBm.js +32 -0
  194. package/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
  195. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
  196. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
  197. package/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
  198. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-AwgwcYie.css +1 -0
  199. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-hRvO4yU2.js +12 -0
  200. package/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
  201. package/node_modules/playwright-core/lib/vite/dashboard/index.html +29 -0
  202. package/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +24 -0
  203. package/node_modules/playwright-core/lib/vite/htmlReport/index.html +16 -0
  204. package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -0
  205. package/node_modules/playwright-core/lib/vite/htmlReport/report.js +32 -0
  206. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  207. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DK72OMRi.js +32 -0
  208. package/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
  209. package/node_modules/playwright-core/lib/vite/recorder/assets/index-BZpYJZ-P.css +1 -0
  210. package/node_modules/playwright-core/lib/vite/recorder/assets/index-Cn0mmDuv.js +129 -0
  211. package/node_modules/playwright-core/lib/vite/recorder/index.html +29 -0
  212. package/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +9 -0
  213. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-BEB2e8M3.js +32 -0
  214. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-D-g2vrvf.js +184 -0
  215. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  216. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-B794rD2Z.js +7 -0
  217. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  218. package/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
  219. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.C8BeHuxd.css +1 -0
  220. package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  221. package/node_modules/playwright-core/lib/vite/traceViewer/index.html +44 -0
  222. package/node_modules/playwright-core/lib/vite/traceViewer/index.oNry3Hjv.js +1 -0
  223. package/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +16 -0
  224. package/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +9 -0
  225. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  226. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +10 -0
  227. package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +4 -0
  228. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CYwC_gH1.js +5 -0
  229. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CyMfwkXJ.css +1 -0
  230. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +18 -0
  231. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  232. package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
  233. package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
  234. package/node_modules/playwright-core/lib/xdg-open +1267 -0
  235. package/node_modules/playwright-core/package.json +34 -0
  236. package/package.json +13 -7
  237. package/protocol/index.json +75 -21
  238. package/protocol/operations/agent.sessions.list.json +100 -0
  239. package/protocol/operations/agent.sessions.read.json +266 -0
  240. package/protocol/operations/conversation.settings.check.json +147 -4
  241. package/protocol/operations/conversation.settings.read.json +145 -3
  242. package/protocol/operations/conversation.settings.update.json +166 -4
  243. package/protocol/operations/conversation.start.json +2 -1
  244. package/protocol/operations/extension.call.json +132 -0
  245. package/protocol/operations/extension.content.json +78 -0
  246. package/protocol/operations/extension.describe.json +195 -0
  247. package/protocol/operations/extension.install.json +198 -0
  248. package/protocol/operations/extension.list.json +123 -0
  249. package/protocol/operations/extension.manage.json +55 -0
  250. package/protocol/operations/extension.read.json +112 -0
  251. package/protocol/operations/kernel.interrupt.json +20 -0
  252. package/protocol/operations/kernel.read.json +30 -0
  253. package/protocol/operations/kernel.run.json +36 -1
  254. package/protocol/operations/kernel.stop.json +10 -0
  255. package/protocol/operations/project.work.read.json +3 -2
  256. package/protocol/operations/session.grant.json +9 -0
  257. package/scripts/browser-checks.mjs +53 -0
  258. package/scripts/screen-check.mjs +193 -0
  259. package/scripts/third-party-acceptance.mjs +77 -0
  260. package/vendor/composition.json +111 -111
  261. package/vendor/rho-workspace-plugin-agent-0.4.29.tgz +0 -0
  262. package/vendor/rho-workspace-plugin-annotations-0.5.2.tgz +0 -0
  263. package/vendor/rho-workspace-plugin-documents-0.4.11.tgz +0 -0
  264. package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
  265. package/web/theme.css +17 -2
  266. package/dist/web/assets/choices-B-y2OYvE.js +0 -1
  267. package/dist/web/assets/dist-CBM2oq6g.js +0 -1
  268. package/dist/web/assets/dist-CH56yNDn.js +0 -3
  269. package/dist/web/assets/explore-in-muWXOGOY.js +0 -1
  270. package/dist/web/assets/graph-CK-df5lh.js +0 -2
  271. package/dist/web/assets/index-DBd77u4a.css +0 -1
  272. package/dist/web/assets/overview-D1Ev2OC_.js +0 -6
  273. package/dist/web/assets/preview-HbmQKUGY.js +0 -1
  274. package/dist/web/assets/workspace-d5Hw_ClZ.js +0 -5
  275. package/vendor/rho-workspace-plugin-agent-0.4.27.tgz +0 -0
  276. package/vendor/rho-workspace-plugin-documents-0.4.10.tgz +0 -0
  277. package/vendor/rho-workspace-plugin-kernel-0.5.2.tgz +0 -0
@@ -52,6 +52,15 @@ var RequestError = class extends Error {
52
52
  this.name = "RequestError";
53
53
  }
54
54
  };
55
+ var validateRequestTarget = (target) => {
56
+ const backslash = target.indexOf("\\");
57
+ if (backslash !== -1) {
58
+ const query = target.indexOf("?");
59
+ if (query === -1 || backslash < query) {
60
+ throw new RequestError("Backslash in request path");
61
+ }
62
+ }
63
+ };
55
64
  var toRequestError = (e) => {
56
65
  if (e instanceof RequestError) {
57
66
  return e;
@@ -212,6 +221,7 @@ var newRequest = (incoming, defaultHostname) => {
212
221
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
213
222
  }
214
223
  try {
224
+ validateRequestTarget(incomingUrl);
215
225
  const url2 = new URL(incomingUrl);
216
226
  req[urlKey] = url2.href;
217
227
  } catch (e) {
@@ -232,6 +242,7 @@ var newRequest = (incoming, defaultHostname) => {
232
242
  } else {
233
243
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
234
244
  }
245
+ validateRequestTarget(incomingUrl);
235
246
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
236
247
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
237
248
  throw new RequestError("Invalid host header");
@@ -13,6 +13,15 @@ var RequestError = class extends Error {
13
13
  this.name = "RequestError";
14
14
  }
15
15
  };
16
+ var validateRequestTarget = (target) => {
17
+ const backslash = target.indexOf("\\");
18
+ if (backslash !== -1) {
19
+ const query = target.indexOf("?");
20
+ if (query === -1 || backslash < query) {
21
+ throw new RequestError("Backslash in request path");
22
+ }
23
+ }
24
+ };
16
25
  var toRequestError = (e) => {
17
26
  if (e instanceof RequestError) {
18
27
  return e;
@@ -173,6 +182,7 @@ var newRequest = (incoming, defaultHostname) => {
173
182
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
174
183
  }
175
184
  try {
185
+ validateRequestTarget(incomingUrl);
176
186
  const url2 = new URL(incomingUrl);
177
187
  req[urlKey] = url2.href;
178
188
  } catch (e) {
@@ -193,6 +203,7 @@ var newRequest = (incoming, defaultHostname) => {
193
203
  } else {
194
204
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
195
205
  }
206
+ validateRequestTarget(incomingUrl);
196
207
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
197
208
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
198
209
  throw new RequestError("Invalid host header");
@@ -44,6 +44,15 @@ var RequestError = class extends Error {
44
44
  this.name = "RequestError";
45
45
  }
46
46
  };
47
+ var validateRequestTarget = (target) => {
48
+ const backslash = target.indexOf("\\");
49
+ if (backslash !== -1) {
50
+ const query = target.indexOf("?");
51
+ if (query === -1 || backslash < query) {
52
+ throw new RequestError("Backslash in request path");
53
+ }
54
+ }
55
+ };
47
56
  var toRequestError = (e) => {
48
57
  if (e instanceof RequestError) {
49
58
  return e;
@@ -204,6 +213,7 @@ var newRequest = (incoming, defaultHostname) => {
204
213
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
205
214
  }
206
215
  try {
216
+ validateRequestTarget(incomingUrl);
207
217
  const url2 = new URL(incomingUrl);
208
218
  req[urlKey] = url2.href;
209
219
  } catch (e) {
@@ -224,6 +234,7 @@ var newRequest = (incoming, defaultHostname) => {
224
234
  } else {
225
235
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
226
236
  }
237
+ validateRequestTarget(incomingUrl);
227
238
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
228
239
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
229
240
  throw new RequestError("Invalid host header");
@@ -10,6 +10,15 @@ var RequestError = class extends Error {
10
10
  this.name = "RequestError";
11
11
  }
12
12
  };
13
+ var validateRequestTarget = (target) => {
14
+ const backslash = target.indexOf("\\");
15
+ if (backslash !== -1) {
16
+ const query = target.indexOf("?");
17
+ if (query === -1 || backslash < query) {
18
+ throw new RequestError("Backslash in request path");
19
+ }
20
+ }
21
+ };
13
22
  var toRequestError = (e) => {
14
23
  if (e instanceof RequestError) {
15
24
  return e;
@@ -170,6 +179,7 @@ var newRequest = (incoming, defaultHostname) => {
170
179
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
171
180
  }
172
181
  try {
182
+ validateRequestTarget(incomingUrl);
173
183
  const url2 = new URL(incomingUrl);
174
184
  req[urlKey] = url2.href;
175
185
  } catch (e) {
@@ -190,6 +200,7 @@ var newRequest = (incoming, defaultHostname) => {
190
200
  } else {
191
201
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
192
202
  }
203
+ validateRequestTarget(incomingUrl);
193
204
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
194
205
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
195
206
  throw new RequestError("Invalid host header");
@@ -38,6 +38,15 @@ var RequestError = class extends Error {
38
38
  this.name = "RequestError";
39
39
  }
40
40
  };
41
+ var validateRequestTarget = (target) => {
42
+ const backslash = target.indexOf("\\");
43
+ if (backslash !== -1) {
44
+ const query = target.indexOf("?");
45
+ if (query === -1 || backslash < query) {
46
+ throw new RequestError("Backslash in request path");
47
+ }
48
+ }
49
+ };
41
50
  var toRequestError = (e) => {
42
51
  if (e instanceof RequestError) {
43
52
  return e;
@@ -198,6 +207,7 @@ var newRequest = (incoming, defaultHostname) => {
198
207
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
199
208
  }
200
209
  try {
210
+ validateRequestTarget(incomingUrl);
201
211
  const url2 = new URL(incomingUrl);
202
212
  req[urlKey] = url2.href;
203
213
  } catch (e) {
@@ -218,6 +228,7 @@ var newRequest = (incoming, defaultHostname) => {
218
228
  } else {
219
229
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
220
230
  }
231
+ validateRequestTarget(incomingUrl);
221
232
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
222
233
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
223
234
  throw new RequestError("Invalid host header");
@@ -7,6 +7,15 @@ var RequestError = class extends Error {
7
7
  this.name = "RequestError";
8
8
  }
9
9
  };
10
+ var validateRequestTarget = (target) => {
11
+ const backslash = target.indexOf("\\");
12
+ if (backslash !== -1) {
13
+ const query = target.indexOf("?");
14
+ if (query === -1 || backslash < query) {
15
+ throw new RequestError("Backslash in request path");
16
+ }
17
+ }
18
+ };
10
19
  var toRequestError = (e) => {
11
20
  if (e instanceof RequestError) {
12
21
  return e;
@@ -167,6 +176,7 @@ var newRequest = (incoming, defaultHostname) => {
167
176
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
168
177
  }
169
178
  try {
179
+ validateRequestTarget(incomingUrl);
170
180
  const url2 = new URL(incomingUrl);
171
181
  req[urlKey] = url2.href;
172
182
  } catch (e) {
@@ -187,6 +197,7 @@ var newRequest = (incoming, defaultHostname) => {
187
197
  } else {
188
198
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
189
199
  }
200
+ validateRequestTarget(incomingUrl);
190
201
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
191
202
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
192
203
  throw new RequestError("Invalid host header");
@@ -48,6 +48,15 @@ var RequestError = class extends Error {
48
48
  this.name = "RequestError";
49
49
  }
50
50
  };
51
+ var validateRequestTarget = (target) => {
52
+ const backslash = target.indexOf("\\");
53
+ if (backslash !== -1) {
54
+ const query = target.indexOf("?");
55
+ if (query === -1 || backslash < query) {
56
+ throw new RequestError("Backslash in request path");
57
+ }
58
+ }
59
+ };
51
60
  var toRequestError = (e) => {
52
61
  if (e instanceof RequestError) {
53
62
  return e;
@@ -208,6 +217,7 @@ var newRequest = (incoming, defaultHostname) => {
208
217
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
209
218
  }
210
219
  try {
220
+ validateRequestTarget(incomingUrl);
211
221
  const url2 = new URL(incomingUrl);
212
222
  req[urlKey] = url2.href;
213
223
  } catch (e) {
@@ -228,6 +238,7 @@ var newRequest = (incoming, defaultHostname) => {
228
238
  } else {
229
239
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
230
240
  }
241
+ validateRequestTarget(incomingUrl);
231
242
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
232
243
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
233
244
  throw new RequestError("Invalid host header");
@@ -13,6 +13,15 @@ var RequestError = class extends Error {
13
13
  this.name = "RequestError";
14
14
  }
15
15
  };
16
+ var validateRequestTarget = (target) => {
17
+ const backslash = target.indexOf("\\");
18
+ if (backslash !== -1) {
19
+ const query = target.indexOf("?");
20
+ if (query === -1 || backslash < query) {
21
+ throw new RequestError("Backslash in request path");
22
+ }
23
+ }
24
+ };
16
25
  var toRequestError = (e) => {
17
26
  if (e instanceof RequestError) {
18
27
  return e;
@@ -173,6 +182,7 @@ var newRequest = (incoming, defaultHostname) => {
173
182
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
174
183
  }
175
184
  try {
185
+ validateRequestTarget(incomingUrl);
176
186
  const url2 = new URL(incomingUrl);
177
187
  req[urlKey] = url2.href;
178
188
  } catch (e) {
@@ -193,6 +203,7 @@ var newRequest = (incoming, defaultHostname) => {
193
203
  } else {
194
204
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
195
205
  }
206
+ validateRequestTarget(incomingUrl);
196
207
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
197
208
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
198
209
  throw new RequestError("Invalid host header");
@@ -46,6 +46,15 @@ var RequestError = class extends Error {
46
46
  this.name = "RequestError";
47
47
  }
48
48
  };
49
+ var validateRequestTarget = (target) => {
50
+ const backslash = target.indexOf("\\");
51
+ if (backslash !== -1) {
52
+ const query = target.indexOf("?");
53
+ if (query === -1 || backslash < query) {
54
+ throw new RequestError("Backslash in request path");
55
+ }
56
+ }
57
+ };
49
58
  var toRequestError = (e) => {
50
59
  if (e instanceof RequestError) {
51
60
  return e;
@@ -206,6 +215,7 @@ var newRequest = (incoming, defaultHostname) => {
206
215
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
207
216
  }
208
217
  try {
218
+ validateRequestTarget(incomingUrl);
209
219
  const url2 = new URL(incomingUrl);
210
220
  req[urlKey] = url2.href;
211
221
  } catch (e) {
@@ -226,6 +236,7 @@ var newRequest = (incoming, defaultHostname) => {
226
236
  } else {
227
237
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
228
238
  }
239
+ validateRequestTarget(incomingUrl);
229
240
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
230
241
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
231
242
  throw new RequestError("Invalid host header");
@@ -10,6 +10,15 @@ var RequestError = class extends Error {
10
10
  this.name = "RequestError";
11
11
  }
12
12
  };
13
+ var validateRequestTarget = (target) => {
14
+ const backslash = target.indexOf("\\");
15
+ if (backslash !== -1) {
16
+ const query = target.indexOf("?");
17
+ if (query === -1 || backslash < query) {
18
+ throw new RequestError("Backslash in request path");
19
+ }
20
+ }
21
+ };
13
22
  var toRequestError = (e) => {
14
23
  if (e instanceof RequestError) {
15
24
  return e;
@@ -170,6 +179,7 @@ var newRequest = (incoming, defaultHostname) => {
170
179
  throw new RequestError("Absolute URL for :path is not allowed in HTTP/2");
171
180
  }
172
181
  try {
182
+ validateRequestTarget(incomingUrl);
173
183
  const url2 = new URL(incomingUrl);
174
184
  req[urlKey] = url2.href;
175
185
  } catch (e) {
@@ -190,6 +200,7 @@ var newRequest = (incoming, defaultHostname) => {
190
200
  } else {
191
201
  scheme = incoming.socket && incoming.socket.encrypted ? "https" : "http";
192
202
  }
203
+ validateRequestTarget(incomingUrl);
193
204
  const url = new URL(`${scheme}://${host}${incomingUrl}`);
194
205
  if (url.hostname.length !== host.length && url.hostname !== host.replace(/:\d+$/, "")) {
195
206
  throw new RequestError("Invalid host header");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hono/node-server",
3
- "version": "1.19.17",
3
+ "version": "1.19.18",
4
4
  "description": "Node.js Adapter for Hono",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -343,7 +343,9 @@ results. Results survive MCP restart. One call to the service may take
343
343
  `RHO_TRANSPORT_MS` (30 s), plus the `waitMs` it asks the service to wait (at most ten
344
344
  minutes, as for a Notebook cell); the call uses `node:http`, whose wait for a reply has
345
345
  no limit of its own. A transport overrun keeps the outcome uncertain and directs
346
- observation of the original identity, never replacement work.
346
+ observation of the original identity, never replacement work. A refused connection is the one
347
+ transport failure whose outcome is known: nothing reached the service, so the call
348
+ returns `unavailable` naming the address and saying nothing ran (`delivered: false`).
347
349
 
348
350
  The pure `./layered-contract` entry defines the four tool inputs and Agent note
349
351
  props. `./layered` provides the bounded complete-object cache and caller checks;
@@ -388,3 +390,42 @@ fetched; an explicit response excludes replies already present in that context.
388
390
  ACP retains Codex's native MCP spelling while identifying its operation, and names
389
391
  an otherwise unnamed permission from the original tool update. These observations
390
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.
@@ -195,10 +195,21 @@ export class AcpConversation {
195
195
  let model;
196
196
  this.observationOrigin = this.sessionId ? 'history' : 'initial';
197
197
  if (this.sessionId) {
198
- if (!initialized.agentCapabilities?.loadSession)
198
+ const caps = initialized.agentCapabilities;
199
+ if (caps?.loadSession) {
200
+ const loaded = await connection.agent.request(acp.methods.agent.session.load, { ...params, sessionId: this.sessionId });
201
+ this.settings = loaded.configOptions ?? [];
202
+ }
203
+ else if (caps?.sessionCapabilities?.resume) {
204
+ // ACP session/resume (e.g. dsh): restore the persisted session on a fresh process. It composes
205
+ // the request's MCP connections and validates only the workspace, so the non-standard
206
+ // systemPrompt is omitted; history is restored from the Agent's own persistence, not replayed.
207
+ const resumed = await connection.agent.request(acp.methods.agent.session.resume, { cwd: o.cwd, mcpServers: o.mcpServers, sessionId: this.sessionId });
208
+ this.settings = resumed?.configOptions ?? [];
209
+ }
210
+ else {
199
211
  throw new Error('This Agent cannot load the original session. Nothing was resent.');
200
- const loaded = await connection.agent.request(acp.methods.agent.session.load, { ...params, sessionId: this.sessionId });
201
- this.settings = loaded.configOptions ?? [];
212
+ }
202
213
  }
203
214
  else {
204
215
  const created = await connection.agent.request(acp.methods.agent.session.new, params);
@@ -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 payload(value, options) {
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 = payload(data, options);
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
  });
@@ -123,5 +123,6 @@ export declare function visibleError(error: unknown, env?: NodeJS.ProcessEnv): s
123
123
  export { ClaudeAcpConversation, type ClaudeAcpOptions } from './claude-acp.js';
124
124
  export { writeClaudePlugin, claudePluginLimits, type ClaudePlugin, type ClaudePluginSkill, type ClaudePluginHook } from './claude-plugin.js';
125
125
  export { CodexAcpConversation, type CodexAcpOptions } from './codex-acp.js';
126
+ export { GenericAcpConversation, type GenericAcpOptions } from './generic-acp.js';
126
127
  export { ClaudeCliConversation } from './claude-cli.js';
127
128
  export type { SessionObservation, SessionTool, ConfigOption, ConfigValue } from './session-observation.js';
@@ -25,4 +25,5 @@ export function visibleError(error, env = process.env) {
25
25
  export { ClaudeAcpConversation } from './claude-acp.js';
26
26
  export { writeClaudePlugin, claudePluginLimits } from './claude-plugin.js';
27
27
  export { CodexAcpConversation } from './codex-acp.js';
28
+ export { GenericAcpConversation } from './generic-acp.js';
28
29
  export { ClaudeCliConversation } from './claude-cli.js';
@@ -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
+ }