@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,110 @@
1
+ /**
2
+ * The `coding.targets` service: for one of this package's tools, the absolute paths a call
3
+ * touches and the roots that contain them. Approval (plan 154 Task 6) and the hard-deny hook
4
+ * read it, so neither has to parse tool names or arguments.
5
+ *
6
+ * A call from a session bound to a worktree (plan 165 Task 7) reports that worktree as its root, so
7
+ * approval and the hard-deny hook follow the session: inside the worktree for the bound session,
8
+ * outside it for every other.
9
+ *
10
+ * Read-effect tools also get one read-only root per effective skill directory (plan 190 Task 6), so
11
+ * a skill's `references/` files can be read without granting any write access outside the workspace.
12
+ */
13
+ import { dirname, isAbsolute, resolve } from "node:path";
14
+ import type { JsonObject, Skill } from "@arnilo/prism";
15
+
16
+ import type { WorkspaceService } from "./workspace/service.js";
17
+
18
+ export interface CodingTargets {
19
+ /** Absolute paths the call touches. Empty for `shell`. */
20
+ readonly paths: readonly string[];
21
+ /** `root` plus every extra root, resolved; read-effect tools also get each skill's directory. */
22
+ readonly roots: readonly string[];
23
+ }
24
+
25
+ /** Argument keys holding paths, per registered tool. `shell` and `shell_output` have none. */
26
+ const PATH_ARG_KEYS: Readonly<Record<string, readonly string[]>> = {
27
+ read: ["path"],
28
+ write: ["path"],
29
+ edit: ["path"],
30
+ delete: ["path"],
31
+ glob: ["path"],
32
+ grep: ["path"],
33
+ diagnostics: ["path"],
34
+ move: ["from", "to"],
35
+ shell: [],
36
+ shell_output: [],
37
+ };
38
+
39
+ /** Every tool this package registers. */
40
+ export const CODING_TOOL_NAMES: ReadonlySet<string> = new Set(Object.keys(PATH_ARG_KEYS));
41
+
42
+ export type CodingTargetsFn = (name: string, args: JsonObject, context?: CodingTargetsContext) => CodingTargets | undefined;
43
+
44
+ /** The caller's identity, when it has one. Only `sessionId` can select a worktree. */
45
+ export interface CodingTargetsContext {
46
+ readonly sessionId?: string;
47
+ }
48
+
49
+ /** Read-effect tools that may read a skill's directory; a write to one stays outside the roots. */
50
+ const SKILL_READ_TOOL_NAMES: ReadonlySet<string> = new Set(["read", "glob", "grep", "diagnostics"]);
51
+ const NO_SKILL_ROOTS: readonly string[] = Object.freeze([]);
52
+
53
+ /**
54
+ * Build the service for `root` and `roots`. Unknown names return `undefined` (the tool is not
55
+ * ours). A relative argument resolves against `root`, matching the tools' own cwd — or against the
56
+ * calling session's worktree when `workspace` reports one, matching the routed tool's cwd.
57
+ *
58
+ * `skills` is read lazily, after setup, and only for read-effect tools. The derived directories are
59
+ * cached until the effective list's paths change; a skill without an absolute path contributes
60
+ * nothing. Reads fail soft, so a host that cannot list skills keeps the coding roots.
61
+ */
62
+ export function createCodingTargets(
63
+ root: string,
64
+ roots: readonly string[],
65
+ workspace?: WorkspaceService,
66
+ skills?: () => readonly Skill[],
67
+ ): CodingTargetsFn {
68
+ const resolvedRoot = resolve(root);
69
+ const extraRoots = Object.freeze(roots.map((extra) => resolve(extra)));
70
+ const rootTargets = Object.freeze([resolvedRoot, ...extraRoots]);
71
+ let cachedSignature: string | undefined;
72
+ let cachedSkillRoots: readonly string[] = NO_SKILL_ROOTS;
73
+ const skillRoots = (): readonly string[] => {
74
+ if (skills === undefined) return NO_SKILL_ROOTS;
75
+ let list: readonly Skill[];
76
+ try {
77
+ list = skills();
78
+ } catch {
79
+ return cachedSkillRoots;
80
+ }
81
+ // The public skills API has no change signal, so reading the list is the only way to notice a
82
+ // registration; only its path signature and the derived directories are cached.
83
+ const signature = JSON.stringify(list.map((skill) => skill.path ?? null));
84
+ if (signature === cachedSignature) return cachedSkillRoots;
85
+ const dirs = new Set<string>();
86
+ for (const skill of list) {
87
+ if (skill.path !== undefined && isAbsolute(skill.path)) dirs.add(dirname(skill.path));
88
+ }
89
+ cachedSignature = signature;
90
+ cachedSkillRoots = Object.freeze([...dirs]);
91
+ return cachedSkillRoots;
92
+ };
93
+ return (name, args, context) => {
94
+ const keys = PATH_ARG_KEYS[name];
95
+ if (!keys) return undefined;
96
+ const worktree = context?.sessionId === undefined ? undefined : workspace?.worktree.bound(context.sessionId);
97
+ const cwd = worktree === undefined ? resolvedRoot : resolve(worktree.path);
98
+ const baseRoots = worktree === undefined ? rootTargets : Object.freeze([cwd, ...extraRoots]);
99
+ const extraSkillRoots = SKILL_READ_TOOL_NAMES.has(name) ? skillRoots() : NO_SKILL_ROOTS;
100
+ const allRoots = extraSkillRoots.length === 0 ? baseRoots : Object.freeze([...baseRoots, ...extraSkillRoots]);
101
+ const paths: string[] = [];
102
+ for (const key of keys) {
103
+ const value = args[key];
104
+ if (typeof value === "string" && value.length > 0) {
105
+ paths.push(isAbsolute(value) ? value : resolve(cwd, value));
106
+ }
107
+ }
108
+ return { paths, roots: allRoots };
109
+ };
110
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Bounded worktree diff: tracked changes against the worktree's base, plus untracked files.
3
+ *
4
+ * `FileActivity` is the driver kit's file-activity type, so a workspace diff and a vendor diff
5
+ * normalize to the same shape. Overlay files never appear: they are listed in the common
6
+ * `.git/info/exclude`, so `ls-files --others --exclude-standard` skips them.
7
+ */
8
+ import { lstat } from "node:fs/promises";
9
+ import { join } from "node:path";
10
+ import { type FileActivity, mapDiff } from "@arnilo/prism/driver-kit";
11
+
12
+ import type { GitOperations } from "../agent/git.js";
13
+
14
+ /** One changed file, in the driver kit's file-activity shape. */
15
+ export type FileChange = FileActivity;
16
+
17
+ export interface WorkspaceDiffBounds {
18
+ /** Maximum files listed. Default 200. */
19
+ readonly maxFiles: number;
20
+ /** Maximum bytes of one file's `diff` text. A larger file is listed with no `diff`. Default 1 MiB. */
21
+ readonly maxBytes: number;
22
+ }
23
+
24
+ export interface WorkspaceDiff {
25
+ readonly changes: readonly FileChange[];
26
+ /** True when a file's `diff` was withheld (over `maxBytes`) or the list was cut at `maxFiles`. */
27
+ readonly truncated: boolean;
28
+ }
29
+
30
+ /**
31
+ * `git diff <base>` for tracked files (committed and uncommitted), then one `created` entry per
32
+ * untracked, non-ignored file. Paths are worktree-relative. A file whose diff exceeds `maxBytes`
33
+ * is listed without `diff` and marks the result truncated.
34
+ */
35
+ export async function workspaceDiff(
36
+ git: GitOperations,
37
+ worktree: string,
38
+ base: string,
39
+ bounds: WorkspaceDiffBounds,
40
+ signal?: AbortSignal,
41
+ ): Promise<WorkspaceDiff> {
42
+ const tracked = await git.diff({ base, signal });
43
+ const untracked = await git.untrackedFiles({ signal });
44
+
45
+ let truncated = tracked.truncated;
46
+ const changes: FileChange[] = [];
47
+ for (const change of mapDiff(tracked.text)) {
48
+ if (Buffer.byteLength(change.diff ?? "", "utf8") > bounds.maxBytes) {
49
+ truncated = true;
50
+ const { diff: _dropped, ...rest } = change;
51
+ changes.push(rest);
52
+ } else {
53
+ changes.push(change);
54
+ }
55
+ }
56
+
57
+ const listed = new Set(changes.map((change) => change.path));
58
+ for (const path of untracked) {
59
+ if (listed.has(path)) continue;
60
+ const size = await fileSize(join(worktree, path));
61
+ if (size !== undefined && size > bounds.maxBytes) truncated = true;
62
+ changes.push({ kind: "file", path, change: "created", source: "diff" });
63
+ }
64
+
65
+ if (changes.length > bounds.maxFiles) {
66
+ return { changes: changes.slice(0, bounds.maxFiles), truncated: true };
67
+ }
68
+ return { changes, truncated };
69
+ }
70
+
71
+ async function fileSize(path: string): Promise<number | undefined> {
72
+ try {
73
+ const stats = await lstat(path);
74
+ return stats.isFile() ? stats.size : undefined;
75
+ } catch {
76
+ return undefined;
77
+ }
78
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The `workspace` service's error type.
3
+ *
4
+ * Codes are plain `workspace_*` strings, deliberately outside the 0.12 error namespace this plan
5
+ * retires, and messages name the rule, never a config value.
6
+ */
7
+ export type WorkspaceServiceErrorCode =
8
+ | "workspace_not_git"
9
+ | "workspace_path_escape"
10
+ | "workspace_limit"
11
+ | "workspace_unknown"
12
+ | "workspace_main_immutable"
13
+ | "workspace_dirty"
14
+ | "workspace_dirty_target"
15
+ | "workspace_identity_missing"
16
+ | "workspace_git_unsupported"
17
+ | "workspace_merge_failed"
18
+ | "workspace_sandbox_unsupported";
19
+
20
+ export class WorkspaceServiceError extends Error {
21
+ readonly code: WorkspaceServiceErrorCode;
22
+
23
+ constructor(code: WorkspaceServiceErrorCode, message: string) {
24
+ super(message);
25
+ this.name = "WorkspaceServiceError";
26
+ this.code = code;
27
+ }
28
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./diff.js";
2
+ export * from "./errors.js";
3
+ export * from "./merge.js";
4
+ export * from "./routing.js";
5
+ export * from "./service.js";
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Merge one worktree's branch into another branch, reporting conflicts instead of resolving them.
3
+ *
4
+ * Everything is decided before anything moves. A dirty worktree is folded into a *dangling* commit
5
+ * of its current state (the index is snapshotted first and restored if the merge turns out to
6
+ * conflict), so a conflict leaves both trees, the index, and every ref byte-for-byte unchanged. A
7
+ * clean probe is turned into a merge commit (`commit-tree`) and the target ref is advanced with a
8
+ * compare-and-swap (`update-ref`); when the target is checked out somewhere, that worktree is
9
+ * fast-forwarded instead so its index and files stay consistent with its HEAD.
10
+ *
11
+ * The service never pushes, never resolves a conflict, and never invents an identity.
12
+ */
13
+ import { GitError, type GitOperations, type GitWorktreeEntry } from "../agent/git.js";
14
+ import { WorkspaceServiceError } from "./errors.js";
15
+
16
+ export interface WorkspaceMergeRequest {
17
+ /** The branch the worktree's branch is merged into. */
18
+ readonly into: string;
19
+ /** Required when the worktree has uncommitted changes: it records them, and names the merge. */
20
+ readonly commit?: { readonly message: string };
21
+ }
22
+
23
+ export type WorkspaceMergeResult =
24
+ | { readonly status: "merged"; readonly commit: string }
25
+ | { readonly status: "conflict"; readonly files: readonly string[] };
26
+
27
+ export interface MergeContext {
28
+ /** The worktree being merged (its `cwd`). */
29
+ readonly worktree: string;
30
+ /** The branch checked out in that worktree. */
31
+ readonly branch: string;
32
+ /** Git operations for a path; the worktree's own instance is created on demand. */
33
+ readonly gitFor: (path: string) => Promise<GitOperations>;
34
+ /** `git worktree list` entries, used to find where `into` is checked out. */
35
+ readonly worktrees: readonly GitWorktreeEntry[];
36
+ /** The repository's configured commit identity, or undefined when it has none. */
37
+ readonly identity: { readonly name: string; readonly email: string } | undefined;
38
+ readonly signal?: AbortSignal;
39
+ }
40
+
41
+ export async function mergeWorktree(context: MergeContext, request: WorkspaceMergeRequest): Promise<WorkspaceMergeResult> {
42
+ const { branch, signal } = context;
43
+ if (request.into === branch) {
44
+ throw new WorkspaceServiceError("workspace_merge_failed", "a worktree cannot be merged into its own branch");
45
+ }
46
+
47
+ const git = await context.gitFor(context.worktree);
48
+ const dirty = await dirtyPaths(git, signal);
49
+ if (dirty.length > 0 && request.commit === undefined) {
50
+ throw new WorkspaceServiceError(
51
+ "workspace_dirty",
52
+ `the worktree has ${dirty.length} uncommitted change(s); pass commit to record them before merging`,
53
+ );
54
+ }
55
+
56
+ // The tree being merged: the worktree's live state when it is dirty, else its branch tip.
57
+ const message = request.commit?.message ?? `Merge ${branch} into ${request.into}`;
58
+ let restore: string | undefined;
59
+ let source = branch;
60
+ if (dirty.length > 0) {
61
+ requireIdentity(context.identity);
62
+ restore = await git.writeTree({ signal });
63
+ await git.stageAll({ signal });
64
+ const staged = await git.writeTree({ signal });
65
+ source = await git.commitTree({ tree: staged, parents: [branch], message, signal });
66
+ }
67
+
68
+ const abort = async (): Promise<void> => {
69
+ // The index is the only thing the fold touched; put it back exactly as it was.
70
+ if (restore !== undefined) await git.readTree({ tree: restore, signal }).catch(() => undefined);
71
+ };
72
+
73
+ const holder = context.worktrees.find((entry) => entry.branch === `refs/heads/${request.into}`);
74
+ if (holder !== undefined) {
75
+ const holderDirty = await dirtyPaths(await context.gitFor(holder.path), signal);
76
+ if (holderDirty.length > 0) {
77
+ await abort();
78
+ throw new WorkspaceServiceError(
79
+ "workspace_dirty_target",
80
+ `${request.into} is checked out in ${holder.path}, which has uncommitted changes`,
81
+ );
82
+ }
83
+ }
84
+
85
+ let tree: string;
86
+ let conflicts: readonly string[];
87
+ try {
88
+ ({ tree, conflicts } = await git.mergeTree({ into: request.into, from: source, signal }));
89
+ } catch (error) {
90
+ await abort();
91
+ throw mapMergeError(error);
92
+ }
93
+ if (conflicts.length > 0) {
94
+ await abort();
95
+ return { status: "conflict", files: conflicts };
96
+ }
97
+
98
+ requireIdentity(context.identity);
99
+ let commit: string;
100
+ try {
101
+ commit = await git.commitTree({ tree, parents: [request.into, source], message, signal });
102
+ } catch (error) {
103
+ await abort();
104
+ throw mapMergeError(error);
105
+ }
106
+
107
+ try {
108
+ if (holder === undefined) {
109
+ await git.updateRef({ ref: `refs/heads/${request.into}`, to: commit, expected: request.into, signal });
110
+ } else {
111
+ await (await context.gitFor(holder.path)).fastForward({ to: commit, signal });
112
+ }
113
+ // Record the worktree's own state on its branch so its index and HEAD agree again.
114
+ if (restore !== undefined) {
115
+ await git.updateRef({ ref: `refs/heads/${branch}`, to: source, expected: branch, signal });
116
+ }
117
+ } catch (error) {
118
+ throw new WorkspaceServiceError(
119
+ "workspace_merge_failed",
120
+ `could not advance ${request.into}: ${error instanceof Error ? error.message : String(error)}`,
121
+ );
122
+ }
123
+ return { status: "merged", commit };
124
+ }
125
+
126
+ /** A merge and a commit both need the repository's configured identity; neither invents one. */
127
+ export function requireIdentity(identity: MergeContext["identity"]): asserts identity is { name: string; email: string } {
128
+ if (identity === undefined) {
129
+ throw new WorkspaceServiceError("workspace_identity_missing", "no commit identity is configured for this repository");
130
+ }
131
+ }
132
+
133
+ async function dirtyPaths(git: GitOperations, signal?: AbortSignal): Promise<string[]> {
134
+ const status = await git.status({ signal });
135
+ return status.entries.filter((entry) => entry.kind !== "ignored").map((entry) => entry.path);
136
+ }
137
+
138
+ function mapMergeError(error: unknown): WorkspaceServiceError {
139
+ if (error instanceof WorkspaceServiceError) return error;
140
+ if (error instanceof GitError) {
141
+ const unsupported = error.exitCode === 129 || /unknown option|is not a git command/i.test(error.message);
142
+ if (unsupported) {
143
+ return new WorkspaceServiceError("workspace_git_unsupported", `git merge-tree is not supported here: ${error.message}`);
144
+ }
145
+ if (/auto-detect email|empty ident name/i.test(error.message)) {
146
+ return new WorkspaceServiceError("workspace_identity_missing", "no commit identity is configured for this repository");
147
+ }
148
+ return new WorkspaceServiceError("workspace_merge_failed", error.message);
149
+ }
150
+ return new WorkspaceServiceError("workspace_merge_failed", error instanceof Error ? error.message : String(error));
151
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Per-session worktree routing (plan 165 Task 7).
3
+ *
4
+ * A session bound by `worktree.create({ sessionId })` runs every cwd-taking tool of this package
5
+ * inside that session's worktree. The registered set wraps each base tool with one `execute` that
6
+ * looks the calling session's worktree up and dispatches to the instance built for that worktree,
7
+ * so no tool needs to know about sessions and no schema changes.
8
+ *
9
+ * The binding is the service's memory, keyed by session id: it is never read from tool arguments or
10
+ * model output. An unbound session (or an unknown session id) falls through to the base tool.
11
+ *
12
+ * ```ts
13
+ * const routing = createWorktreeRouting({ base, create: cwdTools, workspace });
14
+ * prism.hooks.on("session.closed", (event) => routing.forget(event.sessionId), { id: "workspace-routing" });
15
+ * for (const tool of routing.tools) prism.tools.register(tool);
16
+ * ```
17
+ */
18
+ import type { JsonObject, ToolDefinition, ToolExecutionContext, ToolResult } from "@arnilo/prism";
19
+
20
+ import type { WorkspaceService } from "./service.js";
21
+
22
+ export interface WorktreeRoutingOptions {
23
+ /** The tool set built for the package root; registered as-is for a session without a worktree. */
24
+ readonly base: readonly ToolDefinition[];
25
+ /** Builds the cwd-taking tools for one directory. Called once per bound worktree. */
26
+ readonly create: (cwd: string) => readonly ToolDefinition[];
27
+ readonly workspace: WorkspaceService;
28
+ }
29
+
30
+ export interface WorktreeRouting {
31
+ /** `base`, with every tool `create` also returns dispatched per session. */
32
+ readonly tools: readonly ToolDefinition[];
33
+ /** Drops one session's cached instances. Wire to `session.closed`. */
34
+ forget(sessionId: string): void;
35
+ }
36
+
37
+ interface CachedTools {
38
+ readonly path: string;
39
+ readonly byName: ReadonlyMap<string, ToolDefinition>;
40
+ }
41
+
42
+ export function createWorktreeRouting(options: WorktreeRoutingOptions): WorktreeRouting {
43
+ const cache = new Map<string, CachedTools>();
44
+
45
+ /**
46
+ * The tool instances for `sessionId`'s worktree, built on first use. A disposed worktree unbinds,
47
+ * so the entry is dropped on the next call; a session rebound to another worktree rebuilds.
48
+ */
49
+ function instancesFor(sessionId: string): ReadonlyMap<string, ToolDefinition> | undefined {
50
+ const worktree = options.workspace.worktree.bound(sessionId);
51
+ if (worktree === undefined) {
52
+ cache.delete(sessionId);
53
+ return undefined;
54
+ }
55
+ const cached = cache.get(sessionId);
56
+ if (cached !== undefined && cached.path === worktree.path) return cached.byName;
57
+ const byName = new Map(options.create(worktree.path).map((tool) => [tool.name, tool]));
58
+ cache.set(sessionId, { path: worktree.path, byName });
59
+ return byName;
60
+ }
61
+
62
+ const tools = options.base.map((tool) => ({
63
+ ...tool,
64
+ async execute(args: JsonObject, context: ToolExecutionContext): Promise<ToolResult> {
65
+ const instance = instancesFor(context.sessionId)?.get(tool.name);
66
+ return await (instance ?? tool).execute(args, context);
67
+ },
68
+ }));
69
+
70
+ return {
71
+ tools,
72
+ forget(sessionId) {
73
+ cache.delete(sessionId);
74
+ },
75
+ };
76
+ }