clinkx 1.0.0 → 1.1.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 (333) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/README.md +184 -112
  3. package/clinkx-workflows/dist/chairman-knobs.d.ts +24 -0
  4. package/clinkx-workflows/dist/chairman-knobs.js +27 -0
  5. package/clinkx-workflows/dist/chairman-knobs.js.map +1 -0
  6. package/clinkx-workflows/dist/clink-client.d.ts +10 -0
  7. package/clinkx-workflows/dist/clink-client.js +39 -1
  8. package/clinkx-workflows/dist/clink-client.js.map +1 -1
  9. package/clinkx-workflows/dist/config.d.ts +29 -1
  10. package/clinkx-workflows/dist/config.js +35 -2
  11. package/clinkx-workflows/dist/config.js.map +1 -1
  12. package/clinkx-workflows/dist/context-preflight.d.ts +5 -0
  13. package/clinkx-workflows/dist/context-preflight.js.map +1 -1
  14. package/clinkx-workflows/dist/council-execution-policy.d.ts +68 -0
  15. package/clinkx-workflows/dist/council-execution-policy.js +110 -0
  16. package/clinkx-workflows/dist/council-execution-policy.js.map +1 -0
  17. package/clinkx-workflows/dist/council-expander.d.ts +9 -8
  18. package/clinkx-workflows/dist/council-expander.js +18 -9
  19. package/clinkx-workflows/dist/council-expander.js.map +1 -1
  20. package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5557 -4
  21. package/clinkx-workflows/dist/dashboard/activity-contract.js +55 -3
  22. package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -1
  23. package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +9 -5
  24. package/clinkx-workflows/dist/dashboard/activity-reader.js +15 -2
  25. package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -1
  26. package/clinkx-workflows/dist/dashboard/api-routes.d.ts +4 -0
  27. package/clinkx-workflows/dist/dashboard/api-routes.js +133 -25
  28. package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -1
  29. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.d.ts +3865 -0
  30. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js +69 -0
  31. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js.map +1 -0
  32. package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +36 -2
  33. package/clinkx-workflows/dist/dashboard/context-run-reader.js +142 -19
  34. package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -1
  35. package/clinkx-workflows/dist/dashboard/contracts.d.ts +20027 -480
  36. package/clinkx-workflows/dist/dashboard/contracts.js +133 -6
  37. package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
  38. package/clinkx-workflows/dist/dashboard/index.d.ts +4 -0
  39. package/clinkx-workflows/dist/dashboard/index.js +4 -0
  40. package/clinkx-workflows/dist/dashboard/index.js.map +1 -1
  41. package/clinkx-workflows/dist/dashboard/invocation-contract.d.ts +786 -0
  42. package/clinkx-workflows/dist/dashboard/invocation-contract.js +201 -0
  43. package/clinkx-workflows/dist/dashboard/invocation-contract.js.map +1 -0
  44. package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +14 -1
  45. package/clinkx-workflows/dist/dashboard/lazy-content.js +53 -2
  46. package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -1
  47. package/clinkx-workflows/dist/dashboard/native-transcript/contract.d.ts +90 -0
  48. package/clinkx-workflows/dist/dashboard/native-transcript/contract.js +31 -0
  49. package/clinkx-workflows/dist/dashboard/native-transcript/contract.js.map +1 -0
  50. package/clinkx-workflows/dist/dashboard/native-transcript/projection.d.ts +12 -0
  51. package/clinkx-workflows/dist/dashboard/native-transcript/projection.js +286 -0
  52. package/clinkx-workflows/dist/dashboard/native-transcript/projection.js.map +1 -0
  53. package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +147 -4
  54. package/clinkx-workflows/dist/dashboard/progress-report-contract.js +11 -11
  55. package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -1
  56. package/clinkx-workflows/dist/dashboard/progress-report-reader.js +4 -56
  57. package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -1
  58. package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +2 -1
  59. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +6 -5
  60. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -1
  61. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.d.ts +194 -0
  62. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js +89 -0
  63. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js.map +1 -0
  64. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.d.ts +26 -0
  65. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js +102 -0
  66. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js.map +1 -0
  67. package/clinkx-workflows/dist/dashboard/request-admission.js +4 -0
  68. package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -1
  69. package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
  70. package/clinkx-workflows/dist/dashboard/scanner.js +52 -8
  71. package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
  72. package/clinkx-workflows/dist/dashboard/server-factory.d.ts +14 -1
  73. package/clinkx-workflows/dist/dashboard/server-factory.js +73 -21
  74. package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -1
  75. package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +223 -56
  76. package/clinkx-workflows/dist/dashboard/session-log-contract.js +26 -0
  77. package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -1
  78. package/clinkx-workflows/dist/dashboard/session-log-journal.js +10 -3
  79. package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -1
  80. package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +9 -1
  81. package/clinkx-workflows/dist/dashboard/session-log-redaction.js +793 -59
  82. package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
  83. package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
  84. package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
  85. package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
  86. package/clinkx-workflows/dist/delegate-finalizer-policy.d.ts +26 -0
  87. package/clinkx-workflows/dist/delegate-finalizer-policy.js +72 -0
  88. package/clinkx-workflows/dist/delegate-finalizer-policy.js.map +1 -0
  89. package/clinkx-workflows/dist/delegate-signature.d.ts +14 -0
  90. package/clinkx-workflows/dist/delegate-signature.js +14 -0
  91. package/clinkx-workflows/dist/delegate-signature.js.map +1 -0
  92. package/clinkx-workflows/dist/delegate-validate.d.ts +32 -2
  93. package/clinkx-workflows/dist/delegate-validate.js +51 -0
  94. package/clinkx-workflows/dist/delegate-validate.js.map +1 -1
  95. package/clinkx-workflows/dist/engine.d.ts +113 -3
  96. package/clinkx-workflows/dist/engine.js +476 -80
  97. package/clinkx-workflows/dist/engine.js.map +1 -1
  98. package/clinkx-workflows/dist/errors.d.ts +22 -2
  99. package/clinkx-workflows/dist/errors.js +30 -2
  100. package/clinkx-workflows/dist/errors.js.map +1 -1
  101. package/clinkx-workflows/dist/fidelity.d.ts +47 -1
  102. package/clinkx-workflows/dist/fidelity.js +54 -1
  103. package/clinkx-workflows/dist/fidelity.js.map +1 -1
  104. package/clinkx-workflows/dist/fingerprint.d.ts +24 -6
  105. package/clinkx-workflows/dist/fingerprint.js +32 -9
  106. package/clinkx-workflows/dist/fingerprint.js.map +1 -1
  107. package/clinkx-workflows/dist/index.js +53 -1
  108. package/clinkx-workflows/dist/index.js.map +1 -1
  109. package/clinkx-workflows/dist/logger.d.ts +1 -1
  110. package/clinkx-workflows/dist/logger.js +17 -3
  111. package/clinkx-workflows/dist/logger.js.map +1 -1
  112. package/clinkx-workflows/dist/monotonic-clock.d.ts +19 -0
  113. package/clinkx-workflows/dist/monotonic-clock.js +22 -0
  114. package/clinkx-workflows/dist/monotonic-clock.js.map +1 -0
  115. package/clinkx-workflows/dist/plan-review/contract.d.ts +52 -52
  116. package/clinkx-workflows/dist/plan-review/publication.d.ts +4 -4
  117. package/clinkx-workflows/dist/plan-review/run-state.d.ts +16 -16
  118. package/clinkx-workflows/dist/roster-contract.d.ts +889 -0
  119. package/clinkx-workflows/dist/roster-contract.js +180 -0
  120. package/clinkx-workflows/dist/roster-contract.js.map +1 -0
  121. package/clinkx-workflows/dist/rosters.d.ts +14 -0
  122. package/clinkx-workflows/dist/rosters.js +169 -0
  123. package/clinkx-workflows/dist/rosters.js.map +1 -0
  124. package/clinkx-workflows/dist/run-health.d.ts +7 -0
  125. package/clinkx-workflows/dist/run-health.js +25 -0
  126. package/clinkx-workflows/dist/run-health.js.map +1 -0
  127. package/clinkx-workflows/dist/schema.d.ts +64 -0
  128. package/clinkx-workflows/dist/schema.js +24 -0
  129. package/clinkx-workflows/dist/schema.js.map +1 -1
  130. package/clinkx-workflows/dist/server.js +2 -0
  131. package/clinkx-workflows/dist/server.js.map +1 -1
  132. package/clinkx-workflows/dist/shutdown.d.ts +15 -2
  133. package/clinkx-workflows/dist/shutdown.js +60 -16
  134. package/clinkx-workflows/dist/shutdown.js.map +1 -1
  135. package/clinkx-workflows/dist/state-schema.d.ts +23 -0
  136. package/clinkx-workflows/dist/state.d.ts +5 -0
  137. package/clinkx-workflows/dist/state.js +128 -21
  138. package/clinkx-workflows/dist/state.js.map +1 -1
  139. package/clinkx-workflows/dist/stdio-watchdog.d.ts +52 -0
  140. package/clinkx-workflows/dist/stdio-watchdog.js +212 -0
  141. package/clinkx-workflows/dist/stdio-watchdog.js.map +1 -0
  142. package/clinkx-workflows/dist/types.d.ts +6 -0
  143. package/clinkx-workflows/dist/validators/council.d.ts +50 -50
  144. package/clinkx-workflows/dist/validators/plan-review.d.ts +3 -3
  145. package/clinkx-workflows/dist/workflow-tools.d.ts +70 -0
  146. package/clinkx-workflows/dist/workflow-tools.js +760 -168
  147. package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
  148. package/clinkx-workflows/templates/council/answer-chairman.txt +3 -0
  149. package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +3 -0
  150. package/clinkx-workflows/templates/council/answer-specialist.txt +3 -0
  151. package/clinkx-workflows/templates/council/chairman.txt +3 -0
  152. package/clinkx-workflows/templates/council/code-review-chairman.txt +3 -0
  153. package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +3 -0
  154. package/clinkx-workflows/templates/council/code-review-reviewer.txt +3 -0
  155. package/clinkx-workflows/templates/council/debug-chairman.txt +3 -0
  156. package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +3 -0
  157. package/clinkx-workflows/templates/council/debug-debugger.txt +3 -0
  158. package/clinkx-workflows/templates/council/discover-analyst.txt +3 -0
  159. package/clinkx-workflows/templates/council/discover-chairman.txt +3 -0
  160. package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +3 -0
  161. package/clinkx-workflows/templates/council/member.txt +3 -0
  162. package/clinkx-workflows/templates/council/plan-review-chairman.txt +3 -0
  163. package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +3 -0
  164. package/clinkx-workflows/templates/council/plan-review-reviewer.txt +3 -0
  165. package/clinkx-workflows/templates/council/reviewer.txt +3 -0
  166. package/clinkx-workflows/workflows/council-answer.yaml +3 -0
  167. package/clinkx-workflows/workflows/council-code-review.yaml +3 -0
  168. package/clinkx-workflows/workflows/council-debug.yaml +3 -0
  169. package/clinkx-workflows/workflows/council-default.yaml +7 -0
  170. package/clinkx-workflows/workflows/council-discover.yaml +3 -0
  171. package/clinkx-workflows/workflows/council-plan-review.yaml +3 -0
  172. package/conf/adapters/agy.json +9 -2
  173. package/conf/adapters/claude.json +7 -0
  174. package/conf/adapters/codex.json +7 -0
  175. package/conf/adapters/glm.json +19 -3
  176. package/conf/adapters/grok.json +9 -2
  177. package/conf/adapters/hapi/agy.json +9 -2
  178. package/conf/adapters/hapi/claude.json +7 -0
  179. package/conf/adapters/hapi/codex.json +7 -0
  180. package/conf/adapters/hapi/glm.json +20 -4
  181. package/conf/rosters/default-council.json +21 -0
  182. package/dist/activity-journal.d.ts +17 -3
  183. package/dist/activity-journal.js +404 -79
  184. package/dist/activity-journal.js.map +1 -1
  185. package/dist/codex/agent-tools.js +36 -0
  186. package/dist/codex/agent-tools.js.map +1 -1
  187. package/dist/codex/app-server-client.d.ts +3 -0
  188. package/dist/codex/app-server-client.js +11 -0
  189. package/dist/codex/app-server-client.js.map +1 -1
  190. package/dist/codex/app-server-registry.d.ts +18 -0
  191. package/dist/codex/app-server-registry.js +88 -2
  192. package/dist/codex/app-server-registry.js.map +1 -1
  193. package/dist/codex/client-types.d.ts +7 -1
  194. package/dist/codex/protocol.d.ts +4 -4
  195. package/dist/config.d.ts +220 -23
  196. package/dist/config.js +104 -1
  197. package/dist/config.js.map +1 -1
  198. package/dist/context-builder/bundle.d.ts +13 -0
  199. package/dist/context-builder/bundle.js +46 -0
  200. package/dist/context-builder/bundle.js.map +1 -1
  201. package/dist/context-builder/delegate-finalizer-effective.d.ts +33 -0
  202. package/dist/context-builder/delegate-finalizer-effective.js +82 -0
  203. package/dist/context-builder/delegate-finalizer-effective.js.map +1 -0
  204. package/dist/context-builder/delegate-packet.d.ts +18 -0
  205. package/dist/context-builder/delegate-packet.js +73 -9
  206. package/dist/context-builder/delegate-packet.js.map +1 -1
  207. package/dist/context-builder/delegate-reconstruction.d.ts +29 -0
  208. package/dist/context-builder/delegate-reconstruction.js +110 -41
  209. package/dist/context-builder/delegate-reconstruction.js.map +1 -1
  210. package/dist/context-builder/delegate-validation.d.ts +31 -0
  211. package/dist/context-builder/delegate-validation.js +87 -10
  212. package/dist/context-builder/delegate-validation.js.map +1 -1
  213. package/dist/context-builder/delegate.d.ts +9 -0
  214. package/dist/context-builder/delegate.js +210 -59
  215. package/dist/context-builder/delegate.js.map +1 -1
  216. package/dist/context-builder/diagnostics.d.ts +1 -1
  217. package/dist/context-builder/diagnostics.js.map +1 -1
  218. package/dist/context-builder/discovery-progress.d.ts +25 -0
  219. package/dist/context-builder/discovery-progress.js +64 -0
  220. package/dist/context-builder/discovery-progress.js.map +1 -0
  221. package/dist/context-builder/discovery-watchdog.d.ts +15 -0
  222. package/dist/context-builder/discovery-watchdog.js +39 -0
  223. package/dist/context-builder/discovery-watchdog.js.map +1 -0
  224. package/dist/context-builder/discovery.d.ts +7 -0
  225. package/dist/context-builder/discovery.js +119 -7
  226. package/dist/context-builder/discovery.js.map +1 -1
  227. package/dist/context-builder/external-gaps.d.ts +73 -0
  228. package/dist/context-builder/external-gaps.js +248 -0
  229. package/dist/context-builder/external-gaps.js.map +1 -0
  230. package/dist/context-builder/globs.d.ts +1 -0
  231. package/dist/context-builder/globs.js +15 -0
  232. package/dist/context-builder/globs.js.map +1 -1
  233. package/dist/context-builder/handler.d.ts +42 -1
  234. package/dist/context-builder/handler.js +690 -62
  235. package/dist/context-builder/handler.js.map +1 -1
  236. package/dist/context-builder/invocation-sidecar.d.ts +41 -0
  237. package/dist/context-builder/invocation-sidecar.js +189 -0
  238. package/dist/context-builder/invocation-sidecar.js.map +1 -0
  239. package/dist/context-builder/lifecycle-contract.d.ts +51 -51
  240. package/dist/context-builder/mutation-lock.d.ts +32 -0
  241. package/dist/context-builder/mutation-lock.js +321 -0
  242. package/dist/context-builder/mutation-lock.js.map +1 -0
  243. package/dist/context-builder/review-coverage.d.ts +32 -0
  244. package/dist/context-builder/review-coverage.js +130 -23
  245. package/dist/context-builder/review-coverage.js.map +1 -1
  246. package/dist/context-builder/schema.d.ts +76 -1
  247. package/dist/context-builder/schema.js +21 -1
  248. package/dist/context-builder/schema.js.map +1 -1
  249. package/dist/context-builder/secrets.d.ts +31 -0
  250. package/dist/context-builder/secrets.js +871 -84
  251. package/dist/context-builder/secrets.js.map +1 -1
  252. package/dist/context-builder/status.d.ts +1 -1
  253. package/dist/context-builder/status.js +1 -0
  254. package/dist/context-builder/status.js.map +1 -1
  255. package/dist/context-builder/storage.d.ts +29 -5
  256. package/dist/context-builder/storage.js +311 -236
  257. package/dist/context-builder/storage.js.map +1 -1
  258. package/dist/context-builder/versions.d.ts +19 -4
  259. package/dist/context-builder/versions.js +19 -4
  260. package/dist/context-builder/versions.js.map +1 -1
  261. package/dist/context-builder/workflow-runner.d.ts +6 -0
  262. package/dist/context-builder/workflow-runner.js +363 -173
  263. package/dist/context-builder/workflow-runner.js.map +1 -1
  264. package/dist/dashboard-adapter-catalog.js +7 -0
  265. package/dist/dashboard-adapter-catalog.js.map +1 -1
  266. package/dist/dashboard-cli.d.ts +2 -0
  267. package/dist/dashboard-cli.js +40 -2
  268. package/dist/dashboard-cli.js.map +1 -1
  269. package/dist/dashboard-companion/client.d.ts +1 -0
  270. package/dist/dashboard-companion/client.js +22 -3
  271. package/dist/dashboard-companion/client.js.map +1 -1
  272. package/dist/dashboard-companion/protocol.js +4 -1
  273. package/dist/dashboard-companion/protocol.js.map +1 -1
  274. package/dist/dashboard-gateway/index.d.ts +1 -0
  275. package/dist/dashboard-gateway/index.js +33 -2
  276. package/dist/dashboard-gateway/index.js.map +1 -1
  277. package/dist/handler.js +5 -1
  278. package/dist/handler.js.map +1 -1
  279. package/dist/index.d.ts +18 -1
  280. package/dist/index.js +145 -7
  281. package/dist/index.js.map +1 -1
  282. package/dist/logger.d.ts +1 -1
  283. package/dist/logger.js +18 -3
  284. package/dist/logger.js.map +1 -1
  285. package/dist/parsers/agy-stream-json.d.ts +14 -0
  286. package/dist/parsers/agy-stream-json.js +172 -0
  287. package/dist/parsers/agy-stream-json.js.map +1 -0
  288. package/dist/parsers/extract.js +5 -1
  289. package/dist/parsers/extract.js.map +1 -1
  290. package/dist/parsers/observed-invocation.d.ts +20 -0
  291. package/dist/parsers/observed-invocation.js +42 -0
  292. package/dist/parsers/observed-invocation.js.map +1 -0
  293. package/dist/pipeline.d.ts +13 -0
  294. package/dist/pipeline.js +60 -10
  295. package/dist/pipeline.js.map +1 -1
  296. package/dist/progress-report/lock.d.ts +74 -0
  297. package/dist/progress-report/lock.js +343 -0
  298. package/dist/progress-report/lock.js.map +1 -0
  299. package/dist/progress-report/schema.d.ts +808 -0
  300. package/dist/progress-report/schema.js +119 -0
  301. package/dist/progress-report/schema.js.map +1 -0
  302. package/dist/progress-report/store.d.ts +66 -0
  303. package/dist/progress-report/store.js +766 -0
  304. package/dist/progress-report/store.js.map +1 -0
  305. package/dist/progress-report/tools.d.ts +20 -0
  306. package/dist/progress-report/tools.js +141 -0
  307. package/dist/progress-report/tools.js.map +1 -0
  308. package/dist/prompt-mode.d.ts +1 -1
  309. package/dist/prompt-mode.js +7 -2
  310. package/dist/prompt-mode.js.map +1 -1
  311. package/dist/runner.d.ts +7 -0
  312. package/dist/runner.js +10 -3
  313. package/dist/runner.js.map +1 -1
  314. package/dist/safety.d.ts +50 -0
  315. package/dist/safety.js +182 -1
  316. package/dist/safety.js.map +1 -1
  317. package/dist/schema.d.ts +8 -0
  318. package/dist/schema.js +20 -0
  319. package/dist/schema.js.map +1 -1
  320. package/dist/server.d.ts +6 -1
  321. package/dist/server.js +94 -8
  322. package/dist/server.js.map +1 -1
  323. package/dist/session-log-stream.d.ts +1 -1
  324. package/dist/session-log-stream.js +280 -33
  325. package/dist/session-log-stream.js.map +1 -1
  326. package/dist/stdio-activity.d.ts +28 -0
  327. package/dist/stdio-activity.js +72 -0
  328. package/dist/stdio-activity.js.map +1 -0
  329. package/docs/configuration.md +426 -0
  330. package/docs/security.md +293 -0
  331. package/package.json +5 -5
  332. package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +0 -25
  333. package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +0 -1
@@ -3,28 +3,208 @@ const REDACTION_MARKER = "[REDACTED]";
3
3
  const MAX_EVENT_TEXT_CHARS = 32_768;
4
4
  const MAX_EVENT_SUMMARY_CHARS = 4_096;
5
5
  const MAX_REDACTIONS = 10_000;
6
- const BEARER_PATTERN = /\bBearer\s+(?!\[REDACTED\])[^\s,;]+/gi;
7
- const BASIC_AUTHORIZATION_PATTERN = /(\b(?:Proxy-)?Authorization\s*:\s*Basic\s+)(?!\[REDACTED\])([A-Za-z0-9+/=._~-]+)/gi;
6
+ const MAX_ARGUMENT_BYTES = 32_768;
7
+ const MAX_ARGUMENT_INPUT_BYTES = 1024 * 1024;
8
+ const MAX_ARGUMENT_DEPTH = 64;
9
+ const MAX_ARGUMENT_NODES = 10_000;
10
+ const SENSITIVE_KEY_SOURCE = "api[-_]?key|apikey|access[-_]?token|auth[-_]?token|token|secret|client[-_]?secret|password|authorization|proxy[-_]?authorization|cookie|set[-_]?cookie|private[-_]?key";
11
+ const SENSITIVE_KEY_PATTERN = new RegExp(`^(?:${SENSITIVE_KEY_SOURCE}|pgpassword)$`, "i");
12
+ const BEARER_PATTERN = /\bBearer\s+(?!\[REDACTED\])[^\s,;"']+/gi;
8
13
  const PRIVATE_KEY_SIGNAL_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/i;
9
- const SENSITIVE_QUERY_PATTERN = /([?&](?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|client[-_]?secret|signature|sig|key)=)(?!\[REDACTED\])([^&#\s]+)/gi;
10
- const QUOTED_ASSIGNMENT_PATTERN = /(\b(?:api[-_]?key|apikey|access[-_]?token|auth[-_]?token|token|secret|client[-_]?secret|password)\b\s*[:=]\s*)(["'])(?!\[REDACTED\])([^\r\n]*?)\2/gi;
11
- const BARE_ASSIGNMENT_PATTERN = /(\b(?:api[-_]?key|apikey|access[-_]?token|auth[-_]?token|token|secret|client[-_]?secret|password)\b\s*[:=]\s*)(?!["']|\[REDACTED\])([^\s,;&#}\]]+)/gi;
12
- const VENDOR_TOKEN_PATTERN = /\b(?:sk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{12,}|github_pat_[A-Za-z0-9_]{12,}|gh[pousr]_[A-Za-z0-9]{12,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g;
14
+ const SENSITIVE_QUERY_PATTERN = /([?&](?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|client[-_]?secret|signature|sig|key)=)(?!\[REDACTED\])([^&#\s"']+)/gi;
15
+ const ASSIGNMENT_PATTERN = /(^|[^A-Za-z0-9_])([A-Za-z_][A-Za-z0-9_]{0,127})([ \t]*[=:][ \t]*)/g;
16
+ const VENDOR_TOKEN_PATTERN = /\b(?:sk-(?:ant-|proj-|svcacct-)?[A-Za-z0-9_-]{12,}|(?:sk|pk|github_pat|cxel|cxmsg|npm)_[A-Za-z0-9_:-]{12,}|gh[pousr]_[A-Za-z0-9]{12,}|xox[baprs]-[A-Za-z0-9-]{10,}|glpat-[A-Za-z0-9_-]{12,}|(?:AKIA|ASIA)[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35})\b/g;
17
+ const JWT_PATTERN = /\beyJ[A-Za-z0-9_-]{1,4096}\.[A-Za-z0-9_-]{1,4096}\.[A-Za-z0-9_-]{1,4096}(?![A-Za-z0-9_.-])/g;
18
+ const HEADER_PATTERN = /^(Proxy-Authorization|Authorization|Set-Cookie|Cookie)([ \t]*:[ \t]*)(.*)$/i;
19
+ const SHELL_DELIMITER_PATTERN = /[\s;|&]/;
13
20
  const POSIX_USER_HOME_PATTERN = /\/(?:Users|home)\/[^/\s"'`<>]+/g;
14
21
  const ROOT_USER_HOME_PATTERN = /\/root(?=\/|[\s"'`<>]|$)/g;
15
22
  const WINDOWS_USER_HOME_PATTERN = /\b[A-Za-z]:\\Users\\[^\\/\s"'`<>]+/gi;
16
- const SENSITIVE_ASSIGNMENT_PREFIX_PATTERN = /\b(?:api[-_]?key|apikey|access[-_]?token|auth[-_]?token|token|secret|client[-_]?secret|password)\b\s*[:=]\s*/gi;
17
- export function prepareSessionLogEventV1(input) {
23
+ function isSensitiveKey(key) {
24
+ const normalized = key.toLowerCase().replace(/[-_ ]+/g, "_");
25
+ return SENSITIVE_KEY_PATTERN.test(normalized) || /(?:^|_)(?:token|secret|password|api_key|secret_access_key)$/.test(normalized);
26
+ }
27
+ const preparationHistory = new WeakMap();
28
+ export function prepareSessionLogEventV1(input, options) {
29
+ const prior = input !== null && typeof input === "object"
30
+ ? preparationHistory.get(input)
31
+ : undefined;
32
+ if (prior !== undefined) {
33
+ if (options?.coverage === undefined || options.coverage === prior.redaction.coverage)
34
+ return prior;
35
+ const result = Object.freeze({ ...prior, redaction: Object.freeze({ ...prior.redaction, coverage: options.coverage }) });
36
+ preparationHistory.set(prior.event, result);
37
+ return result;
38
+ }
18
39
  const normalized = validateNormalizedEvent(input);
19
40
  const prepared = redactEvent(normalized);
20
41
  assertTextWithinContract(prepared.event);
42
+ if (prepared.event.kind === "tool_call" && prepared.event.arguments !== undefined) {
43
+ Object.freeze(prepared.event.arguments);
44
+ }
21
45
  const event = Object.freeze(prepared.event);
46
+ const count = Math.min(MAX_REDACTIONS, prepared.count);
22
47
  const redaction = Object.freeze({
23
48
  ran: true,
24
- status: prepared.count === 0 ? "no_matches" : "redacted",
25
- count: prepared.count,
49
+ status: count === 0 ? "no_matches" : "redacted",
50
+ count,
51
+ ...(options?.coverage === undefined ? {} : { coverage: options.coverage }),
52
+ });
53
+ const result = Object.freeze({
54
+ event,
55
+ redaction,
56
+ ...(prepared.truncated ? { truncated: true } : {}),
57
+ ...(event.kind === "tool_call" && event.arguments !== undefined && event.arguments.omission !== "none"
58
+ ? { omitted: true } : {}),
59
+ });
60
+ preparationHistory.set(event, result);
61
+ return result;
62
+ }
63
+ class ArgumentAdmissionError extends Error {
64
+ reason;
65
+ constructor(reason) {
66
+ super("argument admission failed");
67
+ this.reason = reason;
68
+ }
69
+ }
70
+ export function prepareSessionLogToolCallV1(event, value, options) {
71
+ const args = sanitizeArguments(value);
72
+ const candidate = { ...event, summary: args.summary ?? event.summary, arguments: args.detail };
73
+ const prepared = prepareSessionLogEventV1(candidate, options);
74
+ const count = Math.min(MAX_REDACTIONS, args.count + prepared.redaction.count);
75
+ const result = Object.freeze({
76
+ ...prepared,
77
+ redaction: Object.freeze({ ...prepared.redaction, status: count > 0 ? "redacted" : "no_matches", count }),
26
78
  });
27
- return Object.freeze({ event, redaction });
79
+ preparationHistory.set(result.event, result);
80
+ return result;
81
+ }
82
+ function sanitizeArgumentDetail(detail) {
83
+ if (detail.omission !== "none")
84
+ return { detail: { ...detail }, count: 0 };
85
+ if (detail.content.length > MAX_ARGUMENT_INPUT_BYTES || Buffer.byteLength(detail.content, "utf8") > MAX_ARGUMENT_INPUT_BYTES) {
86
+ return { detail: { content: "", omission: "size_limit" }, count: 0 };
87
+ }
88
+ try {
89
+ return sanitizeArguments(JSON.parse(detail.content));
90
+ }
91
+ catch {
92
+ return { detail: { content: "", omission: "invalid" }, count: 0 };
93
+ }
94
+ }
95
+ function sanitizeArguments(input, baseDepth = 0) {
96
+ let nodes = 0;
97
+ let bytes = 0;
98
+ let count = 0;
99
+ const active = new Set();
100
+ const charge = (text) => {
101
+ if (text.length > MAX_ARGUMENT_INPUT_BYTES)
102
+ throw new ArgumentAdmissionError("size_limit");
103
+ bytes += Buffer.byteLength(JSON.stringify(text), "utf8");
104
+ if (bytes > MAX_ARGUMENT_INPUT_BYTES)
105
+ throw new ArgumentAdmissionError("size_limit");
106
+ };
107
+ const visit = (value, depth) => {
108
+ if (++nodes > MAX_ARGUMENT_NODES || depth > MAX_ARGUMENT_DEPTH)
109
+ throw new ArgumentAdmissionError("structure_limit");
110
+ if (value === null || typeof value === "boolean")
111
+ return value;
112
+ if (typeof value === "number") {
113
+ if (!Number.isFinite(value))
114
+ throw new ArgumentAdmissionError("invalid");
115
+ return value;
116
+ }
117
+ if (typeof value === "string") {
118
+ charge(value);
119
+ const trimmed = value.trimStart();
120
+ if (/^[{["]/.test(trimmed)) {
121
+ let decoded;
122
+ let valid = false;
123
+ try {
124
+ decoded = JSON.parse(value);
125
+ valid = true;
126
+ }
127
+ catch {
128
+ valid = false;
129
+ }
130
+ if (valid)
131
+ return JSON.stringify(visit(decoded, depth + 1));
132
+ }
133
+ const redacted = redactText(value, Number.POSITIVE_INFINITY, depth + 1);
134
+ count += redacted.count;
135
+ return redacted.text;
136
+ }
137
+ if (typeof value !== "object" || value === undefined)
138
+ throw new ArgumentAdmissionError("invalid");
139
+ if (active.has(value))
140
+ throw new ArgumentAdmissionError("invalid");
141
+ active.add(value);
142
+ try {
143
+ if (Array.isArray(value)) {
144
+ if (value.length + nodes > MAX_ARGUMENT_NODES)
145
+ throw new ArgumentAdmissionError("structure_limit");
146
+ const result = [];
147
+ for (let index = 0; index < value.length; index += 1) {
148
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
149
+ if (descriptor === undefined || !("value" in descriptor))
150
+ throw new ArgumentAdmissionError("invalid");
151
+ result.push(visit(descriptor.value, depth + 1));
152
+ }
153
+ return result;
154
+ }
155
+ if (!isPlainRecord(value))
156
+ throw new ArgumentAdmissionError("invalid");
157
+ const keys = Object.keys(value);
158
+ if (keys.length + nodes > MAX_ARGUMENT_NODES)
159
+ throw new ArgumentAdmissionError("structure_limit");
160
+ const result = Object.create(null);
161
+ const sensitiveHeaderPair = typeof value["name"] === "string" && isSensitiveKey(value["name"]);
162
+ for (const key of keys) {
163
+ charge(key);
164
+ const child = visit(value[key], depth + 1);
165
+ const redactedKey = redactText(key);
166
+ count += redactedKey.count;
167
+ const retainedKey = redactedKey.count === 0 ? key
168
+ : `redacted_key_${createHash("sha256").update(key).digest("hex")}`;
169
+ if (isSensitiveKey(key) || (key === "value" && sensitiveHeaderPair)) {
170
+ if (value[key] !== REDACTION_MARKER)
171
+ count += 1;
172
+ result[retainedKey] = REDACTION_MARKER;
173
+ }
174
+ else {
175
+ if (Object.hasOwn(result, retainedKey))
176
+ throw new ArgumentAdmissionError("invalid");
177
+ result[retainedKey] = child;
178
+ }
179
+ }
180
+ return result;
181
+ }
182
+ finally {
183
+ active.delete(value);
184
+ }
185
+ };
186
+ try {
187
+ const value = visit(input, baseDepth);
188
+ const content = JSON.stringify(value);
189
+ let summary;
190
+ if (isPlainRecord(value)) {
191
+ for (const key of ["command", "cmd", "CommandLine", "file_path", "path", "pattern", "query"]) {
192
+ if (typeof value[key] === "string" && value[key].length > 0) {
193
+ summary = value[key];
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ return {
199
+ detail: Buffer.byteLength(content, "utf8") > MAX_ARGUMENT_BYTES
200
+ ? { content: "", omission: "size_limit" } : { content, omission: "none" },
201
+ ...(summary === undefined ? {} : { summary }),
202
+ count,
203
+ };
204
+ }
205
+ catch (error) {
206
+ return { detail: { content: "", omission: error instanceof ArgumentAdmissionError ? error.reason : "invalid" }, count };
207
+ }
28
208
  }
29
209
  function assertTextWithinContract(event) {
30
210
  let text;
@@ -60,27 +240,31 @@ function redactEvent(event) {
60
240
  switch (event.kind) {
61
241
  case "user":
62
242
  case "assistant": {
63
- const redacted = redactText(event.content, MAX_EVENT_TEXT_CHARS);
64
- return { event: { ...event, content: redacted.text }, count: redacted.count };
243
+ const redacted = redactText(event.content, MAX_EVENT_TEXT_CHARS, 0, "prose");
244
+ return { event: { ...event, content: redacted.text }, count: redacted.count,
245
+ ...(redacted.truncated ? { truncated: true } : {}) };
65
246
  }
66
247
  case "tool_call": {
67
248
  const toolName = redactIdentifier(event.toolName);
68
249
  const callId = redactIdentifier(event.callId);
69
- const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS);
250
+ const args = event.arguments === undefined ? undefined : sanitizeArgumentDetail(event.arguments);
251
+ const redacted = redactText(args?.summary ?? event.summary, MAX_EVENT_SUMMARY_CHARS);
70
252
  return {
71
253
  event: {
72
254
  ...event,
73
255
  toolName: toolName.value,
74
256
  callId: callId.value,
75
257
  summary: redacted.text,
258
+ ...(args === undefined ? {} : { arguments: args.detail }),
76
259
  },
77
- count: toolName.count + callId.count + redacted.count,
260
+ count: toolName.count + callId.count + redacted.count + (args?.count ?? 0),
261
+ ...(redacted.truncated ? { truncated: true } : {}),
78
262
  };
79
263
  }
80
264
  case "tool_result": {
81
265
  const toolName = redactIdentifier(event.toolName);
82
266
  const callId = redactIdentifier(event.callId);
83
- const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS);
267
+ const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS, 0, "prose");
84
268
  return {
85
269
  event: {
86
270
  ...event,
@@ -89,6 +273,7 @@ function redactEvent(event) {
89
273
  summary: redacted.text,
90
274
  },
91
275
  count: toolName.count + callId.count + redacted.count,
276
+ ...(redacted.truncated ? { truncated: true } : {}),
92
277
  };
93
278
  }
94
279
  case "approval_request": {
@@ -97,6 +282,7 @@ function redactEvent(event) {
97
282
  return {
98
283
  event: { ...event, approvalId: approvalId.value, action: redacted.text },
99
284
  count: approvalId.count + redacted.count,
285
+ ...(redacted.truncated ? { truncated: true } : {}),
100
286
  };
101
287
  }
102
288
  case "approval_resolution": {
@@ -108,22 +294,25 @@ function redactEvent(event) {
108
294
  }
109
295
  case "error": {
110
296
  const code = redactIdentifier(event.code);
111
- const redacted = redactText(event.message, MAX_EVENT_SUMMARY_CHARS);
297
+ const redacted = redactText(event.message, MAX_EVENT_SUMMARY_CHARS, 0, "prose");
112
298
  return {
113
299
  event: { ...event, code: code.value, message: redacted.text },
114
300
  count: code.count + redacted.count,
301
+ ...(redacted.truncated ? { truncated: true } : {}),
115
302
  };
116
303
  }
117
304
  case "reasoning": {
118
- const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS);
119
- return { event: { ...event, summary: redacted.text }, count: redacted.count };
305
+ const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS, 0, "prose");
306
+ return { event: { ...event, summary: redacted.text }, count: redacted.count,
307
+ ...(redacted.truncated ? { truncated: true } : {}) };
120
308
  }
121
309
  case "unknown": {
122
310
  const originalKind = redactIdentifier(event.originalKind);
123
- const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS);
311
+ const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS, 0, "prose");
124
312
  return {
125
313
  event: { ...event, originalKind: originalKind.value, summary: redacted.text },
126
314
  count: originalKind.count + redacted.count,
315
+ ...(redacted.truncated ? { truncated: true } : {}),
127
316
  };
128
317
  }
129
318
  case "lifecycle":
@@ -141,58 +330,594 @@ function redactIdentifier(value) {
141
330
  .slice(0, 16);
142
331
  return { value: `redacted_${digest}`, count: 1 };
143
332
  }
144
- function redactText(value, maxChars = Number.POSITIVE_INFINITY) {
145
- if (PRIVATE_KEY_SIGNAL_PATTERN.test(value)) {
146
- return { text: REDACTION_MARKER, count: 1 };
147
- }
148
- let text = value;
333
+ function applyTextEdits(text, edits) {
334
+ const parts = [];
335
+ let end = 0;
149
336
  let count = 0;
337
+ for (const edit of edits) {
338
+ parts.push(text.slice(end, edit.start), edit.text);
339
+ end = edit.end;
340
+ count += edit.count;
341
+ }
342
+ parts.push(text.slice(end));
343
+ return { text: parts.join(""), count };
344
+ }
345
+ function redactEmbeddedJson(text, depth) {
346
+ const edits = [];
347
+ for (let start = 0; start < text.length; start += 1) {
348
+ if (text[start] !== "{" && text[start] !== "[")
349
+ continue;
350
+ let shellEscaped = false;
351
+ const stack = [];
352
+ const decoded = [];
353
+ let quoted = false;
354
+ let escaped = false;
355
+ let end = start;
356
+ for (; end < text.length; end += 1) {
357
+ let char = text[end];
358
+ if (!quoted && char === "\\" && text[end + 1] === '"')
359
+ shellEscaped = true;
360
+ if (shellEscaped && char === "\\" && (text[end + 1] === "\\" || text[end + 1] === '"'))
361
+ char = text[++end];
362
+ decoded.push(char);
363
+ if (quoted) {
364
+ if (escaped)
365
+ escaped = false;
366
+ else if (char === "\\")
367
+ escaped = true;
368
+ else if (char === '"')
369
+ quoted = false;
370
+ }
371
+ else if (char === '"')
372
+ quoted = true;
373
+ else if (char === "{" || char === "[")
374
+ stack.push(char);
375
+ else if (char === "}" || char === "]") {
376
+ const open = stack.pop();
377
+ if ((char === "}" && open !== "{") || (char === "]" && open !== "["))
378
+ break;
379
+ if (stack.length === 0) {
380
+ end += 1;
381
+ break;
382
+ }
383
+ }
384
+ }
385
+ const source = decoded.join("");
386
+ try {
387
+ const parsed = JSON.parse(source);
388
+ const sanitized = sanitizeArguments(parsed, depth + 1);
389
+ if (sanitized.detail.omission !== "none") {
390
+ edits.push({ start, end, text: REDACTION_MARKER, count: Math.max(1, sanitized.count) });
391
+ }
392
+ else if (sanitized.count > 0) {
393
+ const replacement = shellEscaped ? sanitized.detail.content.replace(/[\\"]/g, "\\$&") : sanitized.detail.content;
394
+ edits.push({ start, end, text: replacement, count: sanitized.count });
395
+ }
396
+ }
397
+ catch {
398
+ const keyPattern = /"((?:\\.|[^"\\]){1,512})"\s*:/g;
399
+ let match;
400
+ while ((match = keyPattern.exec(source)) !== null) {
401
+ let key;
402
+ try {
403
+ key = JSON.parse(`"${match[1]}"`);
404
+ }
405
+ catch {
406
+ continue;
407
+ }
408
+ if (typeof key === "string" && isSensitiveKey(key)) {
409
+ return { text: REDACTION_MARKER, count: 1 };
410
+ }
411
+ }
412
+ }
413
+ start = Math.max(start, end - 1);
414
+ }
415
+ return applyTextEdits(text, edits);
416
+ }
417
+ function isWordApostrophe(text, index) {
418
+ const attachedFlag = index >= 2 && /^-[uab]$/.test(text.slice(index - 2, index))
419
+ && (index === 2 || SHELL_DELIMITER_PATTERN.test(text[index - 3]));
420
+ return text[index] === "'"
421
+ && !attachedFlag
422
+ && /(?:\p{L}|\p{N})$/u.test(text.slice(Math.max(0, index - 2), index))
423
+ && /^\p{L}/u.test(text.slice(index + 1, index + 3));
424
+ }
425
+ function shellTokens(text, mode = "command") {
426
+ const tokens = [];
427
+ for (let index = 0; index < text.length;) {
428
+ const start = index;
429
+ if (SHELL_DELIMITER_PATTERN.test(text[index])) {
430
+ index += 1;
431
+ if (/[;|&\n]/.test(text[start]))
432
+ tokens.push({ start, end: index, value: text[start], quoted: false });
433
+ continue;
434
+ }
435
+ let quote;
436
+ let quoted = false;
437
+ const parts = [];
438
+ while (index < text.length) {
439
+ const char = text[index];
440
+ if (quote === undefined && SHELL_DELIMITER_PATTERN.test(char))
441
+ break;
442
+ const wordApostrophe = mode === "prose" && quote === undefined && isWordApostrophe(text, index);
443
+ index += 1;
444
+ if (wordApostrophe)
445
+ parts.push(char);
446
+ else if (quote === undefined && (char === "'" || char === '"')) {
447
+ quote = char;
448
+ quoted = true;
449
+ }
450
+ else if (char === quote)
451
+ quote = undefined;
452
+ else if (char === "\\" && quote !== "'" && index < text.length) {
453
+ if (text[index] === "\n")
454
+ index += 1;
455
+ else if (text[index] === "\r" && text[index + 1] === "\n")
456
+ index += 2;
457
+ else
458
+ parts.push(text[index++]);
459
+ }
460
+ else
461
+ parts.push(char);
462
+ }
463
+ if (index <= start) {
464
+ index = start + 1;
465
+ continue;
466
+ }
467
+ if (parts.length > 0 || quoted)
468
+ tokens.push({ start, end: index, value: parts.join(""), quoted });
469
+ }
470
+ return tokens;
471
+ }
472
+ function redactHeader(value) {
473
+ const match = HEADER_PATTERN.exec(value);
474
+ if (match === null)
475
+ return { text: value, count: 0 };
476
+ const body = match[3].trim();
477
+ const scheme = /authorization/i.test(match[1]) ? /^(?:Bearer|Basic)[ \t]+/i.exec(body)?.[0] ?? "" : "";
478
+ if (body === "" || body === REDACTION_MARKER || body === `${scheme}${REDACTION_MARKER}`)
479
+ return { text: value, count: 0 };
480
+ return { text: `${match[1]}${match[2]}${scheme}${REDACTION_MARKER}`, count: 1 };
481
+ }
482
+ function isMysqlCredential(value) {
483
+ return value.startsWith("-p") && value.length > 2
484
+ && !/^(?:(?:\d{1,3}\.){3}\d{1,3}:|\[[0-9A-Fa-f:]+\]:)?\d+(?::\d+)?(?:\/\w+)?$/.test(value.slice(2));
485
+ }
486
+ function credentialOption(tokens, index) {
487
+ const token = tokens[index];
488
+ const match = /^(--[\w-]+)(?:=([\s\S]*))?$|^(-[uab])=?([\s\S]*)$/.exec(token.value);
489
+ if (match === null)
490
+ return undefined;
491
+ const flag = match[1] ?? match[3];
492
+ const attached = match[2] !== undefined || (match[3] !== undefined && token.value.length > 2);
493
+ const operand = attached ? token : tokens[index + 1];
494
+ const value = attached ? match[2] ?? match[4] : operand?.value ?? "";
495
+ const colon = value.indexOf(":");
496
+ const sensitive = ["-u", "--user", "--proxy-user", "--http-user", "-a", "--auth"].includes(flag) ? colon >= 0 && colon < value.length - 1
497
+ : ["-b", "--cookie"].includes(flag) ? value.includes("=")
498
+ : ["--password", "--http-password", "--proxy-password", "--passwd"].includes(flag);
499
+ return sensitive && operand !== undefined && !(!operand.quoted && /^[;|&\n]$/.test(value))
500
+ ? { flag, attached, operand, value } : undefined;
501
+ }
502
+ function proseOperandWord(line, start, enclosingEnd) {
503
+ if (start >= line.length || SHELL_DELIMITER_PATTERN.test(line[start]))
504
+ return undefined;
505
+ let quote;
506
+ let quoted = false;
507
+ const parts = [];
508
+ let end = start;
509
+ while (end < line.length) {
510
+ if (quote === undefined && SHELL_DELIMITER_PATTERN.test(line[end]))
511
+ break;
512
+ const char = line[end++];
513
+ if (quote === undefined && (char === "'" || char === '"')) {
514
+ quote = char;
515
+ quoted = true;
516
+ }
517
+ else if (char === quote)
518
+ quote = undefined;
519
+ else if (char === "\\" && quote !== "'" && end < line.length)
520
+ parts.push(line[end++]);
521
+ else
522
+ parts.push(char);
523
+ }
524
+ if (quote !== undefined && enclosingEnd !== undefined && enclosingEnd > start && enclosingEnd < end) {
525
+ return proseOperandWord(line.slice(0, enclosingEnd), start);
526
+ }
527
+ return { start, end, value: parts.join(""), quoted };
528
+ }
529
+ function recoveredProseCredentialOption(line, tokens, index, enclosingEnd) {
530
+ const token = tokens[index];
531
+ const attached = /^(--[\w-]+=|-[uab]=?)(?=\S)/.exec(line.slice(token.start, token.end));
532
+ if (attached === null && !/^(?:--[\w-]+|-[uab])$/.test(token.value))
533
+ return undefined;
534
+ const start = attached === null ? tokens[index + 1]?.start : token.start + attached[0].length;
535
+ if (start === undefined)
536
+ return undefined;
537
+ const operand = proseOperandWord(line, start, enclosingEnd);
538
+ if (operand === undefined)
539
+ return undefined;
540
+ const flag = attached === null ? token : { ...token, end: operand.end, value: attached[0] + operand.value };
541
+ const credential = credentialOption([flag, operand], 0);
542
+ return credential?.value === REDACTION_MARKER ? undefined : credential;
543
+ }
544
+ function proseEvidenceTokens(text) {
545
+ return Array.from(text.matchAll(/[^\s;|&]+|[;|&\n]/g), (word) => ({
546
+ start: word.index, end: word.index + word[0].length, value: word[0].replace(/["']/g, ""), quoted: false,
547
+ }));
548
+ }
549
+ function redactMultilineProseTokens(text) {
550
+ if (!text.includes("\n"))
551
+ return { text, count: 0 };
552
+ // Recover only credential-owned words; preceding prose quotes cannot own later lines.
553
+ const tokens = proseEvidenceTokens(text);
554
+ const edits = [];
555
+ let mysql = false;
556
+ let enclosingEnd;
557
+ for (let index = 0; index < tokens.length; index += 1) {
558
+ const token = tokens[index];
559
+ if (enclosingEnd !== undefined && token.start >= enclosingEnd)
560
+ enclosingEnd = undefined;
561
+ if (/^[;|&\n]$/.test(token.value)) {
562
+ mysql = false;
563
+ continue;
564
+ }
565
+ if (["mysql", "mariadb"].includes(token.value.split(/[\\/]/).at(-1)))
566
+ mysql = true;
567
+ const assignment = /^["']?([A-Za-z_][A-Za-z0-9_]{0,127})([ \t]*[=:][ \t]*)/.exec(text.slice(token.start));
568
+ const sensitiveAssignment = assignment !== null && isSensitiveKey(assignment[1])
569
+ && !(assignment[2].includes(":") && /authorization|cookie/i.test(assignment[1]));
570
+ const spacedAssignment = sensitiveAssignment && /[ \t]/.test(assignment[2]);
571
+ const credential = recoveredProseCredentialOption(text, tokens, index, enclosingEnd);
572
+ const mysqlOperand = mysql && isMysqlCredential(token.value) && token.value !== `-p${REDACTION_MARKER}`;
573
+ const headerPrefix = token.value.startsWith("--header=") ? "--header=" : token.value.startsWith("-H") ? "-H" : "";
574
+ const headerOperand = headerPrefix !== "" || ["-H", "--header"].includes(tokens[index - 1]?.value ?? "");
575
+ const wrapper = tokens[index - 1]?.value.split(/[\\/]/).at(-1);
576
+ const commandFlag = (["sh", "bash", "zsh"].includes(wrapper ?? "") && /^-[A-Za-z]*c[A-Za-z]*$/.test(token.value))
577
+ || (wrapper === "env" && ["-S", "--split-string"].includes(token.value));
578
+ const operand = credential?.operand ?? (commandFlag ? proseOperandWord(text, tokens[index + 1]?.start ?? text.length, enclosingEnd)
579
+ : sensitiveAssignment || mysqlOperand || headerOperand ? proseOperandWord(text, spacedAssignment ? token.start + assignment[0].length : token.start + headerPrefix.length, enclosingEnd) : undefined);
580
+ if (operand === undefined) {
581
+ const quote = text[token.start];
582
+ if (quote === "'" || quote === '"') {
583
+ const enclosing = proseOperandWord(text, token.start);
584
+ if (text.slice(token.start, enclosing.end).includes("\n") && text[enclosing.end - 1] === quote)
585
+ enclosingEnd = enclosing.end - 1;
586
+ }
587
+ continue;
588
+ }
589
+ const source = text.slice(operand.start, operand.end);
590
+ if (source.includes("\n") || operand.end === enclosingEnd) {
591
+ let replacement;
592
+ if (spacedAssignment) {
593
+ const quote = source[0];
594
+ replacement = (quote === "'" || quote === '"') && source.endsWith(quote)
595
+ ? `${quote}${REDACTION_MARKER}${quote}` : REDACTION_MARKER;
596
+ }
597
+ else if (sensitiveAssignment) {
598
+ const prefix = `${assignment[1]}${assignment[2]}`;
599
+ if (operand.value.slice(prefix.length) !== REDACTION_MARKER) {
600
+ replacement = REDACTION_MARKER;
601
+ const quote = source[0];
602
+ if ((quote === "'" || quote === '"') && source === `${quote}${operand.value}${quote}`) {
603
+ replacement = `${quote}${prefix}${REDACTION_MARKER}${quote}`;
604
+ }
605
+ else if (source.startsWith(prefix)) {
606
+ const rawValue = source.slice(prefix.length);
607
+ const quote = rawValue[0];
608
+ if (quote !== "'" && quote !== '"')
609
+ replacement = prefix + REDACTION_MARKER;
610
+ else if (rawValue === `${quote}${operand.value.slice(prefix.length)}${quote}`) {
611
+ replacement = `${prefix}${quote}${REDACTION_MARKER}${quote}`;
612
+ }
613
+ }
614
+ }
615
+ }
616
+ else if (credential !== undefined) {
617
+ replacement = (credential.attached ? credential.flag + (token.value[credential.flag.length] === "=" ? "=" : "") : "") + REDACTION_MARKER;
618
+ }
619
+ else if (mysqlOperand)
620
+ replacement = `-p${REDACTION_MARKER}`;
621
+ else if (headerOperand && redactHeader(operand.value.replace(/\r?\n/g, " ")).count > 0)
622
+ replacement = REDACTION_MARKER;
623
+ else if (commandFlag && firstProseCredentialEvidence(operand.value.replace(/\r?\n/g, " ")) !== undefined)
624
+ replacement = REDACTION_MARKER;
625
+ if (replacement !== undefined)
626
+ edits.push({
627
+ start: operand.start, end: operand.end + (sensitiveAssignment && operand.end === enclosingEnd ? 1 : 0), text: replacement, count: 1,
628
+ });
629
+ }
630
+ // A locally decoded word owns its quote fragments, even when no edit is needed.
631
+ while (tokens[index + 1] !== undefined && tokens[index + 1].start < operand.end)
632
+ index += 1;
633
+ }
634
+ return applyTextEdits(text, edits);
635
+ }
636
+ function firstProseCredentialEvidence(line) {
637
+ let first = Number.POSITIVE_INFINITY;
638
+ ASSIGNMENT_PATTERN.lastIndex = 0;
639
+ let match;
640
+ while ((match = ASSIGNMENT_PATTERN.exec(line)) !== null) {
641
+ const valueStart = ASSIGNMENT_PATTERN.lastIndex;
642
+ // Leave trailing space available as the boundary of a following assignment.
643
+ ASSIGNMENT_PATTERN.lastIndex -= /[ \t]+$/.exec(match[3])?.[0].length ?? 0;
644
+ if (match[1] === "-" && line[match.index - 1] === "-" && match[3].startsWith("=")
645
+ && /^(?:user|proxy-user|http-user|auth|cookie|password|http-password|proxy-password|passwd)$/.test(match[2])
646
+ && (match.index === 1 || /[\s;|&"']/.test(line[match.index - 2])))
647
+ continue;
648
+ if (isSensitiveKey(match[2]) && !(match[3].includes(":") && /authorization|cookie/i.test(match[2]))) {
649
+ if (/^["']?\[REDACTED\]["']?(?=$|[\s;|&,])/.test(line.slice(valueStart)))
650
+ continue;
651
+ const operand = proseOperandWord(line, valueStart);
652
+ if (operand !== undefined && operand.value !== REDACTION_MARKER) {
653
+ first = match.index + match[1].length;
654
+ break;
655
+ }
656
+ }
657
+ }
658
+ // Ignore quote grouping only for locating evidence in an already ambiguous line.
659
+ const tokens = proseEvidenceTokens(line);
660
+ let mysql = false;
661
+ for (let index = 0; index < tokens.length; index += 1) {
662
+ const token = tokens[index];
663
+ if (token.start >= first)
664
+ break;
665
+ if (/^[;|&]$/.test(token.value)) {
666
+ mysql = false;
667
+ continue;
668
+ }
669
+ const credential = credentialOption(tokens, index);
670
+ if ((credential !== undefined && credential.value !== REDACTION_MARKER) || recoveredProseCredentialOption(line, tokens, index) !== undefined
671
+ || (mysql && isMysqlCredential(token.value) && token.value !== `-p${REDACTION_MARKER}`)) {
672
+ first = Math.min(first, token.start);
673
+ }
674
+ const headerPrefix = token.value.startsWith("--header=") ? "--header=" : token.value.startsWith("-H") ? "-H" : "";
675
+ if (HEADER_PATTERN.test(token.value.slice(headerPrefix.length))) {
676
+ const headerStart = token.start + headerPrefix.length;
677
+ const quoted = line[headerStart] === "'" || line[headerStart] === '"';
678
+ const value = (quoted ? proseOperandWord(line, headerStart)?.value : undefined) ?? line.slice(headerStart).replace(/["']/g, "");
679
+ const header = HEADER_PATTERN.exec(value);
680
+ if (header !== null && redactHeader(value).count > 0) {
681
+ const previous = tokens[index - 1];
682
+ const start = previous !== undefined && ["-H", "--header"].includes(previous.value) ? previous.start : token.start;
683
+ first = Math.min(first, start);
684
+ }
685
+ }
686
+ if (["mysql", "mariadb"].includes(token.value.split(/[\\/]/).at(-1)))
687
+ mysql = true;
688
+ }
689
+ const url = redactUrlUserinfo(line);
690
+ if (url.firstStart !== undefined)
691
+ first = Math.min(first, url.firstStart);
692
+ for (const pattern of [VENDOR_TOKEN_PATTERN, JWT_PATTERN, BEARER_PATTERN]) {
693
+ pattern.lastIndex = 0;
694
+ const evidence = pattern.exec(line);
695
+ if (evidence !== null)
696
+ first = Math.min(first, evidence.index);
697
+ }
698
+ return Number.isFinite(first) ? first : undefined;
699
+ }
700
+ function redactProseCommandsAndHeaders(text) {
701
+ const multiline = redactMultilineProseTokens(text);
702
+ let count = multiline.count;
703
+ const lines = multiline.text.split("\n").map((source) => {
704
+ const cr = source.endsWith("\r") ? "\r" : "";
705
+ const line = cr === "" ? source : source.slice(0, -1);
706
+ let single = 0;
707
+ let double = 0;
708
+ for (let index = 0; index < line.length; index += 1) {
709
+ if (line[index] === "\\") {
710
+ index += 1;
711
+ continue;
712
+ }
713
+ if (line[index] === "'" && !isWordApostrophe(line, index))
714
+ single += 1;
715
+ if (line[index] === '"')
716
+ double += 1;
717
+ }
718
+ const assignments = redactAssignments(line);
719
+ if (single % 2 !== 0 || double % 2 !== 0 || (assignments.text === REDACTION_MARKER && assignments.count > 0)) {
720
+ const first = firstProseCredentialEvidence(line);
721
+ if (first === undefined)
722
+ return source;
723
+ count += 1;
724
+ return line.slice(0, first) + REDACTION_MARKER + cr;
725
+ }
726
+ const redacted = redactCommandsAndHeaders(line, 0, false, "prose");
727
+ count += redacted.count;
728
+ return redacted.text + cr;
729
+ });
730
+ return { text: lines.join("\n"), count };
731
+ }
732
+ function redactCommandsAndHeaders(text, depth = 0, evidenceOnly = false, mode = "command") {
733
+ const lines = text.split("\n");
734
+ let lineCount = 0;
735
+ text = lines.map((line) => {
736
+ const indent = /^[ \t]*/.exec(line)[0];
737
+ const redacted = redactHeader(line.slice(indent.length));
738
+ lineCount += redacted.count;
739
+ return indent + redacted.text;
740
+ }).join("\n");
741
+ const tokens = shellTokens(text, mode);
742
+ const edits = [];
743
+ let mysql = false;
744
+ for (let index = 0; index < tokens.length; index += 1) {
745
+ const token = tokens[index];
746
+ if (/^[;|&\n]$/.test(token.value) && !token.quoted) {
747
+ mysql = false;
748
+ continue;
749
+ }
750
+ const source = text.slice(token.start, token.end);
751
+ const previous = tokens[index - 1];
752
+ const wrapper = tokens[index - 2]?.value.split(/[\\/]/).at(-1);
753
+ const quotedCommand = token.quoted && previous !== undefined && !previous.quoted && ((["sh", "bash", "zsh"].includes(wrapper ?? "") && /^-[A-Za-z]*c[A-Za-z]*$/.test(previous.value))
754
+ || (wrapper === "env" && ["-S", "--split-string"].includes(previous.value)));
755
+ const assignment = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(token.value);
756
+ if (assignment !== null && isSensitiveKey(assignment[1]) && !quotedCommand) {
757
+ // Spaced prose values are handled by redactAssignments, including existing markers.
758
+ if (mode === "prose" && token.value === assignment[0] && /[ \t]/.test(text[token.end] ?? ""))
759
+ continue;
760
+ if (!source.startsWith(assignment[0])) {
761
+ const quote = source[0];
762
+ const mapped = (quote === '"' || quote === "'") && source === `${quote}${token.value}${quote}`;
763
+ const replacement = mapped ? `${quote}${assignment[0]}${REDACTION_MARKER}${quote}` : REDACTION_MARKER;
764
+ if (source !== replacement)
765
+ edits.push({ start: token.start, end: token.end, text: replacement, count: 1 });
766
+ continue;
767
+ }
768
+ const start = token.start + assignment[0].length;
769
+ const quote = text[start] === '"' || text[start] === "'" ? text[start] : "";
770
+ if (quote !== "" && (text.slice(start, token.end).includes("\n") || text[token.end - 1] !== quote)) {
771
+ return { text: REDACTION_MARKER, count: 1 };
772
+ }
773
+ const replacement = `${quote}${REDACTION_MARKER}${quote}${quote === "" && source.endsWith(",") ? "," : ""}`;
774
+ if (text.slice(start, token.end) !== replacement)
775
+ edits.push({ start, end: token.end, text: replacement, count: 1 });
776
+ continue;
777
+ }
778
+ const headerPrefix = token.value.startsWith("--header=") ? "--header=" : token.value.startsWith("-H") ? "-H" : "";
779
+ const header = headerPrefix !== "" || token.quoted || tokens[index - 1]?.value === "-H" || tokens[index - 1]?.value === "--header"
780
+ ? redactHeader(token.value.slice(headerPrefix.length)) : { text: token.value, count: 0 };
781
+ if (header.count > 0) {
782
+ edits.push({ start: token.start, end: token.end, text: headerPrefix + JSON.stringify(header.text), count: 1 });
783
+ continue;
784
+ }
785
+ if (mysql && isMysqlCredential(token.value)) {
786
+ if (token.value.slice(2) !== REDACTION_MARKER)
787
+ edits.push({ start: token.start, end: token.end, text: `-p${REDACTION_MARKER}`, count: 1 });
788
+ continue;
789
+ }
790
+ if (!token.quoted && ["mysql", "mariadb"].includes(token.value.split(/[\\/]/).at(-1)))
791
+ mysql = true;
792
+ const credential = credentialOption(tokens, index);
793
+ if (credential !== undefined) {
794
+ const { flag, attached, operand, value } = credential;
795
+ const prefix = attached ? flag + (token.value[flag.length] === "=" ? "=" : "") : "";
796
+ if (value !== REDACTION_MARKER)
797
+ edits.push({ start: operand.start, end: operand.end, text: prefix + REDACTION_MARKER, count: 1 });
798
+ if (!attached)
799
+ index += 1;
800
+ continue;
801
+ }
802
+ if (token.quoted && (quotedCommand || /\s/.test(token.value)) && !evidenceOnly) {
803
+ const nested = redactCommandsAndHeaders(token.value, 1, depth > 0);
804
+ const count = nested.count + (depth === 0 ? 0 : redactAssignments(token.value).count + redactUrlUserinfo(token.value).count
805
+ + Number(Boolean(token.value.match(VENDOR_TOKEN_PATTERN) || token.value.match(JWT_PATTERN) || token.value.match(BEARER_PATTERN))));
806
+ if (count > 0) {
807
+ const quote = source[0];
808
+ const mapped = (quote === '"' || quote === "'") && source === `${quote}${token.value}${quote}`;
809
+ edits.push({ start: token.start, end: token.end, text: mapped && depth === 0 ? `${quote}${nested.text}${quote}` : REDACTION_MARKER, count: depth === 0 ? count : 1 });
810
+ }
811
+ }
812
+ }
813
+ const result = applyTextEdits(text, edits);
814
+ return { text: result.text, count: result.count + lineCount };
815
+ }
816
+ function redactAssignments(text) {
817
+ const edits = [];
818
+ ASSIGNMENT_PATTERN.lastIndex = 0;
819
+ let match;
820
+ while ((match = ASSIGNMENT_PATTERN.exec(text)) !== null) {
821
+ if (!isSensitiveKey(match[2]))
822
+ continue;
823
+ if (match[1] === "-" && text[match.index - 1] === "-" && match[3].startsWith("=")
824
+ && /^(?:user|proxy-user|http-user|auth|cookie|password|http-password|proxy-password|passwd)$/.test(match[2])
825
+ && (match.index === 1 || /[\s;|&"']/.test(text[match.index - 2])))
826
+ continue;
827
+ if (match[3].includes(":") && /authorization|cookie/i.test(match[2]))
828
+ continue;
829
+ const start = ASSIGNMENT_PATTERN.lastIndex;
830
+ const quote = text[start] === '"' || text[start] === "'" ? text[start] : undefined;
831
+ let end = start + (quote === undefined ? 0 : 1);
832
+ if (quote === undefined && text.startsWith(REDACTION_MARKER, start))
833
+ end += REDACTION_MARKER.length;
834
+ for (; end < text.length; end += 1) {
835
+ const char = text[end];
836
+ if (quote === undefined && (/[\s,;&#}\]]/.test(char) || ((match[1] === '"' || match[1] === "'") && char === match[1])))
837
+ break;
838
+ if (quote !== undefined && char === "\n")
839
+ return { text: REDACTION_MARKER, count: 1 };
840
+ if (quote !== undefined && char === "\\")
841
+ end += 1;
842
+ else if (quote !== undefined && char === quote) {
843
+ end += 1;
844
+ break;
845
+ }
846
+ }
847
+ const replacement = quote === undefined ? REDACTION_MARKER : `${quote}${REDACTION_MARKER}${quote}`;
848
+ if (text.slice(start, end) === replacement || end === start)
849
+ continue;
850
+ if (quote !== undefined && text[end - 1] !== quote)
851
+ return { text: REDACTION_MARKER, count: 1 };
852
+ if (quote !== undefined && end < text.length && !/[\s,;&#}\]]/.test(text[end]))
853
+ return { text: REDACTION_MARKER, count: 1 };
854
+ edits.push({ start, end, text: replacement, count: 1 });
855
+ ASSIGNMENT_PATTERN.lastIndex = end;
856
+ }
857
+ return applyTextEdits(text, edits);
858
+ }
859
+ function redactUrlUserinfo(text) {
860
+ const schemePattern = /\b[A-Za-z][A-Za-z0-9+.-]{0,63}:\/\//g;
861
+ const edits = [];
862
+ let firstStart;
863
+ let match;
864
+ while ((match = schemePattern.exec(text)) !== null) {
865
+ const start = schemePattern.lastIndex;
866
+ let end = start;
867
+ let colon = -1;
868
+ let at = -1;
869
+ for (; end < text.length && !/[\s/?#"'<>`]/.test(text[end]); end += 1) {
870
+ if (text[end] === ":" && colon < 0)
871
+ colon = end;
872
+ if (text[end] === "@")
873
+ at = end;
874
+ }
875
+ if (colon >= start && at > colon) {
876
+ firstStart ??= match.index;
877
+ edits.push({ start, end: at, text: REDACTION_MARKER, count: 1 });
878
+ }
879
+ schemePattern.lastIndex = end;
880
+ }
881
+ return { ...applyTextEdits(text, edits), firstStart };
882
+ }
883
+ function redactText(value, maxChars = Number.POSITIVE_INFINITY, depth = 0, mode = "command") {
884
+ if (PRIVATE_KEY_SIGNAL_PATTERN.test(value))
885
+ return { text: REDACTION_MARKER, count: 1 };
886
+ const embedded = redactEmbeddedJson(value, depth);
887
+ const commands = mode === "prose" ? redactProseCommandsAndHeaders(embedded.text) : redactCommandsAndHeaders(embedded.text);
888
+ let text = commands.text;
889
+ let count = embedded.count + commands.count;
150
890
  const replace = (pattern, replacement) => {
151
891
  text = text.replace(pattern, (...args) => {
152
892
  count += 1;
153
893
  return replacement(...args.slice(0, -2).map(String));
154
894
  });
155
895
  };
156
- replace(BASIC_AUTHORIZATION_PATTERN, (_match, prefix) => `${prefix}${REDACTION_MARKER}`);
157
896
  replace(BEARER_PATTERN, (match) => `${match.slice(0, match.search(/\s/))} ${REDACTION_MARKER}`);
158
897
  replace(SENSITIVE_QUERY_PATTERN, (_match, prefix) => `${prefix}${REDACTION_MARKER}`);
159
- replace(QUOTED_ASSIGNMENT_PATTERN, (_match, prefix, quote) => `${prefix}${quote}${REDACTION_MARKER}${quote}`);
160
- replace(BARE_ASSIGNMENT_PATTERN, (_match, prefix) => `${prefix}${REDACTION_MARKER}`);
898
+ const assignments = redactAssignments(text);
899
+ text = assignments.text;
900
+ count += assignments.count;
901
+ const urls = redactUrlUserinfo(text);
902
+ text = urls.text;
903
+ count += urls.count;
161
904
  replace(VENDOR_TOKEN_PATTERN, () => REDACTION_MARKER);
905
+ replace(JWT_PATTERN, () => REDACTION_MARKER);
162
906
  replace(POSIX_USER_HOME_PATTERN, () => REDACTION_MARKER);
163
907
  replace(ROOT_USER_HOME_PATTERN, () => REDACTION_MARKER);
164
908
  replace(WINDOWS_USER_HOME_PATTERN, () => REDACTION_MARKER);
165
- if (count > MAX_REDACTIONS ||
166
- text.length > maxChars ||
167
- hasSensitiveAssignmentResidual(text)) {
909
+ if (count > MAX_REDACTIONS)
168
910
  return { text: REDACTION_MARKER, count: 1 };
169
- }
170
- return { text, count };
171
- }
172
- function hasSensitiveAssignmentResidual(text) {
173
- SENSITIVE_ASSIGNMENT_PREFIX_PATTERN.lastIndex = 0;
174
- let match;
175
- while ((match = SENSITIVE_ASSIGNMENT_PREFIX_PATTERN.exec(text)) !== null) {
176
- const tail = text.slice(SENSITIVE_ASSIGNMENT_PREFIX_PATTERN.lastIndex);
177
- if (tail.length === 0 || /^[&;,}\]\r\n]/.test(tail))
178
- continue;
179
- if (tail.startsWith(REDACTION_MARKER)) {
180
- const next = tail[REDACTION_MARKER.length];
181
- if (next === undefined || /[\s&;,}\]]/.test(next))
182
- continue;
183
- }
184
- const quote = tail[0];
185
- if (quote === "\"" || quote === "'") {
186
- const safeQuoted = `${quote}${REDACTION_MARKER}${quote}`;
187
- if (tail.startsWith(safeQuoted)) {
188
- const next = tail[safeQuoted.length];
189
- if (next === undefined || /[\s&;,}\]]/.test(next))
190
- continue;
191
- }
192
- }
193
- return true;
194
- }
195
- return false;
911
+ if (text.length <= maxChars)
912
+ return { text, count };
913
+ let end = maxChars;
914
+ const markerStart = text.lastIndexOf(REDACTION_MARKER, end - 1);
915
+ if (markerStart >= 0 && markerStart + REDACTION_MARKER.length > end)
916
+ end = markerStart;
917
+ const code = text.charCodeAt(end - 1);
918
+ if (code >= 0xd800 && code <= 0xdbff)
919
+ end -= 1;
920
+ return { text: text.slice(0, end), count, truncated: true };
196
921
  }
197
922
  function validateNormalizedEvent(value) {
198
923
  if (!isPlainRecord(value) || typeof value["kind"] !== "string")
@@ -205,7 +930,7 @@ function validateNormalizedEvent(value) {
205
930
  }
206
931
  return { kind: value["kind"], content: value["content"] };
207
932
  case "tool_call":
208
- if (!hasExactKeys(value, ["kind", "toolName", "callId", "summary"]) ||
933
+ if (!hasExactKeys(value, ["kind", "toolName", "callId", "summary", ...(Object.hasOwn(value, "arguments") ? ["arguments"] : [])]) ||
209
934
  !isAdmissibleIdentifier(value["toolName"]) ||
210
935
  !isAdmissibleIdentifier(value["callId"]) ||
211
936
  typeof value["summary"] !== "string")
@@ -215,6 +940,7 @@ function validateNormalizedEvent(value) {
215
940
  toolName: value["toolName"],
216
941
  callId: value["callId"],
217
942
  summary: value["summary"],
943
+ ...(Object.hasOwn(value, "arguments") ? { arguments: validateArgumentDetail(value["arguments"]) } : {}),
218
944
  };
219
945
  case "tool_result":
220
946
  if (!hasExactKeys(value, ["kind", "toolName", "callId", "outcome", "summary"]) ||
@@ -325,6 +1051,14 @@ function validateNormalizedEvent(value) {
325
1051
  return invalidEvent();
326
1052
  }
327
1053
  }
1054
+ function validateArgumentDetail(value) {
1055
+ if (!isPlainRecord(value) || !hasExactKeys(value, ["content", "omission"]) ||
1056
+ typeof value["content"] !== "string" ||
1057
+ !isOneOf(value["omission"], ["none", "size_limit", "structure_limit", "invalid"]) ||
1058
+ (value["omission"] !== "none" && value["content"] !== ""))
1059
+ return invalidEvent();
1060
+ return { content: value["content"], omission: value["omission"] };
1061
+ }
328
1062
  function invalidEvent() {
329
1063
  throw new TypeError("unsupported session-log-v1 normalized event");
330
1064
  }