@arnilo/prism-tools 0.0.0-stage → 0.13.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 (331) hide show
  1. package/CHANGELOG.md +410 -0
  2. package/LICENSE +21 -0
  3. package/README.md +44 -2
  4. package/browser/README.md +98 -0
  5. package/browser/skills/browser-automation/SKILL.md +14 -0
  6. package/browser/src/actions/cdp.ts +131 -0
  7. package/browser/src/actions/dialog.ts +25 -0
  8. package/browser/src/actions/download_release.ts +22 -0
  9. package/browser/src/actions/index.ts +37 -0
  10. package/browser/src/actions/locator.ts +70 -0
  11. package/browser/src/actions/navigate.ts +23 -0
  12. package/browser/src/actions/screenshot.ts +27 -0
  13. package/browser/src/actions/scroll.ts +24 -0
  14. package/browser/src/actions/select_page.ts +21 -0
  15. package/browser/src/actions/types.ts +114 -0
  16. package/browser/src/actions/upload.ts +44 -0
  17. package/browser/src/actions/wait.ts +45 -0
  18. package/browser/src/browser.ts +197 -0
  19. package/browser/src/cdp.ts +178 -0
  20. package/browser/src/checkpoint.ts +189 -0
  21. package/browser/src/config.ts +72 -0
  22. package/browser/src/downloads.ts +253 -0
  23. package/browser/src/errors.ts +31 -0
  24. package/browser/src/evaluate.ts +101 -0
  25. package/browser/src/index.ts +129 -0
  26. package/browser/src/limits.ts +271 -0
  27. package/browser/src/manager.ts +730 -0
  28. package/browser/src/network.ts +263 -0
  29. package/browser/src/observe.ts +178 -0
  30. package/browser/src/policy.ts +133 -0
  31. package/browser/src/screenshot.ts +107 -0
  32. package/browser/src/shared-sandbox.ts +53 -0
  33. package/browser/src/snapshot.ts +99 -0
  34. package/browser/src/targets.ts +165 -0
  35. package/browser/src/tools.ts +480 -0
  36. package/browser/src/types.ts +316 -0
  37. package/browser/src/uploads.ts +174 -0
  38. package/coding/README.md +337 -0
  39. package/coding/src/agent/acp-operations.ts +146 -0
  40. package/coding/src/agent/artifacts.ts +38 -0
  41. package/coding/src/agent/ask-user-decision.ts +623 -0
  42. package/coding/src/agent/atomic-write.ts +23 -0
  43. package/coding/src/agent/bounded-file.ts +21 -0
  44. package/coding/src/agent/checks.ts +301 -0
  45. package/coding/src/agent/coding-checkpoint.ts +828 -0
  46. package/coding/src/agent/delete.ts +237 -0
  47. package/coding/src/agent/diagnostics-tool.ts +163 -0
  48. package/coding/src/agent/diagnostics.ts +262 -0
  49. package/coding/src/agent/edit-diff.ts +382 -0
  50. package/coding/src/agent/edit.ts +322 -0
  51. package/coding/src/agent/effects.ts +102 -0
  52. package/coding/src/agent/env.ts +28 -0
  53. package/coding/src/agent/execution-policy.ts +89 -0
  54. package/coding/src/agent/file-mutation-queue.ts +72 -0
  55. package/coding/src/agent/forge/github.ts +603 -0
  56. package/coding/src/agent/forge/index.ts +16 -0
  57. package/coding/src/agent/forge/types.ts +185 -0
  58. package/coding/src/agent/git-aware-repository.ts +305 -0
  59. package/coding/src/agent/git-exec.ts +326 -0
  60. package/coding/src/agent/git-status.ts +177 -0
  61. package/coding/src/agent/git-tools.ts +360 -0
  62. package/coding/src/agent/git.ts +1074 -0
  63. package/coding/src/agent/glob-match.ts +146 -0
  64. package/coding/src/agent/glob.ts +182 -0
  65. package/coding/src/agent/goal-verify.ts +385 -0
  66. package/coding/src/agent/grep-worker.ts +74 -0
  67. package/coding/src/agent/grep.ts +459 -0
  68. package/coding/src/agent/identity.ts +48 -0
  69. package/coding/src/agent/index.ts +676 -0
  70. package/coding/src/agent/language/client.ts +429 -0
  71. package/coding/src/agent/language/framing.ts +190 -0
  72. package/coding/src/agent/language/index.ts +23 -0
  73. package/coding/src/agent/language/intelligence.ts +682 -0
  74. package/coding/src/agent/language/types.ts +187 -0
  75. package/coding/src/agent/lifecycle-events.ts +121 -0
  76. package/coding/src/agent/lifecycle.ts +166 -0
  77. package/coding/src/agent/limits.ts +214 -0
  78. package/coding/src/agent/move.ts +178 -0
  79. package/coding/src/agent/mutation-path.ts +52 -0
  80. package/coding/src/agent/output-accumulator.ts +304 -0
  81. package/coding/src/agent/path-utils.ts +120 -0
  82. package/coding/src/agent/process/index.ts +58 -0
  83. package/coding/src/agent/process/recovery.ts +508 -0
  84. package/coding/src/agent/process/sessions-host.ts +222 -0
  85. package/coding/src/agent/process/sessions-monitor.ts +141 -0
  86. package/coding/src/agent/process/sessions-recovery.ts +241 -0
  87. package/coding/src/agent/process/sessions-spawn.ts +613 -0
  88. package/coding/src/agent/process/sessions-teardown.ts +281 -0
  89. package/coding/src/agent/process/sessions.ts +30 -0
  90. package/coding/src/agent/process/types.ts +358 -0
  91. package/coding/src/agent/read-path-set.ts +126 -0
  92. package/coding/src/agent/read.ts +713 -0
  93. package/coding/src/agent/repository/glob.ts +147 -0
  94. package/coding/src/agent/repository/indexed-search.ts +480 -0
  95. package/coding/src/agent/repository/list.ts +131 -0
  96. package/coding/src/agent/repository/operations.ts +22 -0
  97. package/coding/src/agent/repository/path.ts +98 -0
  98. package/coding/src/agent/repository/search.ts +277 -0
  99. package/coding/src/agent/repository/types.ts +243 -0
  100. package/coding/src/agent/repository/walk.ts +126 -0
  101. package/coding/src/agent/repository.ts +12 -0
  102. package/coding/src/agent/shell-background.ts +264 -0
  103. package/coding/src/agent/shell-output.ts +75 -0
  104. package/coding/src/agent/shell.ts +526 -0
  105. package/coding/src/agent/todo.ts +399 -0
  106. package/coding/src/agent/truncate.ts +251 -0
  107. package/coding/src/agent/write.ts +170 -0
  108. package/coding/src/ask-user-host.ts +48 -0
  109. package/coding/src/config.ts +278 -0
  110. package/coding/src/hard-deny.ts +97 -0
  111. package/coding/src/index.ts +318 -0
  112. package/coding/src/principal-guard.ts +84 -0
  113. package/coding/src/sandbox.ts +92 -0
  114. package/coding/src/security/approval.ts +144 -0
  115. package/coding/src/security/command-rules.ts +57 -0
  116. package/coding/src/security/docker-cli.ts +222 -0
  117. package/coding/src/security/docker-sandbox.ts +1205 -0
  118. package/coding/src/security/e2b-sandbox.ts +853 -0
  119. package/coding/src/security/egress/dns-pin.ts +88 -0
  120. package/coding/src/security/egress/index.ts +32 -0
  121. package/coding/src/security/egress/limits.ts +57 -0
  122. package/coding/src/security/egress/policy.ts +137 -0
  123. package/coding/src/security/egress/proxy.ts +599 -0
  124. package/coding/src/security/egress/types.ts +30 -0
  125. package/coding/src/security/index.ts +199 -0
  126. package/coding/src/security/native-sandbox.ts +639 -0
  127. package/coding/src/security/path-containment.ts +55 -0
  128. package/coding/src/security/sandbox-coding-operations.ts +392 -0
  129. package/coding/src/security/sandbox-fs-operations.ts +644 -0
  130. package/coding/src/security/sandbox-limits.ts +147 -0
  131. package/coding/src/security/sandbox-tar.ts +308 -0
  132. package/coding/src/security/sandbox.ts +179 -0
  133. package/coding/src/security/semaphore.ts +55 -0
  134. package/coding/src/targets.ts +110 -0
  135. package/coding/src/workspace/diff.ts +78 -0
  136. package/coding/src/workspace/errors.ts +28 -0
  137. package/coding/src/workspace/index.ts +5 -0
  138. package/coding/src/workspace/merge.ts +151 -0
  139. package/coding/src/workspace/routing.ts +76 -0
  140. package/coding/src/workspace/service.ts +545 -0
  141. package/computer-use/README.md +105 -0
  142. package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
  143. package/computer-use/src/classify.ts +53 -0
  144. package/computer-use/src/command.ts +29 -0
  145. package/computer-use/src/config.ts +69 -0
  146. package/computer-use/src/create.ts +170 -0
  147. package/computer-use/src/devices.ts +247 -0
  148. package/computer-use/src/env.ts +28 -0
  149. package/computer-use/src/index.ts +157 -0
  150. package/computer-use/src/skill.ts +21 -0
  151. package/openapi/README.md +97 -0
  152. package/openapi/src/compile.ts +386 -0
  153. package/openapi/src/config.ts +73 -0
  154. package/openapi/src/errors.ts +16 -0
  155. package/openapi/src/execute.ts +241 -0
  156. package/openapi/src/index.ts +124 -0
  157. package/openapi/src/limits.ts +63 -0
  158. package/openapi/src/specs.ts +86 -0
  159. package/openapi/src/tools.ts +60 -0
  160. package/openapi/src/types.ts +61 -0
  161. package/package.json +171 -4
  162. package/research/README.md +72 -0
  163. package/research/skills/research-report/SKILL.md +12 -0
  164. package/research/skills/research-scoping/SKILL.md +10 -0
  165. package/research/skills/research-synthesis/SKILL.md +10 -0
  166. package/research/skills/source-triage/SKILL.md +10 -0
  167. package/research/src/check.ts +71 -0
  168. package/research/src/config.ts +41 -0
  169. package/research/src/index.ts +147 -0
  170. package/research/src/ledger.ts +157 -0
  171. package/research/src/urls.ts +21 -0
  172. package/web/README.md +287 -0
  173. package/web/skills/web-research/SKILL.md +17 -0
  174. package/web/src/brave.ts +66 -0
  175. package/web/src/cache.ts +151 -0
  176. package/web/src/config.ts +210 -0
  177. package/web/src/controls.ts +128 -0
  178. package/web/src/domains.ts +114 -0
  179. package/web/src/exa.ts +60 -0
  180. package/web/src/firecrawl.ts +160 -0
  181. package/web/src/index.ts +387 -0
  182. package/web/src/kagi.ts +73 -0
  183. package/web/src/limits.ts +55 -0
  184. package/web/src/native/convert.ts +84 -0
  185. package/web/src/native/fetch.ts +226 -0
  186. package/web/src/normalize.ts +131 -0
  187. package/web/src/obscura/cdp.ts +201 -0
  188. package/web/src/obscura/classify.ts +35 -0
  189. package/web/src/obscura/cli.ts +254 -0
  190. package/web/src/obscura/errors.ts +8 -0
  191. package/web/src/obscura/index.ts +59 -0
  192. package/web/src/obscura/limits.ts +92 -0
  193. package/web/src/obscura/mcp.ts +104 -0
  194. package/web/src/obscura/playwright.ts +133 -0
  195. package/web/src/obscura/process.ts +235 -0
  196. package/web/src/obscura/search-profile.ts +73 -0
  197. package/web/src/obscura/types.ts +48 -0
  198. package/web/src/obscura/web.ts +419 -0
  199. package/web/src/prompt-fetch.ts +88 -0
  200. package/web/src/render.ts +75 -0
  201. package/web/src/searxng.ts +78 -0
  202. package/web/src/slice.ts +139 -0
  203. package/web/src/tavily.ts +77 -0
  204. package/web/src/tools.ts +182 -0
  205. package/web/src/transport.ts +310 -0
  206. package/web/src/types.ts +199 -0
  207. package/work/README.md +77 -0
  208. package/work/THIRD_PARTY_NOTICES +24 -0
  209. package/work/docling/ocr.py +150 -0
  210. package/work/sandbox/Dockerfile +36 -0
  211. package/work/sandbox/soffice.sh +12 -0
  212. package/work/src/command.ts +29 -0
  213. package/work/src/config.ts +138 -0
  214. package/work/src/connectors/cli.ts +343 -0
  215. package/work/src/connectors/drafts.ts +647 -0
  216. package/work/src/connectors/errors.ts +8 -0
  217. package/work/src/connectors/file-bytes.ts +149 -0
  218. package/work/src/connectors/google-workspace-http.ts +399 -0
  219. package/work/src/connectors/google-workspace.ts +465 -0
  220. package/work/src/connectors/gws-tools.ts +378 -0
  221. package/work/src/connectors/http.ts +123 -0
  222. package/work/src/connectors/idempotency.ts +235 -0
  223. package/work/src/connectors/identity.ts +34 -0
  224. package/work/src/connectors/index.ts +93 -0
  225. package/work/src/connectors/limits.ts +50 -0
  226. package/work/src/connectors/m365-tools.ts +308 -0
  227. package/work/src/connectors/microsoft365-http.ts +428 -0
  228. package/work/src/connectors/microsoft365.ts +402 -0
  229. package/work/src/connectors/normalize.ts +120 -0
  230. package/work/src/connectors/tool-helpers.ts +398 -0
  231. package/work/src/connectors/tools.ts +42 -0
  232. package/work/src/connectors/types.ts +399 -0
  233. package/work/src/diagrams/canonicalize.ts +166 -0
  234. package/work/src/diagrams/caps.ts +52 -0
  235. package/work/src/diagrams/embed.ts +340 -0
  236. package/work/src/diagrams/errors.ts +76 -0
  237. package/work/src/diagrams/index.ts +81 -0
  238. package/work/src/diagrams/messages.ts +150 -0
  239. package/work/src/diagrams/origin.ts +52 -0
  240. package/work/src/diagrams/telemetry.ts +34 -0
  241. package/work/src/diagrams/xml.ts +267 -0
  242. package/work/src/document-extraction/index.ts +563 -0
  243. package/work/src/document-reader/ambient.d.ts +2 -0
  244. package/work/src/document-reader/errors.ts +7 -0
  245. package/work/src/document-reader/index.ts +181 -0
  246. package/work/src/document-reader/mistral-ocr.ts +318 -0
  247. package/work/src/document-reader/parsers.ts +171 -0
  248. package/work/src/documents/caps.ts +135 -0
  249. package/work/src/documents/diff.ts +269 -0
  250. package/work/src/documents/errors.ts +44 -0
  251. package/work/src/documents/fidelity.ts +98 -0
  252. package/work/src/documents/generate.ts +96 -0
  253. package/work/src/documents/hash.ts +8 -0
  254. package/work/src/documents/index.ts +14 -0
  255. package/work/src/documents/model-schema.ts +639 -0
  256. package/work/src/documents/parse.ts +252 -0
  257. package/work/src/documents/patch-history.ts +81 -0
  258. package/work/src/documents/patch.ts +476 -0
  259. package/work/src/documents/preview-html.ts +245 -0
  260. package/work/src/documents/preview.ts +227 -0
  261. package/work/src/documents/telemetry.ts +36 -0
  262. package/work/src/documents/translate/docx.ts +292 -0
  263. package/work/src/documents/translate/pptx.ts +304 -0
  264. package/work/src/documents/translate/xlsx.ts +137 -0
  265. package/work/src/documents/types.ts +188 -0
  266. package/work/src/index.ts +311 -0
  267. package/work/src/runtime/worker-pool-entry.ts +102 -0
  268. package/work/src/runtime/worker-pool.ts +267 -0
  269. package/work/src/sandbox/composition.ts +212 -0
  270. package/work/src/sandbox/image.ts +6 -0
  271. package/work/src/sandbox/index.ts +14 -0
  272. package/work/src/sheets/caps.ts +68 -0
  273. package/work/src/sheets/csv.ts +351 -0
  274. package/work/src/sheets/decimal.ts +388 -0
  275. package/work/src/sheets/errors.ts +52 -0
  276. package/work/src/sheets/index.ts +62 -0
  277. package/work/src/sheets/inference.ts +378 -0
  278. package/work/src/sheets/telemetry.ts +34 -0
  279. package/work/src/sheets/types.ts +162 -0
  280. package/work/src/sheets/xlsx.ts +388 -0
  281. package/work/src/skills/index.ts +8 -0
  282. package/work/src/skills/load.ts +43 -0
  283. package/work/src/tools/filesystem.ts +21 -0
  284. package/work/src/tools/index.ts +1 -0
  285. package/work/src/tools/local-filesystem.ts +33 -0
  286. package/work/src/tools/office.ts +311 -0
  287. package/work/vendor/hermes-agent/LICENSE +21 -0
  288. package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
  289. package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
  290. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
  291. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
  292. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
  293. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
  294. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
  295. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
  296. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
  297. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
  298. package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
  299. package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
  300. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
  301. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
  302. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
  303. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
  304. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
  305. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
  306. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
  307. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
  308. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
  309. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
  310. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
  311. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
  312. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
  313. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
  314. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
  315. package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
  316. package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
  317. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
  318. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
  319. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
  320. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
  321. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
  322. package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
  323. package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
  324. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
  325. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
  326. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
  327. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
  328. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
  329. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
  330. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
  331. package/work/vendor-lock.json +11 -0
@@ -0,0 +1,644 @@
1
+ /**
2
+ * ExecFile-backed filesystem and repository operations for a disposable sandbox tree.
3
+ *
4
+ * Paths must stay under `workspaceRoot` (default `/workspace`). Commands use fixed
5
+ * `/bin/sh -c` scripts with positional args so content never needs shell escaping.
6
+ * No host↔container sync loops — each op is one (or chunked-write) execFile call.
7
+ */
8
+ import { posix } from "node:path";
9
+ import {
10
+ literalMatcher,
11
+ type DeleteOperations,
12
+ type EditOperations,
13
+ HARD_MAX_EDIT_FILE_BYTES,
14
+ HARD_MAX_TEXT_SCAN_BYTES,
15
+ HARD_MAX_WRITE_BYTES,
16
+ isBinaryBuffer,
17
+ type MoveOperations,
18
+ compileGlob,
19
+ type ReadOperations,
20
+ type ReadTextOptions,
21
+ type ReadTextResult,
22
+ type RepositoryGlobResult,
23
+ type RepositoryLimitOptions,
24
+ type RepositoryListResult,
25
+ type RepositoryOperations,
26
+ type RepositorySearchMatch,
27
+ type RepositorySearchResult,
28
+ resolveRepositoryLimits,
29
+ validateGlobPattern,
30
+ type WriteOperations,
31
+ } from "../agent/index.js";
32
+ import type { DisposableSandbox } from "./sandbox.js";
33
+
34
+ /** Fixed scripts — tests match these exact strings. */
35
+ export const SANDBOX_FS_SCRIPTS = Object.freeze({
36
+ read: 'dd if="$1" bs="$2" count=1 status=none 2>/dev/null',
37
+ write: 'printf "%s" "$1" | base64 -d > "$2"',
38
+ append: 'printf "%s" "$1" | base64 -d >> "$2"',
39
+ truncate: ': > "$1"',
40
+ mkdir: 'mkdir -p -- "$1"',
41
+ access: 'test -e -- "$1"',
42
+ stat: 'wc -c < "$1"',
43
+ // Absolute paths; prune common heavy dirs. Extra excludes filtered in JS.
44
+ find: `find "$1" -mindepth 1 -maxdepth "$2" \\( -name .git -o -name node_modules -o -name dist \\) -prune -o \\( -type f -o -type d -o -type l \\) -print 2>/dev/null`,
45
+ delete: 'if [ -L "$1" ] || [ -f "$1" ]; then rm -f -- "$1"; elif [ -d "$1" ]; then rmdir -- "$1"; else exit 1; fi',
46
+ listdir: 'ls -A -- "$1" 2>/dev/null',
47
+ kind: 'if [ -L "$1" ]; then echo symlink; elif [ -f "$1" ]; then echo file; elif [ -d "$1" ]; then echo dir; else echo other; fi',
48
+ move: 'mv -- "$1" "$2"',
49
+ remove: 'rm -f -- "$1"',
50
+ });
51
+
52
+ export class SandboxFsError extends Error {
53
+ readonly code = "ERR_PRISM_SANDBOX_FS";
54
+ constructor(message: string) {
55
+ super(message);
56
+ this.name = "SandboxFsError";
57
+ }
58
+ }
59
+
60
+ export interface SandboxFsOperationsOptions {
61
+ readonly workspaceRoot?: string;
62
+ /** Cap collected exec stdout (default HARD_MAX_TEXT_SCAN_BYTES). */
63
+ readonly maxOutputBytes?: number;
64
+ }
65
+
66
+ export interface SandboxRepositoryOperationsOptions {
67
+ readonly workspaceRoot?: string;
68
+ readonly limits?: RepositoryLimitOptions;
69
+ readonly maxOutputBytes?: number;
70
+ }
71
+
72
+ /** Stay under typical ARG_MAX when base64-encoding write chunks. */
73
+ const WRITE_CHUNK_BYTES = 256 * 1024;
74
+
75
+ function normalizeWorkspaceRoot(root: string | undefined): string {
76
+ const value = root ?? "/workspace";
77
+ if (!posix.isAbsolute(value) || value.includes("\0")) {
78
+ throw new SandboxFsError("workspaceRoot must be an absolute container path");
79
+ }
80
+ return posix.resolve(value);
81
+ }
82
+
83
+ /** Fail closed if target escapes workspace root (posix string containment; no host realpath). */
84
+ export function assertSandboxPath(workspaceRoot: string, absolutePath: string): string {
85
+ if (!absolutePath || absolutePath.includes("\0")) {
86
+ throw new SandboxFsError("path must be a non-empty absolute container path");
87
+ }
88
+ if (!posix.isAbsolute(absolutePath)) {
89
+ throw new SandboxFsError(`path must be absolute under sandbox workspace: ${absolutePath}`);
90
+ }
91
+ const root = posix.resolve(workspaceRoot);
92
+ const target = posix.resolve(absolutePath);
93
+ if (target !== root && !target.startsWith(`${root}/`)) {
94
+ throw new SandboxFsError(`path escapes sandbox workspace: ${absolutePath}`);
95
+ }
96
+ return target;
97
+ }
98
+
99
+ async function execCollect(
100
+ sandbox: DisposableSandbox,
101
+ file: string,
102
+ args: readonly string[],
103
+ options: {
104
+ cwd: string;
105
+ signal?: AbortSignal;
106
+ timeout?: number;
107
+ maxBytes: number;
108
+ },
109
+ ): Promise<{ exitCode: number | null; stdout: Buffer }> {
110
+ if (options.signal?.aborted) throw new SandboxFsError("Operation aborted");
111
+ const chunks: Buffer[] = [];
112
+ let size = 0;
113
+ const { exitCode } = await sandbox.execFile({
114
+ file,
115
+ args: [...args],
116
+ cwd: options.cwd,
117
+ signal: options.signal,
118
+ timeout: options.timeout,
119
+ onData: (data) => {
120
+ if (size + data.byteLength > options.maxBytes) {
121
+ throw new SandboxFsError(`sandbox exec output exceeded ${options.maxBytes} bytes`);
122
+ }
123
+ chunks.push(data);
124
+ size += data.byteLength;
125
+ },
126
+ });
127
+ return { exitCode, stdout: Buffer.concat(chunks, size) };
128
+ }
129
+
130
+ function sh(
131
+ sandbox: DisposableSandbox,
132
+ script: string,
133
+ positional: readonly string[],
134
+ options: { cwd: string; signal?: AbortSignal; timeout?: number; maxBytes: number },
135
+ ): Promise<{ exitCode: number | null; stdout: Buffer }> {
136
+ return execCollect(sandbox, "/bin/sh", ["-c", script, "prism-sandbox-fs", ...positional], options);
137
+ }
138
+
139
+ function paginateText(buffer: Buffer, options: ReadTextOptions): ReadTextResult {
140
+ const text = buffer.toString("utf8");
141
+ const lines = text.split("\n");
142
+ if (text.endsWith("\n") && lines.length > 0 && lines[lines.length - 1] === "") {
143
+ lines.pop();
144
+ }
145
+ const totalLines = lines.length;
146
+ const startIndex = Math.max(0, options.offset - 1);
147
+ const maxLines = options.limit ?? options.maxLines;
148
+ let retained = 0;
149
+ let outputLines = 0;
150
+ const out: string[] = [];
151
+ let truncatedBy: "lines" | "bytes" | null = null;
152
+ let firstLineExceedsLimit = false;
153
+
154
+ for (let i = startIndex; i < lines.length; i++) {
155
+ const line = lines[i];
156
+ const lineBytes = Buffer.byteLength(line, "utf8") + 1;
157
+ if (outputLines === 0 && Buffer.byteLength(line, "utf8") > options.maxBytes) {
158
+ firstLineExceedsLimit = true;
159
+ truncatedBy = "bytes";
160
+ break;
161
+ }
162
+ if (outputLines >= maxLines) {
163
+ truncatedBy = "lines";
164
+ break;
165
+ }
166
+ if (retained + lineBytes > options.maxBytes) {
167
+ truncatedBy = "bytes";
168
+ break;
169
+ }
170
+ out.push(line);
171
+ retained += lineBytes;
172
+ outputLines += 1;
173
+ }
174
+
175
+ const hasMore = startIndex + outputLines < totalLines || truncatedBy !== null;
176
+ return {
177
+ content: firstLineExceedsLimit ? "" : out.join("\n"),
178
+ startLine: options.offset,
179
+ outputLines: firstLineExceedsLimit ? 0 : outputLines,
180
+ hasMore,
181
+ nextOffset: hasMore ? options.offset + (firstLineExceedsLimit ? 0 : outputLines) : undefined,
182
+ truncatedBy,
183
+ firstLineExceedsLimit,
184
+ scannedBytes: buffer.byteLength,
185
+ totalLines,
186
+ totalBytes: buffer.byteLength,
187
+ };
188
+ }
189
+
190
+ function toRel(root: string, abs: string): string {
191
+ const prefix = root.endsWith("/") ? root : `${root}/`;
192
+ if (abs === root) return ".";
193
+ if (abs.startsWith(prefix)) return abs.slice(prefix.length);
194
+ return abs;
195
+ }
196
+
197
+ export function createSandboxFilesystemOperations(
198
+ sandbox: DisposableSandbox,
199
+ options?: SandboxFsOperationsOptions,
200
+ ): { read: ReadOperations; write: WriteOperations; edit: EditOperations; delete: DeleteOperations; move: MoveOperations } {
201
+ const workspaceRoot = normalizeWorkspaceRoot(options?.workspaceRoot);
202
+ const maxOutputBytes = options?.maxOutputBytes ?? HARD_MAX_TEXT_SCAN_BYTES;
203
+
204
+ const readFile = async (absolutePath: string, opts: { maxBytes: number; signal?: AbortSignal }): Promise<Buffer> => {
205
+ const path = assertSandboxPath(workspaceRoot, absolutePath);
206
+ const maxBytes = Math.min(Math.max(1, opts.maxBytes), HARD_MAX_TEXT_SCAN_BYTES);
207
+ const { exitCode, stdout } = await sh(sandbox, SANDBOX_FS_SCRIPTS.read, [path, String(maxBytes)], {
208
+ cwd: workspaceRoot,
209
+ signal: opts.signal,
210
+ maxBytes,
211
+ });
212
+ if (exitCode !== 0) throw new SandboxFsError(`failed to read ${path}`);
213
+ return stdout.byteLength > maxBytes ? stdout.subarray(0, maxBytes) : stdout;
214
+ };
215
+
216
+ const access = async (absolutePath: string, opts?: { signal?: AbortSignal }): Promise<void> => {
217
+ const path = assertSandboxPath(workspaceRoot, absolutePath);
218
+ const { exitCode } = await sh(sandbox, SANDBOX_FS_SCRIPTS.access, [path], {
219
+ cwd: workspaceRoot,
220
+ signal: opts?.signal,
221
+ maxBytes: 64,
222
+ });
223
+ if (exitCode !== 0) throw new SandboxFsError(`ENOENT: ${path}`);
224
+ };
225
+
226
+ const statFile = async (absolutePath: string, opts?: { signal?: AbortSignal }): Promise<{ size: number }> => {
227
+ const path = assertSandboxPath(workspaceRoot, absolutePath);
228
+ const { exitCode, stdout } = await sh(sandbox, SANDBOX_FS_SCRIPTS.stat, [path], {
229
+ cwd: workspaceRoot,
230
+ signal: opts?.signal,
231
+ maxBytes: 64,
232
+ });
233
+ if (exitCode !== 0) throw new SandboxFsError(`failed to stat ${path}`);
234
+ const size = Number.parseInt(stdout.toString("utf8").trim(), 10);
235
+ if (!Number.isFinite(size) || size < 0) throw new SandboxFsError(`invalid size for ${path}`);
236
+ return { size };
237
+ };
238
+
239
+ const mkdir = async (dir: string, opts?: { signal?: AbortSignal }): Promise<void> => {
240
+ const path = assertSandboxPath(workspaceRoot, dir);
241
+ const { exitCode } = await sh(sandbox, SANDBOX_FS_SCRIPTS.mkdir, [path], {
242
+ cwd: workspaceRoot,
243
+ signal: opts?.signal,
244
+ maxBytes: 64,
245
+ });
246
+ if (exitCode !== 0) throw new SandboxFsError(`failed to mkdir ${path}`);
247
+ };
248
+
249
+ const writeFile = async (absolutePath: string, content: string, opts?: { maxBytes?: number; signal?: AbortSignal }): Promise<void> => {
250
+ const path = assertSandboxPath(workspaceRoot, absolutePath);
251
+ const maxBytes = Math.min(opts?.maxBytes ?? HARD_MAX_WRITE_BYTES, HARD_MAX_WRITE_BYTES);
252
+ const buf = Buffer.from(content, "utf8");
253
+ if (buf.byteLength > maxBytes) {
254
+ throw new SandboxFsError(`write exceeds ${maxBytes} byte limit`);
255
+ }
256
+
257
+ // Ensure parent exists (best-effort).
258
+ const parent = posix.dirname(path);
259
+ if (parent !== path && parent !== workspaceRoot) {
260
+ await mkdir(parent, { signal: opts?.signal }).catch(() => undefined);
261
+ }
262
+
263
+ if (buf.byteLength === 0) {
264
+ const { exitCode } = await sh(sandbox, SANDBOX_FS_SCRIPTS.truncate, [path], {
265
+ cwd: workspaceRoot,
266
+ signal: opts?.signal,
267
+ maxBytes: 64,
268
+ });
269
+ if (exitCode !== 0) throw new SandboxFsError(`failed to truncate ${path}`);
270
+ return;
271
+ }
272
+
273
+ for (let offset = 0; offset < buf.byteLength; offset += WRITE_CHUNK_BYTES) {
274
+ const chunk = buf.subarray(offset, offset + WRITE_CHUNK_BYTES);
275
+ const b64 = chunk.toString("base64");
276
+ const useScript = offset === 0 ? SANDBOX_FS_SCRIPTS.write : SANDBOX_FS_SCRIPTS.append;
277
+ const { exitCode } = await sh(sandbox, useScript, [b64, path], {
278
+ cwd: workspaceRoot,
279
+ signal: opts?.signal,
280
+ maxBytes: 64,
281
+ });
282
+ if (exitCode !== 0) throw new SandboxFsError(`failed to write ${path}`);
283
+ }
284
+ };
285
+
286
+ const read: ReadOperations = {
287
+ readFile,
288
+ readText: async (absolutePath, options) => {
289
+ if (options.signal?.aborted) throw new SandboxFsError("Operation aborted");
290
+ const maxScan = Math.min(options.maxScanBytes, HARD_MAX_TEXT_SCAN_BYTES);
291
+ const buf = await readFile(absolutePath, { maxBytes: maxScan, signal: options.signal });
292
+ return paginateText(buf, options);
293
+ },
294
+ access,
295
+ statFile,
296
+ };
297
+
298
+ const write: WriteOperations = { writeFile, mkdir };
299
+
300
+ const edit: EditOperations = {
301
+ readFile: async (absolutePath, opts) => {
302
+ const maxBytes = Math.min(opts.maxBytes, HARD_MAX_EDIT_FILE_BYTES);
303
+ return readFile(absolutePath, { maxBytes, signal: opts.signal });
304
+ },
305
+ writeFile: async (absolutePath, content, opts) =>
306
+ writeFile(absolutePath, content, { maxBytes: HARD_MAX_EDIT_FILE_BYTES, signal: opts?.signal }),
307
+ access,
308
+ statFile,
309
+ };
310
+
311
+ const sandboxLstat = async (absolutePath: string, opts?: { signal?: AbortSignal }) => {
312
+ const path = assertSandboxPath(workspaceRoot, absolutePath);
313
+ const [{ exitCode: kindCode, stdout: kindOut }, { exitCode: sizeCode, stdout: sizeOut }] = await Promise.all([
314
+ sh(sandbox, SANDBOX_FS_SCRIPTS.kind, [path], { cwd: workspaceRoot, signal: opts?.signal, maxBytes: 64 }),
315
+ sh(sandbox, SANDBOX_FS_SCRIPTS.stat, [path], { cwd: workspaceRoot, signal: opts?.signal, maxBytes: 64 }),
316
+ ]);
317
+ if (kindCode !== 0 || sizeCode !== 0) throw new SandboxFsError(`failed to stat ${path}`);
318
+ const kind = kindOut.toString("utf8").trim();
319
+ const size = Number.parseInt(sizeOut.toString("utf8").trim(), 10);
320
+ return {
321
+ isFile: () => kind === "file",
322
+ isDirectory: () => kind === "dir",
323
+ isSymbolicLink: () => kind === "symlink",
324
+ size: Number.isFinite(size) && size >= 0 ? size : 0,
325
+ };
326
+ };
327
+
328
+ const del: DeleteOperations = {
329
+ lstat: sandboxLstat,
330
+ unlink: async (absolutePath, opts) => {
331
+ const path = assertSandboxPath(workspaceRoot, absolutePath);
332
+ const { exitCode } = await sh(sandbox, SANDBOX_FS_SCRIPTS.delete, [path], {
333
+ cwd: workspaceRoot,
334
+ signal: opts?.signal,
335
+ maxBytes: 64,
336
+ });
337
+ if (exitCode !== 0) throw new SandboxFsError(`failed to delete ${path}`);
338
+ },
339
+ rmdir: async (absolutePath, opts) => {
340
+ const path = assertSandboxPath(workspaceRoot, absolutePath);
341
+ const { exitCode } = await sh(sandbox, SANDBOX_FS_SCRIPTS.delete, [path], {
342
+ cwd: workspaceRoot,
343
+ signal: opts?.signal,
344
+ maxBytes: 64,
345
+ });
346
+ if (exitCode !== 0) throw new SandboxFsError(`failed to delete ${path}`);
347
+ },
348
+ readdir: async (absolutePath, opts) => {
349
+ const path = assertSandboxPath(workspaceRoot, absolutePath);
350
+ const { exitCode, stdout } = await sh(sandbox, SANDBOX_FS_SCRIPTS.listdir, [path], {
351
+ cwd: workspaceRoot,
352
+ signal: opts?.signal,
353
+ maxBytes: maxOutputBytes,
354
+ });
355
+ if (exitCode !== 0) throw new SandboxFsError(`failed to list ${path}`);
356
+ return stdout
357
+ .toString("utf8")
358
+ .split("\n")
359
+ .map((line) => line.trim())
360
+ .filter((line) => line.length > 0);
361
+ },
362
+ };
363
+
364
+ const move: MoveOperations = {
365
+ lstat: sandboxLstat,
366
+ rename: async (from, to, opts) => {
367
+ const fromPath = assertSandboxPath(workspaceRoot, from);
368
+ const toPath = assertSandboxPath(workspaceRoot, to);
369
+ const { exitCode } = await sh(sandbox, SANDBOX_FS_SCRIPTS.move, [fromPath, toPath], {
370
+ cwd: workspaceRoot,
371
+ signal: opts?.signal,
372
+ maxBytes: 64,
373
+ });
374
+ if (exitCode !== 0) throw new SandboxFsError(`failed to move ${fromPath} to ${toPath}`);
375
+ },
376
+ unlink: async (absolutePath, opts) => {
377
+ const path = assertSandboxPath(workspaceRoot, absolutePath);
378
+ const { exitCode } = await sh(sandbox, SANDBOX_FS_SCRIPTS.remove, [path], {
379
+ cwd: workspaceRoot,
380
+ signal: opts?.signal,
381
+ maxBytes: 64,
382
+ });
383
+ if (exitCode !== 0) throw new SandboxFsError(`failed to remove ${path}`);
384
+ },
385
+ access,
386
+ };
387
+
388
+ return { read, write, edit, delete: del, move };
389
+ }
390
+
391
+ export function createSandboxRepositoryOperations(
392
+ sandbox: DisposableSandbox,
393
+ options?: SandboxRepositoryOperationsOptions,
394
+ ): RepositoryOperations {
395
+ const workspaceRoot = normalizeWorkspaceRoot(options?.workspaceRoot);
396
+ const limits = resolveRepositoryLimits(options?.limits);
397
+ const maxOutputBytes = options?.maxOutputBytes ?? HARD_MAX_TEXT_SCAN_BYTES;
398
+ const { read } = createSandboxFilesystemOperations(sandbox, {
399
+ workspaceRoot,
400
+ maxOutputBytes,
401
+ });
402
+
403
+ async function listAbsPaths(startAbs: string, maxDepth: number, signal?: AbortSignal): Promise<string[]> {
404
+ const root = assertSandboxPath(workspaceRoot, startAbs);
405
+ const depth = Math.min(Math.max(1, maxDepth), limits.maxDepth);
406
+ const { stdout } = await sh(sandbox, SANDBOX_FS_SCRIPTS.find, [root, String(depth)], {
407
+ cwd: workspaceRoot,
408
+ signal,
409
+ maxBytes: maxOutputBytes,
410
+ });
411
+ return stdout
412
+ .toString("utf8")
413
+ .split("\n")
414
+ .map((line) => line.trim())
415
+ .filter((line) => line.length > 0)
416
+ .map((line) => assertSandboxPath(workspaceRoot, line));
417
+ }
418
+
419
+ return {
420
+ async list(request): Promise<RepositoryListResult> {
421
+ if (request.signal?.aborted) throw new SandboxFsError("Operation aborted");
422
+ const root = assertSandboxPath(workspaceRoot, request.root);
423
+ const start = request.path ? assertSandboxPath(workspaceRoot, posix.resolve(root, request.path)) : root;
424
+ const maxDepth = request.maxDepth ?? limits.maxDepth;
425
+ const maxResults = request.maxResults ?? limits.maxResults;
426
+ const offset = request.offset ?? 0;
427
+ const exclude = new Set(request.exclude ?? limits.exclude);
428
+ const includeHidden = request.includeHidden === true;
429
+
430
+ const absPaths = await listAbsPaths(start, maxDepth, request.signal);
431
+ const entries: RepositoryListResult["entries"][number][] = [];
432
+ let scannedEntries = 0;
433
+ let scannedFiles = 0;
434
+ let truncatedBy: RepositoryListResult["truncatedBy"] = null;
435
+
436
+ for (const absPath of absPaths) {
437
+ if (request.signal?.aborted) {
438
+ truncatedBy = "abort";
439
+ break;
440
+ }
441
+ const rel = toRel(start, absPath);
442
+ const base = posix.basename(rel);
443
+ if (!includeHidden && base.startsWith(".")) continue;
444
+ if (exclude.has(base)) continue;
445
+ scannedEntries += 1;
446
+ if (scannedEntries > limits.maxEntries) {
447
+ truncatedBy = "entries";
448
+ break;
449
+ }
450
+
451
+ try {
452
+ const st = await read.statFile(absPath, { signal: request.signal });
453
+ scannedFiles += 1;
454
+ if (scannedFiles > limits.maxFiles) {
455
+ truncatedBy = "files";
456
+ break;
457
+ }
458
+ entries.push({ path: rel, kind: "file", size: st.size });
459
+ } catch {
460
+ entries.push({ path: rel, kind: "directory" });
461
+ }
462
+
463
+ if (entries.length >= offset + maxResults) {
464
+ truncatedBy = truncatedBy ?? "results";
465
+ break;
466
+ }
467
+ }
468
+
469
+ const sliced = entries.slice(offset, offset + maxResults);
470
+ const truncated = truncatedBy !== null || entries.length > offset + sliced.length;
471
+ return {
472
+ entries: sliced,
473
+ truncated,
474
+ truncatedBy: truncated ? (truncatedBy ?? "results") : null,
475
+ scannedEntries,
476
+ scannedFiles,
477
+ offset,
478
+ nextOffset: truncated ? offset + sliced.length : undefined,
479
+ };
480
+ },
481
+
482
+ async search(request): Promise<RepositorySearchResult> {
483
+ if (request.signal?.aborted) throw new SandboxFsError("Operation aborted");
484
+ const root = assertSandboxPath(workspaceRoot, request.root);
485
+ const start = request.path ? assertSandboxPath(workspaceRoot, posix.resolve(root, request.path)) : root;
486
+ const mode = request.mode ?? "literal";
487
+ if (mode !== "literal") {
488
+ throw new SandboxFsError(`unsupported search mode: ${String(mode)} (literal only)`);
489
+ }
490
+ const matcher =
491
+ request.matcher ?? literalMatcher(request.query, request.caseSensitive === true, limits.maxPatternBytes, limits.maxLineBytes);
492
+ const maxMatches = request.maxMatches ?? limits.maxMatches;
493
+ const context = Math.min(request.context ?? limits.maxContextLines, limits.maxContextLines);
494
+ const exclude = new Set(request.exclude ?? limits.exclude);
495
+ const includeHidden = request.includeHidden === true;
496
+ const globMatch = request.glob !== undefined ? compileGlob(request.glob) : undefined;
497
+ const deadlineAt = Date.now() + (request.deadlineMs ?? limits.maxTimeMs);
498
+
499
+ const absPaths = await listAbsPaths(start, limits.maxDepth, request.signal);
500
+ const matches: RepositorySearchMatch[] = [];
501
+ let scannedBytes = 0;
502
+ let scannedFiles = 0;
503
+ let scannedEntries = 0;
504
+ let filesSkippedBinary = 0;
505
+ let filesSkippedOversize = 0;
506
+ let truncatedBy: RepositorySearchResult["truncatedBy"] = null;
507
+
508
+ for (const absPath of absPaths) {
509
+ if (Date.now() >= deadlineAt) {
510
+ truncatedBy = "time";
511
+ break;
512
+ }
513
+ if (request.signal?.aborted) {
514
+ truncatedBy = "abort";
515
+ break;
516
+ }
517
+ const rel = toRel(start, absPath);
518
+ const base = posix.basename(rel);
519
+ if (!includeHidden && base.startsWith(".")) continue;
520
+ if (exclude.has(base)) continue;
521
+ if (globMatch !== undefined && !globMatch(rel)) continue;
522
+ scannedEntries += 1;
523
+
524
+ let buf: Buffer;
525
+ try {
526
+ const st = await read.statFile(absPath, { signal: request.signal });
527
+ if (st.size > limits.maxFileBytes) {
528
+ filesSkippedOversize += 1;
529
+ continue;
530
+ }
531
+ buf = await read.readFile(absPath, {
532
+ maxBytes: Math.min(st.size, limits.maxFileBytes),
533
+ signal: request.signal,
534
+ });
535
+ } catch {
536
+ continue;
537
+ }
538
+
539
+ scannedFiles += 1;
540
+ scannedBytes += buf.byteLength;
541
+ if (scannedBytes > limits.maxScanBytes) {
542
+ truncatedBy = "scan";
543
+ break;
544
+ }
545
+ if (isBinaryBuffer(buf)) {
546
+ filesSkippedBinary += 1;
547
+ continue;
548
+ }
549
+
550
+ for (const match of await matcher(buf.toString("utf8"), context)) {
551
+ if (matches.length >= maxMatches) {
552
+ truncatedBy = "matches";
553
+ break;
554
+ }
555
+ matches.push({ path: rel, ...match });
556
+ }
557
+ if (truncatedBy === "matches") break;
558
+ }
559
+
560
+ return {
561
+ matches,
562
+ truncated: truncatedBy !== null,
563
+ truncatedBy,
564
+ scannedBytes,
565
+ scannedFiles,
566
+ scannedEntries,
567
+ filesSkippedBinary,
568
+ filesSkippedOversize,
569
+ };
570
+ },
571
+
572
+ async glob(request): Promise<RepositoryGlobResult> {
573
+ if (request.signal?.aborted) throw new SandboxFsError("Operation aborted");
574
+ try {
575
+ validateGlobPattern(request.pattern, limits.maxPatternBytes, { braceExpansion: request.braceExpansion });
576
+ } catch (error) {
577
+ throw new SandboxFsError(error instanceof Error ? error.message : String(error));
578
+ }
579
+ const root = assertSandboxPath(workspaceRoot, request.root);
580
+ const start = request.path ? assertSandboxPath(workspaceRoot, posix.resolve(root, request.path)) : root;
581
+ const maxDepth = request.maxDepth ?? limits.maxDepth;
582
+ const maxResults = request.maxResults ?? limits.maxResults;
583
+ const patternMatch = compileGlob(request.pattern, request.braceExpansion !== false);
584
+ const offset = request.offset ?? 0;
585
+ const exclude = new Set(request.exclude ?? limits.exclude);
586
+ const includeHidden = request.includeHidden === true;
587
+ const deadlineAt = Date.now() + (request.deadlineMs ?? limits.maxTimeMs);
588
+
589
+ const absPaths = await listAbsPaths(start, maxDepth, request.signal);
590
+ const paths: string[] = [];
591
+ let scannedEntries = 0;
592
+ let scannedFiles = 0;
593
+ let seen = 0;
594
+ let truncatedBy: RepositoryGlobResult["truncatedBy"] = null;
595
+
596
+ for (const absPath of absPaths) {
597
+ if (Date.now() >= deadlineAt) {
598
+ truncatedBy = "time";
599
+ break;
600
+ }
601
+ if (request.signal?.aborted) {
602
+ truncatedBy = "abort";
603
+ break;
604
+ }
605
+ const rel = toRel(root, absPath);
606
+ const base = posix.basename(rel);
607
+ if (!includeHidden && base.startsWith(".")) continue;
608
+ if (exclude.has(base)) continue;
609
+ scannedEntries += 1;
610
+ if (scannedEntries > limits.maxEntries) {
611
+ truncatedBy = "entries";
612
+ break;
613
+ }
614
+ scannedFiles += 1;
615
+ if (scannedFiles > limits.maxFiles) {
616
+ truncatedBy = "files";
617
+ break;
618
+ }
619
+ if (!patternMatch(rel)) continue;
620
+ if (seen < offset) {
621
+ seen++;
622
+ continue;
623
+ }
624
+ if (paths.length >= maxResults) {
625
+ truncatedBy = "results";
626
+ break;
627
+ }
628
+ paths.push(rel);
629
+ seen++;
630
+ }
631
+
632
+ const truncated = truncatedBy !== null;
633
+ return {
634
+ paths,
635
+ truncated,
636
+ truncatedBy,
637
+ scannedEntries,
638
+ scannedFiles,
639
+ offset,
640
+ nextOffset: truncated ? offset + paths.length : undefined,
641
+ };
642
+ },
643
+ };
644
+ }