@lamplitisles/dsh-nanocodex 0.1.0-beta.0

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 (276) hide show
  1. package/LICENSE +65 -0
  2. package/NOTICE +2 -0
  3. package/README.md +182 -0
  4. package/THIRD_PARTY_NOTICES.md +29 -0
  5. package/cordis.patch.yml +52 -0
  6. package/dist/index.d.ts +435 -0
  7. package/dist/index.js +3405 -0
  8. package/dist/standard-preset.d.ts +7 -0
  9. package/dist/standard-preset.js +25 -0
  10. package/engine-release.json +14 -0
  11. package/package.json +119 -0
  12. package/presets/standard/agent.cordis.yml +6 -0
  13. package/presets/standard/preset.yml +2 -0
  14. package/vendor/nanocodex/README.md +1344 -0
  15. package/vendor/nanocodex/actions/events.d.mts +10 -0
  16. package/vendor/nanocodex/actions/events.mjs +163 -0
  17. package/vendor/nanocodex/actions/index.d.mts +13 -0
  18. package/vendor/nanocodex/actions/index.mjs +35 -0
  19. package/vendor/nanocodex/actions/session.d.mts +66 -0
  20. package/vendor/nanocodex/actions/session.mjs +59 -0
  21. package/vendor/nanocodex/actions/turn.d.mts +59 -0
  22. package/vendor/nanocodex/actions/turn.mjs +37 -0
  23. package/vendor/nanocodex/actions/voice.d.mts +22 -0
  24. package/vendor/nanocodex/actions/voice.mjs +50 -0
  25. package/vendor/nanocodex/browser/Agent.d.mts +55 -0
  26. package/vendor/nanocodex/browser/Agent.mjs +11 -0
  27. package/vendor/nanocodex/browser/ChatGptSubscription.d.mts +7 -0
  28. package/vendor/nanocodex/browser/ChatGptSubscription.mjs +19 -0
  29. package/vendor/nanocodex/browser/InlineAgent.mjs +266 -0
  30. package/vendor/nanocodex/browser/Transport.d.mts +74 -0
  31. package/vendor/nanocodex/browser/Transport.mjs +61 -0
  32. package/vendor/nanocodex/browser/Voice.d.mts +71 -0
  33. package/vendor/nanocodex/browser/Voice.mjs +291 -0
  34. package/vendor/nanocodex/browser/VoiceSession.mjs +688 -0
  35. package/vendor/nanocodex/browser/WorkerAgent.d.mts +65 -0
  36. package/vendor/nanocodex/browser/WorkerAgent.mjs +1472 -0
  37. package/vendor/nanocodex/browser/agent.worker.mjs +3 -0
  38. package/vendor/nanocodex/browser/config.d.mts +36 -0
  39. package/vendor/nanocodex/browser/config.mjs +439 -0
  40. package/vendor/nanocodex/browser/engine.mjs +13 -0
  41. package/vendor/nanocodex/browser/harness.mjs +55 -0
  42. package/vendor/nanocodex/browser/host.d.mts +83 -0
  43. package/vendor/nanocodex/browser/host.mjs +555 -0
  44. package/vendor/nanocodex/browser/hostManagedWebSocket.d.mts +14 -0
  45. package/vendor/nanocodex/browser/hostManagedWebSocket.mjs +110 -0
  46. package/vendor/nanocodex/browser/index.d.mts +92 -0
  47. package/vendor/nanocodex/browser/index.mjs +23 -0
  48. package/vendor/nanocodex/browser/indexeddb-durability-store.mjs +197 -0
  49. package/vendor/nanocodex/browser/workspace.d.mts +16 -0
  50. package/vendor/nanocodex/browser/workspace.mjs +107 -0
  51. package/vendor/nanocodex/cloud/Client.d.mts +83 -0
  52. package/vendor/nanocodex/cloud/Client.mjs +231 -0
  53. package/vendor/nanocodex/cloud/Decorator.d.mts +31 -0
  54. package/vendor/nanocodex/cloud/Decorator.mjs +37 -0
  55. package/vendor/nanocodex/cloud/Dialog.d.mts +113 -0
  56. package/vendor/nanocodex/cloud/Dialog.mjs +366 -0
  57. package/vendor/nanocodex/cloud/Errors.d.mts +22 -0
  58. package/vendor/nanocodex/cloud/Errors.mjs +39 -0
  59. package/vendor/nanocodex/cloud/Principal.d.mts +26 -0
  60. package/vendor/nanocodex/cloud/Principal.mjs +88 -0
  61. package/vendor/nanocodex/cloud/RemoteProvider.mjs +88 -0
  62. package/vendor/nanocodex/cloud/Transport.d.mts +47 -0
  63. package/vendor/nanocodex/cloud/Transport.mjs +454 -0
  64. package/vendor/nanocodex/cloud/actions/account.d.mts +9 -0
  65. package/vendor/nanocodex/cloud/actions/account.mjs +19 -0
  66. package/vendor/nanocodex/cloud/actions/agent.d.mts +18 -0
  67. package/vendor/nanocodex/cloud/actions/agent.mjs +428 -0
  68. package/vendor/nanocodex/cloud/actions/connection.d.mts +133 -0
  69. package/vendor/nanocodex/cloud/actions/connection.mjs +580 -0
  70. package/vendor/nanocodex/cloud/actions/grant.d.mts +10 -0
  71. package/vendor/nanocodex/cloud/actions/grant.mjs +12 -0
  72. package/vendor/nanocodex/cloud/actions/index.d.mts +7 -0
  73. package/vendor/nanocodex/cloud/actions/index.mjs +7 -0
  74. package/vendor/nanocodex/cloud/actions/machineUsd.d.mts +23 -0
  75. package/vendor/nanocodex/cloud/actions/machineUsd.mjs +37 -0
  76. package/vendor/nanocodex/cloud/actions/model.d.mts +12 -0
  77. package/vendor/nanocodex/cloud/actions/model.mjs +50 -0
  78. package/vendor/nanocodex/cloud/actions/mpp.d.mts +28 -0
  79. package/vendor/nanocodex/cloud/actions/mpp.mjs +39 -0
  80. package/vendor/nanocodex/cloud/index.d.mts +30 -0
  81. package/vendor/nanocodex/cloud/index.mjs +9 -0
  82. package/vendor/nanocodex/cloud/internal.mjs +533 -0
  83. package/vendor/nanocodex/cloud/mercator.mjs +108 -0
  84. package/vendor/nanocodex/cloud/server/HostPrincipal.d.mts +34 -0
  85. package/vendor/nanocodex/cloud/server/HostPrincipal.mjs +202 -0
  86. package/vendor/nanocodex/cloud/server/index.d.mts +1 -0
  87. package/vendor/nanocodex/cloud/server/index.mjs +1 -0
  88. package/vendor/nanocodex/cloud/types.d.mts +211 -0
  89. package/vendor/nanocodex/cloudflare/Agent.d.mts +133 -0
  90. package/vendor/nanocodex/cloudflare/Agent.mjs +811 -0
  91. package/vendor/nanocodex/cloudflare/egress-subject.mjs +19 -0
  92. package/vendor/nanocodex/cloudflare/egress.d.mts +41 -0
  93. package/vendor/nanocodex/cloudflare/egress.mjs +119 -0
  94. package/vendor/nanocodex/cloudflare/event-socket.mjs +316 -0
  95. package/vendor/nanocodex/cloudflare/index.d.mts +7 -0
  96. package/vendor/nanocodex/cloudflare/index.mjs +6 -0
  97. package/vendor/nanocodex/host/Agent.d.mts +55 -0
  98. package/vendor/nanocodex/host/Agent.mjs +1 -0
  99. package/vendor/nanocodex/host/index.d.mts +82 -0
  100. package/vendor/nanocodex/host/index.mjs +10 -0
  101. package/vendor/nanocodex/index.d.mts +81 -0
  102. package/vendor/nanocodex/index.mjs +12 -0
  103. package/vendor/nanocodex/internal.mjs +1275 -0
  104. package/vendor/nanocodex/managed/Agent.d.mts +380 -0
  105. package/vendor/nanocodex/managed/Agent.mjs +1694 -0
  106. package/vendor/nanocodex/managed/ManagedError.d.mts +9 -0
  107. package/vendor/nanocodex/managed/ManagedError.mjs +8 -0
  108. package/vendor/nanocodex/managed/README.md +241 -0
  109. package/vendor/nanocodex/managed/Voice.mjs +202 -0
  110. package/vendor/nanocodex/managed/index.d.mts +20 -0
  111. package/vendor/nanocodex/managed/index.mjs +2 -0
  112. package/vendor/nanocodex/managed/internal.mjs +119 -0
  113. package/vendor/nanocodex/node/Agent.d.mts +50 -0
  114. package/vendor/nanocodex/node/Agent.mjs +203 -0
  115. package/vendor/nanocodex/node/ChatGptSubscription.d.mts +9 -0
  116. package/vendor/nanocodex/node/ChatGptSubscription.mjs +17 -0
  117. package/vendor/nanocodex/node/Transport.d.mts +44 -0
  118. package/vendor/nanocodex/node/Transport.mjs +46 -0
  119. package/vendor/nanocodex/node/host.mjs +731 -0
  120. package/vendor/nanocodex/node/index.d.mts +73 -0
  121. package/vendor/nanocodex/node/index.mjs +17 -0
  122. package/vendor/nanocodex/node/workspace.d.mts +10 -0
  123. package/vendor/nanocodex/node/workspace.mjs +198 -0
  124. package/vendor/nanocodex/package.json +200 -0
  125. package/vendor/nanocodex/pkg-node/nanocodex.d.ts +675 -0
  126. package/vendor/nanocodex/pkg-node/nanocodex.js +2628 -0
  127. package/vendor/nanocodex/pkg-node/nanocodex_bg.wasm.d.ts +105 -0
  128. package/vendor/nanocodex/pkg-node/package.json +1 -0
  129. package/vendor/nanocodex/pkg-web/.nanocodex-bindgen-stamp +6 -0
  130. package/vendor/nanocodex/pkg-web/nanocodex-build.json +1 -0
  131. package/vendor/nanocodex/pkg-web/nanocodex.d.ts +805 -0
  132. package/vendor/nanocodex/pkg-web/nanocodex.js +2714 -0
  133. package/vendor/nanocodex/pkg-web/nanocodex_bg.js +2615 -0
  134. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm +0 -0
  135. package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm.d.ts +105 -0
  136. package/vendor/nanocodex/pkg-web/nanocodex_worker.js +9 -0
  137. package/vendor/nanocodex/pkg-web/package.json +1 -0
  138. package/vendor/nanocodex/runtime/chatgpt-subscription.mjs +212 -0
  139. package/vendor/nanocodex/runtime/cloudflare-durability-store.d.mts +26 -0
  140. package/vendor/nanocodex/runtime/cloudflare-durability-store.mjs +168 -0
  141. package/vendor/nanocodex/runtime/code-evaluator.worker.mjs +137 -0
  142. package/vendor/nanocodex/runtime/code-runtime.mjs +1 -0
  143. package/vendor/nanocodex/runtime/durability-store.d.mts +85 -0
  144. package/vendor/nanocodex/runtime/durability-store.mjs +631 -0
  145. package/vendor/nanocodex/runtime/durability.mjs +153 -0
  146. package/vendor/nanocodex/runtime/managed-transport.mjs +460 -0
  147. package/vendor/nanocodex/runtime/mcp-runtime.mjs +534 -0
  148. package/vendor/nanocodex/runtime/postgres-durability-store.d.mts +64 -0
  149. package/vendor/nanocodex/runtime/postgres-durability-store.mjs +532 -0
  150. package/vendor/nanocodex/runtime/quickjs-evaluator.d.mts +17 -0
  151. package/vendor/nanocodex/runtime/quickjs-evaluator.mjs +236 -0
  152. package/vendor/nanocodex/runtime/responses-transport.mjs +22 -0
  153. package/vendor/nanocodex/runtime/subagents.d.mts +107 -0
  154. package/vendor/nanocodex/runtime/subagents.mjs +54 -0
  155. package/vendor/nanocodex/runtime/subscription-store.d.mts +8 -0
  156. package/vendor/nanocodex/runtime/subscription-store.mjs +42 -0
  157. package/vendor/nanocodex/runtime/tempo-provider.d.mts +125 -0
  158. package/vendor/nanocodex/runtime/tempo-provider.mjs +264 -0
  159. package/vendor/nanocodex/runtime/tool-configuration.mjs +1 -0
  160. package/vendor/nanocodex/runtime/tool-router.mjs +1 -0
  161. package/vendor/nanocodex/runtime/utf8.mjs +1 -0
  162. package/vendor/nanocodex/runtime/worker-evaluator.mjs +162 -0
  163. package/vendor/nanocodex/runtime/workspace.d.mts +1 -0
  164. package/vendor/nanocodex/runtime/workspace.mjs +1 -0
  165. package/vendor/nanocodex/tools/Tools.d.mts +56 -0
  166. package/vendor/nanocodex/tools/Tools.mjs +136 -0
  167. package/vendor/nanocodex/tools/artifact.d.mts +1 -0
  168. package/vendor/nanocodex/tools/artifact.mjs +1 -0
  169. package/vendor/nanocodex/tools/attachment.mjs +1 -0
  170. package/vendor/nanocodex/tools/bash.d.mts +1 -0
  171. package/vendor/nanocodex/tools/bash.mjs +1 -0
  172. package/vendor/nanocodex/tools/browser/accountInfo.mjs +795 -0
  173. package/vendor/nanocodex/tools/browser/browserBuffer.mjs +18 -0
  174. package/vendor/nanocodex/tools/browser/browserCompiler.mjs +155 -0
  175. package/vendor/nanocodex/tools/browser/browserEgress.mjs +161 -0
  176. package/vendor/nanocodex/tools/browser/browserPython.mjs +133 -0
  177. package/vendor/nanocodex/tools/browser/browserShell.mjs +1075 -0
  178. package/vendor/nanocodex/tools/browser/browserSprintf.mjs +4 -0
  179. package/vendor/nanocodex/tools/browser/browserSsh.mjs +201 -0
  180. package/vendor/nanocodex/tools/browser/browserZlib.mjs +136 -0
  181. package/vendor/nanocodex/tools/browser/compiler.worker.mjs +160 -0
  182. package/vendor/nanocodex/tools/browser/devTunnelsSshBrowser.mjs +13 -0
  183. package/vendor/nanocodex/tools/browser/index.d.mts +209 -0
  184. package/vendor/nanocodex/tools/browser/index.mjs +174 -0
  185. package/vendor/nanocodex/tools/browser/opfsGit.mjs +248 -0
  186. package/vendor/nanocodex/tools/browser/python.worker.mjs +114 -0
  187. package/vendor/nanocodex/tools/browser/threadGit.mjs +197 -0
  188. package/vendor/nanocodex/tools/browser/unsupportedNodeRsa.mjs +5 -0
  189. package/vendor/nanocodex/tools/browser/workspace.mjs +83 -0
  190. package/vendor/nanocodex/tools/dataset.d.mts +1 -0
  191. package/vendor/nanocodex/tools/dataset.mjs +1 -0
  192. package/vendor/nanocodex/tools/datasetContract.mjs +1 -0
  193. package/vendor/nanocodex/tools/datasetEngine.mjs +1 -0
  194. package/vendor/nanocodex/tools/hostedCatalog.d.mts +1 -0
  195. package/vendor/nanocodex/tools/hostedCatalog.mjs +1 -0
  196. package/vendor/nanocodex/tools/index.d.mts +49 -0
  197. package/vendor/nanocodex/tools/index.mjs +19 -0
  198. package/vendor/nanocodex/tools/namedTool.mjs +1 -0
  199. package/vendor/nanocodex/tools/repository-workspace.d.mts +1 -0
  200. package/vendor/nanocodex/tools/repository-workspace.mjs +1 -0
  201. package/vendor/nanocodex/tools/ssh.d.mts +1 -0
  202. package/vendor/nanocodex/tools/ssh.mjs +1 -0
  203. package/vendor/nanocodex/tools/standard.mjs +1 -0
  204. package/vendor/nanocodex/tools/standardDescriptions.mjs +1 -0
  205. package/vendor/nanocodex/types.d.mts +663 -0
  206. package/vendor/nanocodex/wasm.d.mts +4 -0
  207. package/vendor/nanocodex/worker/ChatGptSubscription.d.mts +9 -0
  208. package/vendor/nanocodex/worker/ChatGptSubscription.mjs +29 -0
  209. package/vendor/nanocodex/worker/index.d.mts +18 -0
  210. package/vendor/nanocodex/worker/index.mjs +5 -0
  211. package/vendor/nanocodex-tools/README.md +65 -0
  212. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.d.ts +7 -0
  213. package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.js +14 -0
  214. package/vendor/nanocodex-tools/dist/hosted/broker-core.d.ts +172 -0
  215. package/vendor/nanocodex-tools/dist/hosted/broker-core.js +1278 -0
  216. package/vendor/nanocodex-tools/dist/hosted/index.d.ts +3 -0
  217. package/vendor/nanocodex-tools/dist/hosted/index.js +3 -0
  218. package/vendor/nanocodex-tools/dist/hosted/protocol.d.ts +120 -0
  219. package/vendor/nanocodex-tools/dist/hosted/protocol.js +415 -0
  220. package/vendor/nanocodex-tools/dist/index.d.ts +19 -0
  221. package/vendor/nanocodex-tools/dist/index.js +14 -0
  222. package/vendor/nanocodex-tools/dist/memory.d.ts +204 -0
  223. package/vendor/nanocodex-tools/dist/memory.js +411 -0
  224. package/vendor/nanocodex-tools/dist/namespace.d.ts +87 -0
  225. package/vendor/nanocodex-tools/dist/namespace.js +338 -0
  226. package/vendor/nanocodex-tools/dist/repository-archive.d.ts +4 -0
  227. package/vendor/nanocodex-tools/dist/repository-archive.js +104 -0
  228. package/vendor/nanocodex-tools/dist/runtime.d.ts +30 -0
  229. package/vendor/nanocodex-tools/dist/runtime.js +37 -0
  230. package/vendor/nanocodex-tools/dist/session.d.ts +167 -0
  231. package/vendor/nanocodex-tools/dist/session.js +286 -0
  232. package/vendor/nanocodex-tools/dist/shell.d.ts +51 -0
  233. package/vendor/nanocodex-tools/dist/shell.js +553 -0
  234. package/vendor/nanocodex-tools/dist/ssh.d.ts +29 -0
  235. package/vendor/nanocodex-tools/dist/ssh.js +66 -0
  236. package/vendor/nanocodex-tools/dist/workspace.d.ts +45 -0
  237. package/vendor/nanocodex-tools/dist/workspace.js +237 -0
  238. package/vendor/nanocodex-tools/package.json +122 -0
  239. package/vendor/nanocodex-tools/runtime/code-runtime.mjs +817 -0
  240. package/vendor/nanocodex-tools/runtime/tool-configuration.mjs +50 -0
  241. package/vendor/nanocodex-tools/runtime/tool-router.mjs +730 -0
  242. package/vendor/nanocodex-tools/runtime/utf8.d.mts +2 -0
  243. package/vendor/nanocodex-tools/runtime/utf8.mjs +25 -0
  244. package/vendor/nanocodex-tools/tools/artifact.d.mts +69 -0
  245. package/vendor/nanocodex-tools/tools/artifact.mjs +270 -0
  246. package/vendor/nanocodex-tools/tools/attachment.mjs +603 -0
  247. package/vendor/nanocodex-tools/tools/bash.d.mts +90 -0
  248. package/vendor/nanocodex-tools/tools/bash.mjs +742 -0
  249. package/vendor/nanocodex-tools/tools/dataset.d.mts +9 -0
  250. package/vendor/nanocodex-tools/tools/dataset.mjs +47 -0
  251. package/vendor/nanocodex-tools/tools/datasetContract.mjs +54 -0
  252. package/vendor/nanocodex-tools/tools/datasetEngine.mjs +1167 -0
  253. package/vendor/nanocodex-tools/tools/execution-contract.d.mts +5 -0
  254. package/vendor/nanocodex-tools/tools/execution-contract.mjs +63 -0
  255. package/vendor/nanocodex-tools/tools/hostedCatalog.d.mts +20 -0
  256. package/vendor/nanocodex-tools/tools/hostedCatalog.mjs +67 -0
  257. package/vendor/nanocodex-tools/tools/hostedMachine.d.mts +10 -0
  258. package/vendor/nanocodex-tools/tools/hostedMachine.mjs +56 -0
  259. package/vendor/nanocodex-tools/tools/just-bash-browser.d.mts +1 -0
  260. package/vendor/nanocodex-tools/tools/just-bash-browser.mjs +1 -0
  261. package/vendor/nanocodex-tools/tools/namedTool.d.mts +3 -0
  262. package/vendor/nanocodex-tools/tools/namedTool.mjs +3 -0
  263. package/vendor/nanocodex-tools/tools/nodeProcess.d.mts +10 -0
  264. package/vendor/nanocodex-tools/tools/nodeProcess.mjs +314 -0
  265. package/vendor/nanocodex-tools/tools/processOutput.mjs +56 -0
  266. package/vendor/nanocodex-tools/tools/repository-workspace.d.mts +55 -0
  267. package/vendor/nanocodex-tools/tools/repository-workspace.mjs +397 -0
  268. package/vendor/nanocodex-tools/tools/ssh.d.mts +56 -0
  269. package/vendor/nanocodex-tools/tools/ssh.mjs +503 -0
  270. package/vendor/nanocodex-tools/tools/standard.mjs +458 -0
  271. package/vendor/nanocodex-tools/tools/standardDescriptions.mjs +3 -0
  272. package/vendor/nanocodex-tools/tools/types.d.mts +83 -0
  273. package/vendor/nanocodex-tools/tools/workspace.d.mts +25 -0
  274. package/vendor/nanocodex-tools/tools/workspace.mjs +282 -0
  275. package/vendor/nanocodex-tools/tools/x.d.mts +30 -0
  276. package/vendor/nanocodex-tools/tools/x.mjs +91 -0
@@ -0,0 +1,5 @@
1
+ export const EXEC_COMMAND_PARAMETERS: Readonly<Record<string, unknown>>;
2
+ export const WRITE_STDIN_PARAMETERS: Readonly<Record<string, unknown>>;
3
+ export const EXECUTION_OUTPUT_SCHEMA: Readonly<Record<string, unknown>>;
4
+ export const MACHINE_PREVIEW_PARAMETERS: Readonly<Record<string, unknown>>;
5
+ export const PREVIEW_OUTPUT_SCHEMA: Readonly<Record<string, unknown>>;
@@ -0,0 +1,63 @@
1
+ export const EXEC_COMMAND_PARAMETERS = Object.freeze({
2
+ type: "object",
3
+ properties: {
4
+ cmd: { type: "string", description: "Shell command to execute." },
5
+ justification: { type: "string", description: "User-facing approval question for `require_escalated`; omit otherwise." },
6
+ workdir: { type: "string", description: "Working directory for the command." },
7
+ shell: { type: "string", description: "Shell binary to launch. Defaults to the selected hand's shell." },
8
+ login: { type: "boolean", description: "True runs with login-shell semantics when supported." },
9
+ tty: { type: "boolean", description: "True allocates a PTY when supported." },
10
+ yield_time_ms: { type: "number", description: "Wait before yielding output. Defaults to 10000 ms." },
11
+ max_output_tokens: { type: "number", description: "Output token budget. Defaults to 10000 tokens." },
12
+ prefix_rule: { type: "array", items: { type: "string" }, description: "Reusable approval prefix for `require_escalated`." },
13
+ sandbox_permissions: { type: "string", enum: ["use_default", "require_escalated"], description: "Per-command sandbox policy request." },
14
+ },
15
+ required: ["cmd"],
16
+ additionalProperties: false,
17
+ });
18
+
19
+ export const WRITE_STDIN_PARAMETERS = Object.freeze({
20
+ type: "object",
21
+ properties: {
22
+ session_id: { type: "number", description: "Session identifier returned by exec_command." },
23
+ chars: { type: "string", description: "Characters to write; omit or pass an empty string to poll." },
24
+ yield_time_ms: { type: "number", description: "Wait before yielding more output." },
25
+ max_output_tokens: { type: "number", description: "Output token budget." },
26
+ },
27
+ required: ["session_id"],
28
+ additionalProperties: false,
29
+ });
30
+
31
+ export const EXECUTION_OUTPUT_SCHEMA = Object.freeze({
32
+ type: "object",
33
+ properties: {
34
+ chunk_id: { type: "string", description: "Chunk identifier included when the response reports one." },
35
+ wall_time_seconds: { type: "number", description: "Elapsed wall time spent waiting for output in seconds." },
36
+ exit_code: { type: "number", description: "Process exit code when the command finished during this call." },
37
+ session_id: { type: "number", description: "Session identifier to pass to write_stdin when the process is still running." },
38
+ original_token_count: { type: "number", description: "Approximate token count before output truncation." },
39
+ output: { type: "string", description: "Command output text, possibly truncated." },
40
+ },
41
+ required: ["wall_time_seconds", "output"],
42
+ additionalProperties: false,
43
+ });
44
+
45
+ export const MACHINE_PREVIEW_PARAMETERS = Object.freeze({
46
+ type: "object",
47
+ properties: {
48
+ port: { type: "integer", minimum: 1024, maximum: 65_535, not: { const: 3_000 } },
49
+ },
50
+ required: ["port"],
51
+ additionalProperties: false,
52
+ });
53
+
54
+ export const PREVIEW_OUTPUT_SCHEMA = Object.freeze({
55
+ type: "object",
56
+ properties: {
57
+ port: { type: "integer" },
58
+ url: { type: "string" },
59
+ persistent: { type: "boolean" },
60
+ },
61
+ required: ["port", "url", "persistent"],
62
+ additionalProperties: false,
63
+ });
@@ -0,0 +1,20 @@
1
+ import type { NamedTool } from "./types.mjs";
2
+
3
+ export type HostedToolCatalogEntry = Readonly<{
4
+ provider: string;
5
+ remote_name: string;
6
+ definition: Readonly<Record<string, unknown>>;
7
+ parallel_safe: boolean;
8
+ summary?: string | undefined;
9
+ timeout_ms: number;
10
+ }>;
11
+
12
+ export type { HostedMachine } from "./hostedMachine.mjs";
13
+
14
+ export function hostedAppToolCatalog(
15
+ tools: readonly NamedTool[] | Readonly<Record<string, unknown>>,
16
+ provider?: string,
17
+ ): readonly HostedToolCatalogEntry[];
18
+
19
+ export function hostedCatalog(catalog: readonly HostedToolCatalogEntry[]): readonly HostedToolCatalogEntry[];
20
+ export function hostedToolCatalogDigest(catalog: readonly HostedToolCatalogEntry[]): Promise<`0x${string}`>;
@@ -0,0 +1,67 @@
1
+ import { resolveTools } from "../runtime/tool-configuration.mjs";
2
+ import { ToolRouter, toolMapSource } from "../runtime/tool-router.mjs";
3
+
4
+ const DEFAULT_TIMEOUT_MS = 120_000;
5
+ const CATALOG_DIGEST_DOMAIN = "nanocodex-app-tool-catalog-v1\0";
6
+
7
+ /** Materializes the exact reverse-tool catalog emitted for app-local named tools. */
8
+ export function hostedAppToolCatalog(tools, provider = "javascript") {
9
+ const resolved = resolveTools(tools ?? [], { defaultSubagents: false });
10
+ if (resolved.subagents) throw new TypeError("hosted app tool catalogs do not accept subagents");
11
+ const router = new ToolRouter();
12
+ if (Object.keys(resolved.tools).length > 0) {
13
+ router.addSource(toolMapSource("custom", resolved.tools, { kind: "cloud" }));
14
+ }
15
+ return hostedCatalog(router.catalog(provider));
16
+ }
17
+
18
+ /** Normalizes an admitted router catalog to the socket-owned wire contract. */
19
+ export function hostedCatalog(catalog) {
20
+ if (!Array.isArray(catalog) || catalog.length > 256) {
21
+ throw new RangeError("tool attachment catalogs contain at most 256 tools");
22
+ }
23
+ return Object.freeze(catalog.map((entry) => Object.freeze({
24
+ provider: entry.provider,
25
+ remote_name: entry.remote_name,
26
+ definition: hostedDefinition(entry.definition),
27
+ parallel_safe: entry.parallel_safe === true,
28
+ ...(entry.summary === undefined ? {} : { summary: entry.summary }),
29
+ timeout_ms: entry.timeout_ms ?? DEFAULT_TIMEOUT_MS,
30
+ })));
31
+ }
32
+
33
+ /** Returns the exact domain-separated digest signed into a Connect grant. */
34
+ export async function hostedToolCatalogDigest(catalog) {
35
+ const normalized = hostedCatalog(catalog);
36
+ const canonical = canonicalJson([...normalized]
37
+ .sort((left, right) => canonicalJson(left).localeCompare(canonicalJson(right))));
38
+ const bytes = new TextEncoder().encode(`${CATALOG_DIGEST_DOMAIN}${canonical}`);
39
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
40
+ return `0x${[...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`;
41
+ }
42
+
43
+ function hostedDefinition(definition) {
44
+ if (definition.type === "custom") return Object.freeze({
45
+ type: "custom", name: definition.name, description: definition.description, format: definition.format,
46
+ });
47
+ return Object.freeze({
48
+ type: "function",
49
+ name: definition.name,
50
+ description: definition.description ?? "Application-defined tool.",
51
+ strict: definition.strict ?? false,
52
+ parameters: definition.parameters ?? { type: "object", additionalProperties: true },
53
+ ...(definition.output_schema === undefined ? {} : { output_schema: definition.output_schema }),
54
+ });
55
+ }
56
+
57
+ function canonicalJson(value) {
58
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
59
+ if (value && typeof value === "object") {
60
+ return `{${Object.keys(value).sort().map((key) => (
61
+ `${JSON.stringify(key)}:${canonicalJson(value[key])}`
62
+ )).join(",")}}`;
63
+ }
64
+ const encoded = JSON.stringify(value);
65
+ if (encoded === undefined) throw new TypeError("hosted tool catalogs must be JSON-serializable");
66
+ return encoded;
67
+ }
@@ -0,0 +1,10 @@
1
+ export type HostedMachine = Readonly<{
2
+ id: string;
3
+ name: string;
4
+ workspace: string;
5
+ capabilities: readonly string[];
6
+ }>;
7
+
8
+ export function normalizeHostedMachines(
9
+ machines?: readonly HostedMachine[],
10
+ ): readonly HostedMachine[];
@@ -0,0 +1,56 @@
1
+ import { utf8ByteLength } from "../runtime/utf8.mjs";
2
+
3
+ const MAX_MACHINES = 1;
4
+ const MAX_MACHINE_CAPABILITIES = 64;
5
+ const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,122}$/;
6
+ const CAPABILITY = /^[a-z0-9][a-z0-9._:-]{0,63}$/;
7
+ const normalizedSnapshots = new WeakSet();
8
+
9
+ /** Normalizes the non-secret machines represented by one attached tool host. */
10
+ export function normalizeHostedMachines(machines = []) {
11
+ if (normalizedSnapshots.has(machines)) return machines;
12
+ if (!Array.isArray(machines) || machines.length > MAX_MACHINES) {
13
+ throw new RangeError(`tool attachments describe at most ${MAX_MACHINES} machines`);
14
+ }
15
+ const ids = new Set();
16
+ const normalized = Object.freeze(machines.map((machine, index) => {
17
+ if (!machine || typeof machine !== "object" || Array.isArray(machine)) {
18
+ throw new TypeError(`machines[${index}] must be an object`);
19
+ }
20
+ const allowed = new Set(["id", "name", "workspace", "capabilities"]);
21
+ for (const key of Object.keys(machine)) {
22
+ if (!allowed.has(key)) throw new TypeError(`machines[${index}] contains unsupported field ${key}`);
23
+ }
24
+ if (typeof machine.id !== "string" || !IDENTIFIER.test(machine.id)) {
25
+ throw new TypeError(`machines[${index}].id must be a safe identifier of at most 123 bytes`);
26
+ }
27
+ if (ids.has(machine.id)) throw new TypeError(`duplicate machine id: ${machine.id}`);
28
+ ids.add(machine.id);
29
+ if (typeof machine.name !== "string" || !machine.name.trim() || utf8ByteLength(machine.name) > 128) {
30
+ throw new TypeError(`machines[${index}].name must be 1-128 UTF-8 bytes`);
31
+ }
32
+ if (typeof machine.workspace !== "string" || !machine.workspace.trim()
33
+ || utf8ByteLength(machine.workspace) > 1024 || machine.workspace.includes("\0")) {
34
+ throw new TypeError(`machines[${index}].workspace must be 1-1024 UTF-8 bytes`);
35
+ }
36
+ if (!Array.isArray(machine.capabilities)
37
+ || machine.capabilities.length > MAX_MACHINE_CAPABILITIES
38
+ || machine.capabilities.some((capability) => (
39
+ typeof capability !== "string" || !CAPABILITY.test(capability)
40
+ ))) {
41
+ throw new TypeError(`machines[${index}].capabilities must be safe identifiers`);
42
+ }
43
+ const capabilities = [...new Set(machine.capabilities)];
44
+ if (capabilities.length !== machine.capabilities.length) {
45
+ throw new TypeError(`machines[${index}].capabilities must be unique`);
46
+ }
47
+ return Object.freeze({
48
+ id: machine.id,
49
+ name: machine.name.trim(),
50
+ workspace: machine.workspace,
51
+ capabilities: Object.freeze(capabilities),
52
+ });
53
+ }));
54
+ normalizedSnapshots.add(normalized);
55
+ return normalized;
56
+ }
@@ -0,0 +1 @@
1
+ export { Bash, defineCommand } from "just-bash/browser";
@@ -0,0 +1 @@
1
+ export { Bash, defineCommand } from "just-bash/browser";
@@ -0,0 +1,3 @@
1
+ import type { NamedTool, Tool } from "./types.mjs";
2
+
3
+ export function namedTool(name: string, tool: Tool): NamedTool;
@@ -0,0 +1,3 @@
1
+ export function namedTool(name, tool) {
2
+ return Object.freeze({ name, ...tool });
3
+ }
@@ -0,0 +1,10 @@
1
+ import type { NamedTool } from "./types.mjs";
2
+ export function createNodeProcessTools(options: {
3
+ workspace: string;
4
+ onActivity?: (event: {
5
+ type: "started" | "completed";
6
+ sessionId: string;
7
+ processId: number;
8
+ exitCode?: number;
9
+ }) => void;
10
+ }): Promise<{ tools: readonly NamedTool[]; close(): Promise<void> }>;
@@ -0,0 +1,314 @@
1
+ import { spawn } from "node:child_process";
2
+ import { realpath, stat } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
5
+ import { setTimeout as delay } from "node:timers/promises";
6
+ import {
7
+ EXEC_COMMAND_PARAMETERS,
8
+ WRITE_STDIN_PARAMETERS,
9
+ EXECUTION_OUTPUT_SCHEMA,
10
+ } from "./execution-contract.mjs";
11
+ import { namedTool } from "./namedTool.mjs";
12
+ import { createProcessOutput } from "./processOutput.mjs";
13
+
14
+ /** Native, explicitly authorized host execution. The workspace is the initial cwd,
15
+ * not an OS sandbox. Credentials from the embedding process are never inherited. */
16
+ export async function createNodeProcessTools({
17
+ workspace,
18
+ onActivity = () => {},
19
+ }) {
20
+ const root = await realpath(workspace);
21
+ const sessions = new Map();
22
+ const processes = new Set();
23
+ let sequence = 0;
24
+ let disposed = false;
25
+ const environment = Object.fromEntries(
26
+ ["PATH", "HOME", "USER", "TMPDIR", "LANG", "LC_ALL", "SYSTEMROOT"]
27
+ .filter((key) => process.env[key] !== undefined)
28
+ .map((key) => [key, process.env[key]]),
29
+ );
30
+ if (process.platform === "darwin") {
31
+ // Finder launches apps with a minimal PATH. Discover common installations
32
+ // without sourcing shell profiles or importing their environment secrets.
33
+ const candidates = [
34
+ "/opt/homebrew/bin",
35
+ "/usr/local/bin",
36
+ join(homedir(), ".cargo", "bin"),
37
+ "/opt/homebrew/sbin",
38
+ ];
39
+ const available = await Promise.all(candidates.map(async directory => {
40
+ try { return (await stat(directory)).isDirectory() ? directory : undefined; }
41
+ catch { return undefined; }
42
+ }));
43
+ const inherited = (environment.PATH || "/usr/bin:/bin:/usr/sbin:/sbin").split(":");
44
+ environment.PATH = [...new Set([...inherited, ...available.filter(Boolean)])].join(":");
45
+ }
46
+ environment.TERM = "dumb";
47
+ const kill = (record, signal = "SIGTERM") => {
48
+ if (!record.child.pid) return;
49
+ try {
50
+ process.kill(
51
+ process.platform === "win32" ? record.child.pid : -record.child.pid,
52
+ signal,
53
+ );
54
+ } catch (error) {
55
+ if (error.code !== "ESRCH") throw error;
56
+ }
57
+ };
58
+ const stopGroup = async (record, signal) => {
59
+ for (let attempt = 0; ; attempt++) {
60
+ try {
61
+ kill(record, signal);
62
+ return;
63
+ } catch (error) {
64
+ // Darwin can briefly return EPERM for a group whose last member is
65
+ // being reaped. Retry that transition; still report a real refusal.
66
+ if (error.code !== "EPERM" || !record.done || attempt === 4)
67
+ throw error;
68
+ await delay(25);
69
+ }
70
+ }
71
+ };
72
+ const stop = (record) =>
73
+ (record.stopping ??= (async () => {
74
+ await stopGroup(record, "SIGTERM");
75
+ await Promise.race([
76
+ record.closed,
77
+ delay(1_500, undefined, { ref: false }),
78
+ ]);
79
+ // Kill descendants too, even if the shell itself exited first.
80
+ await stopGroup(record, "SIGKILL");
81
+ await record.closed;
82
+ await record.output.close();
83
+ sessions.delete(record.id);
84
+ processes.delete(record);
85
+ })());
86
+ const collect = () => {
87
+ for (const record of processes)
88
+ if (record.done) {
89
+ try {
90
+ if (record.child.pid)
91
+ process.kill(
92
+ process.platform === "win32"
93
+ ? record.child.pid
94
+ : -record.child.pid,
95
+ 0,
96
+ );
97
+ else processes.delete(record);
98
+ } catch (error) {
99
+ if (error.code === "ESRCH") processes.delete(record);
100
+ }
101
+ }
102
+ };
103
+ const activity = (event) => {
104
+ try {
105
+ onActivity(event);
106
+ } catch {
107
+ /* Observers cannot interrupt process cleanup. */
108
+ }
109
+ };
110
+ const read = async (record, input, signal, maximumWait = 30_000) => {
111
+ const started = performance.now();
112
+ const wait = integer(input.yield_time_ms, 1_000, 0, maximumWait);
113
+ const maxBytes = integer(input.max_output_tokens, 10_000, 1, 32_000) * 4;
114
+ if (record.reading)
115
+ throw new Error("A read is already pending for this process.");
116
+ record.reading = true;
117
+ const timer = new AbortController();
118
+ try {
119
+ await Promise.race([
120
+ record.closed,
121
+ delay(wait, undefined, {
122
+ signal: signal ? AbortSignal.any([signal, timer.signal]) : timer.signal,
123
+ ref: false,
124
+ }),
125
+ ]);
126
+ if (signal?.aborted) throw signal.reason;
127
+ if (record.outputError) throw record.outputError;
128
+ const output = await record.output.read(maxBytes);
129
+ if (record.outputError) throw record.outputError;
130
+ const result = {
131
+ output,
132
+ wall_time_seconds: (performance.now() - started) / 1_000,
133
+ };
134
+ if (record.done && !record.output.unread) {
135
+ result.output += record.output.end();
136
+ await record.output.close();
137
+ result.exit_code = record.exitCode;
138
+ sessions.delete(record.id);
139
+ collect();
140
+ } else result.session_id = record.id;
141
+ return result;
142
+ } finally {
143
+ // A completed process must not retain a long-poll timer or its abort listener.
144
+ timer.abort();
145
+ record.reading = false;
146
+ }
147
+ };
148
+ const exec = namedTool("exec_command", {
149
+ description:
150
+ "Run a native command in the authorized host workspace. Supports pipe sessions and write_stdin; PTYs are not available on this hand.",
151
+ parameters: EXEC_COMMAND_PARAMETERS,
152
+ outputSchema: EXECUTION_OUTPUT_SCHEMA,
153
+ supportsParallelToolCalls: true,
154
+ async handler(input, context) {
155
+ if (disposed) throw new Error("This compute hand is stopped.");
156
+ if (!input || typeof input.cmd !== "string" || !input.cmd.trim())
157
+ throw new TypeError("cmd is required");
158
+ if (input.tty)
159
+ throw new Error(
160
+ "This native Hand supports pipes. Use a VM Hand for PTY commands.",
161
+ );
162
+ if (input.sandbox_permissions === "require_escalated")
163
+ throw new Error("This Hand does not provide privilege escalation.");
164
+ integer(input.yield_time_ms, 1_000, 0, 30_000);
165
+ integer(input.max_output_tokens, 10_000, 1, 32_000);
166
+ if (input.workdir !== undefined && typeof input.workdir !== "string")
167
+ throw new TypeError("workdir must be text");
168
+ collect();
169
+ const cwd = await realpath(resolve(root, input.workdir ?? root));
170
+ const path = relative(root, cwd);
171
+ if (path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path))
172
+ throw new Error("workdir is outside this Hand's workspace.");
173
+ const shell =
174
+ input.shell ?? (process.platform === "win32" ? "cmd.exe" : "/bin/sh");
175
+ if (typeof shell !== "string" || !isAbsolute(shell))
176
+ throw new Error("shell must be an absolute executable path.");
177
+ if (disposed) throw new Error("This compute hand is stopped.");
178
+ context.signal?.throwIfAborted();
179
+ const output = await createProcessOutput();
180
+ if (disposed || context.signal?.aborted) {
181
+ await output.close();
182
+ context.signal?.throwIfAborted();
183
+ throw new Error("This compute hand is stopped.");
184
+ }
185
+ let child;
186
+ try {
187
+ child = spawn(
188
+ shell,
189
+ [input.login === true ? "-lc" : "-c", input.cmd],
190
+ {
191
+ cwd,
192
+ env: environment,
193
+ detached: process.platform !== "win32",
194
+ stdio: ["pipe", "pipe", "pipe"],
195
+ },
196
+ );
197
+ } catch (error) {
198
+ await output.close();
199
+ throw error;
200
+ }
201
+ let finish;
202
+ const record = {
203
+ id: ++sequence,
204
+ owner: context.sessionId,
205
+ child,
206
+ output,
207
+ done: false,
208
+ exitCode: 0,
209
+ reading: false,
210
+ closed: new Promise((resolve) => {
211
+ finish = resolve;
212
+ }),
213
+ };
214
+ sessions.set(record.id, record);
215
+ processes.add(record);
216
+ activity({
217
+ type: "started",
218
+ sessionId: context.sessionId,
219
+ processId: record.id,
220
+ });
221
+ const append = (data, stream) => {
222
+ stream?.pause();
223
+ void record.output.append(data).catch(error => {
224
+ record.outputError = error;
225
+ void stop(record).catch(() => {});
226
+ }).finally(() => stream?.resume());
227
+ };
228
+ child.stdout.setEncoding("utf8");
229
+ child.stderr.setEncoding("utf8");
230
+ child.stdout.on("data", data => append(data, child.stdout));
231
+ child.stderr.on("data", data => append(data, child.stderr));
232
+ child.stdin.on("error", () => {});
233
+ child.on("error", (error) => {
234
+ append(error.message);
235
+ record.exitCode = 1;
236
+ });
237
+ child.on("close", (code) => {
238
+ record.done = true;
239
+ record.exitCode =
240
+ code !== null && code >= 0 ? code : record.exitCode || 130;
241
+ finish();
242
+ collect();
243
+ activity({
244
+ type: "completed",
245
+ sessionId: context.sessionId,
246
+ processId: record.id,
247
+ exitCode: record.exitCode,
248
+ });
249
+ });
250
+ const abort = () => {
251
+ void stop(record).catch(() => {});
252
+ };
253
+ context.signal?.addEventListener("abort", abort, { once: true });
254
+ if (context.signal?.aborted) abort();
255
+ try {
256
+ return await read(record, input, context.signal);
257
+ } finally {
258
+ context.signal?.removeEventListener("abort", abort);
259
+ }
260
+ },
261
+ releaseSession(owner) {
262
+ for (const record of new Set([...processes, ...sessions.values()]))
263
+ if (record.owner === owner) void stop(record).catch(() => {});
264
+ },
265
+ async dispose() {
266
+ disposed = true;
267
+ await Promise.all([...new Set([...processes, ...sessions.values()])].map(stop));
268
+ sessions.clear();
269
+ },
270
+ });
271
+ const stdin = namedTool("write_stdin", {
272
+ description:
273
+ "Write to or poll a pipe session belonging to this agent on this Hand.",
274
+ parameters: WRITE_STDIN_PARAMETERS,
275
+ outputSchema: EXECUTION_OUTPUT_SCHEMA,
276
+ supportsParallelToolCalls: true,
277
+ async handler(input, context) {
278
+ const record = sessions.get(input?.session_id);
279
+ if (disposed || !record || record.owner !== context.sessionId)
280
+ throw new Error("Process session is unavailable for this agent.");
281
+ if (input.chars !== undefined && typeof input.chars !== "string")
282
+ throw new TypeError("chars must be text");
283
+ const maximumWait = input.chars ? 30_000 : 300_000;
284
+ integer(input.yield_time_ms, 1_000, 0, maximumWait);
285
+ integer(input.max_output_tokens, 10_000, 1, 32_000);
286
+ if (record.reading)
287
+ throw new Error("A read is already pending for this process.");
288
+ context.signal?.throwIfAborted();
289
+ if (input.chars && !record.done) {
290
+ if (input.chars === "\u0003") kill(record, "SIGINT");
291
+ else record.child.stdin.write(input.chars);
292
+ }
293
+ const abort = () => {
294
+ void stop(record).catch(() => {});
295
+ };
296
+ context.signal?.addEventListener("abort", abort, { once: true });
297
+ try {
298
+ return await read(record, input, context.signal, maximumWait);
299
+ } finally {
300
+ context.signal?.removeEventListener("abort", abort);
301
+ }
302
+ },
303
+ });
304
+ return Object.freeze({ tools: [exec, stdin], close: exec.dispose });
305
+ }
306
+
307
+ function integer(value, fallback, minimum, maximum) {
308
+ if (value === undefined) return fallback;
309
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum)
310
+ throw new RangeError(
311
+ `Expected an integer between ${minimum} and ${maximum}.`,
312
+ );
313
+ return value;
314
+ }
@@ -0,0 +1,56 @@
1
+ import { mkdtemp, open, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { StringDecoder } from "node:string_decoder";
5
+
6
+ // Serialize disk operations; callers pause each source until append completes.
7
+ // Reply budgets limit each read, never how much unread output is retained.
8
+ export async function createProcessOutput() {
9
+ const directory = await mkdtemp(join(tmpdir(), "nanocodex-output-"));
10
+ let file;
11
+ try { file = await open(join(directory, "output"), "wx+", 0o600); }
12
+ catch (error) { await rm(directory, { recursive: true, force: true }); throw error; }
13
+ let queued = Promise.resolve();
14
+ let accepted = 0;
15
+ let written = 0;
16
+ let consumed = 0;
17
+ let closing;
18
+ const decoder = new StringDecoder("utf8");
19
+ const enqueue = operation => {
20
+ const result = queued.then(operation);
21
+ queued = result.catch(() => {});
22
+ return result;
23
+ };
24
+ return {
25
+ get unread() { return accepted - consumed; },
26
+ append(data) {
27
+ if (closing) return Promise.reject(new Error("Process output is closed."));
28
+ const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
29
+ accepted += buffer.length;
30
+ return enqueue(async () => {
31
+ let offset = 0;
32
+ while (offset < buffer.length) {
33
+ const { bytesWritten } = await file.write(buffer, offset, buffer.length - offset, written);
34
+ if (!bytesWritten) throw new Error("Could not write process output.");
35
+ offset += bytesWritten;
36
+ written += bytesWritten;
37
+ }
38
+ });
39
+ },
40
+ read(maxBytes) {
41
+ return enqueue(async () => {
42
+ const buffer = Buffer.alloc(Math.min(maxBytes, written - consumed));
43
+ const { bytesRead } = await file.read(buffer, 0, buffer.length, consumed);
44
+ consumed += bytesRead;
45
+ return decoder.write(buffer.subarray(0, bytesRead));
46
+ });
47
+ },
48
+ end() { return decoder.end(); },
49
+ close() {
50
+ return closing ??= enqueue(async () => {
51
+ try { await file.close(); }
52
+ finally { await rm(directory, { recursive: true, force: true }); }
53
+ });
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,55 @@
1
+ import type { Workspace } from "./types.mjs";
2
+
3
+ export type RepositoryWorkspaceFetch = (
4
+ url: string,
5
+ options?: Readonly<{
6
+ method?: string | undefined;
7
+ headers?: Headers | Record<string, string> | undefined;
8
+ body?: Uint8Array | undefined;
9
+ signal?: AbortSignal | undefined;
10
+ }>,
11
+ ) => Promise<Response | Readonly<{
12
+ status: number;
13
+ statusText?: string | undefined;
14
+ headers?: Headers | Record<string, string> | undefined;
15
+ body?: Uint8Array | AsyncIterable<Uint8Array> | undefined;
16
+ url?: string | undefined;
17
+ arrayBuffer?(): Promise<ArrayBuffer>;
18
+ }>>;
19
+
20
+ export type RepositoryWorkspaceRemote = Readonly<{
21
+ name: string;
22
+ url: string;
23
+ branch: string;
24
+ }>;
25
+
26
+ export type RepositoryWorkspaceDescriptor = Readonly<{
27
+ branch: string;
28
+ directory: string;
29
+ gitDirectory: string;
30
+ head: string;
31
+ markerPath: string;
32
+ seedUrl: string;
33
+ writableRemote?: RepositoryWorkspaceRemote | undefined;
34
+ }>;
35
+
36
+ /**
37
+ * Materializes one immutable, generation-pinned Git seed into a package-owned
38
+ * workspace child. A matching retained checkout is reopened without network
39
+ * access or implicit refresh, preserving local worktree changes.
40
+ */
41
+ export function materializeRepositoryWorkspace(options: Readonly<{
42
+ workspace: Workspace;
43
+ fetch: RepositoryWorkspaceFetch;
44
+ /** Credential-free smart-HTTP URL ending in the exact lowercase SHA-1 `head`. */
45
+ seedUrl: string;
46
+ head: string;
47
+ branch?: string | undefined;
48
+ /** One safe child name beneath `workspace.root`; defaults to `repository`. */
49
+ directory?: string | undefined;
50
+ writableRemote?: Readonly<{
51
+ url: string;
52
+ name?: string | undefined;
53
+ branch?: string | undefined;
54
+ }> | undefined;
55
+ }>): Promise<RepositoryWorkspaceDescriptor>;