@arnilo/prism-tools 0.0.0-stage → 0.13.1

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 +418 -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 +29 -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 +584 -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,459 @@
1
+ /**
2
+ * `grep` tool: bounded repository text search. Regex by default, run in a terminate-able worker;
3
+ * `literal: true` matches the pattern's characters on the main thread. Output is ripgrep-style.
4
+ */
5
+ import type { JsonObject, ToolDefinition, ToolExecutionContext, ToolResult } from "@arnilo/prism";
6
+ import { validateGlobPattern } from "./glob-match.js";
7
+ import { CODING_OBSERVATION_EFFECT } from "./effects.js";
8
+ import { enforceExecutionPolicy } from "./execution-policy.js";
9
+ import { HARD_MAX_SEARCH_CONTEXT_LINES, HARD_MAX_SEARCH_MATCHES, validateCodingLimit, validateCodingLimitAllowZero } from "./limits.js";
10
+ import {
11
+ createLocalRepositoryOperations,
12
+ literalMatcher,
13
+ type RepoSearchMode,
14
+ type RepoSearchOutputMode,
15
+ RepositoryError,
16
+ type RepositoryFileMatcher,
17
+ type RepositoryLimitOptions,
18
+ type RepositoryLineMatch,
19
+ type RepositoryOperations,
20
+ type RepositorySearchMatch,
21
+ type RepositorySearchResult,
22
+ resolveRepositoryLimits,
23
+ } from "./repository.js";
24
+ import { truncateLine } from "./truncate.js";
25
+
26
+ import type { ExecutionPolicy } from "./execution-policy.js";
27
+ export interface GrepToolOptions {
28
+ executionPolicy?: ExecutionPolicy;
29
+ operations?: RepositoryOperations;
30
+ repository?: RepositoryLimitOptions;
31
+ maxMatches?: number;
32
+ maxContextLines?: number;
33
+ exclude?: readonly string[];
34
+ /** Modes the tool exposes (default `["literal"]`); indexed modes require a host index via `operations`. */
35
+ modes?: readonly RepoSearchMode[];
36
+ }
37
+
38
+ const ALL_MODES: readonly RepoSearchMode[] = ["literal", "indexed_literal", "semantic"];
39
+ const INDEX_MODE_LABEL: Record<"indexed_literal" | "semantic", string> = {
40
+ indexed_literal: "host-indexed literal match with relevance scores",
41
+ semantic: "host semantic search (requires a semantic-capable index backend)",
42
+ };
43
+
44
+ function errorResult(toolCallId: string, message: string): ToolResult {
45
+ return {
46
+ toolCallId,
47
+ name: "grep",
48
+ content: [{ type: "text", text: message }],
49
+ error: { message },
50
+ };
51
+ }
52
+
53
+ /**
54
+ * ripgrep-style content: `path:line:text` for a hit, `path-line-text` for context, `--` between
55
+ * non-adjacent groups, and every line printed once even when two context windows overlap.
56
+ */
57
+ function formatContent(matches: readonly RepositorySearchMatch[]): string {
58
+ const byPath = new Map<string, Map<number, { text: string; hit: boolean; score?: number }>>();
59
+ for (const match of matches) {
60
+ let lines = byPath.get(match.path);
61
+ if (lines === undefined) {
62
+ lines = new Map();
63
+ byPath.set(match.path, lines);
64
+ }
65
+ // Context never downgrades a hit: adjacent matching lines are both hits, as in ripgrep.
66
+ const setContext = (line: number, text: string): void => {
67
+ const existing = lines.get(line);
68
+ if (existing?.hit !== true) lines.set(line, { text, hit: false });
69
+ };
70
+ for (let index = 0; index < match.before.length; index++) {
71
+ setContext(match.line - match.before.length + index, match.before[index]!);
72
+ }
73
+ lines.set(match.line, { text: match.text, hit: true, ...(match.score !== undefined ? { score: match.score } : {}) });
74
+ for (let index = 0; index < match.after.length; index++) {
75
+ setContext(match.line + 1 + index, match.after[index]!);
76
+ }
77
+ }
78
+ const out: string[] = [];
79
+ for (const [path, lines] of byPath) {
80
+ let previous: number | undefined;
81
+ for (const [line, entry] of [...lines.entries()].sort((a, b) => a[0] - b[0])) {
82
+ if (previous !== undefined && line !== previous + 1) out.push("--");
83
+ const { text } = truncateLine(entry.text, 500);
84
+ const score = entry.score !== undefined ? ` [score ${entry.score.toFixed(3)}]` : "";
85
+ out.push(`${path}${entry.hit ? ":" : "-"}${line}${entry.hit ? ":" : "-"}${text}${score}`);
86
+ previous = line;
87
+ }
88
+ }
89
+ return out.join("\n");
90
+ }
91
+
92
+ function formatSearchText(result: RepositorySearchResult): string {
93
+ if (result.matches.length === 0) {
94
+ return result.truncated ? `[truncated by ${result.truncatedBy ?? "limit"} before any matches]` : "(no matches)";
95
+ }
96
+ const body = formatContent(result.matches);
97
+ if (!result.truncated) return body;
98
+ return `${body}\n[truncated by ${result.truncatedBy ?? "limit"}]`;
99
+ }
100
+
101
+ function uniqueMatchPaths(matches: readonly RepositorySearchMatch[]): string[] {
102
+ return [...new Set(matches.map((m) => m.path))].sort();
103
+ }
104
+
105
+ function formatFilesWithMatches(result: RepositorySearchResult): string {
106
+ if (result.matches.length === 0) {
107
+ return result.truncated ? `[truncated by ${result.truncatedBy ?? "limit"} before any matches]` : "(no matches)";
108
+ }
109
+ const body = uniqueMatchPaths(result.matches).join("\n");
110
+ if (!result.truncated) return body;
111
+ return `${body}\n[truncated by ${result.truncatedBy ?? "limit"}]`;
112
+ }
113
+
114
+ function formatCount(result: RepositorySearchResult): string {
115
+ const matchCount = result.matches.length;
116
+ const fileCount = uniqueMatchPaths(result.matches).length;
117
+ if (matchCount === 0) {
118
+ return result.truncated ? `[truncated by ${result.truncatedBy ?? "limit"} before any matches]` : "0 matches in 0 files";
119
+ }
120
+ const noun = fileCount === 1 ? "file" : "files";
121
+ const body = `${matchCount} matches in ${fileCount} ${noun}`;
122
+ if (!result.truncated) return body;
123
+ return `${body}\n[truncated by ${result.truncatedBy ?? "limit"}]`;
124
+ }
125
+
126
+ function formatSearchResult(result: RepositorySearchResult, outputMode: RepoSearchOutputMode): string {
127
+ switch (outputMode) {
128
+ case "files_with_matches":
129
+ return formatFilesWithMatches(result);
130
+ case "count":
131
+ return formatCount(result);
132
+ default:
133
+ return formatSearchText(result);
134
+ }
135
+ }
136
+
137
+ function parseOutputMode(value: unknown): RepoSearchOutputMode {
138
+ if (value === undefined || value === "content") return "content";
139
+ if (value === "files_with_matches" || value === "count") return value;
140
+ throw new Error(`unsupported outputMode: ${String(value)}`);
141
+ }
142
+
143
+ function buildSearchMetadata(result: RepositorySearchResult, outputMode: RepoSearchOutputMode): Record<string, unknown> {
144
+ const base: Record<string, unknown> = {
145
+ outputMode,
146
+ truncated: result.truncated,
147
+ truncatedBy: result.truncatedBy,
148
+ matchCount: result.matches.length,
149
+ scannedBytes: result.scannedBytes,
150
+ scannedFiles: result.scannedFiles,
151
+ scannedEntries: result.scannedEntries,
152
+ filesSkippedBinary: result.filesSkippedBinary,
153
+ filesSkippedOversize: result.filesSkippedOversize,
154
+ };
155
+ if (result.indexed !== undefined) {
156
+ base.untrusted_index = result.untrusted_index === true;
157
+ base.indexMode = result.indexed.mode;
158
+ base.indexState = result.indexed.state;
159
+ if (result.indexed.sourceRevision !== undefined) base.indexRevision = result.indexed.sourceRevision;
160
+ if (result.indexed.updatedAt !== undefined) base.indexUpdatedAt = result.indexed.updatedAt;
161
+ }
162
+ if (outputMode === "content") {
163
+ return { ...base, matches: result.matches };
164
+ }
165
+ return { ...base, fileCount: uniqueMatchPaths(result.matches).length };
166
+ }
167
+
168
+ function validateModes(modes: readonly RepoSearchMode[] | undefined): readonly RepoSearchMode[] {
169
+ if (modes === undefined) return ["literal"];
170
+ if (!Array.isArray(modes) || modes.length === 0) return ["literal"];
171
+ const seen = new Set<RepoSearchMode>();
172
+ for (const mode of modes) {
173
+ if (!ALL_MODES.includes(mode)) {
174
+ throw new Error(`unsupported search mode in options.modes: ${String(mode)}`);
175
+ }
176
+ seen.add(mode);
177
+ }
178
+ seen.add("literal"); // literal always stays available
179
+ return [...seen].sort((a, b) => ALL_MODES.indexOf(a) - ALL_MODES.indexOf(b));
180
+ }
181
+
182
+ // --- regex worker host ---
183
+
184
+ type WorkerReply =
185
+ | { type: "ready" }
186
+ | { type: "invalid"; message: string }
187
+ | { type: "matches"; id: number; matches: RepositoryLineMatch[] }
188
+ | { type: "error"; id: number; message: string };
189
+
190
+ /** Thrown when a single file's regex exceeds the search deadline; the caller terminates the worker. */
191
+ class GrepTimeout extends Error {
192
+ constructor() {
193
+ super("grep worker exceeded the time limit");
194
+ }
195
+ }
196
+
197
+ /**
198
+ * One worker per search: compile once, match one file at a time, terminate on deadline. A pattern
199
+ * with catastrophic backtracking can only hang the worker, never the main thread.
200
+ */
201
+ class RegexWorker {
202
+ private readonly worker: Worker;
203
+ private readonly ready: Promise<void>;
204
+ private readonly pending = new Map<number, { resolve: (matches: RepositoryLineMatch[]) => void; reject: (error: Error) => void }>();
205
+ private nextId = 1;
206
+ private dead = false;
207
+
208
+ constructor(pattern: string, caseSensitive: boolean, maxLineBytes: number) {
209
+ // Bun resolves the `.js` specifier to the TypeScript source this package ships.
210
+ this.worker = new Worker(new URL("./grep-worker.js", import.meta.url));
211
+ this.ready = new Promise<void>((resolve, reject) => {
212
+ this.worker.onmessage = (event: MessageEvent) => {
213
+ const reply = event.data as WorkerReply;
214
+ if (reply.type === "ready") {
215
+ resolve();
216
+ return;
217
+ }
218
+ if (reply.type === "invalid") {
219
+ reject(new Error(reply.message));
220
+ return;
221
+ }
222
+ const entry = this.pending.get(reply.id);
223
+ if (entry === undefined) return;
224
+ this.pending.delete(reply.id);
225
+ if (reply.type === "matches") entry.resolve(reply.matches);
226
+ else entry.reject(new Error(reply.message));
227
+ };
228
+ this.worker.onerror = (event: ErrorEvent) => {
229
+ const error = new Error(`grep worker failed: ${event.message}`);
230
+ reject(error);
231
+ for (const entry of this.pending.values()) entry.reject(error);
232
+ this.pending.clear();
233
+ };
234
+ });
235
+ // The worker posts "ready" or "invalid"; a rejection here is observed by `start()`.
236
+ this.ready.catch(() => {});
237
+ this.worker.postMessage({ type: "compile", pattern, flags: caseSensitive ? "" : "i", maxLineBytes });
238
+ }
239
+
240
+ /** Resolve once the pattern compiled; rejects with the engine's syntax message otherwise. */
241
+ async start(): Promise<void> {
242
+ await this.ready;
243
+ }
244
+
245
+ async match(text: string, context: number, timeoutMs: number): Promise<readonly RepositoryLineMatch[]> {
246
+ if (this.dead) throw new GrepTimeout();
247
+ const id = this.nextId++;
248
+ return await new Promise<readonly RepositoryLineMatch[]>((resolve, reject) => {
249
+ const timer = setTimeout(
250
+ () => {
251
+ this.pending.delete(id);
252
+ reject(new GrepTimeout());
253
+ },
254
+ Math.max(0, timeoutMs),
255
+ );
256
+ timer.unref?.();
257
+ this.pending.set(id, {
258
+ resolve: (matches) => {
259
+ clearTimeout(timer);
260
+ resolve(matches);
261
+ },
262
+ reject: (error) => {
263
+ clearTimeout(timer);
264
+ reject(error);
265
+ },
266
+ });
267
+ this.worker.postMessage({ type: "match", id, text, context });
268
+ });
269
+ }
270
+
271
+ async terminate(): Promise<void> {
272
+ this.dead = true;
273
+ await this.worker.terminate();
274
+ }
275
+ }
276
+
277
+ export function createGrepTool(cwd: string, options?: GrepToolOptions): ToolDefinition {
278
+ const limits = resolveRepositoryLimits({
279
+ ...options?.repository,
280
+ maxMatches: options?.maxMatches ?? options?.repository?.maxMatches,
281
+ maxContextLines: options?.maxContextLines ?? options?.repository?.maxContextLines,
282
+ exclude: options?.exclude ?? options?.repository?.exclude,
283
+ });
284
+ const ops = options?.operations ?? createLocalRepositoryOperations(limits);
285
+ const modes = validateModes(options?.modes);
286
+ const indexedEnabled = modes.some((m) => m !== "literal");
287
+ const modeDescriptions = modes.map((m) => (m === "literal" ? "literal substring match only" : INDEX_MODE_LABEL[m])).join("; ");
288
+
289
+ const properties: JsonObject = {
290
+ pattern: {
291
+ type: "string",
292
+ description:
293
+ 'Regular expression (JavaScript syntax) to search for, e.g. "function \\\\w+". Set literal: true to match these characters as plain text.',
294
+ },
295
+ literal: { type: "boolean", description: "Match pattern as literal text instead of a regular expression (default false)" },
296
+ path: { type: "string", description: "Workspace-relative directory or file to search (default: workspace root)" },
297
+ glob: { type: "string", description: 'Only search files whose path matches this glob, e.g. "src/**/*.ts"' },
298
+ caseSensitive: { type: "boolean", description: "Case-sensitive matching (default true)" },
299
+ includeHidden: { type: "boolean", description: "Include dotfile/dotdir names (default false)" },
300
+ context: {
301
+ type: "number",
302
+ description: `Context lines before/after each match (default 0, hard ${HARD_MAX_SEARCH_CONTEXT_LINES})`,
303
+ },
304
+ maxMatches: {
305
+ type: "number",
306
+ description: `Maximum matches to retain (default ${limits.maxMatches}, hard ${HARD_MAX_SEARCH_MATCHES})`,
307
+ },
308
+ outputMode: {
309
+ type: "string",
310
+ description:
311
+ 'Result shape: "content" (default, `path:line:text` with `-` for context), "files_with_matches" (unique paths), or "count" (match/file totals)',
312
+ enum: ["content", "files_with_matches", "count"],
313
+ },
314
+ };
315
+ const modeProperty = indexedEnabled
316
+ ? { mode: { type: "string", description: `Search mode: ${modeDescriptions}`, enum: [...modes] } }
317
+ : {};
318
+
319
+ return {
320
+ name: "grep",
321
+ kind: "search",
322
+ effect: CODING_OBSERVATION_EFFECT,
323
+ effects: ["read"],
324
+ description: `Search text files under the workspace with a regular expression (or literal text). Output is ripgrep-style: "path:line:text" for a match, "path-line-text" for context (context: N), "--" between groups. A regex runs in a worker, so a pathological pattern is terminated at the search time limit. Use outputMode "files_with_matches" for paths only or "count" for totals. Skips binary files, excluded basenames (default: ${limits.exclude.join(", ")}), and hidden names unless includeHidden is true. Does not follow symlinks. Caps matches/scanned bytes/time.${indexedEnabled ? ` Modes: ${modeDescriptions}. Index results are untrusted host index output and may be stale; verify before mutation.` : ""}`,
325
+ parameters: {
326
+ type: "object",
327
+ properties: { ...properties, ...modeProperty },
328
+ required: ["pattern"],
329
+ additionalProperties: false,
330
+ } as JsonObject,
331
+ async execute(args, context: ToolExecutionContext): Promise<ToolResult> {
332
+ const toolCallId = context.toolCallId;
333
+ if (context.signal?.aborted) return errorResult(toolCallId, "Operation aborted");
334
+
335
+ const pattern = typeof args.pattern === "string" ? args.pattern : "";
336
+ if (pattern.length === 0) return errorResult(toolCallId, "pattern is required and must be a non-empty string.");
337
+ const literal = args.literal === true;
338
+ if (args.mode !== undefined && !modes.includes(args.mode as RepoSearchMode)) {
339
+ return errorResult(toolCallId, `unsupported search mode: ${String(args.mode)} (enabled: ${modes.join(", ")})`);
340
+ }
341
+ const mode = (args.mode ?? "literal") as RepoSearchMode;
342
+ let outputMode: RepoSearchOutputMode;
343
+ try {
344
+ outputMode = parseOutputMode(args.outputMode);
345
+ } catch (error) {
346
+ return errorResult(toolCallId, error instanceof Error ? error.message : String(error));
347
+ }
348
+ const path = typeof args.path === "string" ? args.path : undefined;
349
+ const glob = typeof args.glob === "string" && args.glob.length > 0 ? args.glob : undefined;
350
+ if (glob !== undefined) {
351
+ try {
352
+ validateGlobPattern(glob, limits.maxPatternBytes);
353
+ } catch (error) {
354
+ return errorResult(toolCallId, error instanceof Error ? error.message : String(error));
355
+ }
356
+ }
357
+ const caseSensitive = args.caseSensitive !== false;
358
+ const includeHidden = args.includeHidden === true;
359
+ let contextLines: number;
360
+ let maxMatches: number | undefined;
361
+ try {
362
+ contextLines =
363
+ args.context === undefined ? 0 : validateCodingLimitAllowZero("context", args.context as number, HARD_MAX_SEARCH_CONTEXT_LINES);
364
+ if (args.maxMatches !== undefined) {
365
+ maxMatches = validateCodingLimit("maxMatches", args.maxMatches as number, HARD_MAX_SEARCH_MATCHES);
366
+ }
367
+ } catch (error) {
368
+ return errorResult(toolCallId, error instanceof Error ? error.message : String(error));
369
+ }
370
+ const patternBytes = Buffer.byteLength(pattern, "utf8");
371
+ if (patternBytes > limits.maxPatternBytes) {
372
+ return errorResult(toolCallId, `pattern exceeds ${limits.maxPatternBytes} byte pattern limit`);
373
+ }
374
+
375
+ const policyCheck = await enforceExecutionPolicy(
376
+ options?.executionPolicy,
377
+ {
378
+ kind: "grep",
379
+ operation: "search",
380
+ paths: [path ? path : cwd],
381
+ risk: "low",
382
+ metadata: {
383
+ mode,
384
+ literal,
385
+ outputMode,
386
+ caseSensitive,
387
+ includeHidden,
388
+ glob,
389
+ context: contextLines,
390
+ maxMatches,
391
+ sessionId: context.sessionId,
392
+ runId: context.runId,
393
+ signal: context.signal,
394
+ },
395
+ },
396
+ toolCallId,
397
+ "grep",
398
+ );
399
+ if (!policyCheck.allowed) return policyCheck.result;
400
+
401
+ const deadlineAt = Date.now() + limits.maxTimeMs;
402
+ let worker: RegexWorker | undefined;
403
+ let matcher: RepositoryFileMatcher | undefined;
404
+ if (literal) {
405
+ matcher = literalMatcher(pattern, caseSensitive, limits.maxPatternBytes, limits.maxLineBytes);
406
+ } else if (!indexedEnabled || mode === "literal") {
407
+ const regexWorker = new RegexWorker(pattern, caseSensitive, limits.maxLineBytes);
408
+ worker = regexWorker;
409
+ try {
410
+ await regexWorker.start();
411
+ } catch (error) {
412
+ await regexWorker.terminate();
413
+ return errorResult(toolCallId, error instanceof Error ? error.message : String(error));
414
+ }
415
+ matcher = async (text, context) => {
416
+ try {
417
+ return await regexWorker.match(text, context, deadlineAt - Date.now());
418
+ } catch (error) {
419
+ if (error instanceof GrepTimeout) {
420
+ await regexWorker.terminate();
421
+ throw new RepositoryError("Repository operation exceeded time limit");
422
+ }
423
+ throw error;
424
+ }
425
+ };
426
+ }
427
+
428
+ try {
429
+ const result = await ops.search({
430
+ root: cwd,
431
+ query: pattern,
432
+ path,
433
+ mode,
434
+ outputMode,
435
+ caseSensitive,
436
+ includeHidden,
437
+ exclude: limits.exclude,
438
+ glob,
439
+ matcher,
440
+ context: contextLines,
441
+ maxMatches: maxMatches ?? limits.maxMatches,
442
+ signal: context.signal,
443
+ deadlineMs: Math.max(0, deadlineAt - Date.now()),
444
+ });
445
+ return {
446
+ toolCallId,
447
+ name: "grep",
448
+ content: [{ type: "text", text: formatSearchResult(result, outputMode) }],
449
+ metadata: buildSearchMetadata(result, outputMode),
450
+ };
451
+ } catch (error) {
452
+ const message = error instanceof RepositoryError ? error.message : error instanceof Error ? error.message : String(error);
453
+ return errorResult(toolCallId, message);
454
+ } finally {
455
+ await worker?.terminate();
456
+ }
457
+ },
458
+ };
459
+ }
@@ -0,0 +1,48 @@
1
+ import type { OwnershipScope, Principal } from "@arnilo/prism";
2
+
3
+ /**
4
+ * Structural stand-in for a host-verified identity.
5
+ * ponytail: active/ownership check only — shape and byte limits stay in `@arnilo/prism-extensions/governance`.
6
+ * This package must not depend on it (plan 159 Task 1 (g)).
7
+ */
8
+ export interface AgentIdentity {
9
+ readonly tenantId: string;
10
+ readonly accountId?: string;
11
+ readonly userId?: string;
12
+ readonly principal: Principal;
13
+ readonly scopes: readonly string[];
14
+ readonly issuedAt: string;
15
+ readonly expiresAt?: string;
16
+ readonly revokedAt?: string;
17
+ readonly verified: true;
18
+ }
19
+
20
+ export function assertIdentityActive(identity: AgentIdentity, now = Date.now()): void {
21
+ if (identity.verified !== true || identity.tenantId.length === 0 || identity.scopes.length === 0) {
22
+ throw new Error("Identity is not host-verified");
23
+ }
24
+ if (identity.revokedAt !== undefined) {
25
+ const revokedAt = Date.parse(identity.revokedAt);
26
+ if (!Number.isFinite(revokedAt) || revokedAt <= now) throw new Error("Identity has been revoked");
27
+ }
28
+ if (identity.expiresAt !== undefined) {
29
+ const expiresAt = Date.parse(identity.expiresAt);
30
+ if (!Number.isFinite(expiresAt) || expiresAt <= now) throw new Error("Identity has expired");
31
+ }
32
+ const issuedAt = Date.parse(identity.issuedAt);
33
+ if (!Number.isFinite(issuedAt) || issuedAt > now + 60_000) throw new Error("Identity issuedAt is invalid");
34
+ }
35
+
36
+ export function assertIdentityMatchesOwnership(identity: AgentIdentity, ownership?: OwnershipScope): void {
37
+ assertIdentityActive(identity);
38
+ if (!ownership) return;
39
+ if (ownership.tenantId !== undefined && ownership.tenantId !== identity.tenantId) {
40
+ throw new Error("Ownership tenant conflicts with identity");
41
+ }
42
+ if (ownership.accountId !== undefined && ownership.accountId !== identity.accountId) {
43
+ throw new Error("Ownership account conflicts with identity");
44
+ }
45
+ if (ownership.userId !== undefined && ownership.userId !== identity.userId) {
46
+ throw new Error("Ownership user conflicts with identity");
47
+ }
48
+ }