@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,613 @@
1
+ /**
2
+ * Spawn / attach a managed process session (native, sandbox, or PTY).
3
+ */
4
+ import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
5
+ import { randomBytes } from "node:crypto";
6
+
7
+ import { buildChildEnv, DEFAULT_CHILD_ENV_INHERIT } from "../env.js";
8
+ /**
9
+ * Start-budget for host PTY attachment (frozen cap). Timeout fails closed with
10
+ * ERR_PRISM_PROCESS_PTY_LIMIT; the underlying promise result is discarded.
11
+ */
12
+ import { assertExecutionAllowed, ExecutionDeniedError } from "../execution-policy.js";
13
+ import { OutputAccumulator } from "../output-accumulator.js";
14
+ import { resolveToCwd } from "../path-utils.js";
15
+ import {
16
+ acquireRecordLease,
17
+ buildProcessRecoveryRecord,
18
+ deleteProcessRecoveryRecord,
19
+ ProcessRecoveryError,
20
+ releaseRecordLease,
21
+ saveProcessRecoveryRecord,
22
+ validateBackendRef,
23
+ } from "./recovery.js";
24
+ import {
25
+ assertNotDisposed,
26
+ commandFingerprint,
27
+ durableSeams,
28
+ emit,
29
+ isInsideRoot,
30
+ notifyOutput,
31
+ nowIso,
32
+ resolveTerminal,
33
+ type SessionRecord,
34
+ type SessionsHost,
35
+ } from "./sessions-host.js";
36
+ import { persistTransition } from "./sessions-monitor.js";
37
+ import { checkSandboxAlive, sweepExpired, terminateRecord } from "./sessions-teardown.js";
38
+ import {
39
+ type ProcessExitResult,
40
+ type ProcessSession,
41
+ ProcessSessionError,
42
+ type ProcessSessionMetadata,
43
+ type ProcessStartRequest,
44
+ } from "./types.js";
45
+
46
+ function withPtyAttachTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
47
+ return new Promise<T>((resolve, reject) => {
48
+ const timer = setTimeout(
49
+ () => reject(new ProcessSessionError("ERR_PRISM_PROCESS_PTY_LIMIT", `PTY attach timed out (${timeoutMs}ms)`)),
50
+ timeoutMs,
51
+ );
52
+ promise.then(
53
+ (value) => {
54
+ clearTimeout(timer);
55
+ resolve(value);
56
+ },
57
+ (error) => {
58
+ clearTimeout(timer);
59
+ reject(error);
60
+ },
61
+ );
62
+ });
63
+ }
64
+
65
+ export async function assertPolicy(
66
+ host: SessionsHost,
67
+ operation: string,
68
+ command: string,
69
+ args: readonly string[],
70
+ cwd: string,
71
+ owner: string,
72
+ ): Promise<string> {
73
+ try {
74
+ await assertExecutionAllowed(host.policy, {
75
+ kind: "shell",
76
+ operation,
77
+ command,
78
+ paths: [cwd],
79
+ risk: "high",
80
+ metadata: { args: [...args], owner },
81
+ });
82
+ return "allow";
83
+ } catch (error) {
84
+ const message =
85
+ error instanceof ExecutionDeniedError
86
+ ? (error.decision.reason ?? error.message)
87
+ : error instanceof Error
88
+ ? error.message
89
+ : String(error);
90
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_POLICY", message);
91
+ }
92
+ }
93
+
94
+ export function makeHandle(host: SessionsHost, record: SessionRecord): ProcessSession {
95
+ const assertAttached = (): void => {
96
+ if (record.state === "released") {
97
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "session released");
98
+ }
99
+ };
100
+
101
+ const handle: ProcessSession = {
102
+ get id() {
103
+ return record.id;
104
+ },
105
+ get state() {
106
+ sweepExpired(host);
107
+ void checkSandboxAlive(host);
108
+ return record.state;
109
+ },
110
+ get owner() {
111
+ return record.owner;
112
+ },
113
+ metadata(): ProcessSessionMetadata {
114
+ sweepExpired(host);
115
+ return {
116
+ id: record.id,
117
+ commandFingerprint: record.commandFingerprint,
118
+ owner: record.owner,
119
+ workspace: record.workspace,
120
+ policyDecision: record.policyDecision,
121
+ startedAt: record.startedAt,
122
+ exitedAt: record.exitedAt,
123
+ state: record.state,
124
+ releaseOnCancel: record.releaseOnCancel,
125
+ pty: record.ptyTerminal !== undefined,
126
+ terminal: record.ptyTerminal,
127
+ ptyBackendMetadata: record.pty?.metadata,
128
+ };
129
+ },
130
+ onOutput(listener) {
131
+ record.outputListeners.add(listener);
132
+ return () => {
133
+ record.outputListeners.delete(listener);
134
+ };
135
+ },
136
+ async output(request) {
137
+ assertAttached();
138
+ sweepExpired(host);
139
+ await checkSandboxAlive(host);
140
+ const cursor = request?.cursor ?? 0;
141
+ const maxBytes = request?.maxBytes ?? host.limits.maxOutputChunkBytes;
142
+ if (maxBytes > host.limits.maxOutputChunkBytes) {
143
+ throw new ProcessSessionError(
144
+ "ERR_PRISM_PROCESS_LIMIT",
145
+ `maxBytes exceeds maxOutputChunkBytes (${host.limits.maxOutputChunkBytes})`,
146
+ );
147
+ }
148
+ const { data, nextCursor } = record.accumulator.readRaw(cursor, maxBytes);
149
+ const eof = record.state !== "running" && record.state !== "starting" && nextCursor >= record.accumulator.getTotalRawBytes();
150
+ return { data: data.toString("utf8"), cursor: nextCursor, eof };
151
+ },
152
+ async input(data) {
153
+ assertAttached();
154
+ sweepExpired(host);
155
+ await checkSandboxAlive(host);
156
+ if (record.state !== "running") {
157
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot input in state ${record.state}`);
158
+ }
159
+ await assertPolicy(host, "process_input", record.command, record.args, record.workspace, record.owner);
160
+ const buf = typeof data === "string" ? Buffer.from(data, "utf8") : Buffer.from(data);
161
+ if (buf.byteLength > host.limits.maxInputBytes) {
162
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_LIMIT", `input exceeds maxInputBytes (${host.limits.maxInputBytes})`);
163
+ }
164
+ if (record.pty) {
165
+ if (buf.includes(0)) {
166
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_POLICY", "NUL bytes are not permitted in PTY input");
167
+ }
168
+ await record.pty.write(buf);
169
+ return;
170
+ }
171
+ if (record.backend) {
172
+ await record.backend.write(buf);
173
+ return;
174
+ }
175
+ if (record.stdinClosed || !record.child?.stdin.writable) {
176
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "stdin closed");
177
+ }
178
+ const child = record.child;
179
+ if (!child) throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "stdin closed");
180
+ await new Promise<void>((resolveWrite, rejectWrite) => {
181
+ child.stdin.write(buf, (err) => (err ? rejectWrite(err) : resolveWrite()));
182
+ });
183
+ },
184
+ async wait(waitOptions) {
185
+ assertAttached();
186
+ sweepExpired(host);
187
+ await checkSandboxAlive(host);
188
+ if (record.state !== "running" && record.state !== "starting") {
189
+ return { exitCode: record.exitCode, state: record.state };
190
+ }
191
+ return await new Promise<ProcessExitResult>((resolveWait, rejectWait) => {
192
+ const onDone = (result: ProcessExitResult) => {
193
+ cleanup();
194
+ resolveWait(result);
195
+ };
196
+ const onAbort = () => {
197
+ cleanup();
198
+ rejectWait(new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "wait aborted"));
199
+ };
200
+ let timer: NodeJS.Timeout | undefined;
201
+ const cleanup = () => {
202
+ if (timer) clearTimeout(timer);
203
+ waitOptions?.signal?.removeEventListener("abort", onAbort);
204
+ const idx = record.waiters.indexOf(onDone);
205
+ if (idx >= 0) record.waiters.splice(idx, 1);
206
+ };
207
+ record.waiters.push(onDone);
208
+ if (waitOptions?.timeoutMs !== undefined) {
209
+ timer = setTimeout(() => {
210
+ cleanup();
211
+ rejectWait(new ProcessSessionError("ERR_PRISM_PROCESS_LIMIT", "wait timed out"));
212
+ }, waitOptions.timeoutMs);
213
+ }
214
+ if (waitOptions?.signal) {
215
+ if (waitOptions.signal.aborted) onAbort();
216
+ else waitOptions.signal.addEventListener("abort", onAbort, { once: true });
217
+ }
218
+ });
219
+ },
220
+ async signal(name) {
221
+ assertAttached();
222
+ sweepExpired(host);
223
+ await checkSandboxAlive(host);
224
+ if (record.state !== "running") {
225
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot signal in state ${record.state}`);
226
+ }
227
+ await assertPolicy(host, "process_signal", record.command, record.args, record.workspace, record.owner);
228
+ if (record.pty) {
229
+ await record.pty.signal(name);
230
+ return;
231
+ }
232
+ if (record.backend) {
233
+ await record.backend.signal(name);
234
+ return;
235
+ }
236
+ if (!record.pid) throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "process has no pid");
237
+ try {
238
+ process.kill(-record.pid, name);
239
+ } catch {
240
+ try {
241
+ process.kill(record.pid, name);
242
+ } catch (error) {
243
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", error instanceof Error ? error.message : String(error));
244
+ }
245
+ }
246
+ },
247
+ async kill() {
248
+ assertAttached();
249
+ sweepExpired(host);
250
+ await checkSandboxAlive(host);
251
+ if (record.state !== "running" && record.state !== "starting") {
252
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot kill in state ${record.state}`);
253
+ }
254
+ await assertPolicy(host, "process_kill", record.command, record.args, record.workspace, record.owner);
255
+ const ptyHandle = record.pty;
256
+ const backendHandle = record.backend;
257
+ // Mark terminal synchronously FIRST: a backend kill may resolve the
258
+ // wait() promise, whose handler must not race into a fabricated
259
+ // 'exited' before the kill is recorded. The explicit call below is
260
+ // the existing parity double-kill; its result is ignored.
261
+ if (ptyHandle || backendHandle) {
262
+ terminateRecord(host, record, "killed", null);
263
+ if (ptyHandle) {
264
+ try {
265
+ await ptyHandle.kill();
266
+ } catch {
267
+ // still marked killed
268
+ }
269
+ } else if (backendHandle) {
270
+ try {
271
+ await backendHandle.kill();
272
+ } catch {
273
+ // still marked killed
274
+ }
275
+ }
276
+ return;
277
+ }
278
+ terminateRecord(host, record, "killed", null);
279
+ },
280
+ async release() {
281
+ assertAttached();
282
+ sweepExpired(host);
283
+ if (record.state !== "running" && record.state !== "starting") {
284
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot release in state ${record.state}`);
285
+ }
286
+ const ptyRelease = record.pty;
287
+ const backendRelease = record.backend;
288
+ if (ptyRelease || backendRelease) {
289
+ terminateRecord(host, record, "released", null);
290
+ if (ptyRelease) {
291
+ try {
292
+ await ptyRelease.release();
293
+ } catch {
294
+ // still marked released
295
+ }
296
+ } else if (backendRelease) {
297
+ try {
298
+ await backendRelease.release();
299
+ } catch {
300
+ // still marked released
301
+ }
302
+ }
303
+ return;
304
+ }
305
+ terminateRecord(host, record, "released", null);
306
+ },
307
+ };
308
+ if (host.ptyResizeCapable) {
309
+ handle.resize = async (dimensions) => {
310
+ assertAttached();
311
+ sweepExpired(host);
312
+ await checkSandboxAlive(host);
313
+ if (record.state !== "running") {
314
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", `cannot resize in state ${record.state}`);
315
+ }
316
+ const columns = dimensions.columns;
317
+ const rows = dimensions.rows;
318
+ if (!Number.isSafeInteger(columns) || columns < 1 || columns > host.limits.maxTerminalColumns) {
319
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_LIMIT", `columns must be 1..${host.limits.maxTerminalColumns}`);
320
+ }
321
+ if (!Number.isSafeInteger(rows) || rows < 1 || rows > host.limits.maxTerminalRows) {
322
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_LIMIT", `rows must be 1..${host.limits.maxTerminalRows}`);
323
+ }
324
+ const pty = record.pty;
325
+ if (!pty || typeof pty.resize !== "function") {
326
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "session does not support resize");
327
+ }
328
+ const terminal = record.ptyTerminal;
329
+ if (!terminal) throw new ProcessSessionError("ERR_PRISM_PROCESS_STATE", "session has no terminal geometry");
330
+ const now = Date.now();
331
+ record.ptyResizeAt = record.ptyResizeAt.filter((t) => now - t < 60_000);
332
+ if (record.ptyResizeAt.length >= host.limits.maxTerminalResizesPerMinute) {
333
+ throw new ProcessSessionError(
334
+ "ERR_PRISM_PROCESS_PTY_LIMIT",
335
+ `resize rate exceeds maxTerminalResizesPerMinute (${host.limits.maxTerminalResizesPerMinute})`,
336
+ );
337
+ }
338
+ await assertPolicy(host, "process_resize", record.command, record.args, record.workspace, record.owner);
339
+ record.ptyResizeAt.push(now);
340
+ try {
341
+ await pty.resize({ columns, rows });
342
+ } catch {
343
+ terminateRecord(host, record, "unknown", null);
344
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_BACKEND", "PTY backend resize failed");
345
+ }
346
+ record.ptyTerminal = { ...terminal, columns, rows };
347
+ };
348
+ }
349
+ return handle;
350
+ }
351
+
352
+ export async function startSession(host: SessionsHost, request: ProcessStartRequest): Promise<ProcessSession> {
353
+ assertNotDisposed(host);
354
+ sweepExpired(host);
355
+ await checkSandboxAlive(host);
356
+
357
+ if (request.pty && (!host.ptyBackend || typeof host.ptyBackend.startPty !== "function")) {
358
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_UNSUPPORTED", "PTY not supported on this host");
359
+ }
360
+ if (!request.command || typeof request.command !== "string") {
361
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_POLICY", "command required");
362
+ }
363
+ const ptyTerminal = request.pty ? resolveTerminal(host, request.terminal) : undefined;
364
+
365
+ if (host.sandbox && typeof host.sandbox.startProcess !== "function") {
366
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_UNSUPPORTED", "sandbox adapter does not support startProcess");
367
+ }
368
+ if (host.sandboxLost) {
369
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_UNSUPPORTED", "sandbox lost; cannot start process");
370
+ }
371
+
372
+ const active = [...host.sessions.values()].filter((s) => s.state === "running" || s.state === "starting").length;
373
+ if (active >= host.limits.maxSessions) {
374
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_LIMIT", `maxSessions (${host.limits.maxSessions}) reached`);
375
+ }
376
+
377
+ const cwdInput = request.cwd ?? ".";
378
+ const cwd = resolveToCwd(cwdInput, host.workspace);
379
+ if (!isInsideRoot(host.workspace, cwd)) {
380
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_POLICY", "cwd escapes workspace root");
381
+ }
382
+
383
+ const args = request.args ?? [];
384
+ const owner = request.owner ?? host.defaultOwner;
385
+ const lifetimeMs = request.lifetimeMs ?? host.limits.maxLifetimeMs;
386
+ if (lifetimeMs > host.limits.maxLifetimeMs) {
387
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_LIMIT", `lifetimeMs exceeds maxLifetimeMs (${host.limits.maxLifetimeMs})`);
388
+ }
389
+
390
+ const policyDecision = await assertPolicy(host, "process_start", request.command, args, cwd, owner);
391
+
392
+ const id = `proc_${randomBytes(8).toString("hex")}`;
393
+ const accumulator = new OutputAccumulator({
394
+ maxBytes: host.limits.maxOutputChunkBytes,
395
+ maxLines: 100_000,
396
+ maxTotalOutputBytes: host.limits.maxTotalOutputBytes,
397
+ tempFilePrefix: "prism-proc",
398
+ });
399
+
400
+ const record: SessionRecord = {
401
+ id,
402
+ owner,
403
+ workspace: host.workspace,
404
+ command: request.command,
405
+ args,
406
+ commandFingerprint: commandFingerprint(request.command, args),
407
+ policyDecision,
408
+ startedAt: nowIso(),
409
+ releaseOnCancel: request.releaseOnCancel === true,
410
+ expiresAt: Date.now() + lifetimeMs,
411
+ state: "starting",
412
+ exitCode: null,
413
+ ptyTerminal,
414
+ ptyResizeAt: [],
415
+ accumulator,
416
+ waiters: [],
417
+ outputListeners: new Set(),
418
+ stdinClosed: false,
419
+ handle: null as unknown as ProcessSession,
420
+ recoveryFencingToken: 0,
421
+ recoveryVersion: 0,
422
+ };
423
+ record.handle = makeHandle(host, record);
424
+ host.sessions.set(id, record);
425
+
426
+ // Durable recovery: intent is persisted BEFORE spawn. The per-record lease
427
+ // fences replica coordination; the fencing token is stored in the record
428
+ // so every later CAS write is monotonic. On any write/fence failure the
429
+ // start fails closed (record removed, no half-durable process).
430
+ let recoveryLease: { token: string } | undefined;
431
+ if (host.durable) {
432
+ const seams = durableSeams(host);
433
+ const lease = await acquireRecordLease({
434
+ leases: seams.leases,
435
+ id,
436
+ ownerId: seams.ownerId,
437
+ ttlMs: host.recoveryLimits.leaseTtlMs,
438
+ ownership: host.ownership,
439
+ signal: request.signal,
440
+ });
441
+ if (!lease) {
442
+ host.sessions.delete(id);
443
+ throw new ProcessRecoveryError("ERR_PRISM_RECOVERY_FENCE", "recovery lease for new session is held by another replica");
444
+ }
445
+ recoveryLease = { token: lease.token };
446
+ record.recoveryLeaseToken = lease.token;
447
+ record.recoveryFencingToken = lease.fencingToken;
448
+ const intent = buildProcessRecoveryRecord({
449
+ id,
450
+ owner,
451
+ workspace: host.workspace,
452
+ command: request.command,
453
+ args,
454
+ commandFingerprint: commandFingerprint(request.command, args),
455
+ policyDecision,
456
+ startedAt: record.startedAt,
457
+ state: "starting",
458
+ exitCode: null,
459
+ releaseOnCancel: record.releaseOnCancel,
460
+ expiresAt: record.expiresAt,
461
+ ...(ptyTerminal !== undefined ? { pty: ptyTerminal } : {}),
462
+ fencingToken: lease.fencingToken,
463
+ });
464
+ const saved = await saveProcessRecoveryRecord({
465
+ checkpoints: seams.checkpoints,
466
+ record: intent,
467
+ expectedVersion: 0,
468
+ version: 1,
469
+ ownership: host.ownership,
470
+ signal: request.signal,
471
+ });
472
+ record.recoveryVersion = saved.version;
473
+ }
474
+
475
+ const onData = (buf: Buffer) => {
476
+ // PTY hosts can deliver trailing bytes after the session already went
477
+ // terminal (host wait() resolving ahead of the last pty drain); ignore
478
+ // them instead of appending to a finished accumulator.
479
+ if (record.state === "running" || record.state === "starting") {
480
+ accumulator.append(buf);
481
+ notifyOutput(record);
482
+ }
483
+ };
484
+
485
+ try {
486
+ if (request.pty) {
487
+ const startPty = host.ptyBackend?.startPty;
488
+ if (!startPty || !ptyTerminal) {
489
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_UNSUPPORTED", "PTY not supported on this host");
490
+ }
491
+ try {
492
+ const handle = await withPtyAttachTimeout(
493
+ startPty({
494
+ file: request.command,
495
+ args,
496
+ cwd,
497
+ env: request.env,
498
+ columns: ptyTerminal.columns,
499
+ rows: ptyTerminal.rows,
500
+ term: ptyTerminal.term,
501
+ onData,
502
+ }),
503
+ host.limits.maxPtyAttachTimeoutMs,
504
+ );
505
+ const metadataJson = handle.metadata !== undefined ? JSON.stringify(handle.metadata) : undefined;
506
+ if (metadataJson !== undefined && Buffer.byteLength(metadataJson, "utf8") > host.limits.maxPtyBackendMetadataBytes) {
507
+ throw new ProcessSessionError(
508
+ "ERR_PRISM_PROCESS_PTY_LIMIT",
509
+ `pty backend metadata exceeds maxPtyBackendMetadataBytes (${host.limits.maxPtyBackendMetadataBytes})`,
510
+ );
511
+ }
512
+ if (handle.ref !== undefined) record.backendRef = validateBackendRef(handle.ref, host.recoveryLimits);
513
+ record.pty = handle;
514
+ record.state = "running";
515
+ persistTransition(host, record);
516
+ void handle
517
+ .wait()
518
+ .then((result) => {
519
+ if (record.state !== "running" && record.state !== "starting") return;
520
+ terminateRecord(host, record, "exited", result.exitCode);
521
+ })
522
+ .catch(() => {
523
+ if (record.state !== "running" && record.state !== "starting") return;
524
+ terminateRecord(host, record, "unknown", null);
525
+ });
526
+ } catch (error) {
527
+ host.sessions.delete(id);
528
+ if (error instanceof ProcessSessionError) throw error;
529
+ if (error instanceof ProcessRecoveryError) throw error;
530
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_PTY_BACKEND", "PTY backend failed to start");
531
+ }
532
+ } else if (host.sandbox?.startProcess) {
533
+ const handle = await host.sandbox.startProcess({
534
+ file: request.command,
535
+ args,
536
+ cwd,
537
+ env: request.env,
538
+ onData,
539
+ });
540
+ if (handle.ref !== undefined) record.backendRef = validateBackendRef(handle.ref, host.recoveryLimits);
541
+ record.backend = handle;
542
+ record.state = "running";
543
+ persistTransition(host, record);
544
+ void handle
545
+ .wait()
546
+ .then((result) => {
547
+ if (record.state !== "running" && record.state !== "starting") return;
548
+ terminateRecord(host, record, "exited", result.exitCode);
549
+ })
550
+ .catch(() => {
551
+ if (record.state !== "running" && record.state !== "starting") return;
552
+ terminateRecord(host, record, "unknown", null);
553
+ });
554
+ } else {
555
+ const env = buildChildEnv({ inherit: DEFAULT_CHILD_ENV_INHERIT, set: request.env }); // allow-list; never pass process.env through
556
+ const child = spawn(request.command, [...args], {
557
+ cwd,
558
+ env,
559
+ stdio: ["pipe", "pipe", "pipe"],
560
+ detached: process.platform !== "win32",
561
+ windowsHide: true,
562
+ }) as ChildProcessWithoutNullStreams;
563
+ record.child = child;
564
+ record.pid = child.pid;
565
+ record.state = "running";
566
+ child.stdout.on("data", onData);
567
+ child.stderr.on("data", onData);
568
+ child.stdin.on("close", () => {
569
+ record.stdinClosed = true;
570
+ });
571
+ child.on("error", () => {
572
+ terminateRecord(host, record, "unknown", null);
573
+ });
574
+ child.on("exit", (code, signal) => {
575
+ if (record.state !== "running" && record.state !== "starting") return;
576
+ if (signal) terminateRecord(host, record, "killed", code);
577
+ else terminateRecord(host, record, "exited", code);
578
+ });
579
+ persistTransition(host, record);
580
+ }
581
+ } catch (error) {
582
+ host.sessions.delete(id);
583
+ if (host.durable) {
584
+ // No half-durable process: drop the intent/running record and release
585
+ // the recovery lease. The host store is authoritative.
586
+ const seams = durableSeams(host);
587
+ await deleteProcessRecoveryRecord({ checkpoints: seams.checkpoints, id, ownership: host.ownership, signal: request.signal });
588
+ if (recoveryLease) {
589
+ await releaseRecordLease({
590
+ leases: seams.leases,
591
+ id,
592
+ ownerId: seams.ownerId,
593
+ token: recoveryLease.token,
594
+ ownership: host.ownership,
595
+ signal: request.signal,
596
+ });
597
+ }
598
+ }
599
+ if (error instanceof ProcessSessionError) throw error;
600
+ if (error instanceof ProcessRecoveryError) throw error;
601
+ throw new ProcessSessionError("ERR_PRISM_PROCESS_UNSUPPORTED", error instanceof Error ? error.message : String(error));
602
+ }
603
+
604
+ emit(host, {
605
+ type: "process_started",
606
+ sessionId: id,
607
+ processId: record.pid !== undefined ? String(record.pid) : id,
608
+ owner,
609
+ at: record.startedAt,
610
+ });
611
+
612
+ return record.handle;
613
+ }