@oh-my-pi/pi-coding-agent 17.0.9 → 17.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 (349) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +1 -1
  3. package/dist/cli.js +4685 -4614
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/model-registry.d.ts +3 -1
  10. package/dist/types/config/models-config-schema.d.ts +18 -6
  11. package/dist/types/config/models-config.d.ts +14 -5
  12. package/dist/types/config/provider-globals.d.ts +2 -2
  13. package/dist/types/config/service-tier.d.ts +5 -1
  14. package/dist/types/config/settings-schema.d.ts +374 -47
  15. package/dist/types/config/settings.d.ts +7 -0
  16. package/dist/types/dap/client.d.ts +20 -0
  17. package/dist/types/exec/bash-executor.d.ts +38 -0
  18. package/dist/types/exec/direnv.d.ts +33 -0
  19. package/dist/types/export/html/args.d.ts +15 -0
  20. package/dist/types/export/html/index.d.ts +11 -26
  21. package/dist/types/export/html/web-palette.d.ts +55 -116
  22. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  23. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  24. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
  25. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
  26. package/dist/types/live/attestation.d.ts +2 -0
  27. package/dist/types/live/controller.d.ts +48 -0
  28. package/dist/types/live/protocol.d.ts +85 -0
  29. package/dist/types/live/protocol.test.d.ts +1 -0
  30. package/dist/types/live/transport.d.ts +35 -0
  31. package/dist/types/live/visualizer.d.ts +30 -0
  32. package/dist/types/lsp/client.d.ts +16 -1
  33. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  34. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  35. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  36. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  37. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  38. package/dist/types/modes/components/assistant-message.d.ts +1 -0
  39. package/dist/types/modes/components/custom-message.d.ts +1 -1
  40. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  41. package/dist/types/modes/components/message-frame.d.ts +8 -4
  42. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  43. package/dist/types/modes/components/session-account-selector.d.ts +11 -0
  44. package/dist/types/modes/components/settings-defs.d.ts +7 -1
  45. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  46. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  47. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  48. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  49. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  50. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  51. package/dist/types/modes/interactive-mode.d.ts +6 -0
  52. package/dist/types/modes/theme/theme.d.ts +1 -1
  53. package/dist/types/modes/types.d.ts +4 -1
  54. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  55. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  56. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  57. package/dist/types/registry/agent-registry.d.ts +11 -4
  58. package/dist/types/sdk.d.ts +15 -1
  59. package/dist/types/secrets/index.d.ts +15 -2
  60. package/dist/types/secrets/obfuscator.d.ts +113 -15
  61. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  62. package/dist/types/session/agent-session-events.d.ts +84 -0
  63. package/dist/types/session/agent-session-types.d.ts +321 -0
  64. package/dist/types/session/agent-session.d.ts +135 -753
  65. package/dist/types/session/async-job-delivery.d.ts +28 -0
  66. package/dist/types/session/auth-storage.d.ts +1 -1
  67. package/dist/types/session/bash-runner.d.ts +75 -0
  68. package/dist/types/session/blob-store.d.ts +11 -1
  69. package/dist/types/session/blob-store.test.d.ts +1 -0
  70. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  71. package/dist/types/session/eval-runner.d.ts +51 -0
  72. package/dist/types/session/irc-bridge.d.ts +46 -0
  73. package/dist/types/session/messages.d.ts +24 -0
  74. package/dist/types/session/model-controls.d.ts +180 -0
  75. package/dist/types/session/prewalk.d.ts +46 -0
  76. package/dist/types/session/queued-messages.d.ts +22 -0
  77. package/dist/types/session/redis-session-storage.d.ts +1 -0
  78. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  79. package/dist/types/session/role-models.d.ts +15 -0
  80. package/dist/types/session/session-advisors.d.ts +228 -0
  81. package/dist/types/session/session-entries.d.ts +8 -0
  82. package/dist/types/session/session-handoff.d.ts +70 -0
  83. package/dist/types/session/session-history-format.d.ts +6 -1
  84. package/dist/types/session/session-maintenance.d.ts +246 -0
  85. package/dist/types/session/session-manager.d.ts +41 -0
  86. package/dist/types/session/session-memory.d.ts +56 -0
  87. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  88. package/dist/types/session/session-stats.d.ts +48 -0
  89. package/dist/types/session/session-tools.d.ts +158 -0
  90. package/dist/types/session/session-workspace.d.ts +32 -0
  91. package/dist/types/session/stream-guards.d.ts +44 -0
  92. package/dist/types/session/streaming-output.d.ts +8 -0
  93. package/dist/types/session/todo-tracker.d.ts +56 -0
  94. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  95. package/dist/types/session/turn-recovery.d.ts +225 -0
  96. package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
  97. package/dist/types/stt/index.d.ts +0 -2
  98. package/dist/types/stt/stt-controller.d.ts +7 -0
  99. package/dist/types/system-prompt.d.ts +2 -0
  100. package/dist/types/task/executor.d.ts +4 -1
  101. package/dist/types/task/output-manager.d.ts +3 -1
  102. package/dist/types/tiny/title-client.d.ts +10 -0
  103. package/dist/types/tools/approval.d.ts +1 -0
  104. package/dist/types/tools/browser/launch.d.ts +5 -0
  105. package/dist/types/tools/builtin-names.d.ts +1 -1
  106. package/dist/types/tools/computer/protocol.d.ts +43 -0
  107. package/dist/types/tools/computer/supervisor.d.ts +32 -0
  108. package/dist/types/tools/computer/worker-entry.d.ts +1 -0
  109. package/dist/types/tools/computer/worker.d.ts +15 -0
  110. package/dist/types/tools/computer-renderer.d.ts +22 -0
  111. package/dist/types/tools/computer.d.ts +71 -0
  112. package/dist/types/tools/context.d.ts +2 -0
  113. package/dist/types/tools/default-renderer.d.ts +21 -0
  114. package/dist/types/tools/image-gen.d.ts +4 -4
  115. package/dist/types/tools/image-providers.d.ts +38 -0
  116. package/dist/types/tools/index.d.ts +7 -0
  117. package/dist/types/tools/path-utils.d.ts +8 -0
  118. package/dist/types/tools/todo.d.ts +10 -4
  119. package/dist/types/tools/xdev.d.ts +5 -1
  120. package/dist/types/tts/streaming-player.d.ts +10 -29
  121. package/dist/types/tts/vocalizer.d.ts +5 -0
  122. package/dist/types/utils/jj.d.ts +29 -0
  123. package/dist/types/utils/lang-from-path.d.ts +1 -0
  124. package/dist/types/utils/title-generator.d.ts +30 -0
  125. package/dist/types/utils/tools-manager.d.ts +1 -2
  126. package/dist/types/vibe/runtime.d.ts +36 -8
  127. package/dist/types/web/search/index.d.ts +1 -1
  128. package/dist/types/web/search/provider.d.ts +15 -5
  129. package/dist/types/web/search/types.d.ts +94 -0
  130. package/package.json +12 -12
  131. package/scripts/generate-share-viewer.ts +4 -6
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +3 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/setup-cli.ts +2 -14
  141. package/src/cli/usage-cli.ts +10 -1
  142. package/src/cli.ts +8 -0
  143. package/src/collab/host.ts +3 -2
  144. package/src/commands/bench.ts +18 -4
  145. package/src/commands/launch.ts +4 -0
  146. package/src/config/model-registry.ts +16 -2
  147. package/src/config/models-config-schema.ts +15 -4
  148. package/src/config/provider-globals.ts +9 -9
  149. package/src/config/service-tier.ts +26 -1
  150. package/src/config/settings-schema.ts +270 -51
  151. package/src/config/settings.ts +81 -2
  152. package/src/dap/client.ts +37 -4
  153. package/src/edit/index.ts +49 -12
  154. package/src/eval/executor-base.ts +1 -0
  155. package/src/eval/js/executor.ts +2 -0
  156. package/src/eval/py/executor.ts +103 -33
  157. package/src/exec/bash-executor.ts +90 -6
  158. package/src/exec/direnv.ts +145 -0
  159. package/src/export/html/args.ts +20 -0
  160. package/src/export/html/index.ts +45 -47
  161. package/src/export/html/template.css +19 -1
  162. package/src/export/html/template.html +6 -0
  163. package/src/export/html/template.js +21 -0
  164. package/src/export/html/tool-views.generated.js +31 -31
  165. package/src/export/html/web-palette.ts +116 -132
  166. package/src/export/share.ts +249 -49
  167. package/src/extensibility/extensions/loader.ts +29 -1
  168. package/src/extensibility/extensions/runner.ts +6 -0
  169. package/src/extensibility/extensions/types.ts +33 -2
  170. package/src/extensibility/extensions/wrapper.ts +68 -12
  171. package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
  172. package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
  173. package/src/hindsight/state.ts +64 -6
  174. package/src/internal-urls/local-protocol.ts +2 -1
  175. package/src/internal-urls/memory-protocol.ts +2 -2
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/attestation.ts +91 -0
  180. package/src/live/controller.ts +517 -0
  181. package/src/live/prompts/agent-final-message.md +3 -0
  182. package/src/live/prompts/live-instructions.md +23 -0
  183. package/src/live/protocol.test.ts +140 -0
  184. package/src/live/protocol.ts +233 -0
  185. package/src/live/transport.ts +422 -0
  186. package/src/live/visualizer.ts +214 -0
  187. package/src/lsp/client.ts +29 -9
  188. package/src/lsp/index.ts +19 -4
  189. package/src/main.ts +8 -1
  190. package/src/mcp/config-writer.test.ts +43 -0
  191. package/src/mcp/config-writer.ts +109 -82
  192. package/src/mcp/oauth-discovery.ts +10 -2
  193. package/src/mcp/smithery-auth.test.ts +29 -0
  194. package/src/mcp/smithery-auth.ts +3 -2
  195. package/src/memories/index.ts +40 -20
  196. package/src/memory-backend/tool-names.ts +2 -0
  197. package/src/mnemopi/backend.ts +24 -7
  198. package/src/modes/acp/acp-agent.ts +56 -10
  199. package/src/modes/acp/acp-event-mapper.ts +8 -1
  200. package/src/modes/components/agent-hub.ts +1 -1
  201. package/src/modes/components/assistant-message.ts +7 -2
  202. package/src/modes/components/custom-message.ts +4 -1
  203. package/src/modes/components/login-dialog.ts +14 -4
  204. package/src/modes/components/message-frame.ts +14 -8
  205. package/src/modes/components/plan-review-overlay.ts +350 -35
  206. package/src/modes/components/session-account-selector.ts +62 -0
  207. package/src/modes/components/settings-defs.ts +23 -1
  208. package/src/modes/components/settings-selector.ts +169 -1
  209. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  210. package/src/modes/components/status-line/component.ts +123 -2
  211. package/src/modes/components/tool-execution.ts +28 -112
  212. package/src/modes/controllers/command-controller.ts +56 -15
  213. package/src/modes/controllers/event-controller.ts +111 -16
  214. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  215. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  216. package/src/modes/controllers/input-controller.ts +47 -39
  217. package/src/modes/controllers/live-command-controller.ts +255 -0
  218. package/src/modes/controllers/mcp-command-controller.ts +10 -1
  219. package/src/modes/controllers/omfg-controller.ts +44 -40
  220. package/src/modes/controllers/selector-controller.ts +80 -10
  221. package/src/modes/controllers/session-focus-controller.ts +6 -1
  222. package/src/modes/interactive-mode.ts +176 -38
  223. package/src/modes/runtime-init.ts +2 -0
  224. package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
  225. package/src/modes/theme/theme.ts +1 -1
  226. package/src/modes/types.ts +4 -1
  227. package/src/modes/utils/context-usage.ts +23 -7
  228. package/src/modes/utils/ui-helpers.ts +10 -5
  229. package/src/plan-mode/plan-files.ts +40 -0
  230. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  231. package/src/prompts/bench/cache-prefix.md +3 -0
  232. package/src/prompts/bench/cache-suffix.md +1 -0
  233. package/src/prompts/system/computer-safety.md +14 -0
  234. package/src/prompts/system/custom-system-prompt.md +1 -1
  235. package/src/prompts/system/project-prompt.md +8 -1
  236. package/src/prompts/system/subagent-async-pending.md +6 -0
  237. package/src/prompts/system/system-prompt.md +1 -1
  238. package/src/prompts/system/workflow-notice.md +48 -40
  239. package/src/prompts/system/xdev-mount-notice.md +4 -0
  240. package/src/prompts/tools/computer.md +26 -0
  241. package/src/prompts/tools/todo.md +3 -1
  242. package/src/registry/agent-lifecycle.ts +116 -46
  243. package/src/registry/agent-registry.ts +35 -8
  244. package/src/registry/persisted-agents.ts +26 -2
  245. package/src/sdk.ts +215 -136
  246. package/src/secrets/index.ts +127 -4
  247. package/src/secrets/obfuscator.ts +2328 -105
  248. package/src/session/acp-permission-gate.ts +165 -0
  249. package/src/session/agent-session-error-log.test.ts +1 -1
  250. package/src/session/agent-session-events.ts +66 -0
  251. package/src/session/agent-session-types.ts +343 -0
  252. package/src/session/agent-session.ts +1940 -12218
  253. package/src/session/artifacts.ts +6 -5
  254. package/src/session/async-job-delivery.ts +74 -0
  255. package/src/session/auth-storage.ts +1 -0
  256. package/src/session/bash-runner.ts +326 -0
  257. package/src/session/blob-store.test.ts +56 -0
  258. package/src/session/blob-store.ts +18 -2
  259. package/src/session/checkpoint-entries.ts +81 -0
  260. package/src/session/eval-runner.ts +217 -0
  261. package/src/session/indexed-session-storage.ts +9 -2
  262. package/src/session/irc-bridge.ts +203 -0
  263. package/src/session/messages.ts +266 -1
  264. package/src/session/model-controls.ts +714 -0
  265. package/src/session/prewalk.ts +252 -0
  266. package/src/session/queued-messages.ts +93 -0
  267. package/src/session/redis-session-storage.ts +51 -11
  268. package/src/session/retry-fallback-chains.ts +455 -0
  269. package/src/session/role-models.ts +85 -0
  270. package/src/session/session-advisors.ts +1679 -0
  271. package/src/session/session-context.ts +6 -3
  272. package/src/session/session-entries.ts +8 -0
  273. package/src/session/session-handoff.ts +308 -0
  274. package/src/session/session-history-format.ts +20 -9
  275. package/src/session/session-loader.ts +0 -46
  276. package/src/session/session-maintenance.ts +2983 -0
  277. package/src/session/session-manager.ts +390 -30
  278. package/src/session/session-memory.ts +222 -0
  279. package/src/session/session-provider-boundary.ts +307 -0
  280. package/src/session/session-stats.ts +293 -0
  281. package/src/session/session-tools.ts +980 -0
  282. package/src/session/session-workspace.ts +53 -0
  283. package/src/session/stream-guards.ts +417 -0
  284. package/src/session/streaming-output.ts +52 -5
  285. package/src/session/todo-tracker.ts +380 -0
  286. package/src/session/ttsr-coordinator.ts +496 -0
  287. package/src/session/turn-recovery.ts +1629 -0
  288. package/src/slash-commands/builtin-registry.ts +257 -20
  289. package/src/slash-commands/helpers/session-pin.ts +44 -0
  290. package/src/slash-commands/helpers/usage-report.ts +22 -3
  291. package/src/stt/downloader.ts +0 -2
  292. package/src/stt/index.ts +0 -2
  293. package/src/stt/stt-controller.ts +57 -146
  294. package/src/system-prompt.ts +45 -17
  295. package/src/task/executor.ts +285 -70
  296. package/src/task/index.ts +98 -105
  297. package/src/task/output-manager.ts +25 -3
  298. package/src/task/persisted-revive.ts +4 -3
  299. package/src/task/structured-subagent.ts +1 -0
  300. package/src/tiny/title-client.ts +22 -0
  301. package/src/tools/approval.ts +55 -10
  302. package/src/tools/bash-interactive.ts +116 -86
  303. package/src/tools/bash-skill-urls.ts +28 -2
  304. package/src/tools/bash.ts +339 -123
  305. package/src/tools/browser/launch.ts +9 -1
  306. package/src/tools/browser/tab-supervisor.ts +8 -5
  307. package/src/tools/browser.ts +94 -47
  308. package/src/tools/builtin-names.ts +1 -0
  309. package/src/tools/computer/protocol.ts +28 -0
  310. package/src/tools/computer/supervisor.ts +258 -0
  311. package/src/tools/computer/worker-entry.ts +25 -0
  312. package/src/tools/computer/worker.ts +135 -0
  313. package/src/tools/computer-renderer.ts +108 -0
  314. package/src/tools/computer.ts +433 -0
  315. package/src/tools/context.ts +2 -0
  316. package/src/tools/default-renderer.ts +139 -0
  317. package/src/tools/essential-tools.ts +1 -0
  318. package/src/tools/image-gen.ts +20 -28
  319. package/src/tools/image-providers.ts +50 -0
  320. package/src/tools/index.ts +14 -0
  321. package/src/tools/path-utils.ts +49 -2
  322. package/src/tools/read.ts +156 -88
  323. package/src/tools/renderers.ts +9 -1
  324. package/src/tools/todo.ts +101 -11
  325. package/src/tools/vibe.ts +1 -2
  326. package/src/tools/write.ts +51 -1
  327. package/src/tools/xdev.ts +121 -29
  328. package/src/tts/streaming-player.ts +82 -283
  329. package/src/tts/vocalizer.ts +18 -1
  330. package/src/utils/clipboard.ts +1 -30
  331. package/src/utils/jj.ts +125 -4
  332. package/src/utils/lang-from-path.ts +7 -0
  333. package/src/utils/mac-file-urls.applescript +37 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/utils/tool-choice.ts +14 -0
  336. package/src/utils/tools-manager.ts +1 -19
  337. package/src/vibe/runtime.ts +852 -72
  338. package/src/web/search/index.ts +3 -6
  339. package/src/web/search/provider.ts +31 -16
  340. package/src/web/search/providers/xai.ts +7 -1
  341. package/src/web/search/types.ts +3 -0
  342. package/dist/types/stt/recorder.d.ts +0 -30
  343. package/dist/types/stt/transcriber.d.ts +0 -14
  344. package/dist/types/stt/wav.d.ts +0 -29
  345. package/dist/types/tts/player.d.ts +0 -32
  346. package/src/stt/recorder.ts +0 -551
  347. package/src/stt/transcriber.ts +0 -60
  348. package/src/stt/wav.ts +0 -173
  349. package/src/tts/player.ts +0 -137
@@ -10,7 +10,7 @@
10
10
  * The relay repo's build script runs this and embeds the output via go:embed.
11
11
  */
12
12
  import * as path from "node:path";
13
- import { generateThemeVars, getTemplate } from "../src/export/html";
13
+ import { generateThemeStyles, getTemplate } from "../src/export/html";
14
14
 
15
15
  const outPath = process.argv[2];
16
16
  if (!outPath) {
@@ -19,13 +19,11 @@ if (!outPath) {
19
19
  }
20
20
 
21
21
  const loaderJs = await Bun.file(new URL("../src/export/html/share-loader.js", import.meta.url).pathname).text();
22
- // Pin the omp brand palette (collab-web pink/purple identity) the viewer is
23
- // a public artifact matching the live my.omp.sh client, not a per-user export
24
- // that should mirror the host's terminal theme.
25
- const themeVars = await generateThemeVars("web");
22
+ // Public artifacts use the bundled omp web themes rather than TUI themes.
23
+ const themeStyles = await generateThemeStyles("web");
26
24
 
27
25
  const html = getTemplate()
28
- .replace("<theme-vars/>", () => `<style>:root { ${themeVars} }</style>`)
26
+ .replace("<theme-vars/>", () => `<style>${themeStyles}</style>`)
29
27
  .replace("<title>Session Export</title>", () => "<title>omp session</title>")
30
28
  .replace("{{SESSION_DATA}}</script>", () => `</script>\n <script>${loaderJs}</script>`);
31
29
 
@@ -1075,6 +1075,56 @@ describe("advisor", () => {
1075
1075
  // The loop re-checked maintenance for the expanded batch.
1076
1076
  expect(maintainCalls).toBe(2);
1077
1077
  });
1078
+ it("re-scrubs coalesced pending updates when a later regex value collides with their friendly prefixes", async () => {
1079
+ const obfuscator = new SecretObfuscator([
1080
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1081
+ { type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
1082
+ ]);
1083
+ const promptInputs: string[] = [];
1084
+ const { promise: firstMaintainStarted, resolve: startFirstMaintain } = Promise.withResolvers<void>();
1085
+ const { promise: finishFirstMaintain, resolve: releaseFirstMaintain } = Promise.withResolvers<boolean>();
1086
+ const { promise: promptStarted, resolve: startPrompt } = Promise.withResolvers<void>();
1087
+ let maintainCalls = 0;
1088
+ const agent: AdvisorAgent = {
1089
+ prompt: async input => {
1090
+ promptInputs.push(input);
1091
+ startPrompt();
1092
+ },
1093
+ abort: () => {},
1094
+ reset: () => {},
1095
+ state: { messages: [] },
1096
+ };
1097
+ const messages: AgentMessage[] = [
1098
+ { role: "user", content: "first OTHERSECRET", timestamp: 1 } as AgentMessage,
1099
+ ];
1100
+ const host: AdvisorRuntimeHost = {
1101
+ snapshotMessages: () => messages,
1102
+ enqueueAdvice: () => {},
1103
+ obfuscator,
1104
+ maintainContext: async () => {
1105
+ maintainCalls++;
1106
+ if (maintainCalls === 1) {
1107
+ startFirstMaintain();
1108
+ return await finishFirstMaintain;
1109
+ }
1110
+ return false;
1111
+ },
1112
+ };
1113
+ const runtime = new AdvisorRuntime(agent, host);
1114
+
1115
+ runtime.onTurnEnd();
1116
+ await firstMaintainStarted;
1117
+
1118
+ messages.push({ role: "user", content: "later tok_abc123", timestamp: 2 } as AgentMessage);
1119
+ runtime.onTurnEnd();
1120
+
1121
+ releaseFirstMaintain(false);
1122
+ await promptStarted;
1123
+
1124
+ expect(promptInputs).toHaveLength(1);
1125
+ expect(promptInputs[0]).not.toContain("TOKABC123_");
1126
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1127
+ });
1078
1128
 
1079
1129
  it("caps maintainContext calls per drain cycle when arrivals never go stable", async () => {
1080
1130
  // Regression guard for MAX_COALESCE_ROUNDS=3: during the first drain cycle,
@@ -1550,6 +1600,599 @@ describe("advisor", () => {
1550
1600
  expect(promptInputs[0]).not.toContain(secret);
1551
1601
  });
1552
1602
 
1603
+ it("does not scan tool details omitted from advisor history", async () => {
1604
+ const obfuscator = new SecretObfuscator([
1605
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1606
+ { type: "regex", content: "tok_[a-z0-9]+" },
1607
+ ]);
1608
+ const promptInputs: string[] = [];
1609
+ const agent = makeAgent(promptInputs);
1610
+ const messages: AgentMessage[] = [
1611
+ { role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
1612
+ {
1613
+ role: "assistant",
1614
+ content: [{ type: "toolCall", id: "c1", name: "read", arguments: { path: "config.ts" } }],
1615
+ timestamp: 2,
1616
+ } as unknown as AgentMessage,
1617
+ {
1618
+ role: "toolResult",
1619
+ toolCallId: "c1",
1620
+ toolName: "read",
1621
+ content: "ok",
1622
+ details: { opaque: "tok_abc123" },
1623
+ timestamp: 3,
1624
+ } as unknown as AgentMessage,
1625
+ ];
1626
+ const host: AdvisorRuntimeHost = {
1627
+ snapshotMessages: () => messages,
1628
+ enqueueAdvice: () => {},
1629
+ obfuscator,
1630
+ };
1631
+ const runtime = new AdvisorRuntime(agent, host);
1632
+
1633
+ runtime.onTurnEnd();
1634
+ await Promise.resolve();
1635
+
1636
+ expect(promptInputs).toHaveLength(1);
1637
+ expect(promptInputs[0]).toContain("#TOKABC123_");
1638
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1639
+ });
1640
+ it("does not scan advisor-hidden successful tool-result bodies", async () => {
1641
+ const obfuscator = new SecretObfuscator([
1642
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1643
+ { type: "regex", content: "tok_[a-z0-9]+" },
1644
+ ]);
1645
+ const promptInputs: string[] = [];
1646
+ const agent = makeAgent(promptInputs);
1647
+ const messages: AgentMessage[] = [
1648
+ { role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
1649
+ {
1650
+ role: "toolResult",
1651
+ toolCallId: "c1",
1652
+ toolName: "read",
1653
+ content: "tok_abc123",
1654
+ isError: false,
1655
+ timestamp: 2,
1656
+ } as unknown as AgentMessage,
1657
+ ];
1658
+ const host: AdvisorRuntimeHost = {
1659
+ snapshotMessages: () => messages,
1660
+ enqueueAdvice: () => {},
1661
+ obfuscator,
1662
+ };
1663
+ const runtime = new AdvisorRuntime(agent, host);
1664
+
1665
+ runtime.onTurnEnd();
1666
+ await Promise.resolve();
1667
+
1668
+ expect(promptInputs).toHaveLength(1);
1669
+ expect(promptInputs[0]).toContain("#TOKABC123_");
1670
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1671
+ });
1672
+ it("does not scan tool-call arguments hidden by the primary-argument preview", async () => {
1673
+ const obfuscator = new SecretObfuscator([
1674
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1675
+ { type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
1676
+ ]);
1677
+ const promptInputs: string[] = [];
1678
+ const agent = makeAgent(promptInputs);
1679
+ const messages: AgentMessage[] = [
1680
+ { role: "user", content: "remember OTHERSECRET", timestamp: 1 } as AgentMessage,
1681
+ {
1682
+ role: "assistant",
1683
+ content: [
1684
+ { type: "toolCall", id: "c1", name: "write", arguments: { path: "a.ts", content: "tok_abc123" } },
1685
+ ],
1686
+ timestamp: 2,
1687
+ } as unknown as AgentMessage,
1688
+ ];
1689
+ const runtime = new AdvisorRuntime(agent, {
1690
+ snapshotMessages: () => messages,
1691
+ enqueueAdvice: () => {},
1692
+ obfuscator,
1693
+ });
1694
+ runtime.onTurnEnd();
1695
+ await runtime.waitForCatchup(1000, 1);
1696
+ expect(promptInputs[0]).toContain("#TOKABC123_");
1697
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1698
+ });
1699
+
1700
+ it("does not scan failed tool-result text beyond its visible preview", async () => {
1701
+ const obfuscator = new SecretObfuscator([
1702
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1703
+ { type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
1704
+ ]);
1705
+ const promptInputs: string[] = [];
1706
+ const agent = makeAgent(promptInputs);
1707
+ const messages: AgentMessage[] = [
1708
+ { role: "user", content: "remember OTHERSECRET", timestamp: 1 } as AgentMessage,
1709
+ {
1710
+ role: "toolResult",
1711
+ toolCallId: "c1",
1712
+ toolName: "read",
1713
+ content: `${"x".repeat(120)} tok_abc123`,
1714
+ isError: true,
1715
+ timestamp: 2,
1716
+ } as unknown as AgentMessage,
1717
+ ];
1718
+ const runtime = new AdvisorRuntime(agent, {
1719
+ snapshotMessages: () => messages,
1720
+ enqueueAdvice: () => {},
1721
+ obfuscator,
1722
+ });
1723
+ runtime.onTurnEnd();
1724
+ await runtime.waitForCatchup(1000, 1);
1725
+ expect(promptInputs[0]).toContain("#TOKABC123_");
1726
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1727
+ });
1728
+
1729
+ it("does not scan advisor-hidden execution output", async () => {
1730
+ const obfuscator = new SecretObfuscator([
1731
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1732
+ { type: "regex", content: "tok_[a-z0-9]+" },
1733
+ ]);
1734
+ const promptInputs: string[] = [];
1735
+ const agent = makeAgent(promptInputs);
1736
+ const messages: AgentMessage[] = [
1737
+ { role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
1738
+ {
1739
+ role: "bashExecution",
1740
+ command: "echo ok",
1741
+ output: "tok_abc123",
1742
+ exitCode: 0,
1743
+ timestamp: 2,
1744
+ } as unknown as AgentMessage,
1745
+ {
1746
+ role: "pythonExecution",
1747
+ code: "print('ok')",
1748
+ output: "tok_abc123",
1749
+ exitCode: 0,
1750
+ timestamp: 3,
1751
+ } as unknown as AgentMessage,
1752
+ ];
1753
+ const host: AdvisorRuntimeHost = {
1754
+ snapshotMessages: () => messages,
1755
+ enqueueAdvice: () => {},
1756
+ obfuscator,
1757
+ };
1758
+ const runtime = new AdvisorRuntime(agent, host);
1759
+
1760
+ runtime.onTurnEnd();
1761
+ await Promise.resolve();
1762
+
1763
+ expect(promptInputs).toHaveLength(1);
1764
+ expect(promptInputs[0]).toContain("#TOKABC123_");
1765
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1766
+ });
1767
+ it("does not scan execution source after the advisor preview cap", async () => {
1768
+ const obfuscator = new SecretObfuscator([
1769
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1770
+ { type: "regex", content: "tok_[a-z0-9]+" },
1771
+ ]);
1772
+ const promptInputs: string[] = [];
1773
+ const agent = makeAgent(promptInputs);
1774
+ const hiddenSuffix = `${"x".repeat(120)} tok_abc123`;
1775
+ const messages: AgentMessage[] = [
1776
+ { role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
1777
+ {
1778
+ role: "bashExecution",
1779
+ command: `echo ${hiddenSuffix}`,
1780
+ exitCode: 0,
1781
+ timestamp: 2,
1782
+ } as unknown as AgentMessage,
1783
+ {
1784
+ role: "pythonExecution",
1785
+ code: `print("${hiddenSuffix}")`,
1786
+ exitCode: 0,
1787
+ timestamp: 3,
1788
+ } as unknown as AgentMessage,
1789
+ ];
1790
+ const host: AdvisorRuntimeHost = {
1791
+ snapshotMessages: () => messages,
1792
+ enqueueAdvice: () => {},
1793
+ obfuscator,
1794
+ };
1795
+ const runtime = new AdvisorRuntime(agent, host);
1796
+
1797
+ runtime.onTurnEnd();
1798
+ await Promise.resolve();
1799
+
1800
+ expect(promptInputs).toHaveLength(1);
1801
+ expect(promptInputs[0]).toContain("#TOKABC123_");
1802
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1803
+ });
1804
+
1805
+ it("does not scan advisor-hidden file mention content", async () => {
1806
+ const obfuscator = new SecretObfuscator([
1807
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1808
+ { type: "regex", content: "tok_[a-z0-9]+" },
1809
+ ]);
1810
+ const promptInputs: string[] = [];
1811
+ const agent = makeAgent(promptInputs);
1812
+ const obfuscate = vi.spyOn(obfuscator, "obfuscate");
1813
+ const messages: AgentMessage[] = [
1814
+ { role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
1815
+ {
1816
+ role: "fileMention",
1817
+ files: [{ path: "config.ts", content: "tok_abc123" }],
1818
+ timestamp: 2,
1819
+ } as unknown as AgentMessage,
1820
+ ];
1821
+ const host: AdvisorRuntimeHost = {
1822
+ snapshotMessages: () => messages,
1823
+ enqueueAdvice: () => {},
1824
+ obfuscator,
1825
+ };
1826
+ const runtime = new AdvisorRuntime(agent, host);
1827
+
1828
+ runtime.onTurnEnd();
1829
+ await Promise.resolve();
1830
+
1831
+ expect(promptInputs).toHaveLength(1);
1832
+ expect(promptInputs[0]).toContain("#TOKABC123_");
1833
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1834
+ expect(obfuscate).not.toHaveBeenCalledWith("tok_abc123", expect.anything());
1835
+ });
1836
+
1837
+ it("does not scan or redact advisor-hidden custom payloads", async () => {
1838
+ const obfuscator = new SecretObfuscator([
1839
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1840
+ { type: "regex", content: "tok_[a-z0-9]+" },
1841
+ ]);
1842
+ const promptInputs: string[] = [];
1843
+ const agent = makeAgent(promptInputs);
1844
+ const obfuscate = vi.spyOn(obfuscator, "obfuscate");
1845
+ const messages: AgentMessage[] = [
1846
+ { role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
1847
+ {
1848
+ role: "custom",
1849
+ customType: "extension-payload",
1850
+ display: false,
1851
+ content: "tok_abc123",
1852
+ details: { payload: "tok_abc123" },
1853
+ timestamp: 2,
1854
+ } as unknown as AgentMessage,
1855
+ ];
1856
+ const host: AdvisorRuntimeHost = {
1857
+ snapshotMessages: () => messages,
1858
+ enqueueAdvice: () => {},
1859
+ obfuscator,
1860
+ };
1861
+ const runtime = new AdvisorRuntime(agent, host);
1862
+
1863
+ runtime.onTurnEnd();
1864
+ await Promise.resolve();
1865
+
1866
+ expect(promptInputs).toHaveLength(1);
1867
+ expect(promptInputs[0]).toContain("#TOKABC123_");
1868
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1869
+ expect(obfuscate).not.toHaveBeenCalledWith("tok_abc123", expect.anything());
1870
+ });
1871
+
1872
+ it("shares regex-protected values across the whole advisor delta so an earlier field's friendly prefix cannot leak a sibling field's secret", async () => {
1873
+ // Regression: obfuscateAdvisorDelta must precompute regex-protected values
1874
+ // (collectAdvisorRegexSecretValues) across every field of the WHOLE advisor
1875
+ // delta before redacting any single message — mirroring the whole-batch
1876
+ // precomputation obfuscateMessages performs for the primary provider path
1877
+ // (see secrets-obfuscator.test.ts). Redacting message fields independently
1878
+ // would let the EARLIER user message's plain secret (OTHERSECRET) mint a
1879
+ // friendly-prefixed placeholder ("#TOKABC123_<hash>#") before the SIBLING
1880
+ // toolResult's `details.diff` field, later in the same delta, reveals the
1881
+ // regex-protected value that friendly name normalizes to
1882
+ // (tok_abc123 -> TOKABC123) — baking a normalized rendering of that
1883
+ // still-undiscovered secret into the advisor-bound prompt as an "innocent"
1884
+ // friendly label instead of a bare placeholder.
1885
+ const obfuscator = new SecretObfuscator([
1886
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1887
+ { type: "regex", content: "tok_[a-z0-9]+" },
1888
+ ]);
1889
+ const promptInputs: string[] = [];
1890
+ const agent = makeAgent(promptInputs);
1891
+ const diff = `--- a/config.ts\n+++ b/config.ts\n@@ -1 +1 @@\n-const token = "old";\n+const token = "tok_abc123";`;
1892
+ const messages: AgentMessage[] = [
1893
+ { role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
1894
+ {
1895
+ role: "assistant",
1896
+ content: [{ type: "toolCall", id: "c1", name: "edit", arguments: { path: "config.ts" } }],
1897
+ timestamp: 2,
1898
+ } as unknown as AgentMessage,
1899
+ {
1900
+ role: "toolResult",
1901
+ toolCallId: "c1",
1902
+ toolName: "edit",
1903
+ content: "ok",
1904
+ details: { diff },
1905
+ timestamp: 3,
1906
+ } as unknown as AgentMessage,
1907
+ ];
1908
+ const host: AdvisorRuntimeHost = {
1909
+ snapshotMessages: () => messages,
1910
+ enqueueAdvice: () => {},
1911
+ obfuscator,
1912
+ };
1913
+ const runtime = new AdvisorRuntime(agent, host);
1914
+
1915
+ runtime.onTurnEnd();
1916
+ await Promise.resolve();
1917
+
1918
+ expect(promptInputs).toHaveLength(1);
1919
+ const prompt = promptInputs[0]!;
1920
+ expect(prompt).not.toContain("OTHERSECRET");
1921
+ expect(prompt).not.toContain("tok_abc123");
1922
+ // The friendly prefix is itself a normalized rendering of the
1923
+ // later-discovered regex value; sharing regex values across the whole
1924
+ // delta up front must strip it to a bare placeholder rather than bake
1925
+ // it into the earlier user message's rendering.
1926
+ expect(prompt).not.toContain("TOKABC123_");
1927
+
1928
+ // Both originals still round-trip through deobfuscation of the
1929
+ // advisor-bound prompt text.
1930
+ const restored = obfuscator.deobfuscate(prompt);
1931
+ expect(restored).toContain("OTHERSECRET");
1932
+ expect(restored).toContain("tok_abc123");
1933
+ });
1934
+
1935
+ it("keeps replace-regex collisions across advisor deltas", async () => {
1936
+ const obfuscator = new SecretObfuscator([
1937
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1938
+ { type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
1939
+ ]);
1940
+ const promptInputs: string[] = [];
1941
+ const agent = makeAgent(promptInputs);
1942
+ const messages: AgentMessage[] = [{ role: "user", content: "first tok_abc123", timestamp: 1 } as AgentMessage];
1943
+ const host: AdvisorRuntimeHost = {
1944
+ snapshotMessages: () => messages,
1945
+ enqueueAdvice: () => {},
1946
+ obfuscator,
1947
+ };
1948
+ const runtime = new AdvisorRuntime(agent, host);
1949
+
1950
+ runtime.onTurnEnd();
1951
+ await runtime.waitForCatchup(1000, 1);
1952
+ messages.push({ role: "user", content: "then OTHERSECRET", timestamp: 2 } as AgentMessage);
1953
+ runtime.onTurnEnd();
1954
+ await runtime.waitForCatchup(1000, 1);
1955
+
1956
+ expect(promptInputs).toHaveLength(2);
1957
+ expect(promptInputs[0]).not.toContain("tok_abc123");
1958
+ expect(promptInputs[1]).not.toContain("OTHERSECRET");
1959
+ expect(promptInputs[1]).not.toContain("TOKABC123_");
1960
+ });
1961
+
1962
+ it("scrubs prior advisor prompts when a later replace regex collides with their friendly prefix", async () => {
1963
+ const obfuscator = new SecretObfuscator([
1964
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
1965
+ { type: "regex", content: "tok_[a-z0-9]+", mode: "replace" },
1966
+ ]);
1967
+ const promptInputs: string[] = [];
1968
+ const agent = makeAgent(promptInputs);
1969
+ const firstStoredPrompt = (): string => {
1970
+ const message = agent.state.messages[0];
1971
+ if (message?.role !== "user" || !("content" in message) || typeof message.content !== "string") {
1972
+ throw new Error("Expected the first advisor history item to be a user prompt");
1973
+ }
1974
+ return message.content;
1975
+ };
1976
+ const messages: AgentMessage[] = [
1977
+ { role: "user", content: "remember OTHERSECRET", timestamp: 1 } as AgentMessage,
1978
+ ];
1979
+ const host: AdvisorRuntimeHost = {
1980
+ snapshotMessages: () => messages,
1981
+ enqueueAdvice: () => {},
1982
+ obfuscator,
1983
+ };
1984
+ const runtime = new AdvisorRuntime(agent, host);
1985
+
1986
+ runtime.onTurnEnd();
1987
+ await runtime.waitForCatchup(1000, 1);
1988
+ agent.state.messages.push({ role: "user", content: promptInputs[0]!, timestamp: 1 } as AgentMessage);
1989
+ expect(firstStoredPrompt()).toContain("TOKABC123_");
1990
+
1991
+ messages.push({ role: "user", content: "later tok_abc123", timestamp: 2 } as AgentMessage);
1992
+ runtime.onTurnEnd();
1993
+ await runtime.waitForCatchup(1000, 1);
1994
+
1995
+ expect(promptInputs).toHaveLength(2);
1996
+ expect(firstStoredPrompt()).not.toContain("TOKABC123_");
1997
+ expect(promptInputs[1]).not.toContain("TOKABC123_");
1998
+ });
1999
+
2000
+ it("redacts secrets inside assistant thinking blocks, honoring the whole-delta friendly-prefix collision set", async () => {
2001
+ // Regression: obfuscateAssistantMessage (the advisor-local redaction path)
2002
+ // must rewrite `thinking` blocks the same way it rewrites `text` blocks.
2003
+ // Mirrors the collision scenario above but sources the friendly-prefixed
2004
+ // placeholder from a PRIOR thinking block: if thinking fell through
2005
+ // unredacted, the advisor prompt would receive both the raw secret AND,
2006
+ // had it been redacted without sharing the regex collision set, a
2007
+ // normalized "#TOKABC123_<hash>#" rendering of the regex-protected value
2008
+ // (tok_abc123) only discovered later in the same delta.
2009
+ const obfuscator = new SecretObfuscator([
2010
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
2011
+ { type: "regex", content: "tok_[a-z0-9]+" },
2012
+ ]);
2013
+ const promptInputs: string[] = [];
2014
+ const agent = makeAgent(promptInputs);
2015
+ const diff = `--- a/config.ts\n+++ b/config.ts\n@@ -1 +1 @@\n-const token = "old";\n+const token = "tok_abc123";`;
2016
+ const messages: AgentMessage[] = [
2017
+ {
2018
+ role: "assistant",
2019
+ content: [
2020
+ { type: "thinking", thinking: "Remember OTHERSECRET for later." },
2021
+ { type: "toolCall", id: "c1", name: "edit", arguments: { path: "config.ts" } },
2022
+ ],
2023
+ timestamp: 1,
2024
+ } as unknown as AgentMessage,
2025
+ {
2026
+ role: "toolResult",
2027
+ toolCallId: "c1",
2028
+ toolName: "edit",
2029
+ content: "ok",
2030
+ details: { diff },
2031
+ timestamp: 2,
2032
+ } as unknown as AgentMessage,
2033
+ ];
2034
+ const host: AdvisorRuntimeHost = {
2035
+ snapshotMessages: () => messages,
2036
+ enqueueAdvice: () => {},
2037
+ obfuscator,
2038
+ };
2039
+ const runtime = new AdvisorRuntime(agent, host);
2040
+
2041
+ runtime.onTurnEnd();
2042
+ await Promise.resolve();
2043
+
2044
+ expect(promptInputs).toHaveLength(1);
2045
+ const prompt = promptInputs[0]!;
2046
+ expect(prompt).toContain("_thinking:_");
2047
+ expect(prompt).not.toContain("OTHERSECRET");
2048
+ expect(prompt).not.toContain("tok_abc123");
2049
+ expect(prompt).not.toContain("TOKABC123_");
2050
+
2051
+ // Both originals still round-trip through deobfuscation of the
2052
+ // advisor-bound prompt text.
2053
+ const restored = obfuscator.deobfuscate(prompt);
2054
+ expect(restored).toContain("OTHERSECRET");
2055
+ expect(restored).toContain("tok_abc123");
2056
+ });
2057
+ it("clears advisor thinking signatures when collision scrubbing rewrites their text", async () => {
2058
+ const obfuscator = new SecretObfuscator([
2059
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
2060
+ { type: "regex", content: "tok_[a-z0-9]+" },
2061
+ ]);
2062
+ const promptInputs: string[] = [];
2063
+ const agent = makeAgent(promptInputs);
2064
+ const staleThinking = obfuscator.obfuscate("OTHERSECRET");
2065
+ agent.state.messages.push({
2066
+ role: "assistant",
2067
+ content: [{ type: "thinking", thinking: staleThinking, thinkingSignature: "signed-thinking" }],
2068
+ timestamp: 1,
2069
+ } as unknown as AgentMessage);
2070
+ const messages: AgentMessage[] = [{ role: "user", content: "later tok_abc123", timestamp: 2 } as AgentMessage];
2071
+ const host: AdvisorRuntimeHost = {
2072
+ snapshotMessages: () => messages,
2073
+ enqueueAdvice: () => {},
2074
+ obfuscator,
2075
+ };
2076
+ const runtime = new AdvisorRuntime(agent, host);
2077
+
2078
+ runtime.onTurnEnd();
2079
+ await runtime.waitForCatchup(1000, 1);
2080
+
2081
+ const storedAssistant = agent.state.messages[0] as AssistantMessage;
2082
+ const thinking = storedAssistant.content.find(block => block.type === "thinking");
2083
+ expect(thinking?.thinking).not.toContain("TOKABC123_");
2084
+ expect(thinking?.thinkingSignature).toBeUndefined();
2085
+ });
2086
+
2087
+ it("skips raw image payload bytes when collecting regex-protected values, so image data cannot spuriously trigger friendly-prefix collision avoidance", async () => {
2088
+ // Regression: collectAdvisorRegexSecretValues's generic tree walk only
2089
+ // skipped strings already shaped like a `data:image/...` URL, but
2090
+ // `ImageContent.data` at rest is raw base64 (that URL form only exists
2091
+ // in the rendered viewer). Left unguarded, every image payload in the
2092
+ // raw message array gets regex-scanned on every advisor turn —
2093
+ // wasteful for large screenshots the advisor never even sees (images
2094
+ // render as the literal "[image]" marker) — and an accidental regex
2095
+ // match inside the base64 bytes would poison the whole-delta collision
2096
+ // set used to decide whether OTHER fields' friendly-name placeholders
2097
+ // are safe to render.
2098
+ const obfuscator = new SecretObfuscator([
2099
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
2100
+ { type: "regex", content: "tok_[a-z0-9]+" },
2101
+ ]);
2102
+ const promptInputs: string[] = [];
2103
+ const agent = makeAgent(promptInputs);
2104
+ const messages: AgentMessage[] = [
2105
+ { role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
2106
+ {
2107
+ role: "user",
2108
+ content: [{ type: "image", data: "binary noise tok_abc123 more noise", mimeType: "image/png" }],
2109
+ timestamp: 2,
2110
+ } as unknown as AgentMessage,
2111
+ ];
2112
+ const host: AdvisorRuntimeHost = {
2113
+ snapshotMessages: () => messages,
2114
+ enqueueAdvice: () => {},
2115
+ obfuscator,
2116
+ };
2117
+ const runtime = new AdvisorRuntime(agent, host);
2118
+
2119
+ runtime.onTurnEnd();
2120
+ await Promise.resolve();
2121
+
2122
+ expect(promptInputs).toHaveLength(1);
2123
+ const prompt = promptInputs[0]!;
2124
+ expect(prompt).not.toContain("OTHERSECRET");
2125
+ // Because the image bytes were skipped by the collision pre-scan, the
2126
+ // plain secret's friendly-name placeholder needed no collision avoidance.
2127
+ expect(prompt).toContain("TOKABC123_");
2128
+ });
2129
+
2130
+ it("ignores assistant provider replay payloads when collecting regex collision values", async () => {
2131
+ const obfuscator = new SecretObfuscator([
2132
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
2133
+ { type: "regex", content: "tok_[a-z0-9]+" },
2134
+ ]);
2135
+ const promptInputs: string[] = [];
2136
+ const agent = makeAgent(promptInputs);
2137
+ const messages: AgentMessage[] = [
2138
+ {
2139
+ role: "assistant",
2140
+ content: [{ type: "text", text: "remember OTHERSECRET for later" }],
2141
+ providerPayload: { items: [{ note: "tok_abc123" }] },
2142
+ timestamp: 1,
2143
+ } as unknown as AgentMessage,
2144
+ ];
2145
+ const host: AdvisorRuntimeHost = {
2146
+ snapshotMessages: () => messages,
2147
+ enqueueAdvice: () => {},
2148
+ obfuscator,
2149
+ };
2150
+ const runtime = new AdvisorRuntime(agent, host);
2151
+
2152
+ runtime.onTurnEnd();
2153
+ await Promise.resolve();
2154
+
2155
+ expect(promptInputs).toHaveLength(1);
2156
+ const prompt = promptInputs[0]!;
2157
+ expect(prompt).not.toContain("OTHERSECRET");
2158
+ expect(prompt).not.toContain("tok_abc123");
2159
+ expect(prompt).toContain("TOKABC123_");
2160
+ });
2161
+
2162
+ it("does not scan tool arguments omitted by the primary-argument preview", async () => {
2163
+ const obfuscator = new SecretObfuscator([
2164
+ { type: "plain", content: "OTHERSECRET", friendlyName: "TOKABC123" },
2165
+ { type: "regex", content: "tok_[a-z0-9]+" },
2166
+ ]);
2167
+ const promptInputs: string[] = [];
2168
+ const agent = makeAgent(promptInputs);
2169
+ const messages: AgentMessage[] = [
2170
+ { role: "user", content: "remember OTHERSECRET for later", timestamp: 1 } as AgentMessage,
2171
+ {
2172
+ role: "assistant",
2173
+ content: [
2174
+ { type: "toolCall", id: "call-1", name: "read", arguments: { type: "image", value: "tok_abc123" } },
2175
+ ],
2176
+ timestamp: 2,
2177
+ } as unknown as AgentMessage,
2178
+ ];
2179
+ const host: AdvisorRuntimeHost = {
2180
+ snapshotMessages: () => messages,
2181
+ enqueueAdvice: () => {},
2182
+ obfuscator,
2183
+ };
2184
+ const runtime = new AdvisorRuntime(agent, host);
2185
+
2186
+ runtime.onTurnEnd();
2187
+ await Promise.resolve();
2188
+
2189
+ expect(promptInputs).toHaveLength(1);
2190
+ const prompt = promptInputs[0]!;
2191
+ expect(prompt).not.toContain("OTHERSECRET");
2192
+ expect(prompt).not.toContain("tok_abc123");
2193
+ expect(prompt).toContain("TOKABC123_");
2194
+ });
2195
+
1553
2196
  it("expands plan-mode context once, then collapses an unchanged re-injection", async () => {
1554
2197
  const promptInputs: string[] = [];
1555
2198
  const { promise: firstPromptDone, resolve: finishFirst } = Promise.withResolvers<void>();