@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,742 @@
1
+ import { namedTool } from "./namedTool.mjs";
2
+ import {
3
+ EXEC_COMMAND_PARAMETERS,
4
+ EXECUTION_OUTPUT_SCHEMA,
5
+ } from "./execution-contract.mjs";
6
+
7
+ const DEFAULT_MAX_OUTPUT_TOKENS = 10_000;
8
+ const MAX_OUTPUT_TOKENS = 100_000;
9
+ const OUTPUT_TRUNCATION_NOTICE = "\n[output truncated by exec_command]";
10
+ const encoder = new TextEncoder();
11
+ const decoder = new TextDecoder();
12
+
13
+ // The host owns resources and cancellation. Interpreter ceilings must not
14
+ // turn otherwise valid commands, repositories, or data files into errors.
15
+ const UNLIMITED_EXECUTION_LIMITS = Object.freeze({
16
+ maxSourceBytes: Infinity,
17
+ maxExecDepth: Infinity,
18
+ maxCallDepth: Infinity,
19
+ maxCommandCount: Infinity,
20
+ maxLoopIterations: Infinity,
21
+ maxAwkIterations: Infinity,
22
+ maxSedIterations: Infinity,
23
+ maxJqIterations: Infinity,
24
+ maxQueryTokens: Infinity,
25
+ maxQueryDepth: Infinity,
26
+ maxQueryElements: Infinity,
27
+ maxAwkParserTokens: Infinity,
28
+ maxAwkParserDepth: Infinity,
29
+ maxAwkParserOperations: Infinity,
30
+ maxCsvRows: Infinity,
31
+ maxCsvCells: Infinity,
32
+ maxWorkUnits: Infinity,
33
+ maxTraversalEntries: Infinity,
34
+ maxTraversalDepth: Infinity,
35
+ maxTraversalWork: Infinity,
36
+ maxLiveBytes: Infinity,
37
+ maxInputBytes: Infinity,
38
+ maxFileSystemBytes: Infinity,
39
+ maxDatabaseBytes: Infinity,
40
+ maxDatabaseResultBytes: Infinity,
41
+ maxArchiveBytes: Infinity,
42
+ maxArchiveCompressedBytes: Infinity,
43
+ maxArchiveEntryBytes: Infinity,
44
+ maxArchiveEntries: Infinity,
45
+ maxWorkerMessageBytes: Infinity,
46
+ maxExecutionTimeMs: Infinity,
47
+ maxSqliteTimeoutMs: Infinity,
48
+ maxPythonTimeoutMs: Infinity,
49
+ maxJsTimeoutMs: Infinity,
50
+ maxGlobOperations: Infinity,
51
+ maxStringLength: Infinity,
52
+ maxArrayElements: Infinity,
53
+ maxHeredocSize: Infinity,
54
+ maxSubstitutionDepth: Infinity,
55
+ maxBraceExpansionResults: Infinity,
56
+ maxOutputSize: Infinity,
57
+ maxFileDescriptors: Infinity,
58
+ maxSourceDepth: Infinity,
59
+ });
60
+
61
+ const DEVICES = new Set(["/dev/full", "/dev/null", "/dev/stderr", "/dev/stdout"]);
62
+
63
+ export async function justBash(options) {
64
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
65
+ throw new TypeError("Just Bash options must be an object");
66
+ }
67
+ validateWorkspace(options.filesystem);
68
+ const executionTimeoutMs = positiveInteger(
69
+ options.executionTimeoutMs,
70
+ undefined,
71
+ "executionTimeoutMs",
72
+ );
73
+ const maxEntries = options.maxEntries === undefined
74
+ ? undefined : positiveInteger(options.maxEntries, undefined, "maxEntries");
75
+ const maxOutputTokens = Math.min(
76
+ MAX_OUTPUT_TOKENS,
77
+ positiveInteger(options.maxOutputTokens, DEFAULT_MAX_OUTPUT_TOKENS, "maxOutputTokens"),
78
+ );
79
+ const shellFilesystem = new WorkspaceShellFileSystem(options.filesystem, maxEntries);
80
+ await shellFilesystem.open();
81
+ const filesystem = shellFilesystem.workspace();
82
+ const runtime = await createJustBashRuntime({
83
+ filesystem: shellFilesystem,
84
+ cwd: filesystem.root,
85
+ env: {
86
+ HOME: filesystem.root,
87
+ PWD: filesystem.root,
88
+ PATH: filesystem.root,
89
+ },
90
+ fetch: options.fetch,
91
+ network: options.network,
92
+ networkMode: options.networkMode ?? (typeof options.fetch === "function"
93
+ ? "host-fetch"
94
+ : options.network === false || options.network === undefined
95
+ ? undefined
96
+ : "restricted-http"),
97
+ customCommands: options.customCommands,
98
+ aroundExecute: options.refreshFilesystemBeforeExec
99
+ ? async ({ execute, signal }) => {
100
+ signal.throwIfAborted();
101
+ await shellFilesystem.open();
102
+ signal.throwIfAborted();
103
+ return execute();
104
+ }
105
+ : undefined,
106
+ executionTimeoutMs,
107
+ defaultMaxOutputTokens: maxOutputTokens,
108
+ maxOutputTokens,
109
+ executionLimits: {
110
+ ...(executionTimeoutMs === undefined ? {} : { maxExecutionTimeMs: executionTimeoutMs }),
111
+ ...(maxEntries === undefined ? {} : { maxTraversalEntries: maxEntries }),
112
+ },
113
+ });
114
+
115
+ return Object.freeze({ ...runtime, filesystem });
116
+ }
117
+
118
+ /**
119
+ * Constructs the common Just Bash interpreter, execution tool, instructions, and descriptor over
120
+ * a caller-owned Just Bash filesystem adapter. Hosts retain ownership of persistence and locking.
121
+ */
122
+ export async function createJustBashRuntime(options) {
123
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
124
+ throw new TypeError("Just Bash runtime options must be an object");
125
+ }
126
+ if (!options.filesystem || typeof options.filesystem !== "object") {
127
+ throw new TypeError("Just Bash runtime filesystem is required");
128
+ }
129
+ const cwd = normalizeRoot(requiredString(options.cwd, "cwd"));
130
+ const executionTimeoutMs = positiveInteger(
131
+ options.executionTimeoutMs,
132
+ undefined,
133
+ "executionTimeoutMs",
134
+ );
135
+ const defaultMaxOutputTokens = positiveInteger(
136
+ options.defaultMaxOutputTokens,
137
+ DEFAULT_MAX_OUTPUT_TOKENS,
138
+ "defaultMaxOutputTokens",
139
+ );
140
+ const maxOutputTokens = positiveInteger(
141
+ options.maxOutputTokens,
142
+ defaultMaxOutputTokens,
143
+ "maxOutputTokens",
144
+ );
145
+ if (defaultMaxOutputTokens > maxOutputTokens) {
146
+ throw new RangeError("defaultMaxOutputTokens cannot exceed maxOutputTokens");
147
+ }
148
+ const executionLimits = Object.freeze({ ...UNLIMITED_EXECUTION_LIMITS, ...options.executionLimits });
149
+ const { Bash, defineCommand } = await import("just-bash/browser");
150
+ const customCommands = typeof options.customCommands === "function"
151
+ ? await options.customCommands({ defineCommand })
152
+ : options.customCommands;
153
+ const bash = new Bash({
154
+ cwd,
155
+ env: options.env,
156
+ fs: options.filesystem,
157
+ ...(typeof options.fetch === "function"
158
+ ? { fetch: options.fetch }
159
+ : options.network === false || options.network === undefined
160
+ ? {}
161
+ : { network: options.network }),
162
+ ...(customCommands === undefined ? {} : { customCommands: [...customCommands] }),
163
+ executionLimitProfile: "normal",
164
+ executionLimits,
165
+ });
166
+ const descriptor = describeRuntime({
167
+ bash,
168
+ cwd,
169
+ customCommands,
170
+ executionLimits,
171
+ networkMode: options.networkMode,
172
+ networkEnabled: typeof options.fetch === "function" || options.network !== false && options.network !== undefined,
173
+ });
174
+ const instructions = typeof options.instructions === "function"
175
+ ? options.instructions(descriptor)
176
+ : defaultInstructions(descriptor);
177
+ let executionTail = Promise.resolve();
178
+
179
+ const tool = namedTool("exec_command", {
180
+ ...(options.supportsParallelToolCalls === undefined
181
+ ? {}
182
+ : { supportsParallelToolCalls: options.supportsParallelToolCalls }),
183
+ description: "Runs a shell command, returning output or a session ID for ongoing interaction.",
184
+ parameters: EXEC_COMMAND_PARAMETERS,
185
+ outputSchema: EXECUTION_OUTPUT_SCHEMA,
186
+ handler(input, context) {
187
+ const execute = () => executeCommand({
188
+ bash,
189
+ input,
190
+ root: cwd,
191
+ signal: context?.signal,
192
+ executionTimeoutMs,
193
+ defaultMaxOutputTokens,
194
+ maxOutputTokens,
195
+ aroundExecute: options.aroundExecute,
196
+ outputTruncationNotice: options.outputTruncationNotice,
197
+ retainNoticeWithinLimit: options.retainNoticeWithinLimit,
198
+ });
199
+ const result = executionTail.then(execute, execute);
200
+ executionTail = result.then(() => undefined, () => undefined);
201
+ return result;
202
+ },
203
+ });
204
+
205
+ return Object.freeze({ bash, descriptor, instructions, tool, exec: tool.handler });
206
+ }
207
+
208
+ async function executeCommand({
209
+ bash,
210
+ input,
211
+ root,
212
+ signal,
213
+ executionTimeoutMs,
214
+ defaultMaxOutputTokens,
215
+ maxOutputTokens,
216
+ aroundExecute,
217
+ outputTruncationNotice = OUTPUT_TRUNCATION_NOTICE,
218
+ retainNoticeWithinLimit = true,
219
+ }) {
220
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
221
+ throw new TypeError("exec_command input must be an object");
222
+ }
223
+ if (typeof input.cmd !== "string" || !input.cmd.trim()) {
224
+ throw new TypeError("exec_command.cmd must be a non-empty string");
225
+ }
226
+ if (input.tty === true) throw new Error("Just Bash does not provide PTY sessions");
227
+ if (input.sandbox_permissions === "require_escalated") {
228
+ throw new Error("Just Bash cannot escape its virtual workspace");
229
+ }
230
+ if (input.shell !== undefined && input.shell !== "bash" && input.shell !== "/bin/bash") {
231
+ throw new Error("exec_command supports only the embedded Bash interpreter");
232
+ }
233
+ const workdir = input.workdir === undefined
234
+ ? root
235
+ : resolvePath(root, root, requiredString(input.workdir, "workdir"));
236
+ const outputTokens = Math.min(
237
+ maxOutputTokens,
238
+ positiveInteger(input.max_output_tokens, defaultMaxOutputTokens, "max_output_tokens"),
239
+ );
240
+ const deadline = new AbortController();
241
+ const abort = () => deadline.abort(signal?.reason);
242
+ signal?.addEventListener("abort", abort, { once: true });
243
+ if (signal?.aborted) abort();
244
+ const timeout = executionTimeoutMs === undefined ? undefined : setTimeout(
245
+ () => deadline.abort(new Error(`exec_command exceeded ${executionTimeoutMs} milliseconds`)),
246
+ executionTimeoutMs,
247
+ );
248
+ const startedAt = now();
249
+ let result;
250
+ try {
251
+ const execute = () => bash.exec(input.cmd, { cwd: workdir, signal: deadline.signal });
252
+ result = typeof aroundExecute === "function"
253
+ ? await aroundExecute({ execute, signal: deadline.signal })
254
+ : await execute();
255
+ } finally {
256
+ clearTimeout(timeout);
257
+ signal?.removeEventListener("abort", abort);
258
+ }
259
+ const combined = `${result.stdout}${result.stderr}`;
260
+ const maxCharacters = outputTokens * 4;
261
+ const truncated = combined.length > maxCharacters;
262
+ const retainedCharacters = retainNoticeWithinLimit
263
+ ? Math.max(0, maxCharacters - outputTruncationNotice.length)
264
+ : maxCharacters;
265
+ return {
266
+ output: truncated
267
+ ? !retainNoticeWithinLimit || maxCharacters >= outputTruncationNotice.length
268
+ ? `${combined.slice(0, retainedCharacters)}${outputTruncationNotice}`
269
+ : combined.slice(0, maxCharacters)
270
+ : combined,
271
+ wall_time_seconds: (now() - startedAt) / 1000,
272
+ exit_code: result.exitCode,
273
+ ...(truncated ? { original_token_count: Math.ceil(combined.length / 4) } : {}),
274
+ };
275
+ }
276
+
277
+ function describeRuntime({
278
+ bash,
279
+ cwd,
280
+ customCommands,
281
+ executionLimits,
282
+ networkEnabled,
283
+ networkMode,
284
+ }) {
285
+ const customCommandNames = [...customCommands ?? []].map(({ name }) => name);
286
+ return Object.freeze({
287
+ shell: "nanocodex-just-bash",
288
+ commands: Object.freeze([...bash.commands.keys()].sort()),
289
+ customCommands: Object.freeze(customCommandNames.sort()),
290
+ cwd,
291
+ limits: Object.freeze(Object.fromEntries(Object.entries(executionLimits).filter(([, value]) => Number.isFinite(value)))),
292
+ network: Object.freeze({
293
+ enabled: networkEnabled,
294
+ mode: networkMode ?? (networkEnabled ? "http" : "disabled"),
295
+ }),
296
+ pty: false,
297
+ sessions: false,
298
+ sandboxEscalation: false,
299
+ });
300
+ }
301
+
302
+ function defaultInstructions(descriptor) {
303
+ const network = descriptor.network.enabled
304
+ ? `HTTP is available through the host-owned ${descriptor.network.mode} fetch boundary.`
305
+ : "Network commands are unavailable.";
306
+ return `You have an in-process Bash interpreter and a persistent virtual filesystem rooted at ${descriptor.cwd}.
307
+ Use exec_command for shell work. When the user requests an explicit shell operation that maps directly to an
308
+ available command, call exec_command immediately and once with the complete command. Do not inspect the runtime,
309
+ account, or workspace, search for another tool, or split the operation into exploratory calls before trying it.
310
+ For an ordinary clone request, use exactly gh repo clone OWNER/REPO DESTINATION or git clone URL DESTINATION.
311
+ By default these commands download and extract a source archive: all current files, without .git or history.
312
+ Use --branch for a requested revision. An explicit --depth requests a Git checkout with that history depth.
313
+ Do not add depth, filter, branch, or other flags unless the user requests them, and do not inspect a successful clone.
314
+ Only investigate after that direct command fails or when the user explicitly asks for investigation.
315
+ Available commands: ${descriptor.commands.join(", ")}. Use ${descriptor.cwd}/tmp, not /tmp, for temporary files. Commands run without a host process, container, PTY, session, or sandbox
316
+ escalation, and cannot access paths outside ${descriptor.cwd}. The shell is one-shot per call, but files persist
317
+ across calls and agent restarts. ${network} Model subscription credentials are never exposed to the shell.`;
318
+ }
319
+
320
+ class WorkspaceShellFileSystem {
321
+ #source;
322
+ #root;
323
+ #maxEntries;
324
+ #entries = new Map();
325
+ #sortedPaths;
326
+
327
+ constructor(workspace, maxEntries) {
328
+ this.#source = workspace;
329
+ this.#root = normalizeRoot(workspace.root);
330
+ this.#maxEntries = maxEntries;
331
+ }
332
+
333
+ async open() {
334
+ const entries = await this.#source.list(".", { recursive: true, ...(this.#maxEntries === undefined ? {} : { maxEntries: this.#maxEntries }) });
335
+ this.#entries.clear();
336
+ this.#sortedPaths = undefined;
337
+ this.#entries.set(this.#root, directoryEntry());
338
+ for (const entry of entries) {
339
+ const path = resolvePath(this.#root, this.#root, entry.path);
340
+ this.#addParents(path);
341
+ this.#set(path, entry.kind === "directory"
342
+ ? directoryEntry(entry.modifiedAt)
343
+ : fileEntry(entry.size, entry.modifiedAt));
344
+ }
345
+ }
346
+
347
+ workspace() {
348
+ return Object.freeze({
349
+ root: this.#root,
350
+ list: (path = ".", options) => this.#source.list(
351
+ resolvePath(this.#root, this.#root, path),
352
+ options,
353
+ ),
354
+ readFile: (path) => this.#source.readFile(resolvePath(this.#root, this.#root, path)),
355
+ writeFile: async (path, contents) => {
356
+ const absolute = resolvePath(this.#root, this.#root, path);
357
+ const bytes = bytesFrom(contents);
358
+ this.#assertCapacity(absolute);
359
+ await this.#source.writeFile(absolute, bytes);
360
+ this.#addParents(absolute);
361
+ this.#set(absolute, fileEntry(bytes.byteLength));
362
+ },
363
+ remove: async (path, options) => {
364
+ const absolute = resolvePath(this.#root, this.#root, path);
365
+ await this.#source.remove(absolute, options);
366
+ this.#remove(absolute);
367
+ },
368
+ mkdir: async (path) => {
369
+ const absolute = resolvePath(this.#root, this.#root, path);
370
+ this.#assertCapacity(absolute);
371
+ await this.#source.mkdir(absolute);
372
+ this.#addParents(absolute);
373
+ this.#set(absolute, directoryEntry());
374
+ },
375
+ });
376
+ }
377
+
378
+ async readFile(path, options) {
379
+ return decode(await this.readFileBuffer(path), encoding(options));
380
+ }
381
+
382
+ async readFileBytes(path) {
383
+ return bytesToLatin1(await this.readFileBuffer(path));
384
+ }
385
+
386
+ async readFileBuffer(path) {
387
+ const absolute = this.#resolve(path);
388
+ if (absolute === "/dev/null") return new Uint8Array();
389
+ const entry = this.#require(absolute);
390
+ if (entry.kind !== "file") throw fsError("EISDIR", `${absolute} is a directory`);
391
+ return this.#source.readFile(absolute);
392
+ }
393
+
394
+ async writeFile(path, content, options) {
395
+ const absolute = this.#resolve(path);
396
+ if (absolute === "/dev/null") return;
397
+ const bytes = encode(content, encoding(options));
398
+ this.#assertCapacity(absolute);
399
+ await this.#source.writeFile(absolute, bytes);
400
+ this.#addParents(absolute);
401
+ this.#set(absolute, fileEntry(bytes.byteLength));
402
+ }
403
+
404
+ async appendFile(path, content, options) {
405
+ const absolute = this.#resolve(path);
406
+ if (absolute === "/dev/null") return;
407
+ const suffix = encode(content, encoding(options));
408
+ const prefix = await this.exists(absolute) ? await this.readFileBuffer(absolute) : new Uint8Array();
409
+ const joined = new Uint8Array(prefix.byteLength + suffix.byteLength);
410
+ joined.set(prefix);
411
+ joined.set(suffix, prefix.byteLength);
412
+ await this.writeFile(absolute, joined);
413
+ }
414
+
415
+ async exists(path) {
416
+ try {
417
+ const absolute = this.#resolve(path);
418
+ return DEVICES.has(absolute) || this.#entries.has(absolute);
419
+ } catch (error) {
420
+ if (error?.code === "EPERM") return false;
421
+ throw error;
422
+ }
423
+ }
424
+
425
+ async stat(path) {
426
+ const absolute = this.#resolve(path);
427
+ if (DEVICES.has(absolute)) return statResult(fileEntry(0, 0), absolute);
428
+ return statResult(this.#require(absolute), absolute);
429
+ }
430
+
431
+ lstat(path) {
432
+ return this.stat(path);
433
+ }
434
+
435
+ async mkdir(path, options = {}) {
436
+ const absolute = resolvePath(this.#root, this.#root, path);
437
+ const existing = this.#entries.get(absolute);
438
+ if (existing) {
439
+ if (options.recursive && existing.kind === "directory") return;
440
+ throw fsError("EEXIST", `${absolute} already exists`);
441
+ }
442
+ const parent = parentPath(absolute);
443
+ if (!options.recursive && !this.#entries.has(parent)) {
444
+ throw fsError("ENOENT", `parent directory ${parent} does not exist`);
445
+ }
446
+ this.#assertCapacity(absolute);
447
+ await this.#source.mkdir(absolute);
448
+ this.#addParents(absolute);
449
+ this.#set(absolute, directoryEntry());
450
+ }
451
+
452
+ async readdir(path) {
453
+ const absolute = resolvePath(this.#root, this.#root, path);
454
+ const entry = this.#require(absolute);
455
+ if (entry.kind !== "directory") throw fsError("ENOTDIR", `${absolute} is not a directory`);
456
+ const prefix = `${absolute}/`;
457
+ const names = new Set();
458
+ for (const candidate of this.#entries.keys()) {
459
+ if (!candidate.startsWith(prefix)) continue;
460
+ const remainder = candidate.slice(prefix.length);
461
+ if (remainder && !remainder.includes("/")) names.add(remainder);
462
+ }
463
+ return [...names].sort();
464
+ }
465
+
466
+ async readdirWithFileTypes(path) {
467
+ const absolute = resolvePath(this.#root, this.#root, path);
468
+ return Promise.all((await this.readdir(absolute)).map(async (name) => {
469
+ const entry = this.#require(`${absolute}/${name}`);
470
+ return {
471
+ name,
472
+ isFile: entry.kind === "file",
473
+ isDirectory: entry.kind === "directory",
474
+ isSymbolicLink: false,
475
+ };
476
+ }));
477
+ }
478
+
479
+ async rm(path, options = {}) {
480
+ const absolute = resolvePath(this.#root, this.#root, path);
481
+ if (absolute === this.#root) throw fsError("EPERM", "cannot remove the workspace root");
482
+ const entry = this.#entries.get(absolute);
483
+ if (!entry) {
484
+ if (options.force) return;
485
+ throw fsError("ENOENT", `${absolute} does not exist`);
486
+ }
487
+ if (entry.kind === "directory" && !options.recursive && this.#hasChildren(absolute)) {
488
+ throw fsError("ENOTEMPTY", `${absolute} is not empty`);
489
+ }
490
+ await this.#source.remove(absolute, { recursive: options.recursive === true });
491
+ this.#remove(absolute);
492
+ }
493
+
494
+ async cp(sourcePath, destinationPath, options = {}) {
495
+ const source = resolvePath(this.#root, this.#root, sourcePath);
496
+ const destination = resolvePath(this.#root, this.#root, destinationPath);
497
+ const entry = this.#require(source);
498
+ if (source === destination || (entry.kind === "directory" && destination.startsWith(`${source}/`))) {
499
+ throw fsError("EINVAL", "cannot copy a path onto itself or into its own subtree");
500
+ }
501
+ if (entry.kind === "directory") {
502
+ if (!options.recursive) throw fsError("EISDIR", "copying a directory requires recursive mode");
503
+ await this.mkdir(destination, { recursive: true });
504
+ for (const name of await this.readdir(source)) {
505
+ await this.cp(`${source}/${name}`, `${destination}/${name}`, options);
506
+ }
507
+ return;
508
+ }
509
+ await this.writeFile(destination, await this.readFileBuffer(source));
510
+ }
511
+
512
+ async mv(source, destination) {
513
+ await this.cp(source, destination, { recursive: true });
514
+ await this.rm(source, { recursive: true });
515
+ }
516
+
517
+ resolvePath(base, path) {
518
+ return resolvePath(this.#root, base, path);
519
+ }
520
+
521
+ getAllPaths() {
522
+ this.#sortedPaths ??= [...this.#entries.keys()].sort();
523
+ return this.#sortedPaths.slice();
524
+ }
525
+
526
+ async chmod(path) {
527
+ await this.stat(path);
528
+ }
529
+
530
+ async symlink() {
531
+ throw fsError("ENOSYS", "the mounted workspace does not support symbolic links");
532
+ }
533
+
534
+ async link() {
535
+ throw fsError("ENOSYS", "the mounted workspace does not support hard links");
536
+ }
537
+
538
+ async readlink() {
539
+ throw fsError("ENOSYS", "the mounted workspace does not support symbolic links");
540
+ }
541
+
542
+ async realpath(path) {
543
+ const absolute = resolvePath(this.#root, this.#root, path);
544
+ await this.stat(absolute);
545
+ return absolute;
546
+ }
547
+
548
+ async utimes(path) {
549
+ await this.stat(path);
550
+ }
551
+
552
+ #resolve(path) {
553
+ if (DEVICES.has(path)) return path;
554
+ return resolvePath(this.#root, this.#root, path);
555
+ }
556
+
557
+ #require(path) {
558
+ const entry = this.#entries.get(path);
559
+ if (!entry) throw fsError("ENOENT", `${path} does not exist`);
560
+ return entry;
561
+ }
562
+
563
+ #set(path, entry) {
564
+ if (this.#maxEntries !== undefined && !this.#entries.has(path) && this.#entries.size - 1 >= this.#maxEntries) {
565
+ throw fsError("EFBIG", `workspace exceeds ${this.#maxEntries} entries`);
566
+ }
567
+ this.#entries.set(path, entry);
568
+ this.#sortedPaths = undefined;
569
+ }
570
+
571
+ #assertCapacity(path) {
572
+ if (this.#maxEntries === undefined) return;
573
+ let additions = this.#entries.has(path) ? 0 : 1;
574
+ const relative = path.slice(this.#root.length + 1);
575
+ let current = this.#root;
576
+ for (const segment of relative.split("/").slice(0, -1)) {
577
+ current += `/${segment}`;
578
+ if (!this.#entries.has(current)) additions += 1;
579
+ }
580
+ if (additions > this.#maxEntries - (this.#entries.size - 1)) {
581
+ throw fsError("EFBIG", `workspace exceeds ${this.#maxEntries} entries`);
582
+ }
583
+ }
584
+
585
+ #addParents(path) {
586
+ const relative = path.slice(this.#root.length + 1);
587
+ if (!relative) return;
588
+ let current = this.#root;
589
+ for (const segment of relative.split("/").slice(0, -1)) {
590
+ current += `/${segment}`;
591
+ if (!this.#entries.has(current)) this.#set(current, directoryEntry());
592
+ }
593
+ }
594
+
595
+ #remove(path) {
596
+ for (const candidate of this.#entries.keys()) {
597
+ if (candidate === path || candidate.startsWith(`${path}/`)) this.#entries.delete(candidate);
598
+ }
599
+ this.#sortedPaths = undefined;
600
+ }
601
+
602
+ #hasChildren(path) {
603
+ for (const candidate of this.#entries.keys()) {
604
+ if (candidate.startsWith(`${path}/`)) return true;
605
+ }
606
+ return false;
607
+ }
608
+ }
609
+
610
+ function directoryEntry(modifiedAt = Date.now()) {
611
+ return { kind: "directory", modifiedAt, size: 0 };
612
+ }
613
+
614
+ function fileEntry(size = 0, modifiedAt = Date.now()) {
615
+ return { kind: "file", modifiedAt, size: size ?? 0 };
616
+ }
617
+
618
+ function statResult(entry, path) {
619
+ return {
620
+ isFile: entry.kind === "file",
621
+ isDirectory: entry.kind === "directory",
622
+ isSymbolicLink: false,
623
+ mode: entry.kind === "directory" ? 0o755 : 0o644,
624
+ size: entry.size ?? 0,
625
+ mtime: new Date(entry.modifiedAt ?? 0),
626
+ identity: `workspace:${path}`,
627
+ };
628
+ }
629
+
630
+ function resolvePath(root, base, path) {
631
+ if (typeof path !== "string" || path.includes("\0")) throw fsError("EINVAL", "invalid path");
632
+ const safeBase = normalizeRoot(base);
633
+ if (safeBase !== root && !safeBase.startsWith(`${root}/`)) {
634
+ throw fsError("EPERM", `working directory escapes ${root}`);
635
+ }
636
+ const source = path.startsWith("/") ? path : `${safeBase}/${path}`;
637
+ const segments = [];
638
+ for (const segment of source.replaceAll("\\", "/").split("/")) {
639
+ if (!segment || segment === ".") continue;
640
+ if (segment === "..") segments.pop();
641
+ else segments.push(segment);
642
+ }
643
+ const absolute = `/${segments.join("/")}`;
644
+ if (absolute !== root && !absolute.startsWith(`${root}/`)) {
645
+ throw fsError("EPERM", `path escapes ${root}`);
646
+ }
647
+ return absolute;
648
+ }
649
+
650
+ function normalizeRoot(root) {
651
+ if (typeof root !== "string" || !root.startsWith("/")) {
652
+ throw new TypeError("workspace root must be an absolute path");
653
+ }
654
+ const normalized = `/${root.split("/").filter((segment) => segment && segment !== ".").join("/")}`;
655
+ if (normalized === "/" || normalized.includes("/../") || normalized.endsWith("/..")) {
656
+ throw new TypeError("workspace root must be a bounded absolute path");
657
+ }
658
+ return normalized;
659
+ }
660
+
661
+ function parentPath(path) {
662
+ return path.slice(0, path.lastIndexOf("/")) || "/";
663
+ }
664
+
665
+ function encoding(options) {
666
+ return typeof options === "string" ? options : options?.encoding ?? "utf8";
667
+ }
668
+
669
+ function encode(content, selectedEncoding) {
670
+ if (content instanceof Uint8Array) return content;
671
+ if (selectedEncoding === "base64") {
672
+ return Uint8Array.from(atob(content), (character) => character.charCodeAt(0));
673
+ }
674
+ if (selectedEncoding === "hex") {
675
+ if (content.length % 2 !== 0 || !/^[a-f0-9]*$/i.test(content)) {
676
+ throw fsError("EINVAL", "invalid hex input");
677
+ }
678
+ return Uint8Array.from(content.match(/../g) ?? [], (pair) => Number.parseInt(pair, 16));
679
+ }
680
+ if (["binary", "latin1", "ascii"].includes(selectedEncoding)) {
681
+ return Uint8Array.from(content, (character) => character.charCodeAt(0) & 0xff);
682
+ }
683
+ return encoder.encode(content);
684
+ }
685
+
686
+ function decode(bytes, selectedEncoding) {
687
+ if (selectedEncoding === "base64") return btoa(bytesToLatin1(bytes));
688
+ if (selectedEncoding === "hex") {
689
+ return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
690
+ }
691
+ if (selectedEncoding === "binary" || selectedEncoding === "latin1") return bytesToLatin1(bytes);
692
+ if (selectedEncoding === "ascii") {
693
+ return bytesToLatin1(Uint8Array.from(bytes, (byte) => byte & 0x7f));
694
+ }
695
+ return decoder.decode(bytes);
696
+ }
697
+
698
+ function bytesFrom(value) {
699
+ if (typeof value === "string") return encoder.encode(value);
700
+ if (value instanceof Uint8Array) return value;
701
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
702
+ if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
703
+ throw new TypeError("workspace contents must be a string or byte array");
704
+ }
705
+
706
+ function bytesToLatin1(bytes) {
707
+ let output = "";
708
+ for (let offset = 0; offset < bytes.length; offset += 32_768) {
709
+ output += String.fromCharCode(...bytes.subarray(offset, offset + 32_768));
710
+ }
711
+ return output;
712
+ }
713
+
714
+ function validateWorkspace(workspace) {
715
+ if (!workspace || typeof workspace !== "object" || typeof workspace.root !== "string") {
716
+ throw new TypeError("Just Bash requires a workspace handle");
717
+ }
718
+ for (const method of ["list", "readFile", "writeFile", "remove", "mkdir"]) {
719
+ if (typeof workspace[method] !== "function") {
720
+ throw new TypeError(`workspace handle requires ${method}()`);
721
+ }
722
+ }
723
+ }
724
+
725
+ function positiveInteger(value, fallback, name) {
726
+ if (value === undefined) return fallback;
727
+ if (!Number.isSafeInteger(value) || value <= 0) throw new TypeError(`${name} must be positive`);
728
+ return value;
729
+ }
730
+
731
+ function requiredString(value, name) {
732
+ if (typeof value !== "string" || !value.trim()) throw new TypeError(`${name} must be non-empty`);
733
+ return value;
734
+ }
735
+
736
+ function fsError(code, message) {
737
+ return Object.assign(new Error(message), { code });
738
+ }
739
+
740
+ function now() {
741
+ return globalThis.performance?.now?.() ?? Date.now();
742
+ }