@maudecode/talaria-web 1.15.0 → 1.17.2

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 (386) hide show
  1. package/_release.json +4 -4
  2. package/dist/api/automation-raw.js +6 -3
  3. package/dist/api/automation-raw.js.map +1 -1
  4. package/dist/api/automation-router.d.ts +158 -16
  5. package/dist/api/automation-router.js +12 -6
  6. package/dist/api/automation-router.js.map +1 -1
  7. package/dist/api/chat-router.d.ts +193 -6
  8. package/dist/api/chat-router.js +39 -16
  9. package/dist/api/chat-router.js.map +1 -1
  10. package/dist/api/git-router.d.ts +12 -1
  11. package/dist/api/git-router.js +60 -38
  12. package/dist/api/git-router.js.map +1 -1
  13. package/dist/api/raw-routes.d.ts +6 -0
  14. package/dist/api/raw-routes.js +35 -58
  15. package/dist/api/raw-routes.js.map +1 -1
  16. package/dist/api/router.d.ts +4 -2
  17. package/dist/api/router.js +8 -8
  18. package/dist/api/router.js.map +1 -1
  19. package/dist/api/sessions-router.d.ts +974 -42
  20. package/dist/api/sessions-router.js +87 -25
  21. package/dist/api/sessions-router.js.map +1 -1
  22. package/dist/api/settings-router.d.ts +254 -11
  23. package/dist/api/settings-router.js +68 -18
  24. package/dist/api/settings-router.js.map +1 -1
  25. package/dist/api/sse-routes.d.ts +11 -3
  26. package/dist/api/sse-routes.js +145 -65
  27. package/dist/api/sse-routes.js.map +1 -1
  28. package/dist/api/tools-raw.js +6 -2
  29. package/dist/api/tools-raw.js.map +1 -1
  30. package/dist/api/tools-router.d.ts +86 -9
  31. package/dist/api/tools-router.js +19 -15
  32. package/dist/api/tools-router.js.map +1 -1
  33. package/dist/app.d.ts +0 -8397
  34. package/dist/app.js +7 -2
  35. package/dist/app.js.map +1 -1
  36. package/dist/auth/gate.js +11 -4
  37. package/dist/auth/gate.js.map +1 -1
  38. package/dist/auth/oidc.d.ts +6 -2
  39. package/dist/auth/oidc.js +16 -11
  40. package/dist/auth/oidc.js.map +1 -1
  41. package/dist/auth/store.d.ts +14 -2
  42. package/dist/auth/store.js +26 -1
  43. package/dist/auth/store.js.map +1 -1
  44. package/dist/cli/dotenv.d.ts +1 -1
  45. package/dist/cli/dotenv.js +36 -3
  46. package/dist/cli/dotenv.js.map +1 -1
  47. package/dist/config/agent-config.d.ts +2 -0
  48. package/dist/config/agent-config.js +9 -0
  49. package/dist/config/agent-config.js.map +1 -1
  50. package/dist/http/addresses.d.ts +6 -0
  51. package/dist/http/addresses.js +26 -0
  52. package/dist/http/addresses.js.map +1 -1
  53. package/dist/http/context.d.ts +6 -3
  54. package/dist/http/context.js +10 -4
  55. package/dist/http/context.js.map +1 -1
  56. package/dist/links.d.ts +26 -0
  57. package/dist/links.js +60 -0
  58. package/dist/links.js.map +1 -0
  59. package/dist/onboarding.js +30 -7
  60. package/dist/onboarding.js.map +1 -1
  61. package/dist/profiles/profiles.js +1 -1
  62. package/dist/profiles/profiles.js.map +1 -1
  63. package/dist/providers/catalog.d.ts +167 -5
  64. package/dist/providers/catalog.js +662 -135
  65. package/dist/providers/catalog.js.map +1 -1
  66. package/dist/providers/tables.d.ts +3 -1
  67. package/dist/providers/tables.js +9 -4
  68. package/dist/providers/tables.js.map +1 -1
  69. package/dist/redact.d.ts +1 -0
  70. package/dist/redact.js +518 -15
  71. package/dist/redact.js.map +1 -1
  72. package/dist/runtime.js +79 -37
  73. package/dist/runtime.js.map +1 -1
  74. package/dist/sessions/anchor.d.ts +24 -3
  75. package/dist/sessions/anchor.js +94 -44
  76. package/dist/sessions/anchor.js.map +1 -1
  77. package/dist/sessions/background-tasks.d.ts +4 -1
  78. package/dist/sessions/background-tasks.js +7 -4
  79. package/dist/sessions/background-tasks.js.map +1 -1
  80. package/dist/sessions/claude-code.d.ts +19 -0
  81. package/dist/sessions/claude-code.js +250 -0
  82. package/dist/sessions/claude-code.js.map +1 -0
  83. package/dist/sessions/completions.d.ts +13 -1
  84. package/dist/sessions/completions.js +64 -12
  85. package/dist/sessions/completions.js.map +1 -1
  86. package/dist/sessions/compress.d.ts +14 -1
  87. package/dist/sessions/compress.js +52 -11
  88. package/dist/sessions/compress.js.map +1 -1
  89. package/dist/sessions/journal.d.ts +8 -0
  90. package/dist/sessions/journal.js +44 -10
  91. package/dist/sessions/journal.js.map +1 -1
  92. package/dist/sessions/list.d.ts +13 -5
  93. package/dist/sessions/list.js +56 -26
  94. package/dist/sessions/list.js.map +1 -1
  95. package/dist/sessions/merge.d.ts +122 -9
  96. package/dist/sessions/merge.js +544 -33
  97. package/dist/sessions/merge.js.map +1 -1
  98. package/dist/sessions/pending.d.ts +5 -0
  99. package/dist/sessions/pending.js +30 -10
  100. package/dist/sessions/pending.js.map +1 -1
  101. package/dist/sessions/service.d.ts +100 -4
  102. package/dist/sessions/service.js +567 -102
  103. package/dist/sessions/service.js.map +1 -1
  104. package/dist/sessions/session.d.ts +16 -1
  105. package/dist/sessions/session.js +29 -2
  106. package/dist/sessions/session.js.map +1 -1
  107. package/dist/sessions/shares.js +11 -2
  108. package/dist/sessions/shares.js.map +1 -1
  109. package/dist/sessions/state-db.d.ts +26 -4
  110. package/dist/sessions/state-db.js +126 -8
  111. package/dist/sessions/state-db.js.map +1 -1
  112. package/dist/sessions/store.js +6 -1
  113. package/dist/sessions/store.js.map +1 -1
  114. package/dist/sessions/streams.d.ts +10 -0
  115. package/dist/sessions/streams.js +14 -3
  116. package/dist/sessions/streams.js.map +1 -1
  117. package/dist/sessions/todo.d.ts +6 -1
  118. package/dist/sessions/todo.js +11 -4
  119. package/dist/sessions/todo.js.map +1 -1
  120. package/dist/sessions/tool-display.d.ts +12 -1
  121. package/dist/sessions/tool-display.js +76 -1
  122. package/dist/sessions/tool-display.js.map +1 -1
  123. package/dist/sessions/turn.d.ts +49 -10
  124. package/dist/sessions/turn.js +459 -185
  125. package/dist/sessions/turn.js.map +1 -1
  126. package/dist/settings.d.ts +20 -2
  127. package/dist/settings.js +49 -2
  128. package/dist/settings.js.map +1 -1
  129. package/dist/sidecar/fake.d.ts +2 -2
  130. package/dist/sidecar/fake.js +7 -2
  131. package/dist/sidecar/fake.js.map +1 -1
  132. package/dist/spa.js +2 -1
  133. package/dist/spa.js.map +1 -1
  134. package/dist/test/harness.js +2 -0
  135. package/dist/test/harness.js.map +1 -1
  136. package/dist/tools/commands.d.ts +15 -0
  137. package/dist/tools/commands.js +52 -0
  138. package/dist/tools/commands.js.map +1 -0
  139. package/dist/tools/crons.d.ts +4 -1
  140. package/dist/tools/crons.js +58 -11
  141. package/dist/tools/crons.js.map +1 -1
  142. package/dist/tools/insights.d.ts +8 -2
  143. package/dist/tools/insights.js +18 -2
  144. package/dist/tools/insights.js.map +1 -1
  145. package/dist/tools/kanban.d.ts +41 -1
  146. package/dist/tools/kanban.js +113 -2
  147. package/dist/tools/kanban.js.map +1 -1
  148. package/dist/tools/mcp.d.ts +2 -1
  149. package/dist/tools/mcp.js +16 -6
  150. package/dist/tools/mcp.js.map +1 -1
  151. package/dist/tools/terminal.d.ts +1 -0
  152. package/dist/tools/terminal.js +1 -1
  153. package/dist/tools/terminal.js.map +1 -1
  154. package/dist/tools/update-notifications.d.ts +1 -1
  155. package/dist/tools/update-notifications.js +1 -1
  156. package/dist/tools/update-notifications.js.map +1 -1
  157. package/dist/tools/updates.d.ts +15 -3
  158. package/dist/tools/updates.js +207 -41
  159. package/dist/tools/updates.js.map +1 -1
  160. package/dist/workspace/fs.d.ts +7 -3
  161. package/dist/workspace/fs.js +5 -1
  162. package/dist/workspace/fs.js.map +1 -1
  163. package/dist/workspace/git.d.ts +57 -10
  164. package/dist/workspace/git.js +302 -195
  165. package/dist/workspace/git.js.map +1 -1
  166. package/dist/workspace/media-refs.d.ts +13 -0
  167. package/dist/workspace/media-refs.js +165 -0
  168. package/dist/workspace/media-refs.js.map +1 -0
  169. package/dist/workspace/media.d.ts +19 -0
  170. package/dist/workspace/media.js +63 -2
  171. package/dist/workspace/media.js.map +1 -1
  172. package/dist/workspace/preview.d.ts +28 -0
  173. package/dist/workspace/preview.js +110 -0
  174. package/dist/workspace/preview.js.map +1 -0
  175. package/dist/workspace/rollback.d.ts +5 -4
  176. package/dist/workspace/rollback.js +12 -9
  177. package/dist/workspace/rollback.js.map +1 -1
  178. package/dist/workspace/upload.d.ts +2 -0
  179. package/dist/workspace/upload.js +4 -1
  180. package/dist/workspace/upload.js.map +1 -1
  181. package/dist/workspace/workspaces.d.ts +7 -1
  182. package/dist/workspace/workspaces.js +30 -21
  183. package/dist/workspace/workspaces.js.map +1 -1
  184. package/dist/workspace/worktrees.d.ts +2 -4
  185. package/dist/workspace/worktrees.js +28 -33
  186. package/dist/workspace/worktrees.js.map +1 -1
  187. package/package.json +4 -2
  188. package/sidecar/scripts/replay_sidecar.py +4 -2
  189. package/sidecar/talaria_sidecar/__init__.py +1 -1
  190. package/sidecar/talaria_sidecar/__main__.py +7 -1
  191. package/sidecar/talaria_sidecar/home.py +98 -2
  192. package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
  193. package/sidecar/talaria_sidecar/methods/aux.py +19 -3
  194. package/sidecar/talaria_sidecar/methods/chat.py +551 -61
  195. package/sidecar/talaria_sidecar/methods/commands.py +61 -33
  196. package/sidecar/talaria_sidecar/methods/config.py +14 -10
  197. package/sidecar/talaria_sidecar/methods/cron.py +10 -6
  198. package/sidecar/talaria_sidecar/methods/kanban.py +115 -15
  199. package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
  200. package/sidecar/talaria_sidecar/methods/process.py +60 -1
  201. package/sidecar/talaria_sidecar/methods/profiles.py +12 -5
  202. package/sidecar/talaria_sidecar/methods/providers.py +161 -1
  203. package/sidecar/talaria_sidecar/methods/runtime.py +5 -0
  204. package/sidecar/talaria_sidecar/methods/text.py +18 -21
  205. package/sidecar/talaria_sidecar/methods/usage.py +264 -5
  206. package/static/dist/FILES.txt +126 -121
  207. package/static/dist/assets/AppShell-fb2td9zi.js +1 -0
  208. package/static/dist/assets/ChatPage-cj96kiy9.js +25 -0
  209. package/static/dist/assets/Dialog-kxcg7ard.js +1 -0
  210. package/static/dist/assets/ExtensionRoute-jf7h0754.js +1 -0
  211. package/static/dist/assets/HubRoute-gmanpd5y.js +2 -0
  212. package/static/dist/assets/{Markdown-dy8t4dy7.js → Markdown-mdwlvj3w.js} +125 -125
  213. package/static/dist/assets/RightPanel-ftvrjlcp.js +1 -0
  214. package/static/dist/assets/Select-bilaypej.js +1 -0
  215. package/static/dist/assets/SessionListPanel-og8n36qs.js +1 -0
  216. package/static/dist/assets/SettingsLayout-jonjefmj.js +1 -0
  217. package/static/dist/assets/States-nmejrwrm.js +1 -0
  218. package/static/dist/assets/Toaster-nnl8zkt9.js +1 -0
  219. package/static/dist/assets/_app-gdidzd94.js +1 -0
  220. package/static/dist/assets/_app.ext._extensionId-opyh9y0v.js +1 -0
  221. package/static/dist/assets/_app.index-hbarzbjx.js +1 -0
  222. package/static/dist/assets/_app.insights-luywlarz.js +1 -0
  223. package/static/dist/assets/_app.kanban-kincnbr8.js +1 -0
  224. package/static/dist/assets/_app.logs-oi2gr3gs.js +1 -0
  225. package/static/dist/assets/_app.memory-b6jfivt6.js +1 -0
  226. package/static/dist/assets/_app.profiles-lpvixmqp.js +1 -0
  227. package/static/dist/assets/{_app.session._sessionId-gyoh5ylx.js → _app.session._sessionId-bfd4h3id.js} +1 -1
  228. package/static/dist/assets/_app.settings-vowx3iq0.js +1 -0
  229. package/static/dist/assets/_app.settings._section-cnntg1tu.js +3 -0
  230. package/static/dist/assets/_app.skills-fb57lpau.js +1 -0
  231. package/static/dist/assets/_app.tasks-eszpx1q7.js +2 -0
  232. package/static/dist/assets/_app.todos-nr05ac6w.js +1 -0
  233. package/static/dist/assets/_app.workspaces-d2luomu6.js +1 -0
  234. package/static/dist/assets/{abnfDiagram-VCTEODGH-cjqletra.js → abnfDiagram-VCTEODGH-b2x1lqot.js} +1 -1
  235. package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +1 -0
  236. package/static/dist/assets/architectureDiagram-5GKGNRK7-h38o5l7b.js +36 -0
  237. package/static/dist/assets/{back-e4m6nsnm.js → back-bemaer09.js} +1 -1
  238. package/static/dist/assets/{blockDiagram-I7D4REHJ-b195z021.js → blockDiagram-I7D4REHJ-bqquxv2u.js} +1 -1
  239. package/static/dist/assets/{c4Diagram-7LVT6UL2-olu212k1.js → c4Diagram-7LVT6UL2-f3fb8w6z.js} +1 -1
  240. package/static/dist/assets/cancel-drd0g6d9.js +1 -0
  241. package/static/dist/assets/channel-cpdgf867.js +1 -0
  242. package/static/dist/assets/{chunk-4HAMMTFA-b8o1nufs.js → chunk-4HAMMTFA-bzfyyzp3.js} +1 -1
  243. package/static/dist/assets/{chunk-75Z2AOVW-l1seddto.js → chunk-75Z2AOVW-ly99rfx5.js} +1 -1
  244. package/static/dist/assets/{chunk-DU6HZSFF-lo5zg15v.js → chunk-DU6HZSFF-bg6ecfqf.js} +3 -3
  245. package/static/dist/assets/{chunk-F27PBJKO-dsdzrp16.js → chunk-F27PBJKO-e8k7mmqw.js} +1 -1
  246. package/static/dist/assets/{chunk-GMAD6QVW-f3fxqq8b.js → chunk-GMAD6QVW-cmalnoaj.js} +1 -1
  247. package/static/dist/assets/{chunk-GVQU2GXP-h8egxtf6.js → chunk-GVQU2GXP-gmif5o01.js} +1 -1
  248. package/static/dist/assets/{chunk-IMKFNOWR-iolwd8s0.js → chunk-IMKFNOWR-f1nwwh6u.js} +1 -1
  249. package/static/dist/assets/{chunk-L3NEJ4N5-b88bbtwi.js → chunk-L3NEJ4N5-oktfh5v7.js} +1 -1
  250. package/static/dist/assets/{chunk-OSK3NFVY-e3e7nr6s.js → chunk-OSK3NFVY-b1sdx96z.js} +1 -1
  251. package/static/dist/assets/{chunk-P2QGCYS3-mdc46rt1.js → chunk-P2QGCYS3-fhu4nzxt.js} +1 -1
  252. package/static/dist/assets/{chunk-POPQ4Y6H-kqlllrrm.js → chunk-POPQ4Y6H-em31rnef.js} +1 -1
  253. package/static/dist/assets/{chunk-PWAF6VOD-he585k2x.js → chunk-PWAF6VOD-bxhhyftw.js} +1 -1
  254. package/static/dist/assets/{chunk-SHT3W25Y-gh94y9a4.js → chunk-SHT3W25Y-fmrvwcav.js} +3 -3
  255. package/static/dist/assets/{chunk-SVP7TREG-euxp9y7e.js → chunk-SVP7TREG-lymukl2j.js} +1 -1
  256. package/static/dist/assets/{chunk-TICWLB2K-c8cxes1q.js → chunk-TICWLB2K-lmza6k23.js} +1 -1
  257. package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +1 -0
  258. package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +1 -0
  259. package/static/dist/assets/{copied-kq0u3ht7.js → copied-ima3pysb.js} +1 -1
  260. package/static/dist/assets/copy-fw5b1pc6.js +1 -0
  261. package/static/dist/assets/{cynefin-OW5HDTMX-lxd44ux4.js → cynefin-OW5HDTMX-czlhbyhs.js} +1 -1
  262. package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-njpiwat8.js → cynefinDiagram-5FMLGOSQ-gq7mvswb.js} +1 -1
  263. package/static/dist/assets/{dagre-GXQ25YYZ-nvgy6yju.js → dagre-GXQ25YYZ-cqdbf7dr.js} +4 -4
  264. package/static/dist/assets/{diagram-S7CK7UJ4-g98nyv72.js → diagram-S7CK7UJ4-o6arxa58.js} +3 -3
  265. package/static/dist/assets/{diagram-UQ7AKVKN-ovgm6lvv.js → diagram-UQ7AKVKN-nhnd68tz.js} +1 -1
  266. package/static/dist/assets/{diagram-VSXAHHWV-pb2bn2e4.js → diagram-VSXAHHWV-dk2sclzn.js} +1 -1
  267. package/static/dist/assets/{diagram-VX7I27RA-6j4mvfqo.js → diagram-VX7I27RA-n3k5nyx3.js} +1 -1
  268. package/static/dist/assets/{diagram-Z3DM3KII-dqmb982r.js → diagram-Z3DM3KII-m6q9cwdh.js} +1 -1
  269. package/static/dist/assets/{ebnfDiagram-PWID7BFC-ozbpzb6d.js → ebnfDiagram-PWID7BFC-lsa5641p.js} +1 -1
  270. package/static/dist/assets/endpoints-d33siokx.js +64 -0
  271. package/static/dist/assets/{erDiagram-RLTQ6QDP-hl7m8vua.js → erDiagram-RLTQ6QDP-fo2ckbe6.js} +1 -1
  272. package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +1 -0
  273. package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +1 -0
  274. package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ompixqmh.js → ganttDiagram-EL5Y4UJY-nzqz6x9g.js} +1 -1
  275. package/static/dist/assets/{gitGraph-4MIJSDKK-b6vxwp9q.js → gitGraph-4MIJSDKK-gukvcxe6.js} +1 -1
  276. package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-k98f57on.js → gitGraphDiagram-WWUBYQGX-lqhdp8f0.js} +1 -1
  277. package/static/dist/assets/{highlighted-body-KPVGNVTW-cd3ff6ge.js → highlighted-body-KPVGNVTW-igw6cp39.js} +1 -1
  278. package/static/dist/assets/index-cxq58xb1.js +11 -0
  279. package/static/dist/assets/index-hgtx08n5.css +2 -0
  280. package/static/dist/assets/{info-A6RAGUB7-hjcwjami.js → info-A6RAGUB7-gvu7a93l.js} +1 -1
  281. package/static/dist/assets/infoDiagram-27XIBGKW-m8t8g6w1.js +2 -0
  282. package/static/dist/assets/{ishikawaDiagram-5VMMS53U-bxak4mwu.js → ishikawaDiagram-5VMMS53U-l07vkbgj.js} +1 -1
  283. package/static/dist/assets/{journeyDiagram-3NMN7TZE-khmihclr.js → journeyDiagram-3NMN7TZE-hybkgj8l.js} +1 -1
  284. package/static/dist/assets/{kanban-definition-UXKFOSKX-h1r32wdx.js → kanban-definition-UXKFOSKX-by13uvtn.js} +1 -1
  285. package/static/dist/assets/{lazyRouteComponent-b2eg0fws.js → lazyRouteComponent-af0lvgjx.js} +1 -1
  286. package/static/dist/assets/{line-friyjof3.js → line-osxq6fq7.js} +1 -1
  287. package/static/dist/assets/link-jyxicdhg.js +1 -0
  288. package/static/dist/assets/login-ec21t39x.js +1 -0
  289. package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +1 -0
  290. package/static/dist/assets/{mermaid-parser.core-f1gvpwrt.js → mermaid-parser.core-i91oxmou.js} +3 -3
  291. package/static/dist/assets/{mindmap-definition-YA3MSWOX-mt2oxwue.js → mindmap-definition-YA3MSWOX-m82mx8ww.js} +1 -1
  292. package/static/dist/assets/onboarding-btnltvyz.js +1 -0
  293. package/static/dist/assets/onboarding_model_label-dp7xffoh.js +1 -0
  294. package/static/dist/assets/{packet-AYTQ26CC-jik4ixlf.js → packet-AYTQ26CC-nbtgge2o.js} +1 -1
  295. package/static/dist/assets/{pegDiagram-XKGWAZYB-erxlv6cc.js → pegDiagram-XKGWAZYB-f1zxzga1.js} +1 -1
  296. package/static/dist/assets/{pie-WAS4IAKB-mnkoldfl.js → pie-WAS4IAKB-of8vuhxs.js} +1 -1
  297. package/static/dist/assets/{pieDiagram-E7YTZNPT-i7lwrada.js → pieDiagram-E7YTZNPT-o7y8ftio.js} +3 -3
  298. package/static/dist/assets/{quadrantDiagram-AXDQQJYC-fr8ejdoy.js → quadrantDiagram-AXDQQJYC-iqiwia1t.js} +1 -1
  299. package/static/dist/assets/{radar-RG4KPBEZ-otarjte2.js → radar-RG4KPBEZ-delnsy2v.js} +1 -1
  300. package/static/dist/assets/{railroad-74A4TZTK-lw362oto.js → railroad-74A4TZTK-nspnyq9u.js} +1 -1
  301. package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +1 -0
  302. package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +1 -0
  303. package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +1 -0
  304. package/static/dist/assets/{railroadDiagram-O6MQD6OU-esyn75pw.js → railroadDiagram-O6MQD6OU-z41tqx5z.js} +1 -1
  305. package/static/dist/assets/refresh-b4uams1i.js +1 -0
  306. package/static/dist/assets/refresh-cw-nt1dtsx4.js +1 -0
  307. package/static/dist/assets/{requirementDiagram-BXWQKSXE-cyhi5u29.js → requirementDiagram-BXWQKSXE-bdvif194.js} +1 -1
  308. package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jljpbo0h.js → sankeyDiagram-P5KCCOFB-h73e0rol.js} +1 -1
  309. package/static/dist/assets/{saved-do49si3v.js → saved-kg52csy7.js} +1 -1
  310. package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fz0dhlu7.js → sequenceDiagram-WJ2MYXX4-iv95mb1z.js} +1 -1
  311. package/static/dist/assets/{settings_section_extensions_title-b7lojn0o.js → settings_section_extensions_title-ej0iapxe.js} +1 -1
  312. package/static/dist/assets/share._token-f97e94r0.js +2 -0
  313. package/static/dist/assets/sliders-horizontal-fj18it47.js +1 -0
  314. package/static/dist/assets/sse-fwpqtzbg.js +1 -0
  315. package/static/dist/assets/{stateDiagram-D77RDMKH-kdnithdx.js → stateDiagram-D77RDMKH-k417ixmf.js} +1 -1
  316. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +1 -0
  317. package/static/dist/assets/{swimlanes-42K2YHIH-esd6gmz3.js → swimlanes-42K2YHIH-esola70w.js} +1 -1
  318. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +8 -0
  319. package/static/dist/assets/{timeline-definition-24CTP7MA-g0qygccv.js → timeline-definition-24CTP7MA-getrz2y8.js} +1 -1
  320. package/static/dist/assets/todoStore-eginf9h4.js +1 -0
  321. package/static/dist/assets/{treeView-Q6P3EWNA-ndchzkuu.js → treeView-Q6P3EWNA-hc7ap1q7.js} +1 -1
  322. package/static/dist/assets/{treemap-WGGIJYW6-hwjxj0zz.js → treemap-WGGIJYW6-b7k1syy5.js} +1 -1
  323. package/static/dist/assets/tts-lxdxnmjd.js +1 -0
  324. package/static/dist/assets/{untitled-mbr6tuhu.js → untitled-ovpfthg4.js} +1 -1
  325. package/static/dist/assets/useForm-bcblv1rx.js +1 -0
  326. package/static/dist/assets/useLocale-fi5jb8ma.js +1 -0
  327. package/static/dist/assets/{useParams-lo9u33d1.js → useParams-lhg566ew.js} +1 -1
  328. package/static/dist/assets/{useSearch-lq1mr090.js → useSearch-lbrh6ejn.js} +1 -1
  329. package/static/dist/assets/{vennDiagram-4TSXK5OY-gubovd1r.js → vennDiagram-4TSXK5OY-n6pcy5qb.js} +1 -1
  330. package/static/dist/assets/{wardley-WFR3VGLG-ir3om65j.js → wardley-WFR3VGLG-lpa18vz1.js} +1 -1
  331. package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ce0or1ie.js → wardleyDiagram-VM6X3IG4-kpclwwg7.js} +2 -2
  332. package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dexylcjg.js → xychartDiagram-S5SC5T6Z-paa2n8y0.js} +1 -1
  333. package/static/dist/index.html +1 -1
  334. package/static/dist/sw.js +2 -2
  335. package/static/dist/assets/AppShell-orat1s43.js +0 -1
  336. package/static/dist/assets/ChatPage-ojhka3hy.js +0 -22
  337. package/static/dist/assets/HubRoute-gxgw1t33.js +0 -2
  338. package/static/dist/assets/RightPanel-c66ani1s.js +0 -1
  339. package/static/dist/assets/Select-fswf66up.js +0 -1
  340. package/static/dist/assets/SessionListPanel-goh5mtt8.js +0 -1
  341. package/static/dist/assets/SettingsLayout-n5d4wq4m.js +0 -1
  342. package/static/dist/assets/States-kxyu50at.js +0 -1
  343. package/static/dist/assets/Toaster-6zmbcqgg.js +0 -1
  344. package/static/dist/assets/_app-fowyruiu.js +0 -1
  345. package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +0 -1
  346. package/static/dist/assets/_app.index-c9zv4y7w.js +0 -1
  347. package/static/dist/assets/_app.insights-jf12nofo.js +0 -1
  348. package/static/dist/assets/_app.kanban-lri5a4yu.js +0 -1
  349. package/static/dist/assets/_app.logs-md1p0zqo.js +0 -1
  350. package/static/dist/assets/_app.memory-bzvf2e28.js +0 -1
  351. package/static/dist/assets/_app.profiles-jpg7zmbn.js +0 -1
  352. package/static/dist/assets/_app.settings-mpuwlx4j.js +0 -1
  353. package/static/dist/assets/_app.settings._section-knz8rwoj.js +0 -1
  354. package/static/dist/assets/_app.skills-fwu1dkir.js +0 -1
  355. package/static/dist/assets/_app.tasks-h9gphtl3.js +0 -2
  356. package/static/dist/assets/_app.todos-hd1dnvrh.js +0 -1
  357. package/static/dist/assets/_app.workspaces-l8toyn5f.js +0 -1
  358. package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +0 -1
  359. package/static/dist/assets/architectureDiagram-5GKGNRK7-k5a7v36n.js +0 -36
  360. package/static/dist/assets/channel-gozy3dp3.js +0 -1
  361. package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +0 -1
  362. package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +0 -1
  363. package/static/dist/assets/endpoints-k8agnro8.js +0 -9
  364. package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +0 -1
  365. package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +0 -1
  366. package/static/dist/assets/index-nfy0ezs6.css +0 -2
  367. package/static/dist/assets/index-nqed3oum.js +0 -11
  368. package/static/dist/assets/infoDiagram-27XIBGKW-fm06rj36.js +0 -2
  369. package/static/dist/assets/link-m90ahx3n.js +0 -1
  370. package/static/dist/assets/login-bw7m2fzo.js +0 -1
  371. package/static/dist/assets/matchContext-f8oy3lo7.js +0 -1
  372. package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +0 -1
  373. package/static/dist/assets/onboarding-ret37j1e.js +0 -1
  374. package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +0 -1
  375. package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +0 -1
  376. package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +0 -1
  377. package/static/dist/assets/refresh-cw-k66keho6.js +0 -1
  378. package/static/dist/assets/search-j5ki6a9o.js +0 -1
  379. package/static/dist/assets/share._token-kg8w647b.js +0 -2
  380. package/static/dist/assets/sliders-horizontal-c4wya4dy.js +0 -1
  381. package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +0 -1
  382. package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +0 -8
  383. package/static/dist/assets/tabBuild-bjmyvmn5.js +0 -56
  384. package/static/dist/assets/todoStore-kjk5l0sy.js +0 -1
  385. package/static/dist/assets/useForm-ocxu460f.js +0 -1
  386. package/static/dist/assets/useLocale-fzy5jhg6.js +0 -1
package/dist/redact.js CHANGED
@@ -1,12 +1,15 @@
1
1
  import { str } from './util.js';
2
2
  import { snapshotArgs, toolArgs, toolDisplay, toolName } from './sessions/tool-display.js';
3
+ import { messageText } from './sessions/merge.js';
4
+ import { stripAttachedFilesMarker } from './sessions/session.js';
3
5
  /**
4
6
  * Credential redaction and the public session projection (Python
5
7
  * `api/helpers.py`). API responses are a hard boundary: transcript-bearing
6
8
  * fields are masked with the local pattern set, and private replay aliases
7
- * are stripped at their schema positions. The Agent's broader redactor is
8
- * not consulted (it lives in-process in Python); the local patterns mirror
9
- * its known credential prefixes.
9
+ * are stripped at their schema positions. The Agent's redactor
10
+ * (`agent/redact.py`, `redact_sensitive_text(force=True)`) is not consulted
11
+ * at runtime; its pattern families are ported here and held to parity by
12
+ * the `Agent redactor parity` tests.
10
13
  */
11
14
  const CRED_RE = new RegExp('(?<![A-Za-z0-9_-])(' +
12
15
  'sk-[A-Za-z0-9_-]{10,}' +
@@ -16,6 +19,7 @@ const CRED_RE = new RegExp('(?<![A-Za-z0-9_-])(' +
16
19
  '|ghu_[A-Za-z0-9]{10,}' +
17
20
  '|ghs_[A-Za-z0-9]{10,}' +
18
21
  '|ghr_[A-Za-z0-9]{10,}' +
22
+ '|xapp-\\d+-[A-Za-z0-9-]{10,}' +
19
23
  '|xox[baprs]-[A-Za-z0-9-]{10,}' +
20
24
  '|AIza[A-Za-z0-9_-]{30,}' +
21
25
  '|pplx-[A-Za-z0-9]{10,}' +
@@ -44,6 +48,14 @@ const CRED_RE = new RegExp('(?<![A-Za-z0-9_-])(' +
44
48
  '|hsk-[A-Za-z0-9]{10,}' +
45
49
  '|mem0_[A-Za-z0-9]{10,}' +
46
50
  '|brv_[A-Za-z0-9]{10,}' +
51
+ '|xai-[A-Za-z0-9]{30,}' +
52
+ '|ntn_[A-Za-z0-9]{10,}' +
53
+ '|fw[-_][A-Za-z0-9]{30,}' +
54
+ '|fpk_[A-Za-z0-9]{30,}' +
55
+ '|(?:glpat|gloas|gldt|glcbt|glptt|glft|glimt|glagent|glsoat|glffct|glwt)-[A-Za-z0-9_-]{10,}' +
56
+ '|glrtr?-[A-Za-z0-9_.-]{10,}' +
57
+ '|GR1348941[A-Za-z0-9_-]{10,}' +
58
+ '|pk-lf-[A-Za-z0-9-]{8,}' +
47
59
  ')(?![A-Za-z0-9_-])', 'g');
48
60
  /** One `name=value` auth parameter: an escaped-quoted, quoted or bare value. */
49
61
  const AUTH_PARAM = String.raw `[A-Za-z0-9_-]+=(?:\\"(?:[^"\\\r\n]|\\[^"])*\\"|"(?:[^"\\\r\n]|\\.)*"|'[^'\r\n]*'|[^\s,"'\\]*)`;
@@ -54,8 +66,8 @@ const AUTH_PARAM = String.raw `[A-Za-z0-9_-]+=(?:\\"(?:[^"\\\r\n]|\\[^"])*\\"|"(
54
66
  * A parameterized credential (`Digest username="bob", response="..."`, `Credential=..., Signature=...`) is masked whole.
55
67
  */
56
68
  const AUTH_HDR_RE = new RegExp(String.raw `(Authorization:\s*(?:(?!\*+\s)[A-Za-z0-9!#$%&*+.^_|~-]+\s+(?=[^\s,\])]))?)(${AUTH_PARAM}(?:\s*,\s*${AUTH_PARAM})*|[^\s,\])][^\s'",\])]*)`, 'gi');
57
- /** A JSON Web Token anywhere (`eyJ<header>.<payload>.<signature>`). */
58
- const JWT_RE = /\beyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}/g;
69
+ /** A JSON Web Token anywhere (`eyJ<header>.<payload>.<signature>`), or its header alone or with its payload. */
70
+ const JWT_RE = /\beyJ(?:[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}|[A-Za-z0-9_-]{10,}(?:\.[A-Za-z0-9_=-]{4,}){0,2})/g;
59
71
  /** A bearer credential in any header or text (`X-Auth: Bearer ...`); `AUTH_HDR_RE` owns the `Authorization:` header. */
60
72
  const BEARER_RE = /((?<!Authorization:\s{0,8})\bBearer\s+)([^\s,\])][^\s'",\])]*)/gi;
61
73
  /** One shell-quoted piece, which may span lines: `'...'`, `"..."` (with backslash escapes), bash `$'...'` / `$"..."`, or JSON escaped inside a shell string (`\"...\"`). */
@@ -100,6 +112,36 @@ const percentDecode = (text) => text.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => S
100
112
  const LISTED_FLAG_RE = /(["'])(-{1,2})([A-Za-z0-9_][A-Za-z0-9_.-]*)\1(\s*,\s*)(?:(["'])((?:\\.|(?!\5)[^\\])*)\5|(\[[^[\]]*\]|\{[^{}]*\}|[^\s,\])}'"[{]+))/g;
101
113
  const EMBEDDED_AWS_RE = /(?:AKIA|ASIA)[A-Z0-9]{16}/g;
102
114
  const ENV_RE = /([A-Z0-9_]{0,50}(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Z0-9_]{0,50})\s*=\s*(['"]?)(\S+)\2/g;
115
+ /**
116
+ * The Agent's other env names: an all-caps one holding `KEY`, `PASS` or `PW` (`OPENAI_KEY`, `DB_PW`), or a `name_key` /
117
+ * `name_pass` / `name_pw` in any case, URL query parameters included (the Agent skips any text with a URL). One attempt per
118
+ * identifier, which must hold a keyword, so the scan stays linear on long runs (`PWPWPW…`). The value is the shell word
119
+ * after `=`, read by `shellWordEnd` (quotes, escapes, `$'…'`).
120
+ */
121
+ const ENV_SUFFIX_RE = /(?<![A-Za-z0-9_])((?=[A-Z0-9_]*(?:KEY|PASS|PW))[A-Z0-9_]+|[A-Za-z0-9_]+_(?:[Kk][Ee][Yy]|[Pp][Aa][Ss][Ss]|[Pp][Ww])(?![A-Za-z0-9_]))[ \t]*=[ \t]*/g;
122
+ /** The prefilter's view of `ENV_SUFFIX_RE`. */
123
+ const ENV_SUFFIX_TEST_RE = new RegExp(ENV_SUFFIX_RE.source);
124
+ /**
125
+ * The Agent's env-name keywords, as whole name words (`DB_PW`, `GITHUB_TOKEN`), never inside a word (`KEYBOARD`, `PASSAGE`,
126
+ * `TOKENIZER`, `AUTHOR_KEY`'s `AUTH`).
127
+ */
128
+ const ENV_KEYWORD_RE = /(?:^|[^A-Za-z])(?:(?:api|auth|access|refresh|session|secret)[ _.-]?(?:key|token)|token|secret|passwd|password|pass|pw|credential|auth|key)s?(?![A-Za-z])/i;
129
+ /**
130
+ * Env names whose value is a credential whatever its shape, as whole name words (`DB_PASS`, not `COMPASS_KEY` or
131
+ * `AUTHOR_KEY`); a bare `KEY` needs an opaque value (`SORT_KEY=name` stays).
132
+ */
133
+ const ENV_STRONG_NAME_RE = /(?:^|[^A-Za-z])(?:(?:api|auth|access|refresh|session|id|bearer)[ _.-]?(?:key|token)|key[ _.-]?material|secret|passwd|password|pass|pw|credentials?|auth|bearer)s?(?![A-Za-z])/i;
134
+ /**
135
+ * The Agent's env-assignment gate: a name holding a whole keyword whose value is a credential, because the name is strong
136
+ * or the value opaque (`GITHUB_TOKEN=abc` and `MAX_TOKENS=100` stay). Env lookups (`os.getenv(…)`) name a variable.
137
+ */
138
+ const isEnvCredential = (key, value) => !/^(?:os\.(?:getenv|environ)|process\.env|\$ENV\{)/.test(value) && ENV_KEYWORD_RE.test(key) && (ENV_STRONG_NAME_RE.test(key) || looksOpaque(value));
139
+ /** The Agent's `_looks_like_opaque_credential`: a value shaped like a generated secret rather than a word. */
140
+ function looksOpaque(value) {
141
+ if (value === '***' || /^[A-Fa-f0-9]{16,}$/.test(value) || /^[A-Za-z0-9_./+=-]{20,}$/.test(value))
142
+ return true;
143
+ return value.length >= 12 && [/[a-z]/, /[A-Z]/, /[0-9]/].filter((re) => re.test(value)).length >= 2;
144
+ }
103
145
  /**
104
146
  * `scheme://user:secret@host` (database and basic-auth URLs): the password is masked, the user and host stay. The user
105
147
  * and password may be assembled from quoted and escaped shell pieces (`bob:hun'ter2'@`), and every delimiter may be
@@ -171,12 +213,214 @@ const USER_FLAG_RE = /(?<![A-Za-z0-9-])(?:-[uU][ \t]*|--(?:user|proxy-u(?:s(?:e(
171
213
  /** The prefilter's view of `USER_FLAG_RE`. */
172
214
  const USER_FLAG_TEST_RE = new RegExp(USER_FLAG_RE.source);
173
215
  const QUERY_KEY_RE = /([?&]key=)([^\s"'&#]+)/gi;
216
+ /**
217
+ * A Telegram bot token (`bot<id>:<secret>`): the id stays. The id starts a digit run, so a long number is scanned once
218
+ * rather than from every digit (a match from mid-run would end at the same `:`).
219
+ */
220
+ const TELEGRAM_TOKEN_RE = /(bot)?(?<!\d)(\d{8,}):([-A-Za-z0-9_]{30,})/g;
221
+ /** An E.164 phone number, masked to its first and last digits; never inside a word or encoded data (`ab+1234567`). */
222
+ const PHONE_RE = /(?<![A-Za-z0-9])\+[1-9]\d{6,14}(?![A-Za-z0-9])/g;
223
+ /**
224
+ * A bare token as URL userinfo (`https://TOKEN@github.com`, `ssh://…@`): no `user:` part, at least 8 characters. Round-trip
225
+ * URLs carry tokens in the query, so a bare userinfo credential is never one.
226
+ */
227
+ const URL_BARE_TOKEN_RE = /((?:https?|wss?|git|ssh|ftps?|sftp):\/\/)([^\s:@/?#]{8,})(?=@\S)/gi;
228
+ /** C0, DEL, C1 and zero-width characters that can split a token body (`ghp_abc\x1bdef`, `sk-abc\u200bdef`). */
229
+ const CONTROL_CHAR_RE = /[\x00-\x1f\x7f-\x9f\u200b-\u200f\u2028-\u202f\u2060\ufeff]/;
230
+ const CONTROL_CHARS_RE = new RegExp(CONTROL_CHAR_RE.source, 'g');
231
+ /** C0 controls a terminal runs inside an escape sequence without ending it (all but CAN, SUB and ESC), and DEL. */
232
+ const SEQ_C0 = String.raw `\x00-\x17\x19\x1c-\x1f\x7f`;
233
+ /** Where a sequence ends early: CAN or SUB cancels it, and ESC, a C1 control or the end of the text interrupts it. */
234
+ const SEQ_CUT = String.raw `(?:[\x18\x1a]|(?=[\x1b\x80-\x9f]|$))`;
235
+ const SEQ_ESC = String.raw `\x1b[${SEQ_C0}]*`;
236
+ /** Parameters and intermediates in any order: a parameter after an intermediate makes a terminal ignore the CSI. */
237
+ const SEQ_CSI = String.raw `(?:${SEQ_ESC}\[|\x9b)[ -?${SEQ_C0}]*(?:[@-~]|${SEQ_CUT})`;
238
+ const SEQ_NF = String.raw `${SEQ_ESC}[ -/][ -/${SEQ_C0}]*(?:[0-~]|${SEQ_CUT})`;
239
+ const SEQ_SINGLE = String.raw `${SEQ_ESC}[0-OQ-WYZ\\\x60-~]`;
240
+ /**
241
+ * An ECMA-48 escape sequence as a terminal parses it, or one control or zero-width character. The sequences, 7-bit
242
+ * `ESC …` or the C1 code point: CSI (`ESC [` … final byte), the strings OSC (… BEL or ST) and DCS, SOS, PM and APC
243
+ * (… ST), a character-set selection (`ESC ( B`), and a single-character escape (`ESC 7`); each also ends at `SEQ_CUT`.
244
+ * No part of a sequence can start the part after it, which keeps the scan linear.
245
+ */
246
+ const ANSI_GAPS_RE = new RegExp([
247
+ SEQ_CSI,
248
+ String.raw `(?:${SEQ_ESC}\]|\x9d)[^\x07\x18\x1a\x1b\x80-\x9f]*(?:\x07|\x1b\\|\x9c|${SEQ_CUT})`,
249
+ String.raw `(?:${SEQ_ESC}[PX^_]|[\x90\x98\x9e\x9f])[^\x18\x1a\x1b\x80-\x9f]*(?:\x1b\\|\x9c|${SEQ_CUT})`,
250
+ SEQ_NF,
251
+ SEQ_SINGLE,
252
+ CONTROL_CHAR_RE.source,
253
+ ].join('|'), 'g');
254
+ /** `ANSI_GAPS_RE` with a string's payload kept: only its opener and terminator are gaps. */
255
+ const ANSI_PAYLOAD_GAPS_RE = new RegExp([SEQ_CSI, String.raw `${SEQ_ESC}[\]PX^_]`, SEQ_NF, SEQ_SINGLE, CONTROL_CHAR_RE.source].join('|'), 'g');
256
+ /**
257
+ * The views a split token is matched on: without whole escape sequences, without them but keeping string payloads (a
258
+ * terminal title can hold a token), and without control characters only (an escape's final byte can be a token's).
259
+ */
260
+ const SPLIT_VIEWS = [ANSI_GAPS_RE, ANSI_PAYLOAD_GAPS_RE, CONTROL_CHARS_RE];
261
+ const ANSI_GAP_AT_RE = new RegExp(ANSI_GAPS_RE.source, 'y');
262
+ const TOKEN_CHAR_RE = /[A-Za-z0-9_-]/;
263
+ /** A string sequence's opener, with an OSC's command number (`8;`), which is no payload. */
264
+ const STRING_OPENER_RE = new RegExp(String.raw `^(?:(?:${SEQ_ESC}\]|\x9d)(?:\d*;)?|${SEQ_ESC}[PX^_]|[\x90\x98\x9e\x9f])`);
265
+ const PAYLOAD_RUN_CAP = 65_536;
266
+ const CRED_TEST_RE = new RegExp(CRED_RE.source);
267
+ /** `CRED_RE`'s prefix and body from a position, without its boundaries (a split token checks the original ones). */
268
+ const CRED_RUN_RE = new RegExp(CRED_RE.source.replace(/^\(\?<!\[A-Za-z0-9_-\]\)/, '').replace(/\(\?!\[A-Za-z0-9_-\]\)$/, ''), 'y');
269
+ const SPLIT_TOKEN_CAP = 1024;
270
+ /** Where a `CRED_RE` prefix and body start at a boundary, however they end. */
271
+ const CRED_START_RE = new RegExp(CRED_RE.source.replace(/\(\?!\[A-Za-z0-9_-\]\)$/, ''), 'g');
272
+ const CRED_WHOLE_RE = new RegExp(`^${CRED_RE.source}$`);
273
+ /** `CRED_START_RE` and `CRED_RUN_RE` without their body: a credential's prefix, however little of its body follows. */
274
+ const CRED_PREFIX_RE = new RegExp(CRED_START_RE.source.replace(/\{\d+,?\}/g, '{0}'), 'g');
275
+ const CRED_PREFIX_RUN_RE = new RegExp(CRED_RUN_RE.source.replace(/\{\d+,?\}/g, '{0}'), 'y');
276
+ /** A URL query parameter's value, up to the next `&` or `#`. */
277
+ const QUERY_VALUE_RE = /[^&#]*/y;
174
278
  const PRIVKEY_RE = /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g;
175
279
  /** A private key whose end marker is missing (a display cap cut it off): masked to the end of the text. */
176
280
  const PRIVKEY_OPEN_RE = /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*$/;
177
281
  const CODE_ENV_KEY_LITERAL_RE = /([A-Z0-9_]{0,50}(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Z0-9_]{0,50}=)(["'][)\]:,]+|[)\]:,]+)/y;
178
282
  const ENV_KEY_PREFIX_RE = /([A-Z0-9_]{0,50}(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Z0-9_]{0,50}=)/g;
179
283
  const REDACTED_ENV_VALUE_RE = /(?:\*{3,}|[A-Za-z0-9][A-Za-z0-9_.:/+-]{0,32}\.\.\.[A-Za-z0-9_.:/+-]{1,16})/y;
284
+ /**
285
+ * Where a credential split by control or zero-width characters (`ghp_abc\x1bdef`, `ghp_abc\ndef…`) starting at `i` of the
286
+ * stripped text ends there, or -1. The run of its prefix and body is read once (capped), then the longest candidate is
287
+ * taken that ends at a piece boundary of the original, is a whole credential, is not the name of a `KEY=` that follows,
288
+ * and never reaches a line holding a whole token itself (that line is the prefix pass's and joins nothing).
289
+ */
290
+ function splitTokenEnd(text, stripped, kept, i) {
291
+ CRED_RUN_RE.lastIndex = 0;
292
+ // ponytail: a split credential longer than SPLIT_TOKEN_CAP is cut at the cap; raise it if longer tokens appear.
293
+ const run = CRED_RUN_RE.exec(stripped.slice(i, i + SPLIT_TOKEN_CAP))?.[0];
294
+ if (!run)
295
+ return -1;
296
+ const start = kept[i];
297
+ let limit = kept[i + run.length - 1] + 1;
298
+ for (let lineStart = start; lineStart < limit;) {
299
+ const lineEnd = text.slice(lineStart, limit).search(/[\r\n]/);
300
+ const end = lineEnd === -1 ? limit : lineStart + lineEnd;
301
+ if (CRED_TEST_RE.test(text.slice(lineStart, end))) {
302
+ limit = lineStart === start ? end : lineStart;
303
+ break;
304
+ }
305
+ if (lineEnd === -1)
306
+ break;
307
+ lineStart = end + 1;
308
+ }
309
+ for (let e = i + run.length; e > i; e -= 1) {
310
+ const atBoundary = e === stripped.length || kept[e] - kept[e - 1] > 1 || (e === i + run.length && !/[A-Za-z0-9_-]/.test(stripped[e]));
311
+ const originalEnd = kept[e - 1] + 1;
312
+ if (!atBoundary || originalEnd > limit || !CRED_WHOLE_RE.test(stripped.slice(i, e)) || /^[ \t]*=/.test(text.slice(originalEnd, originalEnd + 64)))
313
+ continue;
314
+ return e;
315
+ }
316
+ return -1;
317
+ }
318
+ /**
319
+ * The original spans, and the joined text, of prefixed credentials whose body a gap of one of `SPLIT_VIEWS` splits,
320
+ * matched on the text without those gaps, plus `payloadRunSpan`'s from each prefix. A token starts at a boundary of the
321
+ * stripped text, or where a stripped gap hid the original one (`note\nghp_…`); one starting inside a token already
322
+ * masked is part of it.
323
+ */
324
+ function splitTokenSpans(text, gaps) {
325
+ const stripped = text.replace(gaps, '');
326
+ if (stripped.length === text.length)
327
+ return [];
328
+ // The original index of each kept character.
329
+ const kept = [];
330
+ let at = 0;
331
+ for (const m of text.matchAll(gaps)) {
332
+ while (at < m.index)
333
+ kept.push(at++);
334
+ at += m[0].length;
335
+ }
336
+ while (at < text.length)
337
+ kept.push(at++);
338
+ const starts = [...stripped.matchAll(CRED_START_RE)].map((m) => m.index);
339
+ const prefixStarts = [...stripped.matchAll(CRED_PREFIX_RE)].map((m) => m.index);
340
+ for (let i = 1; i < kept.length; i += 1) {
341
+ if (kept[i] - kept[i - 1] <= 1 || !TOKEN_CHAR_RE.test(stripped[i - 1]))
342
+ continue;
343
+ starts.push(i);
344
+ CRED_PREFIX_RUN_RE.lastIndex = i;
345
+ if (CRED_PREFIX_RUN_RE.test(stripped))
346
+ prefixStarts.push(i);
347
+ }
348
+ const spans = [];
349
+ let scanned = 0;
350
+ for (const i of starts.sort((a, b) => a - b)) {
351
+ if (i < scanned)
352
+ continue;
353
+ const e = splitTokenEnd(text, stripped, kept, i);
354
+ if (e === -1)
355
+ continue;
356
+ spans.push([kept[i], kept[e - 1] + 1, stripped.slice(i, e)]);
357
+ scanned = e;
358
+ }
359
+ let walked = 0;
360
+ for (const i of prefixStarts.sort((a, b) => a - b)) {
361
+ if (kept[i] < walked)
362
+ continue;
363
+ const [span, reached] = payloadRunSpan(text, kept[i]);
364
+ if (span)
365
+ spans.push(span);
366
+ walked = reached;
367
+ }
368
+ return spans;
369
+ }
370
+ /**
371
+ * Fail-closed for a token split across string payloads: from a credential prefix at `start`, the span through the run of
372
+ * token characters and whole escape sequences after it, when a string payload in the run holds token characters, and
373
+ * where the run stopped. Any subset of payloads can hold a token's pieces and the rest junk
374
+ * (`\x1b]0;ghp_ab\x1b]0;x_y\x07cd…`), so the run is masked whole rather than rebuilt.
375
+ */
376
+ function payloadRunSpan(text, start) {
377
+ let token = '';
378
+ let end = start;
379
+ let hidden = false;
380
+ let k = start;
381
+ // ponytail: a run longer than PAYLOAD_RUN_CAP is masked in pieces; raise it if longer hidden tokens appear.
382
+ while (k < text.length && k - start < PAYLOAD_RUN_CAP) {
383
+ ANSI_GAP_AT_RE.lastIndex = k;
384
+ const gap = ANSI_GAP_AT_RE.exec(text)?.[0];
385
+ if (gap) {
386
+ const from = STRING_OPENER_RE.exec(gap)?.[0].length ?? gap.length;
387
+ for (let j = from; j < gap.length; j += 1) {
388
+ if (!TOKEN_CHAR_RE.test(gap[j]))
389
+ continue;
390
+ token += gap[j];
391
+ end = k + j + 1;
392
+ hidden = true;
393
+ }
394
+ k += gap.length;
395
+ }
396
+ else if (TOKEN_CHAR_RE.test(text[k])) {
397
+ token += text[k];
398
+ end = k += 1;
399
+ }
400
+ else
401
+ break;
402
+ }
403
+ return [hidden ? [start, end, token] : null, k];
404
+ }
405
+ /** Masks each union of the split-token spans every view finds, so no view masks a piece of a token another one joins. */
406
+ function maskSplitTokens(text) {
407
+ const spans = SPLIT_VIEWS.flatMap((gaps) => splitTokenSpans(text, gaps)).sort((a, b) => a[0] - b[0]);
408
+ let out = '';
409
+ let last = 0;
410
+ for (let k = 0; k < spans.length;) {
411
+ const start = spans[k][0];
412
+ let [, end, token] = spans[k];
413
+ for (k += 1; k < spans.length && spans[k][0] < end; k += 1) {
414
+ const [, nextEnd, nextToken] = spans[k];
415
+ end = Math.max(end, nextEnd);
416
+ if (nextToken.length > token.length)
417
+ token = nextToken;
418
+ }
419
+ out += text.slice(last, start) + mask(token);
420
+ last = end;
421
+ }
422
+ return out + text.slice(last);
423
+ }
180
424
  function mask(token) {
181
425
  // By code point, so a partial mask never splits a surrogate pair into invalid JSON.
182
426
  const chars = Array.from(token);
@@ -437,6 +681,38 @@ function maskShellWord(value) {
437
681
  }
438
682
  /** A shell word's content, for the "nothing to mask" checks: the inside of a single quoted piece, else the word. */
439
683
  const shellWordInner = (value) => splitQuoted(value)?.inner ?? value;
684
+ /**
685
+ * `ENV_SUFFIX_RE` assignments whose name is a credential (`DB_PASS=`, `openai_key=`). A password-class name masks any value,
686
+ * punctuation-only included; a bare `KEY` masks an opaque one. A plain value keeps the Agent's partial mask; a quoted,
687
+ * escaped or composed one is masked whole. Env lookups (`os.getenv(…)`) and masked values stay.
688
+ */
689
+ function redactEnvSuffixes(text) {
690
+ let out = '';
691
+ let last = 0;
692
+ const quoteAt = quoteTracker(text);
693
+ const closeOf = enclosingClose(text);
694
+ // A skipped value does not move the scan past its word: a later match inside that word reuses its end, and a value is
695
+ // judged by its head, so many assignments inside one long word (`a_key=x\ a_key=x\ …`) still scan it once.
696
+ let wordEnd = -1;
697
+ ENV_SUFFIX_RE.lastIndex = 0;
698
+ for (let m = ENV_SUFFIX_RE.exec(text); m; m = ENV_SUFFIX_RE.exec(text)) {
699
+ const key = m[1];
700
+ const valueStart = m.index + m[0].length;
701
+ wordEnd = valueStart < wordEnd ? wordEnd : shellWordEnd(text, valueStart, quoteAt(valueStart), closeOf);
702
+ // A URL query parameter's value ends at the next `&` or `#`, as in `redactCredentialParams`.
703
+ QUERY_VALUE_RE.lastIndex = valueStart;
704
+ const valueEnd = /[?&]/.test(text[m.index - 1] ?? '') ? Math.min(wordEnd, valueStart + QUERY_VALUE_RE.exec(text)[0].length) : wordEnd;
705
+ const value = text.slice(valueStart, valueEnd);
706
+ const head = value.slice(0, 128);
707
+ const inner = shellWordInner(head);
708
+ if (!inner.trim() || inner === '***' || !isEnvCredential(key, shellDequote(head)))
709
+ continue;
710
+ out += text.slice(last, valueStart) + (/^[^\s'"\\$`]+$/.test(value) ? mask(value) : maskShellWord(value));
711
+ last = valueEnd;
712
+ ENV_SUFFIX_RE.lastIndex = Math.max(valueEnd, ENV_SUFFIX_RE.lastIndex);
713
+ }
714
+ return out + text.slice(last);
715
+ }
440
716
  /** Credential parameters (`CRED_PARAM_RE`) with their whole shell-word value fully masked. */
441
717
  function redactCredentialParams(text) {
442
718
  let out = '';
@@ -565,6 +841,198 @@ function redactUserFlags(text) {
565
841
  }
566
842
  return out + text.slice(last);
567
843
  }
844
+ const REGISTRY_LOGIN = { separate: ['-p'], attached: ['-p'], login: true, switches: ['-D', '--debug', '--tls', '--tlsverify', '-r', '--remote', '--syslog'] };
845
+ const MYSQL = { attached: ['-p'] };
846
+ const COMMAND_FLAGS = {
847
+ docker: REGISTRY_LOGIN, podman: REGISTRY_LOGIN, buildah: REGISTRY_LOGIN, nerdctl: REGISTRY_LOGIN, skopeo: REGISTRY_LOGIN, oras: REGISTRY_LOGIN, helm: REGISTRY_LOGIN,
848
+ mysql: MYSQL, mysqladmin: MYSQL, mysqldump: MYSQL, mysqlimport: MYSQL, mysqlshow: MYSQL, mysqlcheck: MYSQL, mysqlslap: MYSQL, mysql_upgrade: MYSQL, mysqlbinlog: MYSQL, mysqlsh: MYSQL,
849
+ mariadb: MYSQL, 'mariadb-admin': MYSQL, 'mariadb-dump': MYSQL, 'mariadb-import': MYSQL, 'mariadb-show': MYSQL, 'mariadb-check': MYSQL, 'mariadb-slap': MYSQL, 'mariadb-upgrade': MYSQL, 'mariadb-binlog': MYSQL,
850
+ sshpass: { separate: ['-p'], attached: ['-p'], values: ['-f', '-d', '-P'], stop: true },
851
+ 'redis-cli': { separate: ['-a', '--pass'] },
852
+ smbclient: { separate: ['-U', '--user'], attached: ['-U', '--user='], percent: true },
853
+ };
854
+ /**
855
+ * A known command's name as a word: bare, after a path, a listed argv element, or composed by quotes (`do"cker"`); or a
856
+ * word the shell computes in whole or part (`$CLIENT`, `$(echo $(which mysql))`, `` `printf docker` ``, `my${EMPTY}sql`),
857
+ * which may name any of them wherever it stands (`sudo -u root $CLIENT -ppw`). A computed word is matched whole when its
858
+ * pieces are flat, so a quoted one reads as a listed element; otherwise up to its first expansion, and read from there.
859
+ */
860
+ /** A parameter, substitution or backtick piece of a word; a name is read whole, so a word splits into pieces one way only. */
861
+ const EXPANSION = String.raw `\$[A-Za-z_][A-Za-z0-9_]*(?![A-Za-z0-9_])|\$\{[^{}\n]*\}|\$\([^()\n]*\)|\x60[^\x60\n]*\x60`;
862
+ const COMMAND_FLAG_RE = new RegExp(String.raw `(?<![^\s;&|()\x60'"/,[\\])(?:(?:${Object.keys(COMMAND_FLAGS).map((name) => name.replaceAll(/(?<=.)(?=.)/g, String.raw `["'\\]*`)).join('|')}|[\w./-]*(?:${EXPANSION})(?:[\w./-]|${EXPANSION})*)(?=[\s;&|)'",\]]|$)|[\w./-]*(?=\$[({A-Za-z_]|\x60))`, 'g');
863
+ /** Every command's flags, for an executable the shell computes. */
864
+ const ALL_COMMAND_FLAGS = [...new Set(Object.values(COMMAND_FLAGS))];
865
+ const COMMAND_FLAG_TEST_RE = new RegExp(COMMAND_FLAG_RE.source);
866
+ /**
867
+ * For an argv (`words` as the programs receive them), the words holding a command's secret, each with the offset it starts
868
+ * at. Every known command named so far is read at once, so a wrapper's operand naming one (`sudo -u mysql docker login -p
869
+ * pw`) does not hide the command it runs.
870
+ */
871
+ function commandFlagMasks(words) {
872
+ const masks = new Map();
873
+ const states = new Map();
874
+ const secret = (flags, index, value, from) => {
875
+ if (value === undefined)
876
+ return;
877
+ const at = flags.percent ? value.indexOf('%', from) + 1 : from;
878
+ if ((at || !flags.percent) && value.slice(at) && value.slice(at) !== '***')
879
+ masks.set(index, at);
880
+ };
881
+ for (let i = 0; i < words.length; i += 1) {
882
+ const word = words[i];
883
+ for (const [flags, state] of states) {
884
+ if (i < state.next)
885
+ continue;
886
+ if (!state.active) {
887
+ // The subcommand is the first operand past the options; one right after a bare option may be its value. One the shell
888
+ // computes (`docker "$ACTION" -p pw`) may be `login`.
889
+ if (word === 'login' || /[$`]/.test(word))
890
+ state.active = true;
891
+ else if (!word.startsWith('-') && word !== 'registry' && !(/^-[^=]*$/.test(words[i - 1]) && !flags.switches?.includes(words[i - 1])))
892
+ states.delete(flags);
893
+ continue;
894
+ }
895
+ if (flags.separate?.includes(word)) {
896
+ secret(flags, i + 1, words[i + 1], 0);
897
+ state.next = i + 2;
898
+ continue;
899
+ }
900
+ const prefix = flags.attached?.find((p) => word.length > p.length && word.startsWith(p));
901
+ if (prefix !== undefined) {
902
+ secret(flags, i, word, prefix.length + (word[prefix.length] === '=' ? 1 : 0));
903
+ continue;
904
+ }
905
+ if (flags.values?.includes(word)) {
906
+ state.next = i + 2;
907
+ continue;
908
+ }
909
+ if (flags.stop && !word.startsWith('-'))
910
+ states.delete(flags);
911
+ }
912
+ const name = word.slice(word.lastIndexOf('/') + 1);
913
+ // A command named again (`docker login docker -p pw`, a host) keeps the state it is in. A word the shell computes may
914
+ // be the executable (`sudo -u root $CLIENT -ppw`), so it may be any known command.
915
+ const named = Object.hasOwn(COMMAND_FLAGS, name) ? [COMMAND_FLAGS[name]] : !word.startsWith('-') && /[$`]/.test(word) ? ALL_COMMAND_FLAGS : [];
916
+ for (const flags of named)
917
+ if (!states.has(flags))
918
+ states.set(flags, { active: !flags.login, next: i + 1 });
919
+ }
920
+ return masks;
921
+ }
922
+ /** `raw`, a shell word reading `plain`, masked from `plain`'s offset `from`; a word whose kept part is not literal is masked whole. */
923
+ function maskWordFrom(raw, plain, from) {
924
+ if (!from)
925
+ return maskShellWord(raw);
926
+ const kept = plain.slice(0, from);
927
+ const quoted = splitQuoted(raw);
928
+ if (quoted?.inner.startsWith(kept))
929
+ return `${quoted.open}${kept}***${quoted.close}`;
930
+ return raw.startsWith(kept) ? kept + maskShellWord(raw.slice(from)) : maskShellWord(raw);
931
+ }
932
+ /** A redirection operator, with its file descriptor. */
933
+ const REDIRECT_RE = /[0-9]*(?:&>>?|<<<|<<-?|<>|>>|>&|<&|>\||[<>])/y;
934
+ /**
935
+ * The words of the simple command (or `list`ed argv) at `from`, up to a shell metacharacter, the line end, a list's close
936
+ * or the close of the `enclosing` quote, and where the scan stopped. An unterminated quote ends the command with its word.
937
+ * Only a list's elements end at `,`, `]` or `}`; in a shell word they are ordinary characters (`-phunter,2`).
938
+ */
939
+ function commandWords(text, from, enclosing, list) {
940
+ const spans = [];
941
+ const [gap, close, stop] = list ? [/[ \t,<>]/, /[\n;&|()\]}]/, /[\s;&|()<>,\]}]/] : [/[ \t]/, /[\n;&|()]/, /[\s;&|()<>]/];
942
+ let i = from;
943
+ let redirect = false;
944
+ for (;;) {
945
+ while (i < text.length && gap.test(text[i]))
946
+ i += 1;
947
+ // A redirection and its target (`</dev/null`, `2> /tmp/e`, `2>&1`) are no argument: the shell removes them.
948
+ REDIRECT_RE.lastIndex = i;
949
+ if (!list && i < text.length && text[i] !== enclosing && REDIRECT_RE.test(text)) {
950
+ i = REDIRECT_RE.lastIndex;
951
+ redirect = true;
952
+ continue;
953
+ }
954
+ if (i >= text.length || text[i] === enclosing || close.test(text[i]))
955
+ return { spans, end: i };
956
+ const start = i;
957
+ while (i < text.length && text[i] !== enclosing && !stop.test(text[i])) {
958
+ const c = text[i];
959
+ if (c === '\\')
960
+ i += 2;
961
+ else if ((c === "'" || c === '"') && c !== enclosing) {
962
+ // A quote groups across newlines, and an inner quote of the other kind (`sh -c "mysql -p'a b'"`) groups for the shell
963
+ // that runs the command.
964
+ let k = i + 1;
965
+ while (k < text.length && text[k] !== c && text[k] !== enclosing)
966
+ k += c === '"' && text[k] === '\\' ? 2 : 1;
967
+ // An unterminated quote runs to the text end (or the enclosing close); its word is still read, so its secret is masked.
968
+ if (text[k] !== c) {
969
+ spans.push([start, Math.min(k, text.length)]);
970
+ return { spans, end: Math.min(k, text.length) };
971
+ }
972
+ i = k + 1;
973
+ }
974
+ else if (c === '`' || (c === '$' && (text[i + 1] === '(' || text[i + 1] === '{'))) {
975
+ // A substitution is part of its word (`-p$(printf pw)`), spaces and nested ones included.
976
+ const [open, close] = c === '`' ? ['', '`'] : [text[i + 1], text[i + 1] === '(' ? ')' : '}'];
977
+ let k = i + 1;
978
+ for (let depth = 1; k < text.length && text[k] !== enclosing; k += 1) {
979
+ if (text[k] === close && (depth -= 1) === 0)
980
+ break;
981
+ if (text[k] === open && k > i + 1)
982
+ depth += 1;
983
+ }
984
+ if (text[k] !== close) {
985
+ spans.push([start, Math.min(k, text.length)]);
986
+ return { spans, end: Math.min(k, text.length) };
987
+ }
988
+ i = k + 1;
989
+ }
990
+ else
991
+ i += 1;
992
+ }
993
+ if (!redirect)
994
+ spans.push([start, Math.min(i, text.length)]);
995
+ redirect = false;
996
+ }
997
+ }
998
+ /**
999
+ * `COMMAND_FLAGS` secrets in shell text and listed argv text (`['mysql', '-phunter2']`). A quoted word that holds a known
1000
+ * command (`sh -c 'mysql -phunter2'`) is read as a command of its own.
1001
+ */
1002
+ function redactCommandFlags(text) {
1003
+ let out = '';
1004
+ let last = 0;
1005
+ const quoteAt = quoteTracker(text);
1006
+ const re = new RegExp(COMMAND_FLAG_RE);
1007
+ for (let m = re.exec(text); m; m = re.exec(text)) {
1008
+ const quote = quoteAt(m.index).slice(-1);
1009
+ // A listed element (`'mysql',`) is read from its opening quote; a command inside a quoted argument ends at its close.
1010
+ let listed = quote !== '' && text[m.index + m[0].length] === quote;
1011
+ // A computed word matched only to its first expansion (`"$(echo $(which docker))"`) is listed when it fills its quotes.
1012
+ if (!listed && quote && text[m.index - 1] === quote) {
1013
+ const probe = commandWords(text, m.index, quote, false);
1014
+ listed = probe.spans.length === 1 && text[probe.end] === quote;
1015
+ }
1016
+ const { spans, end } = commandWords(text, listed ? text.lastIndexOf(quote, m.index) : m.index, listed ? '' : quote, listed);
1017
+ const words = spans.map(([start, stop]) => shellDequote(text.slice(start, stop)));
1018
+ const masks = commandFlagMasks(words);
1019
+ for (const [k, [start, stop]] of spans.entries()) {
1020
+ if (start < last)
1021
+ continue;
1022
+ const raw = text.slice(start, stop);
1023
+ const from = masks.get(k);
1024
+ // A nested command is a quoted or substituted word with a space, strictly inside this text.
1025
+ const nested = (listed || !quote) && raw.length < text.length && /['"]|\$\(|\x60/.test(raw) && /\s/.test(raw) && COMMAND_FLAG_TEST_RE.test(raw);
1026
+ const replaced = from !== undefined ? maskWordFrom(raw, words[k], from) : nested ? redactCommandFlags(raw) : raw;
1027
+ if (replaced === raw)
1028
+ continue;
1029
+ out += text.slice(last, start) + replaced;
1030
+ last = stop;
1031
+ }
1032
+ re.lastIndex = Math.max(end, re.lastIndex, m.index + 1);
1033
+ }
1034
+ return out + text.slice(last);
1035
+ }
568
1036
  /**
569
1037
  * Header credentials (`AUTH_HDR_RE`, `BEARER_RE`: a head group, then the credential). A parameterized credential is masked
570
1038
  * whole; a single credential is read to the end of its shell word, so an adjacent quoted piece (`Bearer foo'bar`, which
@@ -1315,7 +1783,7 @@ export function redactSensitive(text) {
1315
1783
  function redactRules(text) {
1316
1784
  if (!text)
1317
1785
  return text;
1318
- let out = text.replace(CRED_RE, (_, t) => mask(t));
1786
+ let out = maskSplitTokens(text).replace(CRED_RE, (_, t) => mask(t));
1319
1787
  out = out.replace(EMBEDDED_AWS_RE, (t) => mask(t));
1320
1788
  out = redactHeaderCredentials(out, AUTH_HDR_RE);
1321
1789
  out = out.replace(JWT_RE, (t) => mask(t));
@@ -1323,7 +1791,8 @@ function redactRules(text) {
1323
1791
  for (const re of [COOKIE_ANSI_RE, COOKIE_SQ_RE, COOKIE_DQ_RE, COOKIE_BARE_RE])
1324
1792
  out = out.replace(re, (whole, head, value) => (/[A-Za-z0-9]/.test(value) ? `${head}***` : whole));
1325
1793
  out = redactCredentialParams(out);
1326
- out = out.replace(ENV_RE, (whole, key, quote, value) => (/[A-Za-z0-9]/.test(value) ? `${key}=${quote}${mask(value)}${quote}` : whole));
1794
+ out = out.replace(ENV_RE, (whole, key, quote, value) => (/[A-Za-z0-9]/.test(value) && isEnvCredential(key, value) ? `${key}=${quote}${mask(value)}${quote}` : whole));
1795
+ out = redactEnvSuffixes(out);
1327
1796
  out = out.replace(LISTED_FLAG_RE, (whole, q, dash, key, gap, vq, quotedValue, bare) => {
1328
1797
  // An unquoted value (a number, `True`, a nested list) is masked whole.
1329
1798
  if (bare !== undefined)
@@ -1338,7 +1807,11 @@ function redactRules(text) {
1338
1807
  out = out.replace(PERCENT_KEY_RE, (whole, head, value) => (value && value !== '***' && isCredentialKey(percentDecode(head.slice(0, -1))) ? `${head}***` : whole));
1339
1808
  out = out.replace(BARE_USERINFO_RE, (_, head, secret) => head + mask(secret));
1340
1809
  out = out.replace(URL_USERINFO_RE, (_, head, secret) => head + (/['"\\]/.test(secret) ? '***' : mask(secret)));
1810
+ out = out.replace(URL_BARE_TOKEN_RE, (_, head, token) => head + mask(token));
1811
+ out = out.replace(TELEGRAM_TOKEN_RE, (_, bot, id) => `${bot ?? ''}${id}:***`);
1812
+ out = out.replace(PHONE_RE, (phone) => { const keep = phone.length <= 8 ? 2 : 4; return `${phone.slice(0, keep)}****${phone.slice(-keep)}`; });
1341
1813
  out = redactUserFlags(out);
1814
+ out = redactCommandFlags(out);
1342
1815
  out = out.replace(QUERY_KEY_RE, (whole, head, value) => (/[A-Za-z0-9]/.test(value) ? head + mask(value) : whole));
1343
1816
  out = out.replace(PRIVKEY_RE, '[REDACTED PRIVATE KEY]').replace(PRIVKEY_OPEN_RE, '[REDACTED PRIVATE KEY]');
1344
1817
  return restoreCodeEnvKeyLiterals(text, out);
@@ -1346,20 +1819,24 @@ function redactRules(text) {
1346
1819
  const CASE_MARKERS = [
1347
1820
  'sk-', 'ghp_', 'github_pat_', 'gho_', 'ghu_', 'ghs_', 'ghr_', 'AKIA', 'ASIA', 'xoxb-', 'xoxa-', 'xoxp-', 'xoxr-', 'xoxs-', 'AIza', 'pplx-', 'fal_', 'fc-',
1348
1821
  'bb_live_', 'gAAAA', 'sk_live_', 'sk_test_', 'rk_live_', 'SG.', 'hf_', 'r8_', 'npm_', 'pypi-', 'dop_v1_', 'doo_v1_', 'am_', 'sk_', 'tvly-', 'exa_',
1349
- 'gsk_', 'syt_', 'retaindb_', 'hsk-', 'mem0_', 'brv_', 'eyJ', '-----BEGIN',
1822
+ 'gsk_', 'syt_', 'retaindb_', 'hsk-', 'mem0_', 'brv_', 'xapp-', 'xai-', 'ntn_', 'fw-', 'fw_', 'fpk_', 'glpat-', 'gloas-', 'gldt-', 'glrt-', 'glrtr-',
1823
+ 'glcbt-', 'glptt-', 'glft-', 'glimt-', 'glagent-', 'glsoat-', 'glffct-', 'glwt-', 'GR1348941', 'pk-lf-', 'eyJ', '-----BEGIN',
1350
1824
  ];
1351
1825
  const LOWER_MARKERS = [
1352
1826
  'authorization: bearer ', 'authorization: bot ', 'private key', 'postgres://', 'postgresql://', 'mysql://', 'mongodb://', 'redis://', 'amqp://', '://',
1353
1827
  'access_token', 'refresh_token', 'id_token', 'api_key', 'apikey', 'client_secret', 'auth_token', 'raw_secret', 'secret_input', 'key_material',
1354
1828
  'x-amz-signature', 'token=', 'secret=', 'password=', 'passwd', 'password', 'secret', 'token', 'api-key', 'apikey', 'clientsecret', 'private_key', 'credential', ' -u ', '--user ', 'authorization', 'signature', 'bearer ', 'cookie:', 'authorization=', 'key=', '"token"', '"secret"', '"password"', '"bearer"',
1355
1829
  ];
1356
- const TELEGRAM_RE = /(?:bot)?\d{8,}:[-A-Za-z0-9_]{30,}/;
1357
1830
  const DISCORD_RE = /<@!?\d{17,20}>/;
1358
- const PHONE_RE = /(?<![A-Za-z0-9])\+[1-9]\d{6,14}(?![A-Za-z0-9])/;
1831
+ const TELEGRAM_TEST_RE = new RegExp(TELEGRAM_TOKEN_RE.source);
1832
+ const PHONE_TEST_RE = new RegExp(PHONE_RE.source);
1359
1833
  export function mightContainSensitiveText(text) {
1360
1834
  if (!text)
1361
1835
  return false;
1362
- if (CASE_MARKERS.some((m) => text.includes(m)))
1836
+ // A control or zero-width character or an ANSI escape sequence inside a prefix (`x\u200bai-…`) does not hide it: the
1837
+ // redactor joins split tokens.
1838
+ const views = SPLIT_VIEWS.map((gaps) => text.replace(gaps, ''));
1839
+ if (CASE_MARKERS.some((m) => views.some((view) => view.includes(m))))
1363
1840
  return true;
1364
1841
  const lower = text.toLowerCase();
1365
1842
  if (LOWER_MARKERS.some((m) => lower.includes(m)))
@@ -1380,11 +1857,15 @@ export function mightContainSensitiveText(text) {
1380
1857
  return true;
1381
1858
  if (USER_FLAG_TEST_RE.test(text))
1382
1859
  return true;
1383
- if (text.includes(':') && TELEGRAM_RE.test(text))
1860
+ if (COMMAND_FLAG_TEST_RE.test(text))
1861
+ return true;
1862
+ if (text.includes(':') && TELEGRAM_TEST_RE.test(text))
1384
1863
  return true;
1385
1864
  if (text.includes('<@') && DISCORD_RE.test(text))
1386
1865
  return true;
1387
- if (text.includes('+') && PHONE_RE.test(text))
1866
+ if (text.includes('+') && PHONE_TEST_RE.test(text))
1867
+ return true;
1868
+ if (text.includes('=') && ENV_SUFFIX_TEST_RE.test(text))
1388
1869
  return true;
1389
1870
  return false;
1390
1871
  }
@@ -1424,7 +1905,7 @@ export function redactValue(v, enabled) {
1424
1905
  return Object.fromEntries(Object.entries(v).map(([k, val]) => [k, redactValue(val, enabled)]));
1425
1906
  return v;
1426
1907
  }
1427
- export const PUBLIC_MESSAGE_INTERNAL_FIELDS = new Set(['api_content', '_row_id', '_state_db_row_id', '_db_row_id', 'state_db_row_id', '_active_turn_token', '_active_turn_user', '_fork_child_turn']);
1908
+ export const PUBLIC_MESSAGE_INTERNAL_FIELDS = new Set(['api_content', '_row_id', '_state_db_row_id', '_state_db_generation', '_db_row_id', 'state_db_row_id', '_active_turn_token', '_active_turn_user', '_fork_child_turn']);
1428
1909
  export function copyJson(value) {
1429
1910
  if (Array.isArray(value))
1430
1911
  return value.map((v) => copyJson(v));
@@ -1531,6 +2012,19 @@ export function buildActiveTurnToken(streamId, pendingStartedAt) {
1531
2012
  return null;
1532
2013
  return `${str(streamId).trim()}:${formatG17(started)}`;
1533
2014
  }
2015
+ /** TAL-452: a user message longer than either limit ships `_collapsible`, so clients fold it behind "Show more". */
2016
+ const COLLAPSIBLE_USER_MESSAGE_LINES = 20;
2017
+ const COLLAPSIBLE_USER_MESSAGE_CHARS = 2000;
2018
+ /**
2019
+ * Whether a public (redacted) row is a typed user message (no steer, wakeup or marker) longer than either limit, measured
2020
+ * without the `[Attached files: ...]` line clients hide.
2021
+ */
2022
+ function isCollapsibleUserMessage(item) {
2023
+ if (item.role !== 'user' || item._steer !== undefined || item._background_update !== undefined || item._marker_kind !== undefined)
2024
+ return false;
2025
+ const text = stripAttachedFilesMarker(messageText(item.content));
2026
+ return text.length > COLLAPSIBLE_USER_MESSAGE_CHARS || text.split('\n').length > COLLAPSIBLE_USER_MESSAGE_LINES;
2027
+ }
1534
2028
  function publicMessageProjection(message, enabled, activeTurnToken) {
1535
2029
  const isActive = Boolean(message && typeof message === 'object' && message.role === 'user' && activeTurnToken !== null && message._active_turn_token === activeTurnToken);
1536
2030
  const scrubbed = scrubInternalReplayFields([message], { messageRecords: true })[0];
@@ -1544,6 +2038,11 @@ function publicMessageProjection(message, enabled, activeTurnToken) {
1544
2038
  }
1545
2039
  if (isActive)
1546
2040
  item._active_turn_user = true;
2041
+ // Server-owned: a `_collapsible` the stored row carries never survives the projection's own decision.
2042
+ if (isCollapsibleUserMessage(item))
2043
+ item._collapsible = true;
2044
+ else
2045
+ delete item._collapsible;
1547
2046
  return withMessageToolDisplay(scrubbed, item, enabled);
1548
2047
  }
1549
2048
  /**
@@ -1599,7 +2098,11 @@ function redactArgs(value, enabled) {
1599
2098
  if (value.length === 2 && typeof value[0] === 'string' && isCredentialKey(value[0]))
1600
2099
  return [value[0], maskLeaves(value[1])];
1601
2100
  // An argv array (`['login', '--password', 'hunter2']`): a credential flag's value is the next element.
2101
+ const commandMasks = commandFlagMasks(value.map((item) => (typeof item === 'string' || typeof item === 'number' ? String(item) : '')));
1602
2102
  return value.map((item, i) => {
2103
+ const from = commandMasks.get(i);
2104
+ if (from !== undefined)
2105
+ return typeof item === 'string' ? maskWordFrom(item, item, from) : maskLeaves(item);
1603
2106
  const flag = value[i - 1];
1604
2107
  if (typeof flag === 'string' && item !== '' && item !== null && item !== undefined) {
1605
2108
  if (ARGV_USER_FLAG_RE.test(flag))
@@ -1680,7 +2183,7 @@ export function withToolId(data, id) {
1680
2183
  delete frame.tid;
1681
2184
  return frame;
1682
2185
  }
1683
- function redactNestedMessageContainers(value, enabled) {
2186
+ export function redactNestedMessageContainers(value, enabled) {
1684
2187
  const scrubbed = scrubInternalReplayFields(value);
1685
2188
  if (!scrubbed || typeof scrubbed !== 'object' || Array.isArray(scrubbed))
1686
2189
  return redactValue(scrubbed, enabled);