@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,243 @@
1
+ /** Repository types family (0.2.5 plan 025 Task 1 split).
2
+ * Moved verbatim from repository.ts; public surface unchanged behind the barrel. */
3
+ import {
4
+ DEFAULT_BINARY_SNIFF_BYTES,
5
+ DEFAULT_MAX_REPO_CONCURRENCY,
6
+ DEFAULT_MAX_REPO_DEPTH,
7
+ DEFAULT_MAX_REPO_ENTRIES,
8
+ DEFAULT_MAX_REPO_FILES,
9
+ DEFAULT_MAX_REPO_RESULTS,
10
+ DEFAULT_MAX_SEARCH_CONTEXT_LINES,
11
+ DEFAULT_MAX_SEARCH_FILE_BYTES,
12
+ DEFAULT_MAX_SEARCH_LINE_BYTES,
13
+ DEFAULT_MAX_SEARCH_MATCHES,
14
+ DEFAULT_MAX_SEARCH_PATTERN_BYTES,
15
+ DEFAULT_MAX_SEARCH_SCAN_BYTES,
16
+ DEFAULT_MAX_SEARCH_TIME_MS,
17
+ HARD_MAX_REPO_CONCURRENCY,
18
+ HARD_MAX_REPO_DEPTH,
19
+ HARD_MAX_REPO_ENTRIES,
20
+ HARD_MAX_REPO_FILES,
21
+ HARD_MAX_REPO_RESULTS,
22
+ HARD_MAX_SEARCH_CONTEXT_LINES,
23
+ HARD_MAX_SEARCH_FILE_BYTES,
24
+ HARD_MAX_SEARCH_LINE_BYTES,
25
+ HARD_MAX_SEARCH_MATCHES,
26
+ HARD_MAX_SEARCH_PATTERN_BYTES,
27
+ HARD_MAX_SEARCH_SCAN_BYTES,
28
+ HARD_MAX_SEARCH_TIME_MS,
29
+ validateCodingLimit,
30
+ validateCodingLimitAllowZero,
31
+ } from "../limits.js";
32
+
33
+ export type RepoEntryKind = "file" | "directory" | "symlink" | "other";
34
+
35
+ export interface RepoListEntry {
36
+ readonly path: string;
37
+ readonly kind: RepoEntryKind;
38
+ readonly size?: number;
39
+ }
40
+
41
+ export interface RepositoryListResult {
42
+ readonly entries: readonly RepoListEntry[];
43
+ readonly truncated: boolean;
44
+ readonly truncatedBy: "results" | "entries" | "files" | "depth" | "time" | "abort" | null;
45
+ readonly scannedEntries: number;
46
+ readonly scannedFiles: number;
47
+ readonly nextOffset?: number;
48
+ readonly offset: number;
49
+ }
50
+
51
+ export interface RepositoryGlobResult {
52
+ readonly paths: readonly string[];
53
+ readonly truncated: boolean;
54
+ readonly truncatedBy: RepositoryListResult["truncatedBy"];
55
+ readonly scannedEntries: number;
56
+ readonly scannedFiles: number;
57
+ readonly nextOffset?: number;
58
+ readonly offset: number;
59
+ }
60
+
61
+ export type RepoSearchMode = "literal" | "indexed_literal" | "semantic";
62
+
63
+ export type RepoSearchOutputMode = "content" | "files_with_matches" | "count";
64
+
65
+ export interface RepositorySearchMatch {
66
+ readonly path: string;
67
+ readonly line: number;
68
+ readonly column: number;
69
+ readonly text: string;
70
+ readonly before: readonly string[];
71
+ readonly after: readonly string[];
72
+ /** Host index relevance in [0,1]; present only for indexed_literal/semantic results. */
73
+ readonly score?: number;
74
+ }
75
+
76
+ export interface RepositorySearchResult {
77
+ readonly matches: readonly RepositorySearchMatch[];
78
+ readonly truncated: boolean;
79
+ readonly truncatedBy: "matches" | "scan" | "file" | "entries" | "files" | "depth" | "time" | "abort" | "pattern" | "index" | null;
80
+ readonly scannedBytes: number;
81
+ readonly scannedFiles: number;
82
+ readonly scannedEntries: number;
83
+ readonly filesSkippedBinary: number;
84
+ readonly filesSkippedOversize: number;
85
+ /** Present only for indexed_literal/semantic queries: provenance and freshness. */
86
+ readonly indexed?: {
87
+ readonly mode: "indexed_literal" | "semantic";
88
+ readonly state: "empty" | "building" | "ready" | "stale" | "failed";
89
+ readonly sourceRevision?: string;
90
+ readonly updatedAt?: number;
91
+ };
92
+ /** True when match content came from a host index; index text is untrusted. */
93
+ readonly untrusted_index?: boolean;
94
+ }
95
+
96
+ export interface ResolvedRepositoryLimits {
97
+ readonly maxDepth: number;
98
+ readonly maxEntries: number;
99
+ readonly maxFiles: number;
100
+ readonly maxResults: number;
101
+ readonly maxConcurrency: number;
102
+ readonly maxScanBytes: number;
103
+ readonly maxFileBytes: number;
104
+ readonly maxMatches: number;
105
+ readonly maxPatternBytes: number;
106
+ readonly maxLineBytes: number;
107
+ readonly maxContextLines: number;
108
+ readonly maxTimeMs: number;
109
+ readonly binarySniffBytes: number;
110
+ readonly exclude: readonly string[];
111
+ }
112
+
113
+ export interface RepositoryLimitOptions {
114
+ readonly maxDepth?: number;
115
+ readonly maxEntries?: number;
116
+ readonly maxFiles?: number;
117
+ readonly maxResults?: number;
118
+ readonly maxConcurrency?: number;
119
+ readonly maxScanBytes?: number;
120
+ readonly maxFileBytes?: number;
121
+ readonly maxMatches?: number;
122
+ readonly maxPatternBytes?: number;
123
+ readonly maxLineBytes?: number;
124
+ readonly maxContextLines?: number;
125
+ readonly maxTimeMs?: number;
126
+ /** Basename denylist skipped during descent (default `.git`, `node_modules`, `dist`). */
127
+ readonly exclude?: readonly string[];
128
+ }
129
+
130
+ export interface RepositoryListRequest {
131
+ readonly root: string;
132
+ readonly path?: string;
133
+ readonly includeHidden?: boolean;
134
+ readonly exclude?: readonly string[];
135
+ readonly maxDepth?: number;
136
+ readonly maxResults?: number;
137
+ readonly offset?: number;
138
+ readonly signal?: AbortSignal;
139
+ readonly deadlineMs?: number;
140
+ }
141
+
142
+ export interface RepositorySearchRequest {
143
+ readonly root: string;
144
+ readonly query: string;
145
+ readonly path?: string;
146
+ readonly mode?: RepoSearchMode;
147
+ readonly outputMode?: RepoSearchOutputMode;
148
+ readonly caseSensitive?: boolean;
149
+ readonly includeHidden?: boolean;
150
+ readonly exclude?: readonly string[];
151
+ readonly context?: number;
152
+ readonly maxMatches?: number;
153
+ /** Only search files whose workspace-relative path matches this glob (`*`, `?`, `**`). */
154
+ readonly glob?: string;
155
+ /**
156
+ * Injected line matcher: `(fileText, context) => matches`. The `grep` tool passes a
157
+ * worker-backed regex matcher; a backend that does not honor it cannot run regex.
158
+ */
159
+ readonly matcher?: RepositoryFileMatcher;
160
+ readonly signal?: AbortSignal;
161
+ readonly deadlineMs?: number;
162
+ }
163
+
164
+ /** One matching line of one file, with its context window. */
165
+ export interface RepositoryLineMatch {
166
+ /** 1-based line number. */
167
+ readonly line: number;
168
+ /** 1-based column of the first match on the line. */
169
+ readonly column: number;
170
+ readonly text: string;
171
+ readonly before: readonly string[];
172
+ readonly after: readonly string[];
173
+ }
174
+
175
+ /** One file's text → its line matches. Sync (literal) or async (worker-backed regex). */
176
+ export type RepositoryFileMatcher = (
177
+ text: string,
178
+ context: number,
179
+ ) => Promise<readonly RepositoryLineMatch[]> | readonly RepositoryLineMatch[];
180
+
181
+ export interface RepositoryGlobRequest {
182
+ readonly root: string;
183
+ readonly pattern: string;
184
+ readonly path?: string;
185
+ readonly includeHidden?: boolean;
186
+ readonly exclude?: readonly string[];
187
+ readonly maxDepth?: number;
188
+ readonly maxResults?: number;
189
+ readonly offset?: number;
190
+ /** Bounded `{a,b}` expansion (default true; set `false` to reject braces; bounds in glob-match.ts). */
191
+ readonly braceExpansion?: boolean;
192
+ readonly signal?: AbortSignal;
193
+ readonly deadlineMs?: number;
194
+ }
195
+
196
+ export interface RepositoryOperations {
197
+ list(request: RepositoryListRequest): Promise<RepositoryListResult>;
198
+ search(request: RepositorySearchRequest): Promise<RepositorySearchResult>;
199
+ glob(request: RepositoryGlobRequest): Promise<RepositoryGlobResult>;
200
+ }
201
+
202
+ export const DEFAULT_REPO_EXCLUDE = Object.freeze([".git", "node_modules", "dist"]);
203
+
204
+ export class RepositoryError extends Error {
205
+ readonly code = "ERR_PRISM_REPOSITORY";
206
+ constructor(message: string) {
207
+ super(message);
208
+ this.name = "RepositoryError";
209
+ }
210
+ }
211
+
212
+ export function resolveRepositoryLimits(options?: RepositoryLimitOptions): ResolvedRepositoryLimits {
213
+ return {
214
+ maxDepth: validateCodingLimit("maxDepth", options?.maxDepth ?? DEFAULT_MAX_REPO_DEPTH, HARD_MAX_REPO_DEPTH),
215
+ maxEntries: validateCodingLimit("maxEntries", options?.maxEntries ?? DEFAULT_MAX_REPO_ENTRIES, HARD_MAX_REPO_ENTRIES),
216
+ maxFiles: validateCodingLimit("maxFiles", options?.maxFiles ?? DEFAULT_MAX_REPO_FILES, HARD_MAX_REPO_FILES),
217
+ maxResults: validateCodingLimit("maxResults", options?.maxResults ?? DEFAULT_MAX_REPO_RESULTS, HARD_MAX_REPO_RESULTS),
218
+ maxConcurrency: validateCodingLimit(
219
+ "maxConcurrency",
220
+ options?.maxConcurrency ?? DEFAULT_MAX_REPO_CONCURRENCY,
221
+ HARD_MAX_REPO_CONCURRENCY,
222
+ ),
223
+ maxScanBytes: validateCodingLimit("maxScanBytes", options?.maxScanBytes ?? DEFAULT_MAX_SEARCH_SCAN_BYTES, HARD_MAX_SEARCH_SCAN_BYTES),
224
+ maxFileBytes: validateCodingLimit("maxFileBytes", options?.maxFileBytes ?? DEFAULT_MAX_SEARCH_FILE_BYTES, HARD_MAX_SEARCH_FILE_BYTES),
225
+ maxMatches: validateCodingLimit("maxMatches", options?.maxMatches ?? DEFAULT_MAX_SEARCH_MATCHES, HARD_MAX_SEARCH_MATCHES),
226
+ maxPatternBytes: validateCodingLimit(
227
+ "maxPatternBytes",
228
+ options?.maxPatternBytes ?? DEFAULT_MAX_SEARCH_PATTERN_BYTES,
229
+ HARD_MAX_SEARCH_PATTERN_BYTES,
230
+ ),
231
+ maxLineBytes: validateCodingLimit("maxLineBytes", options?.maxLineBytes ?? DEFAULT_MAX_SEARCH_LINE_BYTES, HARD_MAX_SEARCH_LINE_BYTES),
232
+ maxContextLines: validateCodingLimitAllowZero(
233
+ "maxContextLines",
234
+ options?.maxContextLines ?? DEFAULT_MAX_SEARCH_CONTEXT_LINES,
235
+ HARD_MAX_SEARCH_CONTEXT_LINES,
236
+ ),
237
+ maxTimeMs: validateCodingLimit("maxTimeMs", options?.maxTimeMs ?? DEFAULT_MAX_SEARCH_TIME_MS, HARD_MAX_SEARCH_TIME_MS),
238
+ binarySniffBytes: DEFAULT_BINARY_SNIFF_BYTES,
239
+ exclude: Object.freeze([...(options?.exclude ?? DEFAULT_REPO_EXCLUDE)]),
240
+ };
241
+ }
242
+
243
+ /** Normalize a workspace-relative path to stable forward-slash form. */
@@ -0,0 +1,126 @@
1
+ /** Repository walk family (0.2.5 plan 025 Task 1 split).
2
+ * Moved verbatim from repository.ts; public surface unchanged behind the barrel. */
3
+ import type { Dir, Dirent } from "node:fs";
4
+ import { lstat, opendir } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { assertDeadline, assertNotAborted, isPathInsideRoot, kindFromDirent, shouldSkipName, toRepoRelative } from "./path.js";
7
+ import type { RepoListEntry } from "./types.js";
8
+ import { RepositoryError } from "./types.js";
9
+
10
+ export interface RepositoryWalkLimits {
11
+ maxDepth: number;
12
+ maxEntries: number;
13
+ maxFiles: number;
14
+ exclude: ReadonlySet<string>;
15
+ includeHidden: boolean;
16
+ signal?: AbortSignal;
17
+ deadlineAt?: number;
18
+ }
19
+
20
+ export type RepositoryWalkEvent =
21
+ | { type: "entry"; entry: RepoListEntry; absolutePath: string; depth: number }
22
+ | { type: "limit"; truncatedBy: "entries" | "files" | "depth" };
23
+
24
+ /** Injectable enumerator for list/search/glob. Default is the native opendir walker. */
25
+ export type RepositoryWalk = (rootReal: string, startAbsolute: string, limits: RepositoryWalkLimits) => AsyncGenerator<RepositoryWalkEvent>;
26
+
27
+ export async function* walkRepository(
28
+ rootReal: string,
29
+ startAbsolute: string,
30
+ limits: RepositoryWalkLimits,
31
+ ): AsyncGenerator<RepositoryWalkEvent> {
32
+ const queue: Array<{ absolute: string; relative: string; depth: number }> = [
33
+ {
34
+ absolute: startAbsolute,
35
+ relative: toRepoRelative(rootReal, startAbsolute),
36
+ depth: 0,
37
+ },
38
+ ];
39
+ let scannedEntries = 0;
40
+ let scannedFiles = 0;
41
+
42
+ while (queue.length > 0) {
43
+ assertNotAborted(limits.signal);
44
+ assertDeadline(limits.deadlineAt);
45
+ const current = queue.shift()!;
46
+ if (current.depth > limits.maxDepth) {
47
+ yield { type: "limit", truncatedBy: "depth" };
48
+ return;
49
+ }
50
+
51
+ let dir: Dir;
52
+ try {
53
+ dir = await opendir(current.absolute);
54
+ } catch (error) {
55
+ if (current.relative === "." || current.depth === 0) {
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ throw new RepositoryError(`cannot open directory: ${message}`);
58
+ }
59
+ continue;
60
+ }
61
+
62
+ const names: Dirent[] = [];
63
+ try {
64
+ for await (const dirent of dir) {
65
+ assertNotAborted(limits.signal);
66
+ assertDeadline(limits.deadlineAt);
67
+ names.push(dirent);
68
+ }
69
+ } finally {
70
+ await dir.close().catch(() => undefined);
71
+ }
72
+
73
+ names.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
74
+
75
+ for (const dirent of names) {
76
+ assertNotAborted(limits.signal);
77
+ assertDeadline(limits.deadlineAt);
78
+ if (shouldSkipName(dirent.name, limits.includeHidden, limits.exclude)) continue;
79
+
80
+ if (scannedEntries >= limits.maxEntries) {
81
+ yield { type: "limit", truncatedBy: "entries" };
82
+ return;
83
+ }
84
+ scannedEntries++;
85
+
86
+ const absolutePath = join(current.absolute, dirent.name);
87
+ if (!isPathInsideRoot(rootReal, absolutePath)) continue;
88
+
89
+ let kind = kindFromDirent(dirent);
90
+ let size: number | undefined;
91
+
92
+ // Re-check with lstat so we never follow symlinks for type/size.
93
+ try {
94
+ const st = await lstat(absolutePath);
95
+ if (st.isSymbolicLink()) kind = "symlink";
96
+ else if (st.isDirectory()) kind = "directory";
97
+ else if (st.isFile()) kind = "file";
98
+ else kind = "other";
99
+ if (kind === "file") size = st.size;
100
+ } catch {
101
+ continue;
102
+ }
103
+
104
+ if (kind === "file") {
105
+ if (scannedFiles >= limits.maxFiles) {
106
+ yield { type: "limit", truncatedBy: "files" };
107
+ return;
108
+ }
109
+ scannedFiles++;
110
+ }
111
+
112
+ const relativePath = current.relative === "." ? dirent.name : `${current.relative}/${dirent.name}`;
113
+ const entry: RepoListEntry = size === undefined ? { path: relativePath, kind } : { path: relativePath, kind, size };
114
+ yield { type: "entry", entry, absolutePath, depth: current.depth };
115
+
116
+ if (kind === "directory") {
117
+ const nextDepth = current.depth + 1;
118
+ if (nextDepth > limits.maxDepth) {
119
+ yield { type: "limit", truncatedBy: "depth" };
120
+ return;
121
+ }
122
+ queue.push({ absolute: absolutePath, relative: relativePath, depth: nextDepth });
123
+ }
124
+ }
125
+ }
126
+ }
@@ -0,0 +1,12 @@
1
+ /** Repository barrel (0.2.5 plan 025 Task 1 god-module split): re-exports the public
2
+ * repository surface from cohesive family modules so the import surface of
3
+ * `./repository.js` is unchanged (0.1.4 barrel precedent). */
4
+
5
+ export * from "./repository/glob.js";
6
+ export * from "./repository/indexed-search.js";
7
+ export * from "./repository/list.js";
8
+ export * from "./repository/operations.js";
9
+ export * from "./repository/path.js";
10
+ export * from "./repository/search.js";
11
+ export * from "./repository/types.js";
12
+ export * from "./repository/walk.js";
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Background processes for the coding tools (plan 189 Task 9).
3
+ *
4
+ * One lazy runtime over the process-session library: nothing is constructed until the first
5
+ * `shell({ background: true })` call, so a session that never backgrounds a command pays nothing.
6
+ * Every process is owned by the calling session (`owner: sessionId`), which gives the invariants
7
+ * for free: `get(id, sessionId)` refuses another session's id, `cancelOwned(sessionId)` kills
8
+ * exactly that session's processes without touching another's, and the listener in `src/index.ts`
9
+ * makes `session.closed` — also emitted for every open session by `agent.dispose()` — the kill
10
+ * switch.
11
+ *
12
+ * The process-session registry is the single spawner: this module never spawns a child itself.
13
+ */
14
+
15
+ import type { ExecutionPolicy } from "./execution-policy.js";
16
+ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, HARD_MAX_LINES, validateCodingLimit } from "./limits.js";
17
+ import { createProcessSessions } from "./process/sessions.js";
18
+ import type { ProcessSandboxBackend, ProcessSession, ProcessSessions } from "./process/types.js";
19
+ import { ProcessSessionError } from "./process/types.js";
20
+ import type { ShellConfig } from "./shell.js";
21
+ import { formatSize, truncateTail } from "./truncate.js";
22
+
23
+ /** Default live background processes per session. */
24
+ export const DEFAULT_MAX_BACKGROUND_PROCESSES = 4;
25
+ /** Hard cap on live background processes per session. */
26
+ export const HARD_MAX_BACKGROUND_PROCESSES = 16;
27
+ /** How long `shell({ background: true })` waits for first output before returning. */
28
+ export const BACKGROUND_FIRST_OUTPUT_MS = 1000;
29
+ /** Longest `shell_output({ waitSeconds })` may wait. */
30
+ export const HARD_MAX_BACKGROUND_WAIT_SECONDS = 60;
31
+
32
+ export interface BackgroundProcessOptions {
33
+ /** Per-session live process cap (default 4, hard 16). */
34
+ readonly maxProcesses?: number;
35
+ /** Per-process wall lifetime (default: the process-session default, 4 h). */
36
+ readonly maxLifetimeMs?: number;
37
+ /** Caps for the output `shell_output` returns (default 2000 lines / 50KB tail). */
38
+ readonly maxLines?: number;
39
+ readonly maxBytes?: number;
40
+ /** Resolved shell (`<shell> -c <command>`), so background commands run exactly like foreground ones. */
41
+ readonly shell?: ShellConfig;
42
+ /** The same adapter the shell uses; absent means native spawn on the host. */
43
+ readonly sandbox?: ProcessSandboxBackend;
44
+ readonly policy?: ExecutionPolicy;
45
+ }
46
+
47
+ export interface BackgroundStartInput {
48
+ /** The command as the shell tool would run it (after `commandPrefix`, `spawnHook`, policy rewrite). */
49
+ readonly command: string;
50
+ readonly cwd: string;
51
+ readonly env: NodeJS.ProcessEnv;
52
+ readonly sessionId: string;
53
+ }
54
+
55
+ export interface BackgroundStarted {
56
+ readonly id: string;
57
+ /** Output that arrived within {@link BACKGROUND_FIRST_OUTPUT_MS}, formatted with the shell's caps. */
58
+ readonly output: string;
59
+ readonly running: boolean;
60
+ readonly exitCode: number | null;
61
+ }
62
+
63
+ export interface BackgroundReadInput {
64
+ readonly id: string;
65
+ readonly sessionId: string;
66
+ readonly waitSeconds: number;
67
+ readonly kill: boolean;
68
+ }
69
+
70
+ export interface BackgroundRead {
71
+ readonly id: string;
72
+ /** Output produced since the previous `shell_output` call for this id. */
73
+ readonly output: string;
74
+ readonly running: boolean;
75
+ readonly exitCode: number | null;
76
+ readonly state: string;
77
+ }
78
+
79
+ export interface BackgroundProcesses {
80
+ readonly maxProcesses: number;
81
+ start(input: BackgroundStartInput): Promise<BackgroundStarted>;
82
+ read(input: BackgroundReadInput): Promise<BackgroundRead>;
83
+ /** Kill every process this session owns. Idempotent; also runs on `session.closed`. */
84
+ killSession(sessionId: string): Promise<void>;
85
+ /** Live (starting/running) processes owned by `sessionId`. */
86
+ liveCount(sessionId: string): number;
87
+ }
88
+
89
+ interface Tracked {
90
+ readonly handle: ProcessSession;
91
+ readonly cwd: string;
92
+ cursor: number;
93
+ }
94
+
95
+ export function createBackgroundProcesses(options: BackgroundProcessOptions = {}): BackgroundProcesses {
96
+ const maxProcesses = validateCodingLimit(
97
+ "maxProcesses",
98
+ options.maxProcesses ?? DEFAULT_MAX_BACKGROUND_PROCESSES,
99
+ HARD_MAX_BACKGROUND_PROCESSES,
100
+ );
101
+ const maxLines = validateCodingLimit("maxLines", options.maxLines ?? DEFAULT_MAX_LINES, HARD_MAX_LINES);
102
+ const shell: ShellConfig = options.shell ?? { shell: "/bin/sh", args: ["-c"] };
103
+ const tracked = new Map<string, Tracked>();
104
+ // One registry per workspace root: the session library contains every cwd under its own root,
105
+ // and a worktree-bound session runs in a directory outside the package root.
106
+ const registries = new Map<string, ProcessSessions>();
107
+
108
+ const sessions = (cwd: string): ProcessSessions => {
109
+ const existing = registries.get(cwd);
110
+ if (existing !== undefined) return existing;
111
+ const created = createProcessSessions({
112
+ cwd,
113
+ ...(options.sandbox !== undefined ? { sandbox: options.sandbox } : {}),
114
+ ...(options.policy !== undefined ? { policy: options.policy } : {}),
115
+ // The per-session cap below is the real bound; this only bounds the total.
116
+ limits: {
117
+ maxSessions: HARD_MAX_BACKGROUND_PROCESSES * 2,
118
+ ...(options.maxLifetimeMs !== undefined ? { maxLifetimeMs: options.maxLifetimeMs } : {}),
119
+ },
120
+ });
121
+ registries.set(cwd, created);
122
+ return created;
123
+ };
124
+
125
+ /** The registries this session actually owns processes in. */
126
+ const registriesOf = (sessionId: string): ProcessSessions[] => [...new Set(owned(sessionId).map((entry) => sessions(entry.cwd)))];
127
+
128
+ const owned = (sessionId: string): Tracked[] => [...tracked.values()].filter((e) => e.handle.owner === sessionId);
129
+ const alive = (handle: ProcessSession): boolean => handle.state === "starting" || handle.state === "running";
130
+ const live = (sessionId: string): Tracked[] => owned(sessionId).filter((e) => alive(e.handle));
131
+
132
+ const exitOf = async (handle: ProcessSession): Promise<number | null> => {
133
+ if (alive(handle)) return null;
134
+ // Resolves immediately for a finished record; never a fabricated code for a live one.
135
+ return (await handle.wait()).exitCode;
136
+ };
137
+
138
+ const format = (data: string): string => {
139
+ if (data.length === 0) return "";
140
+ const truncation = truncateTail(data, { maxLines, maxBytes: options.maxBytes ?? DEFAULT_MAX_BYTES });
141
+ return truncation.truncated
142
+ ? `${truncation.content}\n\n[showing the last ${formatSize(truncation.outputBytes)} of ${truncation.totalLines} new line(s)]`
143
+ : truncation.content;
144
+ };
145
+
146
+ /** Keep at most 32 finished records per session so a chatty session cannot grow the map. */
147
+ const prune = (sessionId: string): void => {
148
+ const finished = owned(sessionId).filter((e) => !alive(e.handle));
149
+ for (const entry of finished.slice(0, Math.max(0, finished.length - 32))) tracked.delete(entry.handle.id);
150
+ };
151
+
152
+ /** One chunk from the cursor; the chunk cap keeps each read bounded. */
153
+ const readOnce = async (entry: Tracked): Promise<string> => {
154
+ const chunk = await entry.handle.output({ cursor: entry.cursor });
155
+ entry.cursor = chunk.cursor;
156
+ return chunk.data;
157
+ };
158
+
159
+ /**
160
+ * Everything that accumulated since the last call, in whole chunks, so one `shell_output` sees a
161
+ * burst instead of only its first chunk. Bounded at twice the format cap: anything past that is
162
+ * truncated away by `format` anyway.
163
+ */
164
+ const drainLimitBytes = (options.maxBytes ?? DEFAULT_MAX_BYTES) * 2;
165
+ const readAvailable = async (entry: Tracked): Promise<string> => {
166
+ let data = "";
167
+ while (data.length < drainLimitBytes) {
168
+ const chunk = await readOnce(entry);
169
+ data += chunk;
170
+ if (chunk.length === 0) break;
171
+ }
172
+ return data;
173
+ };
174
+
175
+ /**
176
+ * Waits for the next publication (`ProcessSession.onOutput`: a chunk or the terminal transition)
177
+ * or `ms`, never polls. Returns what accumulated in the meantime.
178
+ */
179
+ const waitFor = async (entry: Tracked, ms: number): Promise<string> => {
180
+ let stopped = false;
181
+ let timer: NodeJS.Timeout | undefined;
182
+ let unsubscribe = (): void => undefined;
183
+ const stop = (): void => {
184
+ if (stopped) return;
185
+ stopped = true;
186
+ if (timer !== undefined) clearTimeout(timer);
187
+ unsubscribe();
188
+ };
189
+ const signalled = new Promise<void>((resolve) => {
190
+ // Subscribe before the first read, so a chunk published in between cannot be missed.
191
+ unsubscribe = entry.handle.onOutput(() => {
192
+ stop();
193
+ resolve();
194
+ });
195
+ timer = setTimeout(() => {
196
+ stop();
197
+ resolve();
198
+ }, ms);
199
+ });
200
+ try {
201
+ const immediate = await readAvailable(entry);
202
+ if (immediate.length > 0 || !alive(entry.handle)) return immediate;
203
+ await signalled;
204
+ return await readAvailable(entry);
205
+ } finally {
206
+ stop();
207
+ }
208
+ };
209
+
210
+ return {
211
+ maxProcesses,
212
+
213
+ async start(input) {
214
+ const running = live(input.sessionId).length;
215
+ if (running >= maxProcesses) {
216
+ throw new ProcessSessionError(
217
+ "ERR_PRISM_PROCESS_LIMIT",
218
+ `session already has ${running} background process(es) (max ${maxProcesses}); call shell_output({ id, kill: true }) first`,
219
+ );
220
+ }
221
+ const env: Record<string, string> = {};
222
+ for (const [name, value] of Object.entries(input.env)) if (value !== undefined) env[name] = value;
223
+ const handle = await sessions(input.cwd).start({
224
+ command: shell.shell,
225
+ args: [...shell.args, input.command],
226
+ cwd: input.cwd,
227
+ env,
228
+ owner: input.sessionId,
229
+ });
230
+ const entry: Tracked = { handle, cwd: input.cwd, cursor: 0 };
231
+ tracked.set(handle.id, entry);
232
+ prune(input.sessionId);
233
+ const output = format(await waitFor(entry, BACKGROUND_FIRST_OUTPUT_MS));
234
+ return { id: handle.id, output, running: alive(handle), exitCode: await exitOf(handle) };
235
+ },
236
+
237
+ async read(input) {
238
+ const entry = tracked.get(input.id);
239
+ if (entry === undefined) {
240
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `unknown process: ${input.id}`);
241
+ }
242
+ // Throws ERR_PRISM_PROCESS_OWNERSHIP for another session's id; never leaks its output.
243
+ const handle = sessions(entry.cwd).get(input.id, input.sessionId);
244
+ if (input.kill && alive(handle)) await handle.kill();
245
+ const output = format(
246
+ input.waitSeconds > 0
247
+ ? await waitFor(entry, Math.min(input.waitSeconds, HARD_MAX_BACKGROUND_WAIT_SECONDS) * 1000)
248
+ : await readAvailable(entry),
249
+ );
250
+ return { id: handle.id, output, running: alive(handle), exitCode: await exitOf(handle), state: handle.state };
251
+ },
252
+
253
+ async killSession(sessionId) {
254
+ const entries = owned(sessionId);
255
+ if (entries.length === 0) return;
256
+ for (const registry of registriesOf(sessionId)) await registry.cancelOwned(sessionId);
257
+ for (const [id, entry] of tracked) if (entry.handle.owner === sessionId) tracked.delete(id);
258
+ },
259
+
260
+ liveCount(sessionId) {
261
+ return live(sessionId).length;
262
+ },
263
+ };
264
+ }