@probelabs/visor 0.1.186 → 0.1.187

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 (395) hide show
  1. package/defaults/assistant.yaml +31 -0
  2. package/defaults/code-talk.yaml +32 -0
  3. package/defaults/skills/code-explorer.yaml +6 -0
  4. package/dist/action-cli-bridge.d.ts.map +1 -1
  5. package/dist/agent-protocol/a2a-frontend.d.ts.map +1 -1
  6. package/dist/agent-protocol/index.d.ts.map +1 -1
  7. package/dist/agent-protocol/push-notification-manager.d.ts.map +1 -1
  8. package/dist/agent-protocol/state-transitions.d.ts.map +1 -1
  9. package/dist/agent-protocol/task-evaluator.d.ts.map +1 -1
  10. package/dist/agent-protocol/task-live-update-slack.d.ts +10 -0
  11. package/dist/agent-protocol/task-live-update-slack.d.ts.map +1 -1
  12. package/dist/agent-protocol/task-live-update-teams.d.ts.map +1 -1
  13. package/dist/agent-protocol/task-live-update-telegram.d.ts.map +1 -1
  14. package/dist/agent-protocol/task-live-updates.d.ts +10 -0
  15. package/dist/agent-protocol/task-live-updates.d.ts.map +1 -1
  16. package/dist/agent-protocol/task-progress-tool.d.ts.map +1 -1
  17. package/dist/agent-protocol/task-queue.d.ts.map +1 -1
  18. package/dist/agent-protocol/task-store.d.ts.map +1 -1
  19. package/dist/agent-protocol/task-stream-manager.d.ts.map +1 -1
  20. package/dist/agent-protocol/task-trace-resolution.d.ts.map +1 -1
  21. package/dist/agent-protocol/tasks-cli-handler.d.ts.map +1 -1
  22. package/dist/agent-protocol/tasks-tui.d.ts.map +1 -1
  23. package/dist/agent-protocol/trace-serializer.d.ts +43 -1
  24. package/dist/agent-protocol/trace-serializer.d.ts.map +1 -1
  25. package/dist/agent-protocol/track-execution.d.ts.map +1 -1
  26. package/dist/agent-protocol/types.d.ts.map +1 -1
  27. package/dist/ai-review-service.d.ts +22 -1
  28. package/dist/ai-review-service.d.ts.map +1 -1
  29. package/dist/check-execution-engine.d.ts.map +1 -1
  30. package/dist/cli-main.d.ts +1 -0
  31. package/dist/cli-main.d.ts.map +1 -1
  32. package/dist/cli.d.ts.map +1 -1
  33. package/dist/commands.d.ts.map +1 -1
  34. package/dist/config/cli-handler.d.ts.map +1 -1
  35. package/dist/config/config-reloader.d.ts.map +1 -1
  36. package/dist/config/config-snapshot-store.d.ts.map +1 -1
  37. package/dist/config/config-watcher.d.ts.map +1 -1
  38. package/dist/config/types.d.ts.map +1 -1
  39. package/dist/config.d.ts.map +1 -1
  40. package/dist/cron-scheduler.d.ts.map +1 -1
  41. package/dist/debug-visualizer/debug-span-exporter.d.ts.map +1 -1
  42. package/dist/debug-visualizer/trace-reader.d.ts.map +1 -1
  43. package/dist/debug-visualizer/ws-server.d.ts.map +1 -1
  44. package/dist/defaults/assistant.yaml +31 -0
  45. package/dist/defaults/code-talk.yaml +32 -0
  46. package/dist/defaults/skills/code-explorer.yaml +6 -0
  47. package/dist/dependency-resolver.d.ts.map +1 -1
  48. package/dist/docs/telemetry-setup.md +18 -2
  49. package/dist/email/adapter.d.ts.map +1 -1
  50. package/dist/email/client.d.ts.map +1 -1
  51. package/dist/email/markdown.d.ts.map +1 -1
  52. package/dist/email/polling-runner.d.ts +1 -0
  53. package/dist/email/polling-runner.d.ts.map +1 -1
  54. package/dist/engine/on-finish/orchestrator.d.ts.map +1 -1
  55. package/dist/engine/on-finish/utils.d.ts.map +1 -1
  56. package/dist/event-bus/event-bus.d.ts.map +1 -1
  57. package/dist/event-bus/types.d.ts.map +1 -1
  58. package/dist/event-mapper.d.ts.map +1 -1
  59. package/dist/failure-condition-evaluator.d.ts.map +1 -1
  60. package/dist/footer.d.ts.map +1 -1
  61. package/dist/frontends/email-frontend.d.ts.map +1 -1
  62. package/dist/frontends/github-frontend.d.ts.map +1 -1
  63. package/dist/frontends/host.d.ts.map +1 -1
  64. package/dist/frontends/ndjson-sink.d.ts.map +1 -1
  65. package/dist/frontends/slack-frontend.d.ts.map +1 -1
  66. package/dist/frontends/teams-frontend.d.ts.map +1 -1
  67. package/dist/frontends/telegram-frontend.d.ts.map +1 -1
  68. package/dist/frontends/whatsapp-frontend.d.ts.map +1 -1
  69. package/dist/generated/config-schema.d.ts +16 -8
  70. package/dist/generated/config-schema.d.ts.map +1 -1
  71. package/dist/git-repository-analyzer.d.ts.map +1 -1
  72. package/dist/github-auth.d.ts.map +1 -1
  73. package/dist/github-check-service.d.ts.map +1 -1
  74. package/dist/github-comments.d.ts.map +1 -1
  75. package/dist/github-reactions.d.ts.map +1 -1
  76. package/dist/index.d.ts +1 -0
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +7656 -1458
  79. package/dist/issue-filter.d.ts.map +1 -1
  80. package/dist/liquid-extensions.d.ts.map +1 -1
  81. package/dist/logger.d.ts.map +1 -1
  82. package/dist/mcp-job-manager.d.ts.map +1 -1
  83. package/dist/mcp-server.d.ts.map +1 -1
  84. package/dist/memory-store.d.ts.map +1 -1
  85. package/dist/output/traces/{run-2026-05-05T12-31-47-571Z.ndjson → run-2026-08-20T13-50-06-218Z.ndjson} +2699 -2699
  86. package/dist/output-formatters.d.ts.map +1 -1
  87. package/dist/policy/default-engine.d.ts.map +1 -1
  88. package/dist/policy/index.d.ts.map +1 -1
  89. package/dist/policy/policy-check-command.d.ts.map +1 -1
  90. package/dist/policy/types.d.ts.map +1 -1
  91. package/dist/pr-analyzer.d.ts.map +1 -1
  92. package/dist/pr-detector.d.ts.map +1 -1
  93. package/dist/providers/a2a-check-provider.d.ts.map +1 -1
  94. package/dist/providers/ai-check-provider.d.ts.map +1 -1
  95. package/dist/providers/api-tool-executor.d.ts.map +1 -1
  96. package/dist/providers/check-provider-registry.d.ts.map +1 -1
  97. package/dist/providers/check-provider.interface.d.ts.map +1 -1
  98. package/dist/providers/claude-code-check-provider.d.ts.map +1 -1
  99. package/dist/providers/claude-code-types.d.ts.map +1 -1
  100. package/dist/providers/command-check-provider.d.ts.map +1 -1
  101. package/dist/providers/custom-tool-executor.d.ts.map +1 -1
  102. package/dist/providers/git-checkout-provider.d.ts.map +1 -1
  103. package/dist/providers/github-ops-provider.d.ts.map +1 -1
  104. package/dist/providers/http-check-provider.d.ts.map +1 -1
  105. package/dist/providers/http-client-provider.d.ts.map +1 -1
  106. package/dist/providers/http-input-provider.d.ts.map +1 -1
  107. package/dist/providers/human-input-check-provider.d.ts.map +1 -1
  108. package/dist/providers/index.d.ts.map +1 -1
  109. package/dist/providers/log-check-provider.d.ts.map +1 -1
  110. package/dist/providers/mcp-check-provider.d.ts.map +1 -1
  111. package/dist/providers/mcp-custom-sse-server.d.ts.map +1 -1
  112. package/dist/providers/mcp-tools.d.ts.map +1 -1
  113. package/dist/providers/memory-check-provider.d.ts.map +1 -1
  114. package/dist/providers/noop-check-provider.d.ts.map +1 -1
  115. package/dist/providers/script-check-provider.d.ts.map +1 -1
  116. package/dist/providers/utcp-check-provider.d.ts.map +1 -1
  117. package/dist/providers/workflow-check-provider.d.ts.map +1 -1
  118. package/dist/providers/workflow-tool-executor.d.ts.map +1 -1
  119. package/dist/reviewer.d.ts.map +1 -1
  120. package/dist/runners/config-reload-runtime.d.ts.map +1 -1
  121. package/dist/runners/graceful-restart.d.ts.map +1 -1
  122. package/dist/runners/mcp-server-runner.d.ts.map +1 -1
  123. package/dist/runners/process-cli-handler.d.ts.map +1 -1
  124. package/dist/runners/process-discovery.d.ts.map +1 -1
  125. package/dist/runners/runner-factory.d.ts.map +1 -1
  126. package/dist/runners/runner-host.d.ts.map +1 -1
  127. package/dist/runners/runner.d.ts.map +1 -1
  128. package/dist/runners/telemetry-init.d.ts.map +1 -1
  129. package/dist/sandbox/bubblewrap-sandbox.d.ts.map +1 -1
  130. package/dist/sandbox/cache-volume-manager.d.ts.map +1 -1
  131. package/dist/sandbox/check-runner.d.ts.map +1 -1
  132. package/dist/sandbox/compose-generator.d.ts.map +1 -1
  133. package/dist/sandbox/docker-compose-sandbox.d.ts.map +1 -1
  134. package/dist/sandbox/docker-image-sandbox.d.ts.map +1 -1
  135. package/dist/sandbox/env-filter.d.ts.map +1 -1
  136. package/dist/sandbox/index.d.ts.map +1 -1
  137. package/dist/sandbox/sandbox-manager.d.ts.map +1 -1
  138. package/dist/sandbox/sandbox-telemetry.d.ts.map +1 -1
  139. package/dist/sandbox/seatbelt-sandbox.d.ts.map +1 -1
  140. package/dist/sandbox/trace-ingester.d.ts.map +1 -1
  141. package/dist/sandbox/types.d.ts.map +1 -1
  142. package/dist/scheduler/cli-handler.d.ts.map +1 -1
  143. package/dist/scheduler/index.d.ts.map +1 -1
  144. package/dist/scheduler/message-trigger.d.ts.map +1 -1
  145. package/dist/scheduler/schedule-parser.d.ts.map +1 -1
  146. package/dist/scheduler/schedule-store.d.ts.map +1 -1
  147. package/dist/scheduler/schedule-tool.d.ts.map +1 -1
  148. package/dist/scheduler/scheduler.d.ts.map +1 -1
  149. package/dist/scheduler/store/index.d.ts.map +1 -1
  150. package/dist/scheduler/store/json-migrator.d.ts.map +1 -1
  151. package/dist/scheduler/store/sqlite-store.d.ts.map +1 -1
  152. package/dist/scheduler/store/types.d.ts.map +1 -1
  153. package/dist/sdk/{a2a-frontend-L3TQ7QQ6.mjs → a2a-frontend-XCQ4CQUF.mjs} +3 -3
  154. package/dist/sdk/{check-provider-registry-RQYIEQLJ.mjs → check-provider-registry-2D3MCWP3.mjs} +14 -11
  155. package/dist/sdk/{chunk-WZQMTD7W.mjs → chunk-2LLKENYD.mjs} +3 -3
  156. package/dist/sdk/{chunk-WZQMTD7W.mjs.map → chunk-2LLKENYD.mjs.map} +1 -1
  157. package/dist/sdk/{chunk-XXAEN5KU.mjs → chunk-77AKSSW2.mjs} +43 -65
  158. package/dist/sdk/chunk-77AKSSW2.mjs.map +1 -0
  159. package/dist/sdk/chunk-7QI6F66G.mjs +145 -0
  160. package/dist/sdk/chunk-7QI6F66G.mjs.map +1 -0
  161. package/dist/sdk/{chunk-NEZY2MXM.mjs → chunk-B2LQT6QU.mjs} +4 -4
  162. package/dist/sdk/{chunk-NEZY2MXM.mjs.map → chunk-B2LQT6QU.mjs.map} +1 -1
  163. package/dist/sdk/{chunk-IY5PQ5EN.mjs → chunk-BVEATT5V.mjs} +822 -51
  164. package/dist/sdk/chunk-BVEATT5V.mjs.map +1 -0
  165. package/dist/sdk/{chunk-TSX3YS3F.mjs → chunk-HW4MPCOW.mjs} +5 -139
  166. package/dist/sdk/chunk-HW4MPCOW.mjs.map +1 -0
  167. package/dist/sdk/chunk-PGBV3VZB.mjs +122 -0
  168. package/dist/sdk/chunk-PGBV3VZB.mjs.map +1 -0
  169. package/dist/sdk/{chunk-PDQTEBOJ.mjs → chunk-QT766ZLD.mjs} +5 -1
  170. package/dist/sdk/chunk-QT766ZLD.mjs.map +1 -0
  171. package/dist/sdk/{chunk-5TXIAPRA.mjs → chunk-S3GSZRYC.mjs} +2 -2
  172. package/dist/sdk/{chunk-NPOVLLKH.mjs → chunk-TGQBEM3Y.mjs} +570 -259
  173. package/dist/sdk/chunk-TGQBEM3Y.mjs.map +1 -0
  174. package/dist/sdk/chunk-TL3ZTYFY.mjs +141 -0
  175. package/dist/sdk/chunk-TL3ZTYFY.mjs.map +1 -0
  176. package/dist/sdk/{chunk-4UMDSYC7.mjs → chunk-VS3JE2DL.mjs} +3 -3
  177. package/dist/sdk/{chunk-A4WHMRK2.mjs → chunk-YETG675X.mjs} +2 -2
  178. package/dist/sdk/{chunk-XX6WT332.mjs → chunk-YTG5DN72.mjs} +17 -9
  179. package/dist/sdk/chunk-YTG5DN72.mjs.map +1 -0
  180. package/dist/sdk/{chunk-SRU5TFNY.mjs → chunk-YU3VR5SZ.mjs} +145 -16
  181. package/dist/sdk/chunk-YU3VR5SZ.mjs.map +1 -0
  182. package/dist/sdk/{config-BCYAVJ5S.mjs → config-H5IOLBFA.mjs} +2 -2
  183. package/dist/sdk/{email-frontend-P7QE4UUJ.mjs → email-frontend-AE5TYWNC.mjs} +18 -3
  184. package/dist/sdk/{email-frontend-P7QE4UUJ.mjs.map → email-frontend-AE5TYWNC.mjs.map} +1 -1
  185. package/dist/sdk/{failure-condition-evaluator-ZBOSD574.mjs → failure-condition-evaluator-66PWLV3D.mjs} +4 -4
  186. package/dist/sdk/{github-frontend-4WHT5JEU.mjs → github-frontend-BBFKCY4M.mjs} +4 -4
  187. package/dist/sdk/{host-FKR7H26C.mjs → host-UQRZDLT7.mjs} +8 -8
  188. package/dist/sdk/oauth2-token-cache-OS7WHLDI.mjs +13 -0
  189. package/dist/sdk/{routing-IB2PXYFC.mjs → routing-Q647KUMO.mjs} +5 -5
  190. package/dist/sdk/{schedule-tool-LB33KL2W.mjs → schedule-tool-WMGSM6IT.mjs} +14 -11
  191. package/dist/sdk/{schedule-tool-handler-KYWZHKSS.mjs → schedule-tool-handler-JMDHCGZ2.mjs} +14 -11
  192. package/dist/sdk/sdk.d.mts +14 -2
  193. package/dist/sdk/sdk.d.ts +14 -2
  194. package/dist/sdk/sdk.js +1927 -643
  195. package/dist/sdk/sdk.js.map +1 -1
  196. package/dist/sdk/sdk.mjs +13 -10
  197. package/dist/sdk/sdk.mjs.map +1 -1
  198. package/dist/sdk/session-registry-CENXWIFR.mjs +12 -0
  199. package/dist/sdk/{slack-frontend-K3TPGERM.mjs → slack-frontend-JMX5MUVZ.mjs} +42 -12
  200. package/dist/sdk/slack-frontend-JMX5MUVZ.mjs.map +1 -0
  201. package/dist/sdk/{task-evaluator-H3BXC7SE.mjs → task-evaluator-4QG4CYQA.mjs} +7 -6
  202. package/dist/sdk/task-evaluator-4QG4CYQA.mjs.map +1 -0
  203. package/dist/sdk/{teams-frontend-6RRW533K.mjs → teams-frontend-6MDAN7OR.mjs} +17 -4
  204. package/dist/sdk/teams-frontend-6MDAN7OR.mjs.map +1 -0
  205. package/dist/sdk/{telegram-frontend-5UA77YAT.mjs → telegram-frontend-NLX2ZHS4.mjs} +17 -4
  206. package/dist/sdk/telegram-frontend-NLX2ZHS4.mjs.map +1 -0
  207. package/dist/sdk/{trace-helpers-AMZ2524G.mjs → trace-helpers-AIUGQLK5.mjs} +3 -3
  208. package/dist/sdk/{trace-serializer-EBHTHZYT.mjs → trace-serializer-4TOIAVQL.mjs} +10 -2
  209. package/dist/sdk/{track-execution-VW6P3OYO.mjs → track-execution-CFRZXMEC.mjs} +91 -20
  210. package/dist/sdk/track-execution-CFRZXMEC.mjs.map +1 -0
  211. package/dist/sdk/{utcp-check-provider-Q4PD5EP2.mjs → utcp-check-provider-WDTSQ7DZ.mjs} +3 -2
  212. package/dist/sdk/{whatsapp-frontend-72XEIUIR.mjs → whatsapp-frontend-3EMMNC7R.mjs} +15 -2
  213. package/dist/sdk/whatsapp-frontend-3EMMNC7R.mjs.map +1 -0
  214. package/dist/sdk/{workflow-check-provider-N6KSDPWU.mjs → workflow-check-provider-G7A67EPP.mjs} +14 -11
  215. package/dist/sdk/workflow-check-provider-G7A67EPP.mjs.map +1 -0
  216. package/dist/sdk.d.ts.map +1 -1
  217. package/dist/session-registry.d.ts +2 -7
  218. package/dist/session-registry.d.ts.map +1 -1
  219. package/dist/slack/adapter.d.ts.map +1 -1
  220. package/dist/slack/cache-prewarmer.d.ts.map +1 -1
  221. package/dist/slack/client.d.ts +8 -0
  222. package/dist/slack/client.d.ts.map +1 -1
  223. package/dist/slack/markdown.d.ts.map +1 -1
  224. package/dist/slack/prompt-state.d.ts.map +1 -1
  225. package/dist/slack/rate-limiter.d.ts.map +1 -1
  226. package/dist/slack/schedule-tool-handler.d.ts.map +1 -1
  227. package/dist/slack/signature.d.ts.map +1 -1
  228. package/dist/slack/slack-output-adapter.d.ts.map +1 -1
  229. package/dist/slack/socket-runner.d.ts +1 -0
  230. package/dist/slack/socket-runner.d.ts.map +1 -1
  231. package/dist/slack/thread-cache.d.ts.map +1 -1
  232. package/dist/snapshot-store.d.ts.map +1 -1
  233. package/dist/state-machine/context/build-engine-context.d.ts.map +1 -1
  234. package/dist/state-machine/context/workflow-inputs.d.ts.map +1 -1
  235. package/dist/state-machine/dispatch/debounce-manager.d.ts +44 -0
  236. package/dist/state-machine/dispatch/debounce-manager.d.ts.map +1 -0
  237. package/dist/state-machine/dispatch/dependency-gating.d.ts.map +1 -1
  238. package/dist/state-machine/dispatch/execution-invoker.d.ts.map +1 -1
  239. package/dist/state-machine/dispatch/foreach-processor.d.ts.map +1 -1
  240. package/dist/state-machine/dispatch/history-snapshot.d.ts.map +1 -1
  241. package/dist/state-machine/dispatch/on-init-handlers.d.ts.map +1 -1
  242. package/dist/state-machine/dispatch/policy-gate.d.ts.map +1 -1
  243. package/dist/state-machine/dispatch/renderer-schema.d.ts.map +1 -1
  244. package/dist/state-machine/dispatch/sandbox-routing.d.ts.map +1 -1
  245. package/dist/state-machine/dispatch/stats-manager.d.ts.map +1 -1
  246. package/dist/state-machine/dispatch/template-renderer.d.ts.map +1 -1
  247. package/dist/state-machine/execution/summary.d.ts.map +1 -1
  248. package/dist/state-machine/runner.d.ts.map +1 -1
  249. package/dist/state-machine/states/check-running.d.ts.map +1 -1
  250. package/dist/state-machine/states/completed.d.ts.map +1 -1
  251. package/dist/state-machine/states/error.d.ts.map +1 -1
  252. package/dist/state-machine/states/init.d.ts.map +1 -1
  253. package/dist/state-machine/states/level-dispatch.d.ts.map +1 -1
  254. package/dist/state-machine/states/plan-ready.d.ts.map +1 -1
  255. package/dist/state-machine/states/routing.d.ts.map +1 -1
  256. package/dist/state-machine/states/wave-planning.d.ts.map +1 -1
  257. package/dist/state-machine/workflow-projection.d.ts.map +1 -1
  258. package/dist/state-machine-execution-engine.d.ts +1 -0
  259. package/dist/state-machine-execution-engine.d.ts.map +1 -1
  260. package/dist/teams/adapter.d.ts.map +1 -1
  261. package/dist/teams/client.d.ts.map +1 -1
  262. package/dist/teams/markdown.d.ts.map +1 -1
  263. package/dist/teams/webhook-runner.d.ts +1 -0
  264. package/dist/teams/webhook-runner.d.ts.map +1 -1
  265. package/dist/telegram/adapter.d.ts.map +1 -1
  266. package/dist/telegram/client.d.ts.map +1 -1
  267. package/dist/telegram/markdown.d.ts.map +1 -1
  268. package/dist/telegram/polling-runner.d.ts +1 -0
  269. package/dist/telegram/polling-runner.d.ts.map +1 -1
  270. package/dist/telemetry/fallback-ndjson.d.ts.map +1 -1
  271. package/dist/telemetry/file-span-exporter.d.ts.map +1 -1
  272. package/dist/telemetry/lazy-otel.d.ts.map +1 -1
  273. package/dist/telemetry/metrics.d.ts.map +1 -1
  274. package/dist/telemetry/opentelemetry.d.ts.map +1 -1
  275. package/dist/telemetry/state-capture.d.ts.map +1 -1
  276. package/dist/telemetry/trace-helpers.d.ts.map +1 -1
  277. package/dist/telemetry/trace-report-exporter.d.ts.map +1 -1
  278. package/dist/test-runner/assertions.d.ts.map +1 -1
  279. package/dist/test-runner/conversation-sugar.d.ts.map +1 -1
  280. package/dist/test-runner/core/environment.d.ts.map +1 -1
  281. package/dist/test-runner/core/fixture.d.ts.map +1 -1
  282. package/dist/test-runner/core/flow-stage.d.ts.map +1 -1
  283. package/dist/test-runner/core/mocks.d.ts.map +1 -1
  284. package/dist/test-runner/core/test-execution-wrapper.d.ts.map +1 -1
  285. package/dist/test-runner/evaluators.d.ts.map +1 -1
  286. package/dist/test-runner/fixture-loader.d.ts.map +1 -1
  287. package/dist/test-runner/index.d.ts.map +1 -1
  288. package/dist/test-runner/llm-judge.d.ts.map +1 -1
  289. package/dist/test-runner/recorders/github-recorder.d.ts.map +1 -1
  290. package/dist/test-runner/recorders/global-recorder.d.ts.map +1 -1
  291. package/dist/test-runner/recorders/slack-recorder.d.ts.map +1 -1
  292. package/dist/test-runner/utils/selectors.d.ts.map +1 -1
  293. package/dist/test-runner/validator.d.ts.map +1 -1
  294. package/dist/traces/{run-2026-05-05T12-31-47-571Z.ndjson → run-2026-08-20T13-50-06-218Z.ndjson} +2699 -2699
  295. package/dist/tui/chat-runner.d.ts.map +1 -1
  296. package/dist/tui/chat-state.d.ts.map +1 -1
  297. package/dist/tui/chat-tui.d.ts.map +1 -1
  298. package/dist/tui/components/chat-box.d.ts.map +1 -1
  299. package/dist/tui/components/input-bar.d.ts.map +1 -1
  300. package/dist/tui/components/status-bar.d.ts.map +1 -1
  301. package/dist/tui/components/trace-viewer.d.ts.map +1 -1
  302. package/dist/tui/index.d.ts.map +1 -1
  303. package/dist/tui/tui-frontend.d.ts.map +1 -1
  304. package/dist/types/bot.d.ts.map +1 -1
  305. package/dist/types/cli.d.ts.map +1 -1
  306. package/dist/types/config.d.ts +14 -2
  307. package/dist/types/config.d.ts.map +1 -1
  308. package/dist/types/engine.d.ts.map +1 -1
  309. package/dist/types/execution.d.ts.map +1 -1
  310. package/dist/types/git-checkout.d.ts.map +1 -1
  311. package/dist/types/github.d.ts.map +1 -1
  312. package/dist/types/workflow.d.ts.map +1 -1
  313. package/dist/utils/author-permissions.d.ts.map +1 -1
  314. package/dist/utils/child-process-error-handler.d.ts +19 -0
  315. package/dist/utils/child-process-error-handler.d.ts.map +1 -0
  316. package/dist/utils/command-executor.d.ts.map +1 -1
  317. package/dist/utils/comment-metadata.d.ts.map +1 -1
  318. package/dist/utils/config-loader.d.ts.map +1 -1
  319. package/dist/utils/config-merger.d.ts.map +1 -1
  320. package/dist/utils/diff-processor.d.ts.map +1 -1
  321. package/dist/utils/env-exposure.d.ts.map +1 -1
  322. package/dist/utils/env-resolver.d.ts.map +1 -1
  323. package/dist/utils/fair-concurrency-limiter.d.ts.map +1 -1
  324. package/dist/utils/file-exclusion.d.ts.map +1 -1
  325. package/dist/utils/head-sha.d.ts.map +1 -1
  326. package/dist/utils/human-id.d.ts.map +1 -1
  327. package/dist/utils/instance-id.d.ts.map +1 -1
  328. package/dist/utils/interactive-prompt.d.ts.map +1 -1
  329. package/dist/utils/issue-normalizer.d.ts.map +1 -1
  330. package/dist/utils/json-text-extractor.d.ts.map +1 -1
  331. package/dist/utils/mermaid-telemetry.d.ts.map +1 -1
  332. package/dist/utils/oauth2-token-cache.d.ts.map +1 -1
  333. package/dist/utils/rate-limiter.d.ts.map +1 -1
  334. package/dist/utils/sandbox.d.ts.map +1 -1
  335. package/dist/utils/script-memory-ops.d.ts.map +1 -1
  336. package/dist/utils/script-tool-environment.d.ts.map +1 -1
  337. package/dist/utils/stdin-reader.d.ts.map +1 -1
  338. package/dist/utils/template-context.d.ts.map +1 -1
  339. package/dist/utils/tool-resolver.d.ts.map +1 -1
  340. package/dist/utils/toolkit-expander.d.ts.map +1 -1
  341. package/dist/utils/tracer-init.d.ts.map +1 -1
  342. package/dist/utils/ui-helpers.d.ts.map +1 -1
  343. package/dist/utils/user-facing-error.d.ts +4 -0
  344. package/dist/utils/user-facing-error.d.ts.map +1 -0
  345. package/dist/utils/workspace-manager.d.ts +5 -0
  346. package/dist/utils/workspace-manager.d.ts.map +1 -1
  347. package/dist/utils/worktree-cleanup.d.ts +28 -0
  348. package/dist/utils/worktree-cleanup.d.ts.map +1 -1
  349. package/dist/utils/worktree-manager.d.ts +6 -0
  350. package/dist/utils/worktree-manager.d.ts.map +1 -1
  351. package/dist/webhook-server.d.ts.map +1 -1
  352. package/dist/whatsapp/adapter.d.ts.map +1 -1
  353. package/dist/whatsapp/client.d.ts.map +1 -1
  354. package/dist/whatsapp/markdown.d.ts.map +1 -1
  355. package/dist/whatsapp/webhook-runner.d.ts +1 -0
  356. package/dist/whatsapp/webhook-runner.d.ts.map +1 -1
  357. package/dist/workflow-executor.d.ts +7 -0
  358. package/dist/workflow-executor.d.ts.map +1 -1
  359. package/dist/workflow-registry.d.ts.map +1 -1
  360. package/package.json +3 -3
  361. package/dist/output/traces/run-2026-05-05T12-31-09-587Z.ndjson +0 -252
  362. package/dist/sdk/chunk-IY5PQ5EN.mjs.map +0 -1
  363. package/dist/sdk/chunk-NPOVLLKH.mjs.map +0 -1
  364. package/dist/sdk/chunk-PDQTEBOJ.mjs.map +0 -1
  365. package/dist/sdk/chunk-SRU5TFNY.mjs.map +0 -1
  366. package/dist/sdk/chunk-TSX3YS3F.mjs.map +0 -1
  367. package/dist/sdk/chunk-XX6WT332.mjs.map +0 -1
  368. package/dist/sdk/chunk-XXAEN5KU.mjs.map +0 -1
  369. package/dist/sdk/session-registry-6PV6SGEJ.mjs +0 -10
  370. package/dist/sdk/slack-frontend-K3TPGERM.mjs.map +0 -1
  371. package/dist/sdk/task-evaluator-H3BXC7SE.mjs.map +0 -1
  372. package/dist/sdk/teams-frontend-6RRW533K.mjs.map +0 -1
  373. package/dist/sdk/telegram-frontend-5UA77YAT.mjs.map +0 -1
  374. package/dist/sdk/trace-reader-OVE4DL2D.mjs +0 -270
  375. package/dist/sdk/trace-reader-OVE4DL2D.mjs.map +0 -1
  376. package/dist/sdk/track-execution-VW6P3OYO.mjs.map +0 -1
  377. package/dist/sdk/whatsapp-frontend-72XEIUIR.mjs.map +0 -1
  378. package/dist/traces/run-2026-05-05T12-31-09-587Z.ndjson +0 -252
  379. /package/dist/sdk/{a2a-frontend-L3TQ7QQ6.mjs.map → a2a-frontend-XCQ4CQUF.mjs.map} +0 -0
  380. /package/dist/sdk/{check-provider-registry-RQYIEQLJ.mjs.map → check-provider-registry-2D3MCWP3.mjs.map} +0 -0
  381. /package/dist/sdk/{chunk-5TXIAPRA.mjs.map → chunk-S3GSZRYC.mjs.map} +0 -0
  382. /package/dist/sdk/{chunk-4UMDSYC7.mjs.map → chunk-VS3JE2DL.mjs.map} +0 -0
  383. /package/dist/sdk/{chunk-A4WHMRK2.mjs.map → chunk-YETG675X.mjs.map} +0 -0
  384. /package/dist/sdk/{config-BCYAVJ5S.mjs.map → config-H5IOLBFA.mjs.map} +0 -0
  385. /package/dist/sdk/{failure-condition-evaluator-ZBOSD574.mjs.map → failure-condition-evaluator-66PWLV3D.mjs.map} +0 -0
  386. /package/dist/sdk/{github-frontend-4WHT5JEU.mjs.map → github-frontend-BBFKCY4M.mjs.map} +0 -0
  387. /package/dist/sdk/{host-FKR7H26C.mjs.map → host-UQRZDLT7.mjs.map} +0 -0
  388. /package/dist/sdk/{routing-IB2PXYFC.mjs.map → oauth2-token-cache-OS7WHLDI.mjs.map} +0 -0
  389. /package/dist/sdk/{schedule-tool-LB33KL2W.mjs.map → routing-Q647KUMO.mjs.map} +0 -0
  390. /package/dist/sdk/{schedule-tool-handler-KYWZHKSS.mjs.map → schedule-tool-WMGSM6IT.mjs.map} +0 -0
  391. /package/dist/sdk/{session-registry-6PV6SGEJ.mjs.map → schedule-tool-handler-JMDHCGZ2.mjs.map} +0 -0
  392. /package/dist/sdk/{trace-helpers-AMZ2524G.mjs.map → session-registry-CENXWIFR.mjs.map} +0 -0
  393. /package/dist/sdk/{trace-serializer-EBHTHZYT.mjs.map → trace-helpers-AIUGQLK5.mjs.map} +0 -0
  394. /package/dist/sdk/{utcp-check-provider-Q4PD5EP2.mjs.map → trace-serializer-4TOIAVQL.mjs.map} +0 -0
  395. /package/dist/sdk/{workflow-check-provider-N6KSDPWU.mjs.map → utcp-check-provider-WDTSQ7DZ.mjs.map} +0 -0
package/dist/sdk/sdk.js CHANGED
@@ -1529,7 +1529,7 @@ var require_package = __commonJS({
1529
1529
  "package.json"(exports2, module2) {
1530
1530
  module2.exports = {
1531
1531
  name: "@probelabs/visor",
1532
- version: "0.1.186",
1532
+ version: "0.1.187",
1533
1533
  main: "dist/index.js",
1534
1534
  bin: {
1535
1535
  visor: "./dist/index.js"
@@ -1630,7 +1630,7 @@ var require_package = __commonJS({
1630
1630
  "@apidevtools/swagger-parser": "^12.1.0",
1631
1631
  "@grammyjs/runner": "^2.0.3",
1632
1632
  "@modelcontextprotocol/sdk": "^1.25.3",
1633
- "@nyariv/sandboxjs": "github:probelabs/SandboxJS#23c4bb611f7d05f3cb8c523917b5f57103e48108",
1633
+ "@nyariv/sandboxjs": "github:probelabs/SandboxJS#d0d8c8ab3113d3b73e08b79b88a4b834ba3fae11",
1634
1634
  "@octokit/action": "^8.0.2",
1635
1635
  "@octokit/auth-app": "^8.1.0",
1636
1636
  "@octokit/core": "^7.0.3",
@@ -1649,7 +1649,7 @@ var require_package = __commonJS({
1649
1649
  "@opentelemetry/sdk-node": "^0.203.0",
1650
1650
  "@opentelemetry/sdk-trace-base": "^1.30.1",
1651
1651
  "@opentelemetry/semantic-conventions": "^1.30.1",
1652
- "@probelabs/probe": "^0.6.0-rc311",
1652
+ "@probelabs/probe": "^0.6.0-rc318",
1653
1653
  "@types/commander": "^2.12.0",
1654
1654
  "@types/uuid": "^10.0.0",
1655
1655
  "@utcp/file": "^1.1.0",
@@ -8756,6 +8756,126 @@ var init_policy_gate = __esm({
8756
8756
  }
8757
8757
  });
8758
8758
 
8759
+ // src/state-machine/dispatch/debounce-manager.ts
8760
+ function effectiveDebounceMs(requested) {
8761
+ const override = process.env.VISOR_DEBOUNCE_OVERRIDE;
8762
+ if (override !== void 0) {
8763
+ const ms = parseInt(override, 10);
8764
+ if (!isNaN(ms) && ms >= 0) return ms;
8765
+ }
8766
+ return requested;
8767
+ }
8768
+ function getDebounceManager() {
8769
+ if (!__instance) __instance = new DebounceManager();
8770
+ return __instance;
8771
+ }
8772
+ var DebounceManager, __instance;
8773
+ var init_debounce_manager = __esm({
8774
+ "src/state-machine/dispatch/debounce-manager.ts"() {
8775
+ "use strict";
8776
+ init_logger();
8777
+ DebounceManager = class {
8778
+ pending = /* @__PURE__ */ new Map();
8779
+ /** Waiters that were superseded and should resolve as 'debounced' */
8780
+ superseded = /* @__PURE__ */ new Map();
8781
+ /** Keys currently executing — prevents parallel runs */
8782
+ executing = /* @__PURE__ */ new Set();
8783
+ /**
8784
+ * Enqueue an execution. Returns 'executed' if this invocation won the
8785
+ * debounce race, or 'debounced' if it was superseded by a later one
8786
+ * or if the function is already executing for this key.
8787
+ */
8788
+ enqueue(key, debounceMs, fn) {
8789
+ if (this.executing.has(key)) {
8790
+ logger.info(`[DebounceManager] ${key}: already executing, skipping this invocation`);
8791
+ return Promise.resolve({ outcome: "debounced" });
8792
+ }
8793
+ const actualMs = effectiveDebounceMs(debounceMs);
8794
+ return new Promise(
8795
+ (resolve17, reject) => {
8796
+ const existing = this.pending.get(key);
8797
+ if (existing) {
8798
+ clearTimeout(existing.timer);
8799
+ if (!this.superseded.has(key)) {
8800
+ this.superseded.set(key, []);
8801
+ }
8802
+ this.superseded.get(key).push(existing.resolve);
8803
+ existing.invocations++;
8804
+ logger.info(
8805
+ `[DebounceManager] ${key}: invocation #${existing.invocations}, resetting ${actualMs}ms timer`
8806
+ );
8807
+ } else {
8808
+ logger.info(
8809
+ `[DebounceManager] ${key}: first invocation, starting ${actualMs}ms debounce`
8810
+ );
8811
+ }
8812
+ const invocations = existing ? existing.invocations : 1;
8813
+ const firstTime = existing ? existing.firstInvocationTime : Date.now();
8814
+ const timer = setTimeout(async () => {
8815
+ this.pending.delete(key);
8816
+ const waiters2 = this.superseded.get(key) || [];
8817
+ this.superseded.delete(key);
8818
+ for (const w of waiters2) {
8819
+ w({ outcome: "debounced" });
8820
+ }
8821
+ const elapsed = Date.now() - firstTime;
8822
+ logger.info(
8823
+ `[DebounceManager] ${key}: executing after ${invocations} invocation(s) over ${elapsed}ms`
8824
+ );
8825
+ this.executing.add(key);
8826
+ try {
8827
+ const result = await fn();
8828
+ resolve17({ outcome: "executed", result });
8829
+ } catch (err) {
8830
+ reject(err);
8831
+ } finally {
8832
+ this.executing.delete(key);
8833
+ logger.info(`[DebounceManager] ${key}: execution finished`);
8834
+ }
8835
+ }, actualMs);
8836
+ this.pending.set(key, {
8837
+ timer,
8838
+ invocations,
8839
+ firstInvocationTime: firstTime,
8840
+ resolve: resolve17,
8841
+ reject,
8842
+ fn
8843
+ });
8844
+ }
8845
+ );
8846
+ }
8847
+ /** Cancel a pending debounce. All waiters resolve as 'debounced'. */
8848
+ cancel(key) {
8849
+ const entry = this.pending.get(key);
8850
+ if (!entry) return false;
8851
+ clearTimeout(entry.timer);
8852
+ this.pending.delete(key);
8853
+ entry.resolve({ outcome: "debounced" });
8854
+ const waiters2 = this.superseded.get(key) || [];
8855
+ this.superseded.delete(key);
8856
+ for (const w of waiters2) {
8857
+ w({ outcome: "debounced" });
8858
+ }
8859
+ return true;
8860
+ }
8861
+ /** Cancel all pending debounces. */
8862
+ clear() {
8863
+ for (const [key] of this.pending) {
8864
+ this.cancel(key);
8865
+ }
8866
+ }
8867
+ /** Number of pending debounce keys. */
8868
+ get size() {
8869
+ return this.pending.size;
8870
+ }
8871
+ /** Check if a key is currently executing. */
8872
+ isExecuting(key) {
8873
+ return this.executing.has(key);
8874
+ }
8875
+ };
8876
+ }
8877
+ });
8878
+
8759
8879
  // src/state-machine/dispatch/history-snapshot.ts
8760
8880
  var history_snapshot_exports = {};
8761
8881
  __export(history_snapshot_exports, {
@@ -9192,12 +9312,11 @@ var SessionRegistry;
9192
9312
  var init_session_registry = __esm({
9193
9313
  "src/session-registry.ts"() {
9194
9314
  "use strict";
9315
+ init_logger();
9195
9316
  SessionRegistry = class _SessionRegistry {
9196
9317
  static instance;
9197
9318
  sessions = /* @__PURE__ */ new Map();
9198
- exitHandlerRegistered = false;
9199
9319
  constructor() {
9200
- this.registerExitHandlers();
9201
9320
  }
9202
9321
  /**
9203
9322
  * Get the singleton instance of SessionRegistry
@@ -9212,7 +9331,7 @@ var init_session_registry = __esm({
9212
9331
  * Register a ProbeAgent session
9213
9332
  */
9214
9333
  registerSession(sessionId, agent) {
9215
- console.error(`\u{1F504} Registering AI session: ${sessionId}`);
9334
+ logger.debug(`[SessionRegistry] Registering AI session: ${sessionId}`);
9216
9335
  this.sessions.set(sessionId, agent);
9217
9336
  }
9218
9337
  /**
@@ -9221,23 +9340,24 @@ var init_session_registry = __esm({
9221
9340
  getSession(sessionId) {
9222
9341
  const agent = this.sessions.get(sessionId);
9223
9342
  if (agent) {
9224
- console.error(`\u267B\uFE0F Reusing AI session: ${sessionId}`);
9343
+ logger.debug(`[SessionRegistry] Reusing AI session: ${sessionId}`);
9225
9344
  }
9226
9345
  return agent;
9227
9346
  }
9228
9347
  /**
9229
9348
  * Remove a session from the registry
9230
9349
  */
9231
- unregisterSession(sessionId) {
9350
+ async unregisterSession(sessionId) {
9232
9351
  if (this.sessions.has(sessionId)) {
9233
- console.error(`\u{1F5D1}\uFE0F Unregistering AI session: ${sessionId}`);
9234
9352
  const agent = this.sessions.get(sessionId);
9235
9353
  this.sessions.delete(sessionId);
9236
9354
  if (agent && typeof agent.cleanup === "function") {
9237
9355
  try {
9238
- agent.cleanup();
9356
+ await agent.cleanup();
9239
9357
  } catch (error) {
9240
- console.error(`\u26A0\uFE0F Warning: Failed to cleanup ProbeAgent: ${error}`);
9358
+ logger.warn(
9359
+ `[SessionRegistry] Failed to cleanup ProbeAgent session ${sessionId}: ${error}`
9360
+ );
9241
9361
  }
9242
9362
  }
9243
9363
  }
@@ -9245,17 +9365,26 @@ var init_session_registry = __esm({
9245
9365
  /**
9246
9366
  * Clear all sessions (useful for cleanup)
9247
9367
  */
9248
- clearAllSessions() {
9249
- console.error(`\u{1F9F9} Clearing all AI sessions (${this.sessions.size} sessions)`);
9250
- for (const [, agent] of this.sessions.entries()) {
9251
- if (agent && typeof agent.cleanup === "function") {
9252
- try {
9253
- agent.cleanup();
9254
- } catch {
9255
- }
9256
- }
9368
+ async clearAllSessions() {
9369
+ if (this.sessions.size === 0) {
9370
+ return;
9257
9371
  }
9372
+ logger.debug(`[SessionRegistry] Clearing all AI sessions (${this.sessions.size} sessions)`);
9373
+ const sessions = Array.from(this.sessions.entries());
9258
9374
  this.sessions.clear();
9375
+ await Promise.allSettled(
9376
+ sessions.map(async ([sessionId, agent]) => {
9377
+ if (agent && typeof agent.cleanup === "function") {
9378
+ try {
9379
+ await agent.cleanup();
9380
+ } catch (error) {
9381
+ logger.warn(
9382
+ `[SessionRegistry] Failed to cleanup ProbeAgent session ${sessionId}: ${error}`
9383
+ );
9384
+ }
9385
+ }
9386
+ })
9387
+ );
9259
9388
  }
9260
9389
  /**
9261
9390
  * Get all active session IDs
@@ -9280,7 +9409,7 @@ var init_session_registry = __esm({
9280
9409
  async cloneSession(sourceSessionId, newSessionId, checkName) {
9281
9410
  const sourceAgent = this.sessions.get(sourceSessionId);
9282
9411
  if (!sourceAgent) {
9283
- console.error(`\u26A0\uFE0F Cannot clone session: ${sourceSessionId} not found`);
9412
+ logger.warn(`[SessionRegistry] Cannot clone session: ${sourceSessionId} not found`);
9284
9413
  return void 0;
9285
9414
  }
9286
9415
  try {
@@ -9303,69 +9432,34 @@ var init_session_registry = __esm({
9303
9432
  clonedAgent._traceFilePath = tracerResult.filePath;
9304
9433
  }
9305
9434
  } catch (traceError) {
9306
- console.error(
9307
- "\u26A0\uFE0F Warning: Failed to initialize tracing for cloned session:",
9308
- traceError
9435
+ logger.warn(
9436
+ `[SessionRegistry] Failed to initialize tracing for cloned session ${newSessionId}: ${traceError}`
9309
9437
  );
9310
9438
  }
9311
9439
  }
9312
9440
  if (sourceAgent._mcpInitialized && typeof clonedAgent.initialize === "function") {
9313
9441
  try {
9314
9442
  await clonedAgent.initialize();
9315
- console.error(`\u{1F527} Initialized MCP tools for cloned session`);
9443
+ logger.debug(
9444
+ `[SessionRegistry] Initialized MCP tools for cloned session ${newSessionId}`
9445
+ );
9316
9446
  } catch (initError) {
9317
- console.error(`\u26A0\uFE0F Warning: Failed to initialize cloned agent: ${initError}`);
9447
+ logger.warn(
9448
+ `[SessionRegistry] Failed to initialize cloned agent ${newSessionId}: ${initError}`
9449
+ );
9318
9450
  }
9319
9451
  }
9320
9452
  const historyLength = clonedAgent.history?.length || 0;
9321
- console.error(
9322
- `\u{1F4CB} Cloned session ${sourceSessionId} \u2192 ${newSessionId} using ProbeAgent.clone() (${historyLength} messages, internal messages filtered)`
9453
+ logger.debug(
9454
+ `[SessionRegistry] Cloned session ${sourceSessionId} -> ${newSessionId} using ProbeAgent.clone() (${historyLength} messages, internal messages filtered)`
9323
9455
  );
9324
9456
  this.registerSession(newSessionId, clonedAgent);
9325
9457
  return clonedAgent;
9326
9458
  } catch (error) {
9327
- console.error(`\u26A0\uFE0F Failed to clone session ${sourceSessionId}:`, error);
9459
+ logger.warn(`[SessionRegistry] Failed to clone session ${sourceSessionId}: ${error}`);
9328
9460
  return void 0;
9329
9461
  }
9330
9462
  }
9331
- /**
9332
- * Register process exit handlers to cleanup sessions on exit
9333
- */
9334
- registerExitHandlers() {
9335
- if (this.exitHandlerRegistered) {
9336
- return;
9337
- }
9338
- const cleanupAndExit = (signal) => {
9339
- if (this.sessions.size > 0) {
9340
- console.error(`
9341
- \u{1F9F9} [${signal}] Cleaning up ${this.sessions.size} active AI sessions...`);
9342
- this.clearAllSessions();
9343
- }
9344
- };
9345
- process.on("exit", () => {
9346
- if (this.sessions.size > 0) {
9347
- console.error(`\u{1F9F9} [exit] Cleaning up ${this.sessions.size} active AI sessions...`);
9348
- for (const [, agent] of this.sessions.entries()) {
9349
- if (agent && typeof agent.cleanup === "function") {
9350
- try {
9351
- agent.cleanup();
9352
- } catch {
9353
- }
9354
- }
9355
- }
9356
- this.sessions.clear();
9357
- }
9358
- });
9359
- process.on("SIGINT", () => {
9360
- cleanupAndExit("SIGINT");
9361
- process.exit(0);
9362
- });
9363
- process.on("SIGTERM", () => {
9364
- cleanupAndExit("SIGTERM");
9365
- process.exit(0);
9366
- });
9367
- this.exitHandlerRegistered = true;
9368
- }
9369
9463
  };
9370
9464
  }
9371
9465
  });
@@ -9465,6 +9559,136 @@ var init_comment_metadata = __esm({
9465
9559
  }
9466
9560
  });
9467
9561
 
9562
+ // src/utils/user-facing-error.ts
9563
+ function normalizeWhitespace(text) {
9564
+ return text.replace(/\s+/g, " ").trim();
9565
+ }
9566
+ function stripWrapperPrefixes(text) {
9567
+ let cleaned = text.trim();
9568
+ const wrapperPatterns = [
9569
+ /^AI analysis failed:\s*/i,
9570
+ /^Claude Code analysis failed:\s*/i,
9571
+ /^ProbeAgent execution failed:\s*/i,
9572
+ /^Workflow step '[^']+' failed:\s*/i,
9573
+ /^Error:\s*/i
9574
+ ];
9575
+ let changed = true;
9576
+ while (changed) {
9577
+ changed = false;
9578
+ for (const pattern of wrapperPatterns) {
9579
+ const next = cleaned.replace(pattern, "");
9580
+ if (next !== cleaned) {
9581
+ cleaned = next.trim();
9582
+ changed = true;
9583
+ }
9584
+ }
9585
+ }
9586
+ return cleaned;
9587
+ }
9588
+ function collectErrorStrings(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0, out = []) {
9589
+ if (value == null || depth > 3) return out;
9590
+ if (typeof value === "string") {
9591
+ if (value.trim()) out.push(value.trim());
9592
+ return out;
9593
+ }
9594
+ if (typeof value !== "object") {
9595
+ out.push(String(value));
9596
+ return out;
9597
+ }
9598
+ if (seen.has(value)) return out;
9599
+ seen.add(value);
9600
+ const obj = value;
9601
+ const candidateKeys = ["message", "responseBody", "error", "details", "body", "cause"];
9602
+ for (const key of candidateKeys) {
9603
+ if (key in obj) collectErrorStrings(obj[key], seen, depth + 1, out);
9604
+ }
9605
+ return out;
9606
+ }
9607
+ function matchUserFacingErrorMessage(raw) {
9608
+ if (/no api key provided and neither claude nor codex command found/i.test(raw)) {
9609
+ return "No API key provided and neither claude nor codex command found.";
9610
+ }
9611
+ if (/budget limit exceeded|budget exceeded/i.test(raw)) {
9612
+ return "The AI provider budget limit was exceeded before a response could be generated. Please retry later or switch model/provider.";
9613
+ }
9614
+ if (/currently experiencing high demand|status.?unavailable|spikes in demand are usually temporary/i.test(
9615
+ raw
9616
+ )) {
9617
+ return "The AI provider is currently experiencing high demand and did not generate a response. Please retry shortly.";
9618
+ }
9619
+ if (/rate limit/i.test(raw)) {
9620
+ return "The AI provider rate limit was reached before a response could be generated. Please retry shortly.";
9621
+ }
9622
+ if (/401|authentication|api key|invalid api key|unauthorized/i.test(raw)) {
9623
+ return "The AI provider request was rejected before a response could be generated. Please check credentials and provider access.";
9624
+ }
9625
+ if (/403|forbidden/i.test(raw)) {
9626
+ return "The AI provider rejected the request before a response could be generated. This is usually caused by budget limits, access policy, or model permissions.";
9627
+ }
9628
+ if (/^request timed out$/i.test(raw) || /timed out|timeout/i.test(raw) && /(failed to get response from ai model|ai request|probeagent|provider|model|streamtext)/i.test(
9629
+ raw
9630
+ )) {
9631
+ return "The AI request timed out before a response could be generated. Please retry.";
9632
+ }
9633
+ if (/no output generated|failed to get response from ai model/i.test(raw)) {
9634
+ return "The AI provider failed before generating any response. This is usually caused by a provider-side limit, rate limit, or outage. Please retry.";
9635
+ }
9636
+ return null;
9637
+ }
9638
+ function formatUserFacingExecutionError(error) {
9639
+ const collected = collectErrorStrings(error);
9640
+ const raw = normalizeWhitespace(collected.join(" | ") || String(error ?? "Unknown error"));
9641
+ const matched = matchUserFacingErrorMessage(raw);
9642
+ if (matched) return matched;
9643
+ const cleaned = normalizeWhitespace(stripWrapperPrefixes(raw));
9644
+ return cleaned || "The request failed before a response could be generated. Please retry.";
9645
+ }
9646
+ function formatUserFacingExecutionMessage(message) {
9647
+ return formatUserFacingExecutionError(message);
9648
+ }
9649
+ function issuePriority(issue) {
9650
+ const ruleId = String(issue.ruleId || "");
9651
+ const message = String(issue.message || "");
9652
+ if (ruleId.startsWith("system/") || ruleId.endsWith("/error")) return 0;
9653
+ if (ruleId.includes("timeout") || /timed out|timeout/i.test(message)) return 1;
9654
+ if (ruleId.includes("contract/guarantee_failed") && /output\??\.\s*text|\btext\b.*length\s*>\s*0|\btext\b.*trim/i.test(message)) {
9655
+ return 2;
9656
+ }
9657
+ return 3;
9658
+ }
9659
+ function formatGuaranteeFailureMessage(issue) {
9660
+ const ruleId = String(issue.ruleId || "");
9661
+ const message = String(issue.message || "");
9662
+ if (!ruleId.includes("contract/guarantee_failed")) return null;
9663
+ if (/output\??\.\s*text|\btext\b.*length\s*>\s*0|\btext\b.*trim/i.test(message)) {
9664
+ return "The assistant failed to produce a response. This is usually caused by a provider-side limit, outage, or invalid structured output. Please retry.";
9665
+ }
9666
+ return null;
9667
+ }
9668
+ function summarizeUserFacingIssues(issues) {
9669
+ if (!Array.isArray(issues) || issues.length === 0) return null;
9670
+ const candidates = issues.filter(
9671
+ (issue) => !!issue && typeof issue === "object" && String(issue.severity || "").toLowerCase() === "error"
9672
+ );
9673
+ if (candidates.length === 0) return null;
9674
+ const prioritized = [...candidates].sort((a, b) => issuePriority(a) - issuePriority(b));
9675
+ const seen = /* @__PURE__ */ new Set();
9676
+ const messages = [];
9677
+ for (const issue of prioritized) {
9678
+ const formatted = formatGuaranteeFailureMessage(issue) || formatUserFacingExecutionMessage(String(issue.message || "Execution error"));
9679
+ if (!formatted || seen.has(formatted)) continue;
9680
+ seen.add(formatted);
9681
+ messages.push(formatted);
9682
+ if (messages.length >= 2) break;
9683
+ }
9684
+ return messages.length > 0 ? messages.join("\n") : null;
9685
+ }
9686
+ var init_user_facing_error = __esm({
9687
+ "src/utils/user-facing-error.ts"() {
9688
+ "use strict";
9689
+ }
9690
+ });
9691
+
9468
9692
  // src/slack/markdown.ts
9469
9693
  async function renderMermaidToPng(mermaidCode) {
9470
9694
  const tmpDir = os.tmpdir();
@@ -9523,6 +9747,10 @@ async function renderMermaidToPng(mermaidCode) {
9523
9747
  proc.stderr?.on("data", (data) => {
9524
9748
  stderr += data.toString();
9525
9749
  });
9750
+ proc.stderr?.on("error", () => {
9751
+ });
9752
+ proc.stdout?.on("error", () => {
9753
+ });
9526
9754
  proc.on("close", (code) => {
9527
9755
  if (code === 0) {
9528
9756
  resolve17({ success: true });
@@ -9716,16 +9944,36 @@ function createProbeTracerAdapter(fallbackTracer) {
9716
9944
  "negotiated_timeout.observer_exhausted",
9717
9945
  "negotiated_timeout.abort_summary"
9718
9946
  ]);
9947
+ const isSpanWorthyEvent = (name) => SPAN_WORTHY_EVENTS.has(name) || name.startsWith("task.");
9719
9948
  const emitEvent = (name, attrs) => {
9720
9949
  try {
9721
9950
  const flat = flattenAttrs(attrs);
9722
- if (SPAN_WORTHY_EVENTS.has(name)) {
9951
+ if (isSpanWorthyEvent(name)) {
9723
9952
  try {
9953
+ const startHr = process.hrtime();
9724
9954
  const tracer = trace.getTracer("visor");
9725
9955
  const childSpan = tracer.startSpan(`probe.event.${name}`, {
9726
9956
  attributes: { "probe.event.name": name, ...flat }
9727
9957
  });
9728
9958
  childSpan.end();
9959
+ try {
9960
+ const activeParent = trace.getActiveSpan?.();
9961
+ const parentCtx = activeParent?.spanContext?.();
9962
+ const childCtx = childSpan.spanContext?.();
9963
+ const endHr = process.hrtime();
9964
+ emitNdjsonFullSpan({
9965
+ name: `probe.event.${name}`,
9966
+ traceId: childCtx?.traceId || parentCtx?.traceId,
9967
+ spanId: childCtx?.spanId,
9968
+ parentSpanId: parentCtx?.spanId,
9969
+ startTime: [startHr[0], startHr[1]],
9970
+ endTime: [endHr[0], endHr[1]],
9971
+ attributes: { "probe.event.name": name, ...flat },
9972
+ events: [],
9973
+ status: { code: 1 }
9974
+ });
9975
+ } catch {
9976
+ }
9729
9977
  } catch {
9730
9978
  }
9731
9979
  }
@@ -9916,6 +10164,15 @@ function createProbeTracerAdapter(fallbackTracer) {
9916
10164
  }
9917
10165
  }
9918
10166
  },
10167
+ recordTaskEvent: (phase, attrs) => {
10168
+ emitEvent(`task.${phase}`, attrs);
10169
+ if (fallback && typeof fallback.recordTaskEvent === "function") {
10170
+ try {
10171
+ fallback.recordTaskEvent(phase, attrs);
10172
+ } catch {
10173
+ }
10174
+ }
10175
+ },
9919
10176
  createToolSpan: (toolName, attrs) => {
9920
10177
  let fallbackSpan = null;
9921
10178
  if (fallback && typeof fallback.createToolSpan === "function") {
@@ -10123,6 +10380,7 @@ var init_ai_review_service = __esm({
10123
10380
  init_tracer_init();
10124
10381
  init_diff_processor();
10125
10382
  init_comment_metadata();
10383
+ init_user_facing_error();
10126
10384
  init_markdown();
10127
10385
  PROBE_GRACEFUL_MARGIN_MS = 9e4;
10128
10386
  MIN_TIMEOUT_FOR_MARGIN_MS = PROBE_GRACEFUL_MARGIN_MS + 3e4;
@@ -10541,8 +10799,8 @@ var init_ai_review_service = __esm({
10541
10799
  /**
10542
10800
  * Cleanup a session from the registry
10543
10801
  */
10544
- cleanupSession(sessionId) {
10545
- this.sessionRegistry.unregisterSession(sessionId);
10802
+ async cleanupSession(sessionId) {
10803
+ await this.sessionRegistry.unregisterSession(sessionId);
10546
10804
  }
10547
10805
  /**
10548
10806
  * Build a custom prompt for AI review with XML-formatted data
@@ -11947,9 +12205,7 @@ ${"=".repeat(60)}
11947
12205
  return { response, effectiveSchema, sessionId };
11948
12206
  } catch (error) {
11949
12207
  console.error("\u274C ProbeAgent failed:", error);
11950
- throw new Error(
11951
- `ProbeAgent execution failed: ${error instanceof Error ? error.message : "Unknown error"}`
11952
- );
12208
+ throw new Error(formatUserFacingExecutionError(error));
11953
12209
  } finally {
11954
12210
  Object.keys(originalEnv).forEach((key) => {
11955
12211
  if (originalEnv[key] === void 0) {
@@ -14431,7 +14687,7 @@ var init_workflow_executor = __esm({
14431
14687
  if (step.if) {
14432
14688
  const shouldRun = this.evaluateCondition(step.if, {
14433
14689
  inputs: executionContext.inputs,
14434
- outputs: Object.fromEntries(stepResults),
14690
+ outputs: this.unwrapOutputs(stepResults),
14435
14691
  pr: runOptions.prInfo
14436
14692
  });
14437
14693
  if (!shouldRun) {
@@ -14594,18 +14850,14 @@ var init_workflow_executor = __esm({
14594
14850
  throw new Error("Expression mapping requires expression field");
14595
14851
  }
14596
14852
  const sandbox = createSecureSandbox();
14853
+ const unwrappedExpr = this.unwrapOutputs(stepResults);
14597
14854
  return compileAndRun(
14598
14855
  sandbox,
14599
14856
  typedMapping.expression,
14600
14857
  {
14601
14858
  inputs: executionContext.inputs,
14602
- outputs: Object.fromEntries(stepResults),
14603
- steps: Object.fromEntries(
14604
- Array.from(stepResults.entries()).map(([id, result]) => [
14605
- id,
14606
- result.output
14607
- ])
14608
- )
14859
+ outputs: unwrappedExpr,
14860
+ steps: unwrappedExpr
14609
14861
  },
14610
14862
  { injectLog: true, logPrefix: "workflow.input.expression" }
14611
14863
  );
@@ -14618,7 +14870,7 @@ var init_workflow_executor = __esm({
14618
14870
  if (typedMapping.template) {
14619
14871
  return await this.liquid.parseAndRender(typedMapping.template, {
14620
14872
  inputs: executionContext.inputs,
14621
- outputs: Object.fromEntries(stepResults)
14873
+ outputs: this.unwrapOutputs(stepResults)
14622
14874
  });
14623
14875
  }
14624
14876
  }
@@ -14645,32 +14897,44 @@ var init_workflow_executor = __esm({
14645
14897
  for (const output of workflow.outputs) {
14646
14898
  if (output.value_js) {
14647
14899
  const sandbox = createSecureSandbox();
14900
+ const unwrapped = this.unwrapOutputs(stepResults);
14648
14901
  outputs[output.name] = compileAndRun(
14649
14902
  sandbox,
14650
14903
  output.value_js,
14651
14904
  {
14652
14905
  inputs: executionContext.inputs,
14653
- steps: Object.fromEntries(
14654
- Array.from(stepResults.entries()).map(([id, result]) => [id, result.output])
14655
- ),
14656
- outputs: Object.fromEntries(stepResults),
14906
+ steps: unwrapped,
14907
+ outputs: unwrapped,
14657
14908
  pr: prInfo
14658
14909
  },
14659
14910
  { injectLog: true, logPrefix: `workflow.output.${output.name}` }
14660
14911
  );
14661
14912
  } else if (output.value) {
14913
+ const unwrappedLiquid = this.unwrapOutputs(stepResults);
14662
14914
  outputs[output.name] = await this.liquid.parseAndRender(output.value, {
14663
14915
  inputs: executionContext.inputs,
14664
- steps: Object.fromEntries(
14665
- Array.from(stepResults.entries()).map(([id, result]) => [id, result.output])
14666
- ),
14667
- outputs: Object.fromEntries(stepResults),
14916
+ steps: unwrappedLiquid,
14917
+ outputs: unwrappedLiquid,
14668
14918
  pr: prInfo
14669
14919
  });
14670
14920
  }
14671
14921
  }
14672
14922
  return outputs;
14673
14923
  }
14924
+ /**
14925
+ * Unwrap step outputs from ReviewSummary wrappers.
14926
+ * Script steps and MCP steps store results as { issues: [], output: <actual> }.
14927
+ * Workflow value_js/if/Liquid should see the unwrapped output, consistent with
14928
+ * how script step contexts already unwrap via buildProviderTemplateContext.
14929
+ */
14930
+ unwrapOutputs(stepResults) {
14931
+ const unwrapped = {};
14932
+ for (const [id, result] of stepResults.entries()) {
14933
+ const summary = result;
14934
+ unwrapped[id] = summary.output !== void 0 ? summary.output : summary;
14935
+ }
14936
+ return unwrapped;
14937
+ }
14674
14938
  /**
14675
14939
  * Aggregate results from all steps
14676
14940
  */
@@ -16495,6 +16759,14 @@ var init_config_schema = __esm({
16495
16759
  },
16496
16760
  description: 'Tags for categorizing and filtering checks (e.g., ["local", "fast", "security"])'
16497
16761
  },
16762
+ debounce: {
16763
+ type: "number",
16764
+ description: "Debounce window in milliseconds. When set, multiple invocations of this step (across concurrent engine instances) within the window are coalesced \u2014 only the last invocation actually executes after the window expires. Useful for steps that should run once after a burst of triggers settles.\n\nThe debounce key defaults to the step's fully-qualified check ID. Use `debounce_key` to group different steps under the same debounce."
16765
+ },
16766
+ debounce_key: {
16767
+ type: "string",
16768
+ description: "Custom debounce key. Steps sharing the same key share the same debounce window."
16769
+ },
16498
16770
  criticality: {
16499
16771
  type: "string",
16500
16772
  enum: ["external", "internal", "policy", "info"],
@@ -16704,7 +16976,7 @@ var init_config_schema = __esm({
16704
16976
  description: "Arguments/inputs for the workflow"
16705
16977
  },
16706
16978
  overrides: {
16707
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-15521-30601-src_types_config.ts-0-62422%3E%3E",
16979
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-15521-31208-src_types_config.ts-0-63102%3E%3E",
16708
16980
  description: "Override specific step configurations in the workflow"
16709
16981
  },
16710
16982
  output_mapping: {
@@ -16720,7 +16992,7 @@ var init_config_schema = __esm({
16720
16992
  description: "Config file path - alternative to workflow ID (loads a Visor config file as workflow)"
16721
16993
  },
16722
16994
  workflow_overrides: {
16723
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-15521-30601-src_types_config.ts-0-62422%3E%3E",
16995
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-15521-31208-src_types_config.ts-0-63102%3E%3E",
16724
16996
  description: "Alias for overrides - workflow step overrides (backward compatibility)"
16725
16997
  },
16726
16998
  ref: {
@@ -17448,7 +17720,7 @@ var init_config_schema = __esm({
17448
17720
  description: "Custom output name (defaults to workflow name)"
17449
17721
  },
17450
17722
  overrides: {
17451
- $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-15521-30601-src_types_config.ts-0-62422%3E%3E",
17723
+ $ref: "#/definitions/Record%3Cstring%2CPartial%3Cinterface-src_types_config.ts-15521-31208-src_types_config.ts-0-63102%3E%3E",
17452
17724
  description: "Step overrides"
17453
17725
  },
17454
17726
  output_mapping: {
@@ -17463,13 +17735,13 @@ var init_config_schema = __esm({
17463
17735
  "^x-": {}
17464
17736
  }
17465
17737
  },
17466
- "Record<string,Partial<interface-src_types_config.ts-15521-30601-src_types_config.ts-0-62422>>": {
17738
+ "Record<string,Partial<interface-src_types_config.ts-15521-31208-src_types_config.ts-0-63102>>": {
17467
17739
  type: "object",
17468
17740
  additionalProperties: {
17469
- $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-15521-30601-src_types_config.ts-0-62422%3E"
17741
+ $ref: "#/definitions/Partial%3Cinterface-src_types_config.ts-15521-31208-src_types_config.ts-0-63102%3E"
17470
17742
  }
17471
17743
  },
17472
- "Partial<interface-src_types_config.ts-15521-30601-src_types_config.ts-0-62422>": {
17744
+ "Partial<interface-src_types_config.ts-15521-31208-src_types_config.ts-0-63102>": {
17473
17745
  type: "object",
17474
17746
  additionalProperties: false
17475
17747
  },
@@ -18841,7 +19113,7 @@ var init_config_schema = __esm({
18841
19113
  },
18842
19114
  workflow: {
18843
19115
  type: "string",
18844
- description: "Workflow/check ID to run"
19116
+ description: "Workflow/check ID to run. Omit to treat inputs.text as a user message through the full pipeline."
18845
19117
  },
18846
19118
  inputs: {
18847
19119
  $ref: "#/definitions/Record%3Cstring%2Cunknown%3E",
@@ -18884,7 +19156,7 @@ var init_config_schema = __esm({
18884
19156
  description: "Timezone for schedule (default: UTC or scheduler default)"
18885
19157
  }
18886
19158
  },
18887
- required: ["schedule", "workflow"],
19159
+ required: ["schedule"],
18888
19160
  additionalProperties: false,
18889
19161
  description: "Static cron job defined in YAML configuration These are always executed by the scheduler daemon",
18890
19162
  patternProperties: {
@@ -21228,6 +21500,7 @@ var init_workflow_check_provider = __esm({
21228
21500
  init_liquid_extensions();
21229
21501
  path15 = __toESM(require("path"));
21230
21502
  yaml5 = __toESM(require("js-yaml"));
21503
+ init_user_facing_error();
21231
21504
  WorkflowCheckProvider = class extends CheckProvider {
21232
21505
  registry;
21233
21506
  executor;
@@ -21845,6 +22118,18 @@ var init_workflow_check_provider = __esm({
21845
22118
  scoreCount++;
21846
22119
  }
21847
22120
  }
22121
+ for (const checkStats of result.statistics?.checks || []) {
22122
+ if (checkStats.errorMessage) {
22123
+ allIssues.push({
22124
+ file: "system",
22125
+ line: 0,
22126
+ ruleId: "system/error",
22127
+ message: formatUserFacingExecutionMessage(checkStats.errorMessage),
22128
+ severity: "error",
22129
+ category: "logic"
22130
+ });
22131
+ }
22132
+ }
21848
22133
  const outputs = await this.computeWorkflowOutputsFromState(
21849
22134
  workflow,
21850
22135
  inputs,
@@ -22161,7 +22446,7 @@ function createWorkflowToolDefinition(workflow, argsOverrides, nameOverride) {
22161
22446
  for (const key of Object.keys(argsOverrides)) {
22162
22447
  delete filteredProperties[key];
22163
22448
  }
22164
- const filteredRequired = baseRequired ? baseRequired.filter((r) => !argsOverrides[r]) : void 0;
22449
+ const filteredRequired = baseRequired ? baseRequired.filter((r) => !(r in argsOverrides)) : void 0;
22165
22450
  inputSchema = {
22166
22451
  ...baseSchema,
22167
22452
  properties: filteredProperties,
@@ -23495,283 +23780,19 @@ var init_github_auth = __esm({
23495
23780
  }
23496
23781
  });
23497
23782
 
23498
- // src/debug-visualizer/trace-reader.ts
23499
- var trace_reader_exports = {};
23500
- __export(trace_reader_exports, {
23501
- buildExecutionTree: () => buildExecutionTree,
23502
- computeTimeline: () => computeTimeline,
23503
- extractStateSnapshots: () => extractStateSnapshots,
23504
- parseNDJSONTrace: () => parseNDJSONTrace
23505
- });
23506
- async function parseNDJSONTrace(filePath) {
23507
- const spans = [];
23508
- let lineNumber = 0;
23509
- const fileStream = fs16.createReadStream(filePath);
23510
- const rl = readline.createInterface({
23511
- input: fileStream,
23512
- crlfDelay: Infinity
23513
- });
23514
- for await (const line of rl) {
23515
- lineNumber++;
23516
- if (!line.trim()) {
23517
- continue;
23518
- }
23519
- try {
23520
- const rawSpan = JSON.parse(line);
23521
- const processedSpan = processRawSpan(rawSpan);
23522
- spans.push(processedSpan);
23523
- } catch (error) {
23524
- console.warn(`[trace-reader] Malformed JSON at line ${lineNumber}: ${error}`);
23525
- }
23526
- }
23527
- if (spans.length === 0) {
23528
- throw new Error("No valid spans found in trace file");
23529
- }
23530
- const tree = buildExecutionTree(spans);
23531
- const snapshots = extractStateSnapshots(spans);
23532
- const timeline = computeTimeline(spans);
23533
- const sortedSpans = [...spans].sort((a, b) => compareTimeValues(a.startTime, b.startTime));
23534
- const firstSpan = sortedSpans[0];
23535
- const lastSpan = sortedSpans[sortedSpans.length - 1];
23536
- const startTimeMs = timeValueToMillis(firstSpan.startTime);
23537
- const endTimeMs = timeValueToMillis(lastSpan.endTime);
23538
- return {
23539
- runId: tree.checkId,
23540
- traceId: firstSpan.traceId,
23541
- spans,
23542
- tree,
23543
- timeline,
23544
- snapshots,
23545
- metadata: {
23546
- startTime: timeValueToISO(firstSpan.startTime),
23547
- endTime: timeValueToISO(lastSpan.endTime),
23548
- duration: endTimeMs - startTimeMs,
23549
- totalSpans: spans.length,
23550
- totalSnapshots: snapshots.length
23551
- }
23552
- };
23553
- }
23554
- function processRawSpan(rawSpan) {
23555
- const traceId = rawSpan.traceId || "";
23556
- const spanId = rawSpan.spanId || "";
23557
- const parentSpanId = rawSpan.parentSpanId || void 0;
23558
- const name = rawSpan.name || "unknown";
23559
- const startTime = rawSpan.startTime || [0, 0];
23560
- const endTime = rawSpan.endTime || rawSpan.startTime || [0, 0];
23561
- const startMs = timeValueToMillis(startTime);
23562
- const endMs = timeValueToMillis(endTime);
23563
- const duration = endMs - startMs;
23564
- const attributes = rawSpan.attributes || {};
23565
- const events = (rawSpan.events || []).map((evt) => ({
23566
- name: evt.name || "unknown",
23567
- time: evt.time || [0, 0],
23568
- timestamp: evt.timestamp || timeValueToISO(evt.time || [0, 0]),
23569
- attributes: evt.attributes || {}
23570
- }));
23571
- const status = rawSpan.status?.code === 2 ? "error" : "ok";
23572
- return {
23573
- traceId,
23574
- spanId,
23575
- parentSpanId,
23576
- name,
23577
- startTime,
23578
- endTime,
23579
- duration,
23580
- attributes,
23581
- events,
23582
- status
23583
- };
23584
- }
23585
- function buildExecutionTree(spans) {
23586
- const nodeMap = /* @__PURE__ */ new Map();
23587
- for (const span of spans) {
23588
- const node = createExecutionNode(span);
23589
- nodeMap.set(span.spanId, node);
23590
- }
23591
- let rootNode;
23592
- const orphans = [];
23593
- for (const span of spans) {
23594
- const node = nodeMap.get(span.spanId);
23595
- if (!span.parentSpanId) {
23596
- rootNode = node;
23597
- } else {
23598
- const parent = nodeMap.get(span.parentSpanId);
23599
- if (parent) {
23600
- parent.children.push(node);
23601
- } else {
23602
- orphans.push(node);
23603
- }
23604
- }
23605
- }
23606
- if (!rootNode) {
23607
- console.warn("[trace-reader] No root span found, creating synthetic root");
23608
- rootNode = {
23609
- checkId: "synthetic-root",
23610
- type: "run",
23611
- status: "completed",
23612
- children: Array.from(nodeMap.values()).filter((n) => !n.span.parentSpanId),
23613
- span: spans[0],
23614
- // Use first span as placeholder
23615
- state: {}
23616
- };
23617
- }
23618
- if (orphans.length > 0 && rootNode) {
23619
- rootNode.children.push(...orphans);
23620
- }
23621
- return rootNode;
23622
- }
23623
- function createExecutionNode(span) {
23624
- const attrs = span.attributes;
23625
- const checkId = attrs["visor.check.id"] || attrs["visor.run.id"] || span.spanId;
23626
- let type = "unknown";
23627
- if (span.name === "visor.run") {
23628
- type = "run";
23629
- } else if (span.name === "visor.check") {
23630
- type = "check";
23631
- } else if (span.name.startsWith("visor.provider.")) {
23632
- type = "provider";
23633
- }
23634
- let status = "completed";
23635
- if (span.status === "error") {
23636
- status = "error";
23637
- } else if (attrs["visor.check.skipped"] === true) {
23638
- status = "skipped";
23639
- }
23640
- const state = {};
23641
- if (attrs["visor.check.input.context"]) {
23642
- try {
23643
- state.inputContext = JSON.parse(attrs["visor.check.input.context"]);
23644
- } catch {
23645
- state.inputContext = attrs["visor.check.input.context"];
23646
- }
23647
- }
23648
- if (attrs["visor.check.output"]) {
23649
- try {
23650
- state.output = JSON.parse(attrs["visor.check.output"]);
23651
- } catch {
23652
- state.output = attrs["visor.check.output"];
23653
- }
23654
- }
23655
- if (span.status === "error" || attrs["visor.check.error"]) {
23656
- state.errors = [attrs["visor.check.error"] || "Unknown error"];
23657
- }
23658
- state.metadata = {
23659
- type: attrs["visor.check.type"],
23660
- duration: span.duration,
23661
- provider: attrs["visor.provider.type"]
23662
- };
23663
- return {
23664
- checkId,
23665
- type,
23666
- status,
23667
- children: [],
23668
- span,
23669
- state
23670
- };
23671
- }
23672
- function extractStateSnapshots(spans) {
23673
- const snapshots = [];
23674
- for (const span of spans) {
23675
- for (const event of span.events) {
23676
- if (event.name === "state.snapshot") {
23677
- const attrs = event.attributes || {};
23678
- const snapshot = {
23679
- checkId: attrs["visor.snapshot.check_id"] || span.attributes["visor.check.id"] || "unknown",
23680
- timestamp: attrs["visor.snapshot.timestamp"] || event.timestamp || timeValueToISO(event.time),
23681
- timestampNanos: event.time,
23682
- outputs: parseJSON(attrs["visor.snapshot.outputs"], {}),
23683
- memory: parseJSON(attrs["visor.snapshot.memory"], {})
23684
- };
23685
- snapshots.push(snapshot);
23686
- }
23687
- }
23688
- }
23689
- snapshots.sort((a, b) => compareTimeValues(a.timestampNanos, b.timestampNanos));
23690
- return snapshots;
23691
- }
23692
- function computeTimeline(spans) {
23693
- const events = [];
23694
- for (const span of spans) {
23695
- const checkId = span.attributes["visor.check.id"] || span.spanId;
23696
- events.push({
23697
- type: "check.started",
23698
- checkId,
23699
- timestamp: timeValueToISO(span.startTime),
23700
- timestampNanos: span.startTime,
23701
- metadata: {
23702
- name: span.name,
23703
- type: span.attributes["visor.check.type"]
23704
- }
23705
- });
23706
- events.push({
23707
- type: span.status === "error" ? "check.failed" : "check.completed",
23708
- checkId,
23709
- timestamp: timeValueToISO(span.endTime),
23710
- timestampNanos: span.endTime,
23711
- duration: span.duration,
23712
- status: span.status,
23713
- metadata: {
23714
- name: span.name
23715
- }
23716
- });
23717
- for (const evt of span.events) {
23718
- events.push({
23719
- type: evt.name === "state.snapshot" ? "state.snapshot" : "event",
23720
- checkId: evt.attributes?.["visor.snapshot.check_id"] || checkId,
23721
- timestamp: evt.timestamp || timeValueToISO(evt.time),
23722
- timestampNanos: evt.time,
23723
- metadata: {
23724
- eventName: evt.name,
23725
- attributes: evt.attributes
23726
- }
23727
- });
23728
- }
23729
- }
23730
- events.sort((a, b) => compareTimeValues(a.timestampNanos, b.timestampNanos));
23731
- return events;
23732
- }
23733
- function timeValueToMillis(timeValue) {
23734
- const [seconds, nanos] = timeValue;
23735
- return seconds * 1e3 + nanos / 1e6;
23736
- }
23737
- function timeValueToISO(timeValue) {
23738
- const millis = timeValueToMillis(timeValue);
23739
- return new Date(millis).toISOString();
23740
- }
23741
- function compareTimeValues(a, b) {
23742
- if (a[0] !== b[0]) {
23743
- return a[0] - b[0];
23744
- }
23745
- return a[1] - b[1];
23746
- }
23747
- function parseJSON(value, defaultValue) {
23748
- if (typeof value !== "string") {
23749
- return defaultValue;
23750
- }
23751
- try {
23752
- return JSON.parse(value);
23753
- } catch {
23754
- return defaultValue;
23755
- }
23756
- }
23757
- var fs16, readline;
23758
- var init_trace_reader = __esm({
23759
- "src/debug-visualizer/trace-reader.ts"() {
23760
- "use strict";
23761
- fs16 = __toESM(require("fs"));
23762
- readline = __toESM(require("readline"));
23763
- }
23764
- });
23765
-
23766
23783
  // src/agent-protocol/trace-serializer.ts
23767
23784
  var trace_serializer_exports = {};
23768
23785
  __export(trace_serializer_exports, {
23786
+ buildTraceHeaderLines: () => buildTraceHeaderLines,
23787
+ buildTraceReport: () => buildTraceReport,
23788
+ extractProbeTaskSummary: () => extractProbeTaskSummary,
23769
23789
  fetchTraceSpans: () => fetchTraceSpans,
23770
23790
  findTraceFile: () => findTraceFile,
23771
23791
  readTraceIdFromFile: () => readTraceIdFromFile,
23772
23792
  renderSpanTree: () => renderSpanTree,
23773
23793
  renderSpanYaml: () => renderSpanYaml,
23774
23794
  renderTraceTree: () => renderTraceTree,
23795
+ resolveTraceSpans: () => resolveTraceSpans,
23775
23796
  serializeTraceForPrompt: () => serializeTraceForPrompt
23776
23797
  });
23777
23798
  function resolveBackendConfig(overrides) {
@@ -23782,7 +23803,8 @@ function resolveBackendConfig(overrides) {
23782
23803
  grafanaDatasourceId: overrides?.grafanaDatasourceId || process.env.GRAFANA_TEMPO_DATASOURCE_ID,
23783
23804
  jaegerUrl: overrides?.jaegerUrl || process.env.JAEGER_URL,
23784
23805
  traceDir: overrides?.traceDir || process.env.VISOR_TRACE_DIR || "output/traces",
23785
- authToken: overrides?.authToken || process.env.GRAFANA_TOKEN
23806
+ authToken: overrides?.authToken || process.env.GRAFANA_TOKEN,
23807
+ targetTraceId: overrides?.targetTraceId
23786
23808
  };
23787
23809
  }
23788
23810
  function getAutoBackendOrder() {
@@ -23943,6 +23965,60 @@ async function fetchTraceSpans(traceRef, config) {
23943
23965
  }
23944
23966
  return [];
23945
23967
  }
23968
+ async function resolveTraceSpans(traceIdOrPath, backendConfig, fallbackTraceId) {
23969
+ const cfg = resolveBackendConfig(backendConfig);
23970
+ const backendOrder = getBackendOrder(cfg);
23971
+ const isFilePath = isTraceFilePath(traceIdOrPath);
23972
+ const localTracePath = isFilePath ? traceIdOrPath : void 0;
23973
+ const remoteTraceId = fallbackTraceId || (!isFilePath ? traceIdOrPath : await readTraceIdFromFile(traceIdOrPath) || void 0);
23974
+ const preferLocalFirst = backendOrder[0] === "file";
23975
+ logger.debug(
23976
+ `[TraceSerializer] serializeTraceForPrompt ref=${traceIdOrPath} remoteTraceId=${remoteTraceId || "-"} backendOrder=${backendOrder.join(">")}`
23977
+ );
23978
+ if (preferLocalFirst && localTracePath) {
23979
+ logger.debug(`[TraceSerializer] Trying local trace file first: ${localTracePath}`);
23980
+ const spans = await fetchTraceSpans(localTracePath, {
23981
+ ...cfg,
23982
+ type: "file",
23983
+ targetTraceId: remoteTraceId
23984
+ });
23985
+ if (spans.length > 0) {
23986
+ return { spans, source: "file", remoteTraceId, localTracePath };
23987
+ }
23988
+ }
23989
+ if (remoteTraceId) {
23990
+ logger.debug(`[TraceSerializer] Trying remote trace backends for trace_id=${remoteTraceId}`);
23991
+ const remoteCfg = resolveBackendConfig(backendConfig);
23992
+ const remoteOrder = getBackendOrder(remoteCfg).filter((kind) => kind !== "file");
23993
+ for (const backend of remoteOrder) {
23994
+ if (backend === "grafana") {
23995
+ const spans = await fetchFromGrafanaTempo(remoteTraceId, remoteCfg);
23996
+ if (spans && spans.length > 0) {
23997
+ return { spans, source: "grafana", remoteTraceId, localTracePath };
23998
+ }
23999
+ continue;
24000
+ }
24001
+ if (backend === "jaeger") {
24002
+ const spans = await fetchFromJaeger(remoteTraceId, remoteCfg);
24003
+ if (spans && spans.length > 0) {
24004
+ return { spans, source: "jaeger", remoteTraceId, localTracePath };
24005
+ }
24006
+ }
24007
+ }
24008
+ }
24009
+ if (localTracePath) {
24010
+ logger.debug(`[TraceSerializer] Falling back to local trace file: ${localTracePath}`);
24011
+ const spans = await fetchTraceSpans(localTracePath, {
24012
+ ...cfg,
24013
+ type: "file",
24014
+ targetTraceId: remoteTraceId
24015
+ });
24016
+ if (spans.length > 0) {
24017
+ return { spans, source: "file", remoteTraceId, localTracePath };
24018
+ }
24019
+ }
24020
+ return { spans: [], source: null, remoteTraceId, localTracePath };
24021
+ }
23946
24022
  async function fetchFromGrafanaTempo(traceId, cfg) {
23947
24023
  let grafanaUrl = cfg.grafanaUrl;
23948
24024
  if (!grafanaUrl) {
@@ -24018,9 +24094,29 @@ async function fetchFromLocalFiles(traceRef, cfg) {
24018
24094
  const traceFile = isTraceFilePath(traceRef) ? traceRef : await findTraceFile(traceRef, cfg.traceDir);
24019
24095
  if (!traceFile) return null;
24020
24096
  try {
24021
- const { parseNDJSONTrace: parseNDJSONTrace2 } = await Promise.resolve().then(() => (init_trace_reader(), trace_reader_exports));
24022
- const trace2 = await parseNDJSONTrace2(traceFile);
24023
- return parseLocalNDJSONSpans(trace2.spans);
24097
+ const targetTraceId = cfg.targetTraceId || (!isTraceFilePath(traceRef) ? traceRef : await readTraceIdFromFile(traceFile) || void 0);
24098
+ const rawSpans = [];
24099
+ const rl = readline.createInterface({
24100
+ input: fs16.createReadStream(traceFile, { encoding: "utf8" }),
24101
+ crlfDelay: Infinity
24102
+ });
24103
+ try {
24104
+ for await (const line of rl) {
24105
+ const trimmed = line.trim();
24106
+ if (!trimmed) continue;
24107
+ try {
24108
+ const parsed = JSON.parse(trimmed);
24109
+ if (!targetTraceId || parsed.traceId === targetTraceId) {
24110
+ rawSpans.push(parsed);
24111
+ }
24112
+ } catch {
24113
+ }
24114
+ }
24115
+ } finally {
24116
+ rl.close();
24117
+ }
24118
+ if (rawSpans.length === 0) return [];
24119
+ return parseLocalNDJSONSpans(rawSpans);
24024
24120
  } catch (err) {
24025
24121
  logger.debug(`[TraceSerializer] Local file parse failed: ${err}`);
24026
24122
  return null;
@@ -24045,15 +24141,27 @@ async function httpGet(url, authToken, timeoutMs) {
24045
24141
  }
24046
24142
  async function findTraceFile(traceId, traceDir) {
24047
24143
  const dir = traceDir || process.env.VISOR_TRACE_DIR || "output/traces";
24048
- if (!fs17.existsSync(dir)) return null;
24049
- const files = fs17.readdirSync(dir).filter((f) => f.endsWith(".ndjson"));
24144
+ if (!fs16.existsSync(dir)) return null;
24145
+ const files = fs16.readdirSync(dir).filter((f) => f.endsWith(".ndjson"));
24050
24146
  for (const file of files) {
24051
24147
  const filePath = path19.join(dir, file);
24052
24148
  try {
24053
- const firstLine = await readFirstLine(filePath);
24054
- if (!firstLine) continue;
24055
- const parsed = JSON.parse(firstLine);
24056
- if (parsed.traceId === traceId) return filePath;
24149
+ const rl = readline.createInterface({
24150
+ input: fs16.createReadStream(filePath, { encoding: "utf8" }),
24151
+ crlfDelay: Infinity
24152
+ });
24153
+ try {
24154
+ for await (const line of rl) {
24155
+ if (!line.trim()) continue;
24156
+ const parsed = JSON.parse(line);
24157
+ if (parsed.traceId === traceId) {
24158
+ rl.close();
24159
+ return filePath;
24160
+ }
24161
+ }
24162
+ } finally {
24163
+ rl.close();
24164
+ }
24057
24165
  } catch {
24058
24166
  }
24059
24167
  }
@@ -24061,8 +24169,8 @@ async function findTraceFile(traceId, traceDir) {
24061
24169
  }
24062
24170
  async function readTraceIdFromFile(traceFile) {
24063
24171
  try {
24064
- const rl = readline2.createInterface({
24065
- input: fs17.createReadStream(traceFile, { encoding: "utf8" }),
24172
+ const rl = readline.createInterface({
24173
+ input: fs16.createReadStream(traceFile, { encoding: "utf8" }),
24066
24174
  crlfDelay: Infinity
24067
24175
  });
24068
24176
  for await (const line of rl) {
@@ -24082,25 +24190,6 @@ async function readTraceIdFromFile(traceFile) {
24082
24190
  }
24083
24191
  return null;
24084
24192
  }
24085
- async function readFirstLine(filePath) {
24086
- return new Promise((resolve17, reject) => {
24087
- const stream = fs17.createReadStream(filePath, { encoding: "utf-8" });
24088
- const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
24089
- let resolved = false;
24090
- rl.on("line", (line) => {
24091
- if (!resolved) {
24092
- resolved = true;
24093
- rl.close();
24094
- stream.destroy();
24095
- resolve17(line.trim() || null);
24096
- }
24097
- });
24098
- rl.on("close", () => {
24099
- if (!resolved) resolve17(null);
24100
- });
24101
- rl.on("error", reject);
24102
- });
24103
- }
24104
24193
  function isNoiseSpan(span) {
24105
24194
  return NOISE_SPAN_NAMES.has(span.name);
24106
24195
  }
@@ -24258,6 +24347,454 @@ function getLifecycleSpanInfo(name) {
24258
24347
  }
24259
24348
  return null;
24260
24349
  }
24350
+ function parseTaskStatusSnapshot(raw) {
24351
+ if (!raw || !raw.includes("<task_status>")) return [];
24352
+ const items = [];
24353
+ const taskRegex = /<task\s+id="([^"]+)"\s+status="([^"]+)"(?:\s+priority="[^"]*")?>([\s\S]*?)<\/task>/g;
24354
+ let match;
24355
+ while ((match = taskRegex.exec(raw)) !== null) {
24356
+ items.push({
24357
+ id: match[1],
24358
+ status: match[2],
24359
+ title: match[3].replace(/\s+/g, " ").trim()
24360
+ });
24361
+ }
24362
+ return items;
24363
+ }
24364
+ function parseTaskIds(value) {
24365
+ if (!value) return [];
24366
+ return String(value).split(",").map((part) => part.trim()).filter(Boolean);
24367
+ }
24368
+ function parseBatchItemsJson(value) {
24369
+ if (!value) return [];
24370
+ try {
24371
+ const parsed = JSON.parse(String(value));
24372
+ if (!Array.isArray(parsed)) return [];
24373
+ return parsed.filter((item) => item && typeof item.id === "string");
24374
+ } catch {
24375
+ return [];
24376
+ }
24377
+ }
24378
+ function isTaskTelemetrySpan(span) {
24379
+ const toolName = span.attributes["tool.name"] || span.attributes["visor.tool.name"];
24380
+ if (toolName === "task") return true;
24381
+ return /(?:^probe\.event\.)?task\.[a-z_]+$/.test(normalizeSpanName(span.name));
24382
+ }
24383
+ function summarizeTaskTelemetrySpans(spans) {
24384
+ const ordered = [...spans].sort((a, b) => {
24385
+ if (a.startTimeMs !== b.startTimeMs) return a.startTimeMs - b.startTimeMs;
24386
+ if (a.endTimeMs !== b.endTimeMs) return a.endTimeMs - b.endTimeMs;
24387
+ return a.spanId.localeCompare(b.spanId);
24388
+ });
24389
+ const tasks = /* @__PURE__ */ new Map();
24390
+ let tasksEnabled = false;
24391
+ let snapshotCount = 0;
24392
+ let eventCount = 0;
24393
+ let sawSnapshot = false;
24394
+ const upsertTask = (id, patch) => {
24395
+ const existing = tasks.get(id) || { id, title: "", status: "pending" };
24396
+ const next = {
24397
+ id,
24398
+ title: patch.title ?? existing.title,
24399
+ status: patch.status ?? existing.status
24400
+ };
24401
+ tasks.set(id, next);
24402
+ };
24403
+ for (const span of ordered) {
24404
+ const name = normalizeSpanName(span.name);
24405
+ const attrs = span.attributes;
24406
+ const toolName = attrs["tool.name"] || attrs["visor.tool.name"];
24407
+ if (toolName === "task") {
24408
+ const snapshot = parseTaskStatusSnapshot(String(attrs["tool.result"] || ""));
24409
+ if (snapshot.length > 0) {
24410
+ tasksEnabled = true;
24411
+ snapshotCount += 1;
24412
+ sawSnapshot = true;
24413
+ tasks.clear();
24414
+ for (const task of snapshot) {
24415
+ tasks.set(task.id, {
24416
+ id: task.id,
24417
+ title: task.title,
24418
+ status: task.status
24419
+ });
24420
+ }
24421
+ }
24422
+ }
24423
+ const taskEventMatch = name.match(/(?:^probe\.event\.)?task\.([a-z_]+)$/);
24424
+ if (!taskEventMatch) continue;
24425
+ tasksEnabled = true;
24426
+ eventCount += 1;
24427
+ const eventType = taskEventMatch[1];
24428
+ const taskId = attrs["task.id"] ? String(attrs["task.id"]) : "";
24429
+ const taskTitle = attrs["task.title"] ? String(attrs["task.title"]).trim() : "";
24430
+ const newStatus = attrs["task.new_status"] ? String(attrs["task.new_status"]) : "";
24431
+ const taskIds = parseTaskIds(attrs["task.ids"]);
24432
+ switch (eventType) {
24433
+ case "created":
24434
+ if (taskId) upsertTask(taskId, { title: taskTitle, status: "pending" });
24435
+ break;
24436
+ case "updated":
24437
+ if (taskId) {
24438
+ upsertTask(taskId, {
24439
+ title: taskTitle || void 0,
24440
+ status: newStatus && newStatus !== "unchanged" ? newStatus : void 0
24441
+ });
24442
+ }
24443
+ break;
24444
+ case "completed":
24445
+ if (taskId) upsertTask(taskId, { title: taskTitle || void 0, status: "completed" });
24446
+ break;
24447
+ case "deleted":
24448
+ if (taskId) upsertTask(taskId, { title: taskTitle || void 0, status: "deleted" });
24449
+ break;
24450
+ case "batch_created": {
24451
+ const items = parseBatchItemsJson(attrs["task.items_json"]);
24452
+ if (items.length > 0) {
24453
+ for (const item of items)
24454
+ upsertTask(item.id, { title: item.title || "", status: item.status || "pending" });
24455
+ } else {
24456
+ for (const id of taskIds) upsertTask(id, { status: "pending" });
24457
+ }
24458
+ break;
24459
+ }
24460
+ case "batch_updated": {
24461
+ const items = parseBatchItemsJson(attrs["task.items_json"]);
24462
+ if (items.length > 0) {
24463
+ for (const item of items)
24464
+ upsertTask(item.id, {
24465
+ title: item.title || void 0,
24466
+ status: item.status || void 0
24467
+ });
24468
+ } else {
24469
+ for (const id of taskIds) {
24470
+ upsertTask(id, {
24471
+ status: newStatus && newStatus !== "unchanged" ? newStatus : void 0
24472
+ });
24473
+ }
24474
+ }
24475
+ break;
24476
+ }
24477
+ case "batch_completed": {
24478
+ const items = parseBatchItemsJson(attrs["task.items_json"]);
24479
+ if (items.length > 0) {
24480
+ for (const item of items)
24481
+ upsertTask(item.id, { title: item.title || void 0, status: "completed" });
24482
+ } else {
24483
+ for (const id of taskIds) upsertTask(id, { status: "completed" });
24484
+ }
24485
+ break;
24486
+ }
24487
+ case "batch_deleted": {
24488
+ const items = parseBatchItemsJson(attrs["task.items_json"]);
24489
+ if (items.length > 0) {
24490
+ for (const item of items)
24491
+ upsertTask(item.id, { title: item.title || void 0, status: "deleted" });
24492
+ } else {
24493
+ for (const id of taskIds) upsertTask(id, { status: "deleted" });
24494
+ }
24495
+ break;
24496
+ }
24497
+ default:
24498
+ break;
24499
+ }
24500
+ }
24501
+ if (!tasksEnabled) return null;
24502
+ return {
24503
+ tasksEnabled,
24504
+ source: sawSnapshot ? "snapshot" : "events",
24505
+ eventCount,
24506
+ snapshotCount,
24507
+ tasks: [...tasks.values()].sort((a, b) => a.id.localeCompare(b.id)),
24508
+ scopes: []
24509
+ };
24510
+ }
24511
+ function titleCaseWords(raw) {
24512
+ return raw.split(/[\s_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
24513
+ }
24514
+ function getTaskScopeCheckId(span) {
24515
+ const concreteName = getConcreteCheckName(span);
24516
+ if (!concreteName) return null;
24517
+ return concreteName.replace(/^visor\.check\./, "");
24518
+ }
24519
+ function isTaskScopeNode(span) {
24520
+ const checkId = getTaskScopeCheckId(span);
24521
+ if (checkId) return !TASK_SCOPE_EXCLUDED_CHECKS.has(checkId);
24522
+ const normalized = normalizeSpanName(span.name);
24523
+ return normalized === "search.delegate" || normalized.startsWith("engineer.");
24524
+ }
24525
+ function getTaskScopeLabel(span) {
24526
+ if (!span) return ROOT_TASK_SCOPE_LABEL;
24527
+ const checkId = getTaskScopeCheckId(span);
24528
+ if (checkId) {
24529
+ if (checkId === "explore-code") return "Code Explorer";
24530
+ return titleCaseWords(checkId);
24531
+ }
24532
+ const normalized = normalizeSpanName(span.name);
24533
+ if (normalized === "search.delegate") return "Search Delegate";
24534
+ if (normalized.startsWith("engineer.")) {
24535
+ return `Engineer ${titleCaseWords(normalized.slice("engineer.".length))}`;
24536
+ }
24537
+ return titleCaseWords(normalized);
24538
+ }
24539
+ function collectTaskSpansForScope(node, stopAtNestedScopes) {
24540
+ const spans = [];
24541
+ const visit = (current, isRoot) => {
24542
+ if (!isRoot && stopAtNestedScopes && isTaskScopeNode(current.span)) return;
24543
+ if (isTaskTelemetrySpan(current.span)) spans.push(current.span);
24544
+ for (const child of current.children) visit(child, false);
24545
+ };
24546
+ visit(node, true);
24547
+ return spans;
24548
+ }
24549
+ function findImmediateNestedTaskScopeNodes(node) {
24550
+ const scopes = [];
24551
+ const visit = (current, isRoot) => {
24552
+ if (!isRoot && isTaskScopeNode(current.span)) {
24553
+ scopes.push(current);
24554
+ return;
24555
+ }
24556
+ for (const child of current.children) visit(child, false);
24557
+ };
24558
+ visit(node, true);
24559
+ return scopes;
24560
+ }
24561
+ function isSubAgentScope(span) {
24562
+ const checkId = getTaskScopeCheckId(span);
24563
+ if (checkId && SUB_AGENT_SCOPE_CHECKS.has(checkId)) return true;
24564
+ const normalized = normalizeSpanName(span.name);
24565
+ return normalized.startsWith("engineer.") || normalized === "search.delegate";
24566
+ }
24567
+ function getSubAgentStatus(node) {
24568
+ const normalized = normalizeSpanName(node.span.name);
24569
+ if (normalized.endsWith(".completed")) return "completed";
24570
+ if (normalized.endsWith(".started") || normalized.endsWith(".progress")) return "in_progress";
24571
+ let hasCompleted = false;
24572
+ let hasProgress = false;
24573
+ const visit = (current) => {
24574
+ const n = normalizeSpanName(current.span.name);
24575
+ if (n.endsWith(".completed")) hasCompleted = true;
24576
+ if (n.endsWith(".progress")) hasProgress = true;
24577
+ for (const child of current.children) visit(child);
24578
+ };
24579
+ visit(node);
24580
+ if (hasCompleted) return "completed";
24581
+ if (hasProgress) return "in_progress";
24582
+ return "pending";
24583
+ }
24584
+ function buildProbeTaskScopeSummary(node, label, isRootScope = false, allSpans) {
24585
+ const nestedScopeNodes = findImmediateNestedTaskScopeNodes(node);
24586
+ if (!isRootScope && isSubAgentScope(node.span)) {
24587
+ const localTasks = summarizeTaskTelemetrySpans(collectTaskSpansForScope(node, true));
24588
+ const hasMeaningfulTitles = localTasks && localTasks.tasks.some(
24589
+ (t) => t.title && t.title.length > 3 && !/^(task|engineer|code.?explorer)\s*/i.test(t.title)
24590
+ );
24591
+ if (hasMeaningfulTitles) {
24592
+ return {
24593
+ label: isRootScope ? ROOT_TASK_SCOPE_LABEL : label,
24594
+ source: localTasks.source,
24595
+ eventCount: localTasks.eventCount,
24596
+ snapshotCount: localTasks.snapshotCount,
24597
+ tasks: localTasks.tasks,
24598
+ children: []
24599
+ };
24600
+ }
24601
+ const status = getSubAgentStatus(node);
24602
+ const scopeLabel = getTaskScopeLabel(node.span);
24603
+ return {
24604
+ label: isRootScope ? ROOT_TASK_SCOPE_LABEL : label,
24605
+ source: "events",
24606
+ eventCount: 0,
24607
+ snapshotCount: 0,
24608
+ tasks: [{ id: `__scope_${node.span.spanId}`, title: scopeLabel, status, synthetic: true }],
24609
+ children: []
24610
+ };
24611
+ }
24612
+ const local = summarizeTaskTelemetrySpans(collectTaskSpansForScope(node, true));
24613
+ const children = [];
24614
+ const subAgentGroups = /* @__PURE__ */ new Map();
24615
+ const nonSubAgentNodes = [];
24616
+ for (const child of nestedScopeNodes) {
24617
+ if (isSubAgentScope(child.span)) {
24618
+ const checkId = getTaskScopeCheckId(child.span) || normalizeSpanName(child.span.name);
24619
+ const group = subAgentGroups.get(checkId) || [];
24620
+ group.push(child);
24621
+ subAgentGroups.set(checkId, group);
24622
+ } else {
24623
+ nonSubAgentNodes.push(child);
24624
+ }
24625
+ }
24626
+ for (const child of nonSubAgentNodes) {
24627
+ const childScope = buildProbeTaskScopeSummary(
24628
+ child,
24629
+ getTaskScopeLabel(child.span),
24630
+ false,
24631
+ allSpans
24632
+ );
24633
+ if (childScope) children.push(childScope);
24634
+ }
24635
+ for (const [checkId, nodes] of subAgentGroups) {
24636
+ const scopeLabel = checkId === "engineer-task" ? "Engineer" : checkId === "explore-code" ? "Code Explorer" : titleCaseWords(checkId);
24637
+ const tasks = [];
24638
+ for (const n of nodes) {
24639
+ const iterScope = buildProbeTaskScopeSummary(n, scopeLabel, false, allSpans);
24640
+ if (iterScope && iterScope.tasks.length > 0) {
24641
+ tasks.push(...iterScope.tasks);
24642
+ } else {
24643
+ const status = getSubAgentStatus(n);
24644
+ tasks.push({
24645
+ id: `__scope_${n.span.spanId}`,
24646
+ title: scopeLabel,
24647
+ status,
24648
+ synthetic: true
24649
+ });
24650
+ }
24651
+ }
24652
+ const dedupedTasks = [];
24653
+ for (const task of tasks) {
24654
+ const prev = dedupedTasks[dedupedTasks.length - 1];
24655
+ if (prev && prev.title === task.title && prev.status === task.status) continue;
24656
+ dedupedTasks.push(task);
24657
+ }
24658
+ children.push({
24659
+ label: scopeLabel,
24660
+ source: "events",
24661
+ eventCount: nodes.length,
24662
+ snapshotCount: 0,
24663
+ tasks: dedupedTasks,
24664
+ children: []
24665
+ });
24666
+ }
24667
+ if (!local && children.length === 0) return null;
24668
+ return {
24669
+ label: isRootScope ? ROOT_TASK_SCOPE_LABEL : label,
24670
+ source: local?.source ?? "events",
24671
+ eventCount: local?.eventCount ?? 0,
24672
+ snapshotCount: local?.snapshotCount ?? 0,
24673
+ tasks: local?.tasks ?? [],
24674
+ children
24675
+ };
24676
+ }
24677
+ function buildTaskSummaryRoot(spans) {
24678
+ const nodeMap = /* @__PURE__ */ new Map();
24679
+ for (const span of spans) {
24680
+ nodeMap.set(span.spanId, { span, children: [] });
24681
+ }
24682
+ const roots = [];
24683
+ for (const span of spans) {
24684
+ const node = nodeMap.get(span.spanId);
24685
+ if (span.parentSpanId && nodeMap.has(span.parentSpanId)) {
24686
+ nodeMap.get(span.parentSpanId).children.push(node);
24687
+ } else {
24688
+ roots.push(node);
24689
+ }
24690
+ }
24691
+ const sortChildren = (node) => {
24692
+ node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
24693
+ node.children.forEach(sortChildren);
24694
+ };
24695
+ roots.forEach(sortChildren);
24696
+ return {
24697
+ span: {
24698
+ traceId: spans[0]?.traceId || "",
24699
+ spanId: "__task-root__",
24700
+ name: "__task_root__",
24701
+ startTimeMs: roots[0]?.span.startTimeMs || 0,
24702
+ endTimeMs: roots[roots.length - 1]?.span.endTimeMs || 0,
24703
+ durationMs: 0,
24704
+ attributes: {},
24705
+ events: [],
24706
+ status: "ok"
24707
+ },
24708
+ children: roots
24709
+ };
24710
+ }
24711
+ function flattenTaskScopes(scopes) {
24712
+ const seen = /* @__PURE__ */ new Set();
24713
+ const items = [];
24714
+ const visit = (scope) => {
24715
+ for (const task of scope.tasks) {
24716
+ if (task.synthetic) continue;
24717
+ if (seen.has(task.id)) continue;
24718
+ seen.add(task.id);
24719
+ items.push(task);
24720
+ }
24721
+ for (const child of scope.children) visit(child);
24722
+ };
24723
+ for (const scope of scopes) visit(scope);
24724
+ return items;
24725
+ }
24726
+ function buildTemporalTaskScopes(spans) {
24727
+ const ordered = [...spans].sort((a, b) => {
24728
+ if (a.startTimeMs !== b.startTimeMs) return a.startTimeMs - b.startTimeMs;
24729
+ if (a.endTimeMs !== b.endTimeMs) return a.endTimeMs - b.endTimeMs;
24730
+ return a.spanId.localeCompare(b.spanId);
24731
+ });
24732
+ const grouped = /* @__PURE__ */ new Map();
24733
+ const scopeOrder = [];
24734
+ const ensureGroup = (label) => {
24735
+ if (!grouped.has(label)) {
24736
+ grouped.set(label, []);
24737
+ scopeOrder.push(label);
24738
+ }
24739
+ return grouped.get(label);
24740
+ };
24741
+ for (let i = 0; i < ordered.length; i += 1) {
24742
+ const span = ordered[i];
24743
+ if (!isTaskTelemetrySpan(span)) continue;
24744
+ let label = ROOT_TASK_SCOPE_LABEL;
24745
+ for (let j = i - 1; j >= 0; j -= 1) {
24746
+ if (isTaskScopeNode(ordered[j])) {
24747
+ label = getTaskScopeLabel(ordered[j]);
24748
+ break;
24749
+ }
24750
+ }
24751
+ ensureGroup(label).push(span);
24752
+ }
24753
+ if (grouped.size === 0) return [];
24754
+ const rootSummary = summarizeTaskTelemetrySpans(grouped.get(ROOT_TASK_SCOPE_LABEL) || []);
24755
+ const children = [];
24756
+ for (const label of scopeOrder) {
24757
+ if (label === ROOT_TASK_SCOPE_LABEL) continue;
24758
+ const summary = summarizeTaskTelemetrySpans(grouped.get(label) || []);
24759
+ if (!summary) continue;
24760
+ children.push({
24761
+ label,
24762
+ source: summary.source,
24763
+ eventCount: summary.eventCount,
24764
+ snapshotCount: summary.snapshotCount,
24765
+ tasks: summary.tasks,
24766
+ children: []
24767
+ });
24768
+ }
24769
+ return [
24770
+ {
24771
+ label: ROOT_TASK_SCOPE_LABEL,
24772
+ source: rootSummary?.source ?? "events",
24773
+ eventCount: rootSummary?.eventCount ?? 0,
24774
+ snapshotCount: rootSummary?.snapshotCount ?? 0,
24775
+ tasks: rootSummary?.tasks ?? [],
24776
+ children
24777
+ }
24778
+ ];
24779
+ }
24780
+ function formatTaskStatusMarker(status) {
24781
+ switch (status) {
24782
+ case "completed":
24783
+ return "[x]";
24784
+ case "in_progress":
24785
+ return "[~]";
24786
+ case "blocked":
24787
+ case "failed":
24788
+ case "error":
24789
+ return "[!]";
24790
+ case "cancelled":
24791
+ case "canceled":
24792
+ case "deleted":
24793
+ return "[-]";
24794
+ default:
24795
+ return "[ ]";
24796
+ }
24797
+ }
24261
24798
  function buildRenderContext(allSpans) {
24262
24799
  const realSpanNames = /* @__PURE__ */ new Set();
24263
24800
  let hasChildWorkSpans = false;
@@ -24284,28 +24821,7 @@ function shouldSkipLifecycleSpan(span, renderContext) {
24284
24821
  return renderContext.realSpanNames.has(lifecycle.baseName);
24285
24822
  }
24286
24823
  async function serializeTraceForPrompt(traceIdOrPath, maxChars, backendConfig, taskResponse, fallbackTraceId) {
24287
- let spans = [];
24288
- const cfg = resolveBackendConfig(backendConfig);
24289
- const backendOrder = getBackendOrder(cfg);
24290
- const isFilePath = isTraceFilePath(traceIdOrPath);
24291
- const localTracePath = isFilePath ? traceIdOrPath : void 0;
24292
- const remoteTraceId = fallbackTraceId || (!isFilePath ? traceIdOrPath : await readTraceIdFromFile(traceIdOrPath) || void 0);
24293
- const preferLocalFirst = backendOrder[0] === "file";
24294
- logger.debug(
24295
- `[TraceSerializer] serializeTraceForPrompt ref=${traceIdOrPath} remoteTraceId=${remoteTraceId || "-"} backendOrder=${backendOrder.join(">")}`
24296
- );
24297
- if (preferLocalFirst && localTracePath) {
24298
- logger.debug(`[TraceSerializer] Trying local trace file first: ${localTracePath}`);
24299
- spans = await fetchTraceSpans(localTracePath, { ...cfg, type: "file" });
24300
- }
24301
- if (spans.length === 0 && remoteTraceId) {
24302
- logger.debug(`[TraceSerializer] Trying remote trace backends for trace_id=${remoteTraceId}`);
24303
- spans = await fetchTraceSpans(remoteTraceId, cfg);
24304
- }
24305
- if (spans.length === 0 && localTracePath) {
24306
- logger.debug(`[TraceSerializer] Falling back to local trace file: ${localTracePath}`);
24307
- spans = await fetchTraceSpans(localTracePath, { ...cfg, type: "file" });
24308
- }
24824
+ const { spans } = await resolveTraceSpans(traceIdOrPath, backendConfig, fallbackTraceId);
24309
24825
  if (spans.length === 0) {
24310
24826
  return "(no trace data available)";
24311
24827
  }
@@ -24319,6 +24835,101 @@ async function serializeTraceForPrompt(traceIdOrPath, maxChars, backendConfig, t
24319
24835
  taskResponse
24320
24836
  });
24321
24837
  }
24838
+ function renderTraceTreeFromSpans(spans, maxChars, taskResponse) {
24839
+ const tree = buildSpanTree(spans);
24840
+ const routeIntentTopic = extractRouteIntentTopic(spans);
24841
+ const fullOutput = (maxChars ?? 4e3) > 1e5;
24842
+ return renderSpanYaml(tree, spans, {
24843
+ maxChars: maxChars ?? 4e3,
24844
+ fallbackIntent: routeIntentTopic,
24845
+ fullOutput,
24846
+ taskResponse
24847
+ });
24848
+ }
24849
+ function formatTaskStatus(status) {
24850
+ return status === "completed" ? "[x]" : status === "in_progress" ? "[~]" : status === "blocked" || status === "failed" || status === "error" ? "[!]" : status === "deleted" || status === "cancelled" || status === "canceled" ? "[-]" : "[ ]";
24851
+ }
24852
+ function formatTaskLabel(task) {
24853
+ const title = String(task.title || "").trim();
24854
+ return title || task.id || task.status;
24855
+ }
24856
+ function scopeHasRenderableTasks(scope) {
24857
+ if (scope.tasks.some((task) => !task.synthetic)) return true;
24858
+ return scope.children.some((child) => scopeHasRenderableTasks(child));
24859
+ }
24860
+ function appendTaskScopeLines(lines, scope, depth = 0) {
24861
+ if (!scopeHasRenderableTasks(scope)) return;
24862
+ const prefix = " ".repeat(depth);
24863
+ if (depth > 0 || scope.label !== ROOT_TASK_SCOPE_LABEL) {
24864
+ lines.push(`${prefix}${scope.label}`);
24865
+ }
24866
+ for (const task of scope.tasks.filter((task2) => !task2.synthetic)) {
24867
+ lines.push(`${prefix} ${formatTaskStatus(task.status)} ${formatTaskLabel(task)}`);
24868
+ }
24869
+ for (const child of scope.children) {
24870
+ appendTaskScopeLines(lines, child, depth + 1);
24871
+ }
24872
+ }
24873
+ function buildTraceHeaderLines(traceData, taskSummary) {
24874
+ const lines = [`Trace source: ${traceData.source || "unknown"}`];
24875
+ if (!taskSummary || taskSummary.tasks.length === 0) {
24876
+ lines.push("Tasks: no task telemetry found in this trace");
24877
+ return lines;
24878
+ }
24879
+ const eventsSuffix = taskSummary.snapshotCount > 0 ? `, ${taskSummary.eventCount} task events, ${taskSummary.snapshotCount} snapshots` : `, ${taskSummary.eventCount} task events`;
24880
+ lines.push(`Tasks: ${taskSummary.tasks.length} tracked (${taskSummary.source}${eventsSuffix})`);
24881
+ if (taskSummary.scopes.length > 0) {
24882
+ for (const scope of taskSummary.scopes) {
24883
+ appendTaskScopeLines(lines, scope);
24884
+ }
24885
+ } else {
24886
+ for (const task of taskSummary.tasks) {
24887
+ lines.push(` ${formatTaskStatus(task.status)} ${formatTaskLabel(task)}`);
24888
+ }
24889
+ }
24890
+ return lines;
24891
+ }
24892
+ async function buildTraceReport(traceIdOrPath, maxChars, backendConfig, taskResponse, fallbackTraceId) {
24893
+ const traceData = await resolveTraceSpans(traceIdOrPath, backendConfig, fallbackTraceId);
24894
+ if (traceData.spans.length === 0) {
24895
+ return null;
24896
+ }
24897
+ const taskSummary = extractProbeTaskSummary(traceData.spans);
24898
+ const headerLines = buildTraceHeaderLines(traceData, taskSummary);
24899
+ const headerText = headerLines.join("\n");
24900
+ const tree = renderTraceTreeFromSpans(traceData.spans, maxChars, taskResponse);
24901
+ return {
24902
+ traceData,
24903
+ tree,
24904
+ taskSummary,
24905
+ headerText,
24906
+ text: `${headerText}
24907
+
24908
+ ${tree}`
24909
+ };
24910
+ }
24911
+ function extractProbeTaskSummary(spans) {
24912
+ const overall = summarizeTaskTelemetrySpans(spans);
24913
+ if (!overall) return null;
24914
+ const rootScope = buildProbeTaskScopeSummary(
24915
+ buildTaskSummaryRoot(spans),
24916
+ ROOT_TASK_SCOPE_LABEL,
24917
+ true,
24918
+ spans
24919
+ );
24920
+ let scopes = rootScope ? [rootScope] : [];
24921
+ if (scopes.length === 0 || scopes[0].children.length === 0) {
24922
+ const temporalScopes = buildTemporalTaskScopes(spans);
24923
+ if (temporalScopes.length > 0 && temporalScopes[0].children.length > 0) {
24924
+ scopes = temporalScopes;
24925
+ }
24926
+ }
24927
+ return {
24928
+ ...overall,
24929
+ tasks: scopes.length > 0 ? flattenTaskScopes(scopes) : overall.tasks,
24930
+ scopes
24931
+ };
24932
+ }
24322
24933
  function renderSpanTree(tree, opts) {
24323
24934
  const maxChars = opts?.maxChars ?? 4e3;
24324
24935
  const maxDepth = opts?.maxDepth ?? 20;
@@ -24446,6 +25057,19 @@ function renderYamlNode(node, indent, lines, dedup, renderContext, fallbackInten
24446
25057
  const toolInput = extractToolInput(String(toolName), attrs);
24447
25058
  const toolResultLen = attrs["tool.result.length"] || attrs["tool.result.count"];
24448
25059
  const tn = String(toolName);
25060
+ const taskSnapshot = tn === "task" ? parseTaskStatusSnapshot(String(attrs["tool.result"] || "")) : [];
25061
+ if (tn === "task" && taskSnapshot.length > 0) {
25062
+ lines.push(`${pad}tasks snapshot${childSuffix}:`);
25063
+ for (const task of taskSnapshot) {
25064
+ lines.push(
25065
+ `${pad} ${formatTaskStatusMarker(task.status)} ${task.id}: ${truncate(task.title, 100)}`
25066
+ );
25067
+ }
25068
+ return;
25069
+ }
25070
+ if (tn === "task") {
25071
+ return;
25072
+ }
24449
25073
  const isSearchTool = tn === "search" || tn === "searchCode" || tn === "search_code";
24450
25074
  const numLen = toolResultLen ? Number(toolResultLen) : -1;
24451
25075
  const noResults = isSearchTool && numLen >= 0 && numLen < 500;
@@ -24459,10 +25083,12 @@ function renderYamlNode(node, indent, lines, dedup, renderContext, fallbackInten
24459
25083
  const action = attrs["dedup.action"] || "?";
24460
25084
  const reason = attrs["dedup.reason"] || "";
24461
25085
  const rewritten = attrs["dedup.rewritten"] || "";
25086
+ const error = attrs["dedup.error"] || "";
24462
25087
  const prevCount = attrs["dedup.previous_count"] || "0";
24463
25088
  let detail = `${action}`;
24464
25089
  if (rewritten) detail += ` \u2192 "${truncate(String(rewritten), 60)}"`;
24465
25090
  if (reason) detail += ` (${truncate(String(reason), 80)})`;
25091
+ if (error) detail += ` [error: ${truncate(String(error), 120)}]`;
24466
25092
  lines.push(
24467
25093
  `${pad}dedup("${truncate(String(query), 60)}") [${prevCount} prior]: ${detail} \u2014 ${duration}`
24468
25094
  );
@@ -24656,6 +25282,86 @@ function renderYamlNode(node, indent, lines, dedup, renderContext, fallbackInten
24656
25282
  }
24657
25283
  return;
24658
25284
  }
25285
+ const taskEventMatch = name.match(/(?:^probe\.event\.)?task\.([a-z_]+)$/);
25286
+ if (taskEventMatch) {
25287
+ const eventType = taskEventMatch[1];
25288
+ const taskId = attrs["task.id"] ? String(attrs["task.id"]) : "";
25289
+ const taskTitle = attrs["task.title"] ? truncate(String(attrs["task.title"]), 80) : "";
25290
+ const taskIds = attrs["task.ids"] ? truncate(String(attrs["task.ids"]), 120) : "";
25291
+ const count = attrs["task.count"] ? Number(attrs["task.count"]) : void 0;
25292
+ const totalCount = attrs["task.total_count"] ? Number(attrs["task.total_count"]) : void 0;
25293
+ const incompleteCount = attrs["task.incomplete_count"] ? Number(attrs["task.incomplete_count"]) : void 0;
25294
+ const completedCount = attrs["task.completed_count"] ? Number(attrs["task.completed_count"]) : void 0;
25295
+ const remaining = attrs["task.incomplete_remaining"] ? Number(attrs["task.incomplete_remaining"]) : void 0;
25296
+ const newStatus = attrs["task.new_status"] ? String(attrs["task.new_status"]) : "";
25297
+ const fieldsUpdated = attrs["task.fields_updated"] ? truncate(String(attrs["task.fields_updated"]), 80) : "";
25298
+ const error = attrs["task.error"] ? truncate(String(attrs["task.error"]), 100) : "";
25299
+ const action = attrs["task.action"] ? String(attrs["task.action"]) : "";
25300
+ switch (eventType) {
25301
+ case "session_started":
25302
+ lines.push(`${pad}tasks enabled \u2014 ${duration}`);
25303
+ return;
25304
+ case "created":
25305
+ lines.push(`${pad}[ ] ${taskId}${taskTitle ? `: ${taskTitle}` : ""} \u2014 ${duration}`);
25306
+ return;
25307
+ case "batch_created": {
25308
+ const totalSuffix = totalCount !== void 0 ? `, total ${totalCount}` : "";
25309
+ lines.push(
25310
+ `${pad}tasks created (${count ?? "?"}${totalSuffix}): ${taskIds || "unknown"} \u2014 ${duration}`
25311
+ );
25312
+ return;
25313
+ }
25314
+ case "updated": {
25315
+ const statusSuffix = newStatus && newStatus !== "unchanged" ? ` -> ${newStatus}` : "";
25316
+ const fieldSuffix = fieldsUpdated ? ` [${fieldsUpdated}]` : "";
25317
+ lines.push(`${pad}task updated: ${taskId}${statusSuffix}${fieldSuffix} \u2014 ${duration}`);
25318
+ return;
25319
+ }
25320
+ case "batch_updated":
25321
+ lines.push(`${pad}tasks updated (${count ?? "?"}): ${taskIds || "unknown"} \u2014 ${duration}`);
25322
+ return;
25323
+ case "completed": {
25324
+ const remainingSuffix = remaining !== void 0 ? ` (${remaining} remaining)` : "";
25325
+ lines.push(
25326
+ `${pad}[x] ${taskId}${taskTitle ? `: ${taskTitle}` : ""}${remainingSuffix} \u2014 ${duration}`
25327
+ );
25328
+ return;
25329
+ }
25330
+ case "batch_completed": {
25331
+ const remainingSuffix = remaining !== void 0 ? `, ${remaining} remaining` : "";
25332
+ lines.push(
25333
+ `${pad}tasks completed (${count ?? "?"}${remainingSuffix}): ${taskIds || "unknown"} \u2014 ${duration}`
25334
+ );
25335
+ return;
25336
+ }
25337
+ case "deleted":
25338
+ lines.push(`${pad}task deleted: ${taskId} \u2014 ${duration}`);
25339
+ return;
25340
+ case "batch_deleted":
25341
+ lines.push(`${pad}tasks deleted (${count ?? "?"}): ${taskIds || "unknown"} \u2014 ${duration}`);
25342
+ return;
25343
+ case "listed": {
25344
+ const stats = [];
25345
+ if (totalCount !== void 0) stats.push(`${totalCount} total`);
25346
+ if (incompleteCount !== void 0) stats.push(`${incompleteCount} incomplete`);
25347
+ if (completedCount !== void 0) stats.push(`${completedCount} completed`);
25348
+ lines.push(
25349
+ `${pad}tasks listed${stats.length > 0 ? ` (${stats.join(", ")})` : ""} \u2014 ${duration}`
25350
+ );
25351
+ return;
25352
+ }
25353
+ case "validation_error":
25354
+ case "error":
25355
+ lines.push(`${pad}task.${eventType}: ${error || "unknown error"} \u2014 ${duration}`);
25356
+ return;
25357
+ case "unknown_action":
25358
+ lines.push(`${pad}task.unknown_action: ${action || "unknown"} \u2014 ${duration}`);
25359
+ return;
25360
+ default:
25361
+ lines.push(`${pad}task.${eventType}${taskId ? `: ${taskId}` : ""} \u2014 ${duration}`);
25362
+ return;
25363
+ }
25364
+ }
24659
25365
  const errMark = node.span.status === "error" ? " \u2717" : "";
24660
25366
  const hasChildren = node.children.length > 0;
24661
25367
  lines.push(`${pad}${name}${childSuffix} \u2014 ${duration}${errMark}${hasChildren ? ":" : ""}`);
@@ -24935,9 +25641,11 @@ function formatSpanLine(span, parentSpan, fallbackIntent, fullOutput, dedup, has
24935
25641
  const action = attrs["dedup.action"] || "?";
24936
25642
  const reason = attrs["dedup.reason"] || "";
24937
25643
  const rewritten = attrs["dedup.rewritten"] || "";
25644
+ const error = attrs["dedup.error"] || "";
24938
25645
  let detail = `${action}`;
24939
25646
  if (rewritten) detail += ` \u2192 "${truncate(String(rewritten), 50)}"`;
24940
25647
  if (reason) detail += ` \u2014 ${truncate(String(reason), 60)}`;
25648
+ if (error) detail += ` [error: ${truncate(String(error), 80)}]`;
24941
25649
  return { line: `dedup("${truncate(String(query), 50)}") ${detail} (${duration})` };
24942
25650
  }
24943
25651
  if (name === "search.delegate") {
@@ -25122,6 +25830,72 @@ function formatSpanLine(span, parentSpan, fallbackIntent, fullOutput, dedup, has
25122
25830
  const reasonStr = reason ? `: ${truncate(String(reason), 80)}` : "";
25123
25831
  return { line: `graceful_stop${reasonStr} (${duration})` };
25124
25832
  }
25833
+ const taskEventMatch = name.match(/(?:^probe\.event\.)?task\.([a-z_]+)$/);
25834
+ if (taskEventMatch) {
25835
+ const eventType = taskEventMatch[1];
25836
+ const taskId = attrs["task.id"] ? String(attrs["task.id"]) : "";
25837
+ const taskTitle = attrs["task.title"] ? truncate(String(attrs["task.title"]), 80) : "";
25838
+ const taskIds = attrs["task.ids"] ? truncate(String(attrs["task.ids"]), 120) : "";
25839
+ const count = attrs["task.count"] ? Number(attrs["task.count"]) : void 0;
25840
+ const totalCount = attrs["task.total_count"] ? Number(attrs["task.total_count"]) : void 0;
25841
+ const incompleteCount = attrs["task.incomplete_count"] ? Number(attrs["task.incomplete_count"]) : void 0;
25842
+ const completedCount = attrs["task.completed_count"] ? Number(attrs["task.completed_count"]) : void 0;
25843
+ const remaining = attrs["task.incomplete_remaining"] ? Number(attrs["task.incomplete_remaining"]) : void 0;
25844
+ const newStatus = attrs["task.new_status"] ? String(attrs["task.new_status"]) : "";
25845
+ const fieldsUpdated = attrs["task.fields_updated"] ? truncate(String(attrs["task.fields_updated"]), 80) : "";
25846
+ const error = attrs["task.error"] ? truncate(String(attrs["task.error"]), 100) : "";
25847
+ const action = attrs["task.action"] ? String(attrs["task.action"]) : "";
25848
+ const tail = ` (${duration})`;
25849
+ switch (eventType) {
25850
+ case "session_started":
25851
+ return { line: `tasks enabled${tail}` };
25852
+ case "created":
25853
+ return { line: `[ ] ${taskId}${taskTitle ? `: ${taskTitle}` : ""}${tail}` };
25854
+ case "batch_created": {
25855
+ const totalSuffix = totalCount !== void 0 ? `, total ${totalCount}` : "";
25856
+ return {
25857
+ line: `tasks created (${count ?? "?"}${totalSuffix}): ${taskIds || "unknown"}${tail}`
25858
+ };
25859
+ }
25860
+ case "updated": {
25861
+ const statusSuffix = newStatus && newStatus !== "unchanged" ? ` -> ${newStatus}` : "";
25862
+ const fieldSuffix = fieldsUpdated ? ` [${fieldsUpdated}]` : "";
25863
+ return { line: `task updated: ${taskId}${statusSuffix}${fieldSuffix}${tail}` };
25864
+ }
25865
+ case "batch_updated":
25866
+ return { line: `tasks updated (${count ?? "?"}): ${taskIds || "unknown"}${tail}` };
25867
+ case "completed": {
25868
+ const remainingSuffix = remaining !== void 0 ? ` (${remaining} remaining)` : "";
25869
+ return {
25870
+ line: `[x] ${taskId}${taskTitle ? `: ${taskTitle}` : ""}${remainingSuffix}${tail}`
25871
+ };
25872
+ }
25873
+ case "batch_completed": {
25874
+ const remainingSuffix = remaining !== void 0 ? `, ${remaining} remaining` : "";
25875
+ return {
25876
+ line: `tasks completed (${count ?? "?"}${remainingSuffix}): ${taskIds || "unknown"}${tail}`
25877
+ };
25878
+ }
25879
+ case "deleted":
25880
+ return { line: `task deleted: ${taskId}${tail}` };
25881
+ case "batch_deleted":
25882
+ return { line: `tasks deleted (${count ?? "?"}): ${taskIds || "unknown"}${tail}` };
25883
+ case "listed": {
25884
+ const stats = [];
25885
+ if (totalCount !== void 0) stats.push(`${totalCount} total`);
25886
+ if (incompleteCount !== void 0) stats.push(`${incompleteCount} incomplete`);
25887
+ if (completedCount !== void 0) stats.push(`${completedCount} completed`);
25888
+ return { line: `tasks listed${stats.length > 0 ? ` (${stats.join(", ")})` : ""}${tail}` };
25889
+ }
25890
+ case "validation_error":
25891
+ case "error":
25892
+ return { line: `task.${eventType}: ${error || "unknown error"}${tail}` };
25893
+ case "unknown_action":
25894
+ return { line: `task.unknown_action: ${action || "unknown"}${tail}` };
25895
+ default:
25896
+ return { line: `task.${eventType}${taskId ? `: ${taskId}` : ""}${tail}` };
25897
+ }
25898
+ }
25125
25899
  return { line: `${name} (${duration})${span.status === "error" ? " \u2717" : ""}` };
25126
25900
  }
25127
25901
  function parseWorkspacePath(fullPath) {
@@ -25457,12 +26231,12 @@ function truncate(str, max) {
25457
26231
  if (head < 10) return str.slice(0, max - 3) + "...";
25458
26232
  return str.slice(0, head) + " ...[truncated]... " + str.slice(-tail);
25459
26233
  }
25460
- var fs17, readline2, path19, NOISE_SPAN_NAMES, WRAPPER_SPAN_NAMES;
26234
+ var fs16, readline, path19, NOISE_SPAN_NAMES, WRAPPER_SPAN_NAMES, ROOT_TASK_SCOPE_LABEL, TASK_SCOPE_EXCLUDED_CHECKS, SUB_AGENT_SCOPE_CHECKS;
25461
26235
  var init_trace_serializer = __esm({
25462
26236
  "src/agent-protocol/trace-serializer.ts"() {
25463
26237
  "use strict";
25464
- fs17 = __toESM(require("fs"));
25465
- readline2 = __toESM(require("readline"));
26238
+ fs16 = __toESM(require("fs"));
26239
+ readline = __toESM(require("readline"));
25466
26240
  path19 = __toESM(require("path"));
25467
26241
  init_logger();
25468
26242
  NOISE_SPAN_NAMES = /* @__PURE__ */ new Set([
@@ -25476,6 +26250,20 @@ var init_trace_serializer = __esm({
25476
26250
  "visor.ai_check",
25477
26251
  "probe.delegation"
25478
26252
  ]);
26253
+ ROOT_TASK_SCOPE_LABEL = "Main Agent";
26254
+ TASK_SCOPE_EXCLUDED_CHECKS = /* @__PURE__ */ new Set([
26255
+ "chat",
26256
+ "route-intent",
26257
+ "classify",
26258
+ "build-config",
26259
+ "generate-response",
26260
+ "setup-projects",
26261
+ "checkout-docs",
26262
+ "route-projects",
26263
+ "project-items",
26264
+ "checkout-projects"
26265
+ ]);
26266
+ SUB_AGENT_SCOPE_CHECKS = /* @__PURE__ */ new Set(["engineer-task", "explore-code"]);
25479
26267
  }
25480
26268
  });
25481
26269
 
@@ -25603,6 +26391,67 @@ function formatProgressMetadata(timing) {
25603
26391
  }
25604
26392
  return `_Metadata: ${parts.join(" | ")}_`;
25605
26393
  }
26394
+ function formatTaskStatusMarker2(status) {
26395
+ switch (status) {
26396
+ case "completed":
26397
+ return "[x]";
26398
+ case "in_progress":
26399
+ return "[~]";
26400
+ case "blocked":
26401
+ case "failed":
26402
+ case "error":
26403
+ return "[!]";
26404
+ case "deleted":
26405
+ case "cancelled":
26406
+ case "canceled":
26407
+ return "[-]";
26408
+ default:
26409
+ return "[ ]";
26410
+ }
26411
+ }
26412
+ function formatTaskLabel2(task) {
26413
+ const title = String(task.title || "").trim();
26414
+ return title || task.id || task.status;
26415
+ }
26416
+ function scopeHasRenderableTasks2(scope) {
26417
+ if (scope.tasks.some((task) => !task.synthetic)) return true;
26418
+ return scope.children.some((child) => scopeHasRenderableTasks2(child));
26419
+ }
26420
+ function appendTaskScopeLines2(lines, scope, depth = 0) {
26421
+ if (!scopeHasRenderableTasks2(scope)) return;
26422
+ const prefix = " ".repeat(depth);
26423
+ if (depth > 0 || scope.label !== "Main Agent") {
26424
+ lines.push(`${prefix}${scope.label}`);
26425
+ }
26426
+ const visibleTasks = scope.tasks.filter((task) => !task.synthetic).slice(0, 8);
26427
+ for (const task of visibleTasks) {
26428
+ lines.push(`${prefix}\u2022 ${formatTaskStatusMarker2(task.status)} ${formatTaskLabel2(task)}`);
26429
+ }
26430
+ const hiddenCount = scope.tasks.filter((task) => !task.synthetic).length - visibleTasks.length;
26431
+ if (hiddenCount > 0) {
26432
+ lines.push(`${prefix}\u2022 ... ${hiddenCount} more`);
26433
+ }
26434
+ for (const child of scope.children) {
26435
+ appendTaskScopeLines2(lines, child, depth + 1);
26436
+ }
26437
+ }
26438
+ function formatTaskSummary(summary) {
26439
+ if (!summary || summary.tasks.length === 0) return void 0;
26440
+ const lines = ["*Tasks*"];
26441
+ if (summary.scopes.length > 0) {
26442
+ for (const scope of summary.scopes) appendTaskScopeLines2(lines, scope);
26443
+ } else {
26444
+ const visibleTasks = summary.tasks.filter((task) => !task.synthetic).slice(0, 8);
26445
+ for (const task of visibleTasks) {
26446
+ lines.push(`\u2022 ${formatTaskStatusMarker2(task.status)} ${formatTaskLabel2(task)}`);
26447
+ }
26448
+ const hiddenCount = summary.tasks.filter((task) => !task.synthetic).length - visibleTasks.length;
26449
+ if (hiddenCount > 0) {
26450
+ lines.push(`\u2022 ... ${hiddenCount} more`);
26451
+ }
26452
+ }
26453
+ return lines.length > 1 ? lines.join("\n") : void 0;
26454
+ }
25606
26455
  function formatSkillList(skills) {
25607
26456
  const normalized = dedupeStrings(skills);
25608
26457
  if (normalized.length <= 4) return normalized.join(", ");
@@ -25615,7 +26464,7 @@ function dedupeStrings(values) {
25615
26464
  async function extractTraceSkillMetadata(traceRef, traceId) {
25616
26465
  if (!traceRef && !traceId) return void 0;
25617
26466
  try {
25618
- const spans = await fetchTraceSpans(traceId || traceRef);
26467
+ const spans = await fetchTraceSpans(traceRef || traceId, { targetTraceId: traceId });
25619
26468
  if (!spans.length) return void 0;
25620
26469
  const routeIntentSpan = spans.find(
25621
26470
  (span) => span.attributes["visor.check.id"] === "route-intent"
@@ -25634,8 +26483,12 @@ async function extractTraceSkillMetadata(traceRef, traceId) {
25634
26483
  (Array.isArray(routeIntentOutput?.skills) ? routeIntentOutput.skills : void 0) || (Array.isArray(classifyOutput?.skills) ? classifyOutput.skills : void 0)
25635
26484
  );
25636
26485
  const finalActivatedSkills = activatedSkills.length > 0 ? activatedSkills : fallbackActivatedSkills;
25637
- if (!finalActivatedSkills.length) return void 0;
25638
- return { activatedSkills: finalActivatedSkills };
26486
+ const taskSummary = extractProbeTaskSummary(spans);
26487
+ if (!finalActivatedSkills.length && !taskSummary) return void 0;
26488
+ return {
26489
+ activatedSkills: finalActivatedSkills.length ? finalActivatedSkills : void 0,
26490
+ taskSummary: taskSummary || void 0
26491
+ };
25639
26492
  } catch (err) {
25640
26493
  logger.debug(
25641
26494
  `[TaskLiveUpdates] Failed to extract skill metadata from trace: ${err instanceof Error ? err.message : String(err)}`
@@ -25691,7 +26544,7 @@ function extractProgressField(text, label) {
25691
26544
  function escapeRegExp(value) {
25692
26545
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
25693
26546
  }
25694
- var import_crypto5, DEFAULT_TASK_LIVE_UPDATE_INTERVAL_SECONDS, DEFAULT_TASK_LIVE_UPDATE_MAX_TRACE_CHARS, DEFAULT_TASK_LIVE_UPDATE_FIRST_UPDATE_DELAY_SECONDS, DEFAULT_TASK_LIVE_UPDATE_METADATA_REFRESH_SECONDS, DEFAULT_TASK_LIVE_UPDATE_STALL_FALLBACK_SECONDS, DEFAULT_TASK_LIVE_UPDATE_MODEL, DEFAULT_TASK_LIVE_UPDATE_STALL_NOTICE, DEFAULT_TASK_LIVE_UPDATE_PROMPT, TaskLiveUpdateManager;
26547
+ var import_crypto5, DEFAULT_TASK_LIVE_UPDATE_INTERVAL_SECONDS, DEFAULT_TASK_LIVE_UPDATE_MAX_TRACE_CHARS, DEFAULT_TASK_LIVE_UPDATE_FIRST_UPDATE_DELAY_SECONDS, DEFAULT_TASK_LIVE_UPDATE_METADATA_REFRESH_SECONDS, DEFAULT_TASK_LIVE_UPDATE_STALL_FALLBACK_SECONDS, DEFAULT_TASK_LIVE_UPDATE_MODEL, DEFAULT_TASK_LIVE_UPDATE_STALL_NOTICE, GENERIC_COMPLETION_FALLBACK, DEFAULT_TASK_LIVE_UPDATE_PROMPT, TaskLiveUpdateManager;
25695
26548
  var init_task_live_updates = __esm({
25696
26549
  "src/agent-protocol/task-live-updates.ts"() {
25697
26550
  "use strict";
@@ -25705,6 +26558,7 @@ var init_task_live_updates = __esm({
25705
26558
  DEFAULT_TASK_LIVE_UPDATE_STALL_FALLBACK_SECONDS = 60;
25706
26559
  DEFAULT_TASK_LIVE_UPDATE_MODEL = "gemini-3.1-flash-lite-preview";
25707
26560
  DEFAULT_TASK_LIVE_UPDATE_STALL_NOTICE = "_No new meaningful progress is visible yet. Some steps can stay quiet for up to 5 minutes before there is new news._";
26561
+ GENERIC_COMPLETION_FALLBACK = "Execution completed";
25708
26562
  DEFAULT_TASK_LIVE_UPDATE_PROMPT = `You are generating a short live progress update for a user while an AI task is still running.
25709
26563
 
25710
26564
  This is NOT the final answer.
@@ -25787,6 +26641,9 @@ Good update example:
25787
26641
  firstTickTimer;
25788
26642
  metadataRefreshTimer;
25789
26643
  running = false;
26644
+ inflightTick;
26645
+ inflightMetadataRefresh;
26646
+ inflightPublishes = /* @__PURE__ */ new Set();
25790
26647
  started = false;
25791
26648
  completed = false;
25792
26649
  startedAt = /* @__PURE__ */ new Date();
@@ -25822,6 +26679,14 @@ Good update example:
25822
26679
  if (this.completed) return;
25823
26680
  this.completed = true;
25824
26681
  this.stop();
26682
+ await this.waitForInflightSinkPublishes();
26683
+ const cleanedFinalText = finalText.trim();
26684
+ if (!this.lastPostedMessage && cleanedFinalText === GENERIC_COMPLETION_FALLBACK) {
26685
+ logger.info(
26686
+ `[TaskLiveUpdates] Suppressing generic final update for task ${this.ctx.taskId}: no progress update was posted`
26687
+ );
26688
+ return;
26689
+ }
25825
26690
  try {
25826
26691
  logger.info(`[TaskLiveUpdates] Publishing final success update for task ${this.ctx.taskId}`);
25827
26692
  const result = await this.ctx.sink.complete(this.decorateText(finalText));
@@ -25837,6 +26702,7 @@ Good update example:
25837
26702
  if (this.completed) return;
25838
26703
  this.completed = true;
25839
26704
  this.stop();
26705
+ await this.waitForInflightSinkPublishes();
25840
26706
  try {
25841
26707
  logger.info(`[TaskLiveUpdates] Publishing final failure update for task ${this.ctx.taskId}`);
25842
26708
  const result = await this.ctx.sink.fail(this.decorateText(finalText));
@@ -25863,6 +26729,16 @@ Good update example:
25863
26729
  }
25864
26730
  }
25865
26731
  async tick() {
26732
+ if (this.inflightTick) return this.inflightTick;
26733
+ const promise = this.runTick().finally(() => {
26734
+ if (this.inflightTick === promise) {
26735
+ this.inflightTick = void 0;
26736
+ }
26737
+ });
26738
+ this.inflightTick = promise;
26739
+ return promise;
26740
+ }
26741
+ async runTick() {
25866
26742
  if (this.completed || this.running) return;
25867
26743
  const traceState = this.getTraceState();
25868
26744
  if (!traceState.traceRef && !traceState.traceId) {
@@ -25935,11 +26811,18 @@ Good update example:
25935
26811
  elapsedSeconds: Math.max(0, Math.floor((Date.now() - this.startedAt.getTime()) / 1e3)),
25936
26812
  previousUpdateAt: this.lastUpdateAt,
25937
26813
  secondsSincePreviousUpdate: this.lastUpdateAt ? Math.max(0, Math.floor((Date.now() - this.lastUpdateAt.getTime()) / 1e3)) : void 0,
25938
- activatedSkills: this.lastSkillMetadata?.activatedSkills
26814
+ activatedSkills: this.lastSkillMetadata?.activatedSkills,
26815
+ taskSummary: this.lastSkillMetadata?.taskSummary
25939
26816
  },
25940
26817
  traceState.traceId
25941
26818
  );
25942
- const result = await this.ctx.sink.update(message);
26819
+ if (this.completed) {
26820
+ logger.debug(
26821
+ `[TaskLiveUpdates] Aborting progress publish for task ${this.ctx.taskId}: task already completed before sink update`
26822
+ );
26823
+ return;
26824
+ }
26825
+ const result = await this.trackPublish(this.ctx.sink.update(message));
25943
26826
  this.recordSinkRef(result);
25944
26827
  this.ctx.appendHistory?.(cleaned, "progress");
25945
26828
  this.lastUpdateText = cleaned;
@@ -25967,15 +26850,31 @@ Good update example:
25967
26850
  this.timer.unref();
25968
26851
  }
25969
26852
  this.metadataRefreshTimer = setInterval(() => {
25970
- void this.refreshProgressMetadata();
26853
+ void this.scheduleMetadataRefresh();
25971
26854
  }, DEFAULT_TASK_LIVE_UPDATE_METADATA_REFRESH_SECONDS * 1e3);
25972
26855
  if (typeof this.metadataRefreshTimer?.unref === "function") {
25973
26856
  this.metadataRefreshTimer.unref();
25974
26857
  }
25975
26858
  }
26859
+ async waitForInflightSinkPublishes() {
26860
+ const pending = [...this.inflightPublishes];
26861
+ if (pending.length === 0) return;
26862
+ try {
26863
+ await Promise.allSettled(pending);
26864
+ } catch {
26865
+ }
26866
+ }
25976
26867
  recordSinkRef(result) {
25977
26868
  if (result?.ref) this.ctx.onPostedRef?.(result.ref);
25978
26869
  }
26870
+ async trackPublish(publish) {
26871
+ this.inflightPublishes.add(publish);
26872
+ try {
26873
+ return await publish;
26874
+ } finally {
26875
+ this.inflightPublishes.delete(publish);
26876
+ }
26877
+ }
25979
26878
  getTraceState() {
25980
26879
  const resolved = this.ctx.resolveTraceState?.();
25981
26880
  return {
@@ -25990,16 +26889,30 @@ Good update example:
25990
26889
 
25991
26890
  \`task_id: ${this.ctx.taskId}\``;
25992
26891
  }
25993
- decorateProgressText(text, timing, traceId) {
26892
+ decorateProgressText(text, timing, _traceId) {
25994
26893
  const normalized = normalizeProgressSummary(text);
26894
+ const taskSummary = formatTaskSummary(timing.taskSummary);
25995
26895
  const blocks = [
25996
26896
  "*Live Update*",
25997
26897
  "_Current task is still running. This message updates in place until the final answer is ready._",
25998
26898
  this.lastUpdateKind === "stall" ? DEFAULT_TASK_LIVE_UPDATE_STALL_NOTICE : "",
26899
+ taskSummary,
25999
26900
  normalized,
26000
- formatProgressMetadata(timing)
26901
+ formatProgressMetadata(timing),
26902
+ `\`task_id: ${this.ctx.taskId}\``,
26903
+ "`live_update: true`"
26001
26904
  ].filter(Boolean);
26002
- return this.decorateText(blocks.join("\n\n"), traceId);
26905
+ return blocks.join("\n\n");
26906
+ }
26907
+ scheduleMetadataRefresh() {
26908
+ if (this.inflightMetadataRefresh) return this.inflightMetadataRefresh;
26909
+ const promise = this.refreshProgressMetadata().finally(() => {
26910
+ if (this.inflightMetadataRefresh === promise) {
26911
+ this.inflightMetadataRefresh = void 0;
26912
+ }
26913
+ });
26914
+ this.inflightMetadataRefresh = promise;
26915
+ return promise;
26003
26916
  }
26004
26917
  async refreshProgressMetadata() {
26005
26918
  if (this.completed || this.running || !this.lastUpdateText) return;
@@ -26010,7 +26923,8 @@ Good update example:
26010
26923
  elapsedSeconds: Math.max(0, Math.floor((Date.now() - this.startedAt.getTime()) / 1e3)),
26011
26924
  previousUpdateAt: this.lastUpdateAt,
26012
26925
  secondsSincePreviousUpdate: this.lastUpdateAt ? Math.max(0, Math.floor((Date.now() - this.lastUpdateAt.getTime()) / 1e3)) : void 0,
26013
- activatedSkills: this.lastSkillMetadata?.activatedSkills
26926
+ activatedSkills: this.lastSkillMetadata?.activatedSkills,
26927
+ taskSummary: this.lastSkillMetadata?.taskSummary
26014
26928
  },
26015
26929
  traceState.traceId
26016
26930
  );
@@ -26020,7 +26934,7 @@ Good update example:
26020
26934
  logger.debug(
26021
26935
  `[TaskLiveUpdates] Refreshing metadata-only live update for task ${this.ctx.taskId}`
26022
26936
  );
26023
- const result = await this.ctx.sink.update(message);
26937
+ const result = await this.trackPublish(this.ctx.sink.update(message));
26024
26938
  this.recordSinkRef(result);
26025
26939
  this.lastPostedMessage = message;
26026
26940
  } catch (err) {
@@ -26056,7 +26970,8 @@ Good update example:
26056
26970
  elapsedSeconds: Math.max(0, Math.floor((Date.now() - this.startedAt.getTime()) / 1e3)),
26057
26971
  previousUpdateAt: this.lastUpdateAt,
26058
26972
  secondsSincePreviousUpdate: this.lastUpdateAt ? Math.max(0, Math.floor((Date.now() - this.lastUpdateAt.getTime()) / 1e3)) : void 0,
26059
- activatedSkills: this.lastSkillMetadata?.activatedSkills
26973
+ activatedSkills: this.lastSkillMetadata?.activatedSkills,
26974
+ taskSummary: this.lastSkillMetadata?.taskSummary
26060
26975
  },
26061
26976
  traceId
26062
26977
  );
@@ -26064,7 +26979,8 @@ Good update example:
26064
26979
  this.lastStallFallbackAt = now;
26065
26980
  return;
26066
26981
  }
26067
- const result = await this.ctx.sink.update(message);
26982
+ if (this.completed) return;
26983
+ const result = await this.trackPublish(this.ctx.sink.update(message));
26068
26984
  this.recordSinkRef(result);
26069
26985
  if (!this.lastUpdateText) {
26070
26986
  this.ctx.appendHistory?.(baseText, "progress");
@@ -26093,7 +27009,7 @@ async function resolveTaskTraceReference(metadata) {
26093
27009
  return {
26094
27010
  traceId,
26095
27011
  traceFile,
26096
- primaryRef: traceId || traceFile || void 0
27012
+ primaryRef: traceFile || traceId || void 0
26097
27013
  };
26098
27014
  }
26099
27015
  var init_task_trace_resolution = __esm({
@@ -26187,14 +27103,15 @@ async function evaluateTask(taskId, store, config) {
26187
27103
  if (traceFile || traceId) {
26188
27104
  try {
26189
27105
  const traceRef = traceFile || traceId;
26190
- traceTree = await serializeTraceForPrompt(
27106
+ const report = await buildTraceReport(
26191
27107
  traceRef,
26192
27108
  1e6,
26193
27109
  { traceDir: config?.traceDir },
26194
27110
  responseText !== "No response available" ? responseText : void 0,
26195
27111
  traceId
26196
27112
  );
26197
- if (traceTree === "(no trace data available)") {
27113
+ traceTree = report?.text;
27114
+ if (!traceTree || traceTree === "(no trace data available)") {
26198
27115
  traceTree = void 0;
26199
27116
  }
26200
27117
  } catch (err) {
@@ -26357,6 +27274,77 @@ var track_execution_exports = {};
26357
27274
  __export(track_execution_exports, {
26358
27275
  trackExecution: () => trackExecution
26359
27276
  });
27277
+ function unwrapJsonText(text) {
27278
+ if (text.startsWith("{") && text.includes('"text"')) {
27279
+ try {
27280
+ const parsed = JSON.parse(text);
27281
+ if (typeof parsed?.text === "string" && parsed.text.trim().length > 0) {
27282
+ return parsed.text.trim();
27283
+ }
27284
+ } catch {
27285
+ try {
27286
+ const sanitized = text.replace(
27287
+ /[\x00-\x1F\x7F]/g,
27288
+ (ch) => "\\u" + ch.charCodeAt(0).toString(16).padStart(4, "0")
27289
+ );
27290
+ const parsed = JSON.parse(sanitized);
27291
+ if (typeof parsed?.text === "string" && parsed.text.trim().length > 0) {
27292
+ return parsed.text.trim();
27293
+ }
27294
+ } catch {
27295
+ }
27296
+ }
27297
+ }
27298
+ return text;
27299
+ }
27300
+ function isToolOutput(text) {
27301
+ if (/^[├│└─┬┘┤┼]{1,3}/.test(text)) return true;
27302
+ if (text.startsWith("[task:")) return true;
27303
+ return false;
27304
+ }
27305
+ function extractTextFromEntries(entries) {
27306
+ for (let i = entries.length - 1; i >= 0; i--) {
27307
+ const outputs = entries[i];
27308
+ if (!Array.isArray(outputs)) continue;
27309
+ for (let j = outputs.length - 1; j >= 0; j--) {
27310
+ const text = outputs[j]?.text;
27311
+ if (typeof text === "string" && text.trim().length > 0) {
27312
+ return unwrapJsonText(text.trim());
27313
+ }
27314
+ }
27315
+ }
27316
+ return void 0;
27317
+ }
27318
+ function extractBestResponseText(history, allKeys) {
27319
+ const topLevelEntries = Object.entries(history).filter(([key]) => !key.includes(".")).map(([, v]) => v);
27320
+ const topLevelText = extractTextFromEntries(topLevelEntries);
27321
+ if (topLevelText && !isToolOutput(topLevelText)) {
27322
+ logger.info(
27323
+ `[TaskTracking] Extracted responseText (${topLevelText.length} chars): "${topLevelText.substring(0, 100)}..."`
27324
+ );
27325
+ return topLevelText;
27326
+ }
27327
+ const genResponseKeys = allKeys.filter(
27328
+ (k) => k.endsWith(".generate-response") || k === "generate-response"
27329
+ );
27330
+ if (genResponseKeys.length > 0) {
27331
+ const genEntries = genResponseKeys.map((k) => history[k]);
27332
+ const genText = extractTextFromEntries(genEntries);
27333
+ if (genText && !isToolOutput(genText)) {
27334
+ logger.info(
27335
+ `[TaskTracking] Fallback to generate-response entry (${genText.length} chars): "${genText.substring(0, 100)}..."`
27336
+ );
27337
+ return genText;
27338
+ }
27339
+ }
27340
+ if (topLevelText) {
27341
+ logger.warn(
27342
+ `[TaskTracking] Using tool output as responseText (${topLevelText.length} chars) \u2014 no better candidate found`
27343
+ );
27344
+ return topLevelText;
27345
+ }
27346
+ return "Execution completed";
27347
+ }
26360
27348
  function getPackageVersion() {
26361
27349
  try {
26362
27350
  return require_package()?.version || "dev";
@@ -26435,7 +27423,7 @@ async function trackExecution(opts, executor) {
26435
27423
  };
26436
27424
  }
26437
27425
  return {
26438
- traceRef: current?.metadata?.trace_id || current?.metadata?.trace_file || initialResolvedTrace.primaryRef,
27426
+ traceRef: current?.metadata?.trace_file || current?.metadata?.trace_id || initialResolvedTrace.primaryRef,
26439
27427
  traceId: current?.metadata?.trace_id
26440
27428
  };
26441
27429
  },
@@ -26488,18 +27476,13 @@ async function trackExecution(opts, executor) {
26488
27476
  try {
26489
27477
  const history = result?.reviewSummary?.history;
26490
27478
  if (history) {
26491
- const entries = Object.values(history);
26492
- for (let i = entries.length - 1; i >= 0; i--) {
26493
- const outputs = entries[i];
26494
- if (!Array.isArray(outputs)) continue;
26495
- for (let j = outputs.length - 1; j >= 0; j--) {
26496
- const text = outputs[j]?.text;
26497
- if (typeof text === "string" && text.trim().length > 0) {
26498
- responseText = text.trim();
26499
- break;
26500
- }
26501
- }
26502
- if (responseText !== "Execution completed") break;
27479
+ const allKeys = Object.keys(history);
27480
+ responseText = extractBestResponseText(history, allKeys);
27481
+ }
27482
+ if (responseText === "Execution completed") {
27483
+ const issueSummary = summarizeUserFacingIssues(result?.reviewSummary?.issues);
27484
+ if (issueSummary) {
27485
+ responseText = issueSummary;
26503
27486
  }
26504
27487
  }
26505
27488
  } catch {
@@ -26573,6 +27556,7 @@ var init_track_execution = __esm({
26573
27556
  init_instance_id();
26574
27557
  init_task_live_updates();
26575
27558
  init_task_trace_resolution();
27559
+ init_user_facing_error();
26576
27560
  }
26577
27561
  });
26578
27562
 
@@ -26755,7 +27739,11 @@ var init_scheduler = __esm({
26755
27739
  if (!import_node_cron.default.validate(job.schedule)) {
26756
27740
  throw new Error(`Invalid cron expression: ${job.schedule}`);
26757
27741
  }
26758
- if (job.workflow !== "default") {
27742
+ if (!job.workflow) {
27743
+ if (!job.inputs?.text) {
27744
+ throw new Error("Cron job without a workflow must have inputs.text set");
27745
+ }
27746
+ } else if (job.workflow !== "default") {
26759
27747
  const allChecks = Object.keys(this.visorConfig.checks || {});
26760
27748
  if (!allChecks.includes(job.workflow)) {
26761
27749
  throw new Error(`Workflow "${job.workflow}" not found in configuration`);
@@ -26778,7 +27766,7 @@ var init_scheduler = __esm({
26778
27766
  const nextRun = getNextRunTime(job.schedule, job.timezone || this.defaultTimezone);
26779
27767
  const description = job.description ? ` (${job.description})` : "";
26780
27768
  logger.debug(
26781
- `[Scheduler] Scheduled static cron job '${jobId}'${description}: ${job.schedule} \u2192 ${job.workflow}, next run: ${nextRun.toISOString()}`
27769
+ `[Scheduler] Scheduled static cron job '${jobId}'${description}: ${job.schedule} \u2192 ${job.workflow || "(reminder)"}, next run: ${nextRun.toISOString()}`
26782
27770
  );
26783
27771
  } catch {
26784
27772
  }
@@ -28698,6 +29686,116 @@ var init_schedule_tool_handler = __esm({
28698
29686
  }
28699
29687
  });
28700
29688
 
29689
+ // src/utils/oauth2-token-cache.ts
29690
+ var oauth2_token_cache_exports = {};
29691
+ __export(oauth2_token_cache_exports, {
29692
+ OAuth2TokenCache: () => OAuth2TokenCache
29693
+ });
29694
+ var OAuth2TokenCache;
29695
+ var init_oauth2_token_cache = __esm({
29696
+ "src/utils/oauth2-token-cache.ts"() {
29697
+ "use strict";
29698
+ init_logger();
29699
+ init_env_resolver();
29700
+ OAuth2TokenCache = class _OAuth2TokenCache {
29701
+ static instance;
29702
+ cache = /* @__PURE__ */ new Map();
29703
+ static getInstance() {
29704
+ if (!_OAuth2TokenCache.instance) {
29705
+ _OAuth2TokenCache.instance = new _OAuth2TokenCache();
29706
+ }
29707
+ return _OAuth2TokenCache.instance;
29708
+ }
29709
+ /** Visible for testing */
29710
+ static resetInstance() {
29711
+ _OAuth2TokenCache.instance = void 0;
29712
+ }
29713
+ /**
29714
+ * Get a valid Bearer token for the given config.
29715
+ * Returns a cached token if still valid, otherwise fetches a new one.
29716
+ */
29717
+ async getToken(config) {
29718
+ const clientId = String(EnvironmentResolver.resolveValue(config.client_id));
29719
+ const clientSecret = String(EnvironmentResolver.resolveValue(config.client_secret));
29720
+ const tokenUrl = String(EnvironmentResolver.resolveValue(config.token_url));
29721
+ const bufferMs = (config.token_ttl_buffer ?? 300) * 1e3;
29722
+ const cacheKey = `${tokenUrl}|${clientId}`;
29723
+ const cached = this.cache.get(cacheKey);
29724
+ if (cached && cached.expires_at - bufferMs > Date.now()) {
29725
+ logger.verbose("[oauth2] Using cached token");
29726
+ return cached.access_token;
29727
+ }
29728
+ if (cached?.refreshPromise) {
29729
+ logger.verbose("[oauth2] Awaiting in-flight token refresh");
29730
+ return cached.refreshPromise;
29731
+ }
29732
+ const refreshPromise = this.fetchToken(tokenUrl, clientId, clientSecret, config.scopes);
29733
+ if (cached) {
29734
+ cached.refreshPromise = refreshPromise;
29735
+ } else {
29736
+ this.cache.set(cacheKey, {
29737
+ access_token: "",
29738
+ expires_at: 0,
29739
+ refreshPromise
29740
+ });
29741
+ }
29742
+ try {
29743
+ const token = await refreshPromise;
29744
+ return token;
29745
+ } finally {
29746
+ const entry = this.cache.get(cacheKey);
29747
+ if (entry) {
29748
+ entry.refreshPromise = void 0;
29749
+ }
29750
+ }
29751
+ }
29752
+ async fetchToken(tokenUrl, clientId, clientSecret, scopes) {
29753
+ logger.verbose(`[oauth2] Fetching token from ${tokenUrl}`);
29754
+ const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
29755
+ const bodyParams = new URLSearchParams({ grant_type: "client_credentials" });
29756
+ if (scopes?.length) {
29757
+ bodyParams.set("scope", scopes.join(" "));
29758
+ }
29759
+ const response = await fetch(tokenUrl, {
29760
+ method: "POST",
29761
+ headers: {
29762
+ "Content-Type": "application/x-www-form-urlencoded",
29763
+ Authorization: `Basic ${credentials}`
29764
+ },
29765
+ body: bodyParams.toString()
29766
+ });
29767
+ if (!response.ok) {
29768
+ let errorDetail = "";
29769
+ try {
29770
+ errorDetail = await response.text();
29771
+ } catch {
29772
+ }
29773
+ throw new Error(
29774
+ `OAuth2 token request failed: HTTP ${response.status} ${response.statusText}${errorDetail ? ` - ${errorDetail.substring(0, 200)}` : ""}`
29775
+ );
29776
+ }
29777
+ const data = await response.json();
29778
+ if (!data.access_token) {
29779
+ throw new Error("OAuth2 token response missing access_token");
29780
+ }
29781
+ const expiresIn = data.expires_in ?? 3600;
29782
+ const expiresAt = Date.now() + expiresIn * 1e3;
29783
+ const cacheKey = `${tokenUrl}|${clientId}`;
29784
+ this.cache.set(cacheKey, {
29785
+ access_token: data.access_token,
29786
+ expires_at: expiresAt
29787
+ });
29788
+ logger.verbose(`[oauth2] Token acquired, expires in ${expiresIn}s`);
29789
+ return data.access_token;
29790
+ }
29791
+ /** Clear all cached tokens (for testing or credential rotation) */
29792
+ clear() {
29793
+ this.cache.clear();
29794
+ }
29795
+ };
29796
+ }
29797
+ });
29798
+
28701
29799
  // src/utils/issue-normalizer.ts
28702
29800
  function extractIssuesFromOutput(output, defaultRuleId) {
28703
29801
  if (output === null || output === void 0) {
@@ -28888,7 +29986,7 @@ var utcp_check_provider_exports = {};
28888
29986
  __export(utcp_check_provider_exports, {
28889
29987
  UtcpCheckProvider: () => UtcpCheckProvider
28890
29988
  });
28891
- var fs19, path21, UtcpCheckProvider;
29989
+ var fs18, path21, UtcpCheckProvider;
28892
29990
  var init_utcp_check_provider = __esm({
28893
29991
  "src/providers/utcp-check-provider.ts"() {
28894
29992
  "use strict";
@@ -28898,7 +29996,7 @@ var init_utcp_check_provider = __esm({
28898
29996
  init_sandbox();
28899
29997
  init_env_resolver();
28900
29998
  init_issue_normalizer();
28901
- fs19 = __toESM(require("fs"));
29999
+ fs18 = __toESM(require("fs"));
28902
30000
  path21 = __toESM(require("path"));
28903
30001
  UtcpCheckProvider = class _UtcpCheckProvider extends CheckProvider {
28904
30002
  liquid;
@@ -29152,20 +30250,20 @@ var init_utcp_check_provider = __esm({
29152
30250
  `Path traversal detected: "${manual}" resolves outside the project directory. UTCP manual paths must be within the project directory.`
29153
30251
  );
29154
30252
  }
29155
- if (fs19.existsSync(resolvedPath)) {
29156
- const realPath = fs19.realpathSync(resolvedPath);
30253
+ if (fs18.existsSync(resolvedPath)) {
30254
+ const realPath = fs18.realpathSync(resolvedPath);
29157
30255
  if (realPath !== cwd && !realPath.startsWith(cwdPrefix)) {
29158
30256
  throw new Error(
29159
30257
  `Symlink traversal detected: "${manual}" points outside the project directory via symlink.`
29160
30258
  );
29161
30259
  }
29162
30260
  }
29163
- if (!fs19.existsSync(resolvedPath)) {
30261
+ if (!fs18.existsSync(resolvedPath)) {
29164
30262
  throw new Error(`UTCP manual file not found: ${resolvedPath}`);
29165
30263
  }
29166
30264
  let content;
29167
30265
  try {
29168
- content = fs19.readFileSync(resolvedPath, "utf-8");
30266
+ content = fs18.readFileSync(resolvedPath, "utf-8");
29169
30267
  } catch (err) {
29170
30268
  throw new Error(
29171
30269
  `Failed to read UTCP manual file: ${resolvedPath}: ${err instanceof Error ? err.message : "Unknown error"}`
@@ -29923,9 +31021,24 @@ var init_mcp_custom_sse_server = __esm({
29923
31021
  }
29924
31022
  return record;
29925
31023
  };
31024
+ const fixRequiredFields = (obj) => {
31025
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) return obj;
31026
+ const record = obj;
31027
+ if (Array.isArray(record.required) && record.properties && typeof record.properties === "object") {
31028
+ const validProps = Object.keys(record.properties);
31029
+ record.required = record.required.filter((r) => validProps.includes(r));
31030
+ }
31031
+ for (const key of Object.keys(record)) {
31032
+ if (typeof record[key] === "object" && record[key] !== null) {
31033
+ fixRequiredFields(record[key]);
31034
+ }
31035
+ }
31036
+ return record;
31037
+ };
29926
31038
  const normalizeInputSchema = (schema) => {
29927
31039
  if (schema && schema.type === "object") {
29928
31040
  fixArraySchemas(schema);
31041
+ fixRequiredFields(schema);
29929
31042
  return schema;
29930
31043
  }
29931
31044
  return {
@@ -29934,7 +31047,10 @@ var init_mcp_custom_sse_server = __esm({
29934
31047
  required: []
29935
31048
  };
29936
31049
  };
29937
- const regularTools = await this.toolExecutor.listMcpTools();
31050
+ const regularTools = (await this.toolExecutor.listMcpTools()).map((tool) => ({
31051
+ ...tool,
31052
+ inputSchema: normalizeInputSchema(tool.inputSchema)
31053
+ }));
29938
31054
  const workflowTools = Array.from(this.tools.values()).filter(isWorkflowTool).map((tool) => ({
29939
31055
  name: tool.name,
29940
31056
  description: tool.description || `Execute ${tool.name}`,
@@ -30319,6 +31435,11 @@ var init_mcp_custom_sse_server = __esm({
30319
31435
  if (authType === "bearer" && tool.auth.token) {
30320
31436
  const token = String(EnvironmentResolver.resolveValue(tool.auth.token));
30321
31437
  resolvedHeaders["Authorization"] = `Bearer ${token}`;
31438
+ } else if (authType === "oauth2_client_credentials" && tool.auth.token_url) {
31439
+ const { OAuth2TokenCache: OAuth2TokenCache2 } = await Promise.resolve().then(() => (init_oauth2_token_cache(), oauth2_token_cache_exports));
31440
+ const tokenCache = OAuth2TokenCache2.getInstance();
31441
+ const token = await tokenCache.getToken(tool.auth);
31442
+ resolvedHeaders["Authorization"] = `Bearer ${token}`;
30322
31443
  }
30323
31444
  }
30324
31445
  if (this.debug) {
@@ -30599,6 +31720,7 @@ var init_ai_check_provider = __esm({
30599
31720
  init_schedule_tool();
30600
31721
  init_task_progress_tool();
30601
31722
  init_schedule_tool_handler();
31723
+ init_user_facing_error();
30602
31724
  AICheckProvider = class _AICheckProvider extends CheckProvider {
30603
31725
  aiReviewService;
30604
31726
  liquidEngine;
@@ -32201,13 +33323,14 @@ ${processedPrompt}` : processedPrompt;
32201
33323
  }
32202
33324
  } catch (error) {
32203
33325
  const errorMessage = error instanceof Error ? error.message : String(error);
33326
+ const userFacingMessage = formatUserFacingExecutionError(error);
32204
33327
  console.error(`\u274C AI Check Provider Error for check: ${errorMessage}`);
32205
33328
  const isCriticalError = errorMessage.includes("API rate limit") || errorMessage.includes("403") || errorMessage.includes("401") || errorMessage.includes("authentication") || errorMessage.includes("API key");
32206
33329
  if (isCriticalError) {
32207
33330
  console.error(`\u{1F6A8} CRITICAL ERROR: AI provider authentication or rate limit issue detected`);
32208
33331
  console.error(`\u{1F6A8} This check cannot proceed without valid API credentials`);
32209
33332
  }
32210
- throw new Error(`AI analysis failed: ${errorMessage}`);
33333
+ throw new Error(userFacingMessage);
32211
33334
  } finally {
32212
33335
  if (customToolsServer) {
32213
33336
  try {
@@ -33102,114 +34225,8 @@ var init_template_context = __esm({
33102
34225
  }
33103
34226
  });
33104
34227
 
33105
- // src/utils/oauth2-token-cache.ts
33106
- var OAuth2TokenCache;
33107
- var init_oauth2_token_cache = __esm({
33108
- "src/utils/oauth2-token-cache.ts"() {
33109
- "use strict";
33110
- init_logger();
33111
- init_env_resolver();
33112
- OAuth2TokenCache = class _OAuth2TokenCache {
33113
- static instance;
33114
- cache = /* @__PURE__ */ new Map();
33115
- static getInstance() {
33116
- if (!_OAuth2TokenCache.instance) {
33117
- _OAuth2TokenCache.instance = new _OAuth2TokenCache();
33118
- }
33119
- return _OAuth2TokenCache.instance;
33120
- }
33121
- /** Visible for testing */
33122
- static resetInstance() {
33123
- _OAuth2TokenCache.instance = void 0;
33124
- }
33125
- /**
33126
- * Get a valid Bearer token for the given config.
33127
- * Returns a cached token if still valid, otherwise fetches a new one.
33128
- */
33129
- async getToken(config) {
33130
- const clientId = String(EnvironmentResolver.resolveValue(config.client_id));
33131
- const clientSecret = String(EnvironmentResolver.resolveValue(config.client_secret));
33132
- const tokenUrl = String(EnvironmentResolver.resolveValue(config.token_url));
33133
- const bufferMs = (config.token_ttl_buffer ?? 300) * 1e3;
33134
- const cacheKey = `${tokenUrl}|${clientId}`;
33135
- const cached = this.cache.get(cacheKey);
33136
- if (cached && cached.expires_at - bufferMs > Date.now()) {
33137
- logger.verbose("[oauth2] Using cached token");
33138
- return cached.access_token;
33139
- }
33140
- if (cached?.refreshPromise) {
33141
- logger.verbose("[oauth2] Awaiting in-flight token refresh");
33142
- return cached.refreshPromise;
33143
- }
33144
- const refreshPromise = this.fetchToken(tokenUrl, clientId, clientSecret, config.scopes);
33145
- if (cached) {
33146
- cached.refreshPromise = refreshPromise;
33147
- } else {
33148
- this.cache.set(cacheKey, {
33149
- access_token: "",
33150
- expires_at: 0,
33151
- refreshPromise
33152
- });
33153
- }
33154
- try {
33155
- const token = await refreshPromise;
33156
- return token;
33157
- } finally {
33158
- const entry = this.cache.get(cacheKey);
33159
- if (entry) {
33160
- entry.refreshPromise = void 0;
33161
- }
33162
- }
33163
- }
33164
- async fetchToken(tokenUrl, clientId, clientSecret, scopes) {
33165
- logger.verbose(`[oauth2] Fetching token from ${tokenUrl}`);
33166
- const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
33167
- const bodyParams = new URLSearchParams({ grant_type: "client_credentials" });
33168
- if (scopes?.length) {
33169
- bodyParams.set("scope", scopes.join(" "));
33170
- }
33171
- const response = await fetch(tokenUrl, {
33172
- method: "POST",
33173
- headers: {
33174
- "Content-Type": "application/x-www-form-urlencoded",
33175
- Authorization: `Basic ${credentials}`
33176
- },
33177
- body: bodyParams.toString()
33178
- });
33179
- if (!response.ok) {
33180
- let errorDetail = "";
33181
- try {
33182
- errorDetail = await response.text();
33183
- } catch {
33184
- }
33185
- throw new Error(
33186
- `OAuth2 token request failed: HTTP ${response.status} ${response.statusText}${errorDetail ? ` - ${errorDetail.substring(0, 200)}` : ""}`
33187
- );
33188
- }
33189
- const data = await response.json();
33190
- if (!data.access_token) {
33191
- throw new Error("OAuth2 token response missing access_token");
33192
- }
33193
- const expiresIn = data.expires_in ?? 3600;
33194
- const expiresAt = Date.now() + expiresIn * 1e3;
33195
- const cacheKey = `${tokenUrl}|${clientId}`;
33196
- this.cache.set(cacheKey, {
33197
- access_token: data.access_token,
33198
- expires_at: expiresAt
33199
- });
33200
- logger.verbose(`[oauth2] Token acquired, expires in ${expiresIn}s`);
33201
- return data.access_token;
33202
- }
33203
- /** Clear all cached tokens (for testing or credential rotation) */
33204
- clear() {
33205
- this.cache.clear();
33206
- }
33207
- };
33208
- }
33209
- });
33210
-
33211
34228
  // src/providers/http-client-provider.ts
33212
- var fs22, path23, HttpClientProvider;
34229
+ var fs21, path23, HttpClientProvider;
33213
34230
  var init_http_client_provider = __esm({
33214
34231
  "src/providers/http-client-provider.ts"() {
33215
34232
  "use strict";
@@ -33221,7 +34238,7 @@ var init_http_client_provider = __esm({
33221
34238
  init_oauth2_token_cache();
33222
34239
  init_logger();
33223
34240
  init_rate_limiter();
33224
- fs22 = __toESM(require("fs"));
34241
+ fs21 = __toESM(require("fs"));
33225
34242
  path23 = __toESM(require("path"));
33226
34243
  HttpClientProvider = class extends CheckProvider {
33227
34244
  liquid;
@@ -33355,8 +34372,8 @@ var init_http_client_provider = __esm({
33355
34372
  `[http_client] Resolved relative output_file to workspace: ${resolvedOutputFile}`
33356
34373
  );
33357
34374
  }
33358
- if (skipIfExists && fs22.existsSync(resolvedOutputFile)) {
33359
- const stats = fs22.statSync(resolvedOutputFile);
34375
+ if (skipIfExists && fs21.existsSync(resolvedOutputFile)) {
34376
+ const stats = fs21.statSync(resolvedOutputFile);
33360
34377
  logger.verbose(`[http_client] File cached: ${resolvedOutputFile} (${stats.size} bytes)`);
33361
34378
  return {
33362
34379
  issues: [],
@@ -33587,12 +34604,12 @@ var init_http_client_provider = __esm({
33587
34604
  };
33588
34605
  }
33589
34606
  const parentDir = path23.dirname(outputFile);
33590
- if (parentDir && !fs22.existsSync(parentDir)) {
33591
- fs22.mkdirSync(parentDir, { recursive: true });
34607
+ if (parentDir && !fs21.existsSync(parentDir)) {
34608
+ fs21.mkdirSync(parentDir, { recursive: true });
33592
34609
  }
33593
34610
  const arrayBuffer = await response.arrayBuffer();
33594
34611
  const buffer = Buffer.from(arrayBuffer);
33595
- fs22.writeFileSync(outputFile, buffer);
34612
+ fs21.writeFileSync(outputFile, buffer);
33596
34613
  const contentType = response.headers.get("content-type") || "application/octet-stream";
33597
34614
  logger.verbose(`[http_client] Downloaded: ${outputFile} (${buffer.length} bytes)`);
33598
34615
  return {
@@ -53822,6 +54839,14 @@ var init_mcp_check_provider = __esm({
53822
54839
  }
53823
54840
  );
53824
54841
  const transport = createTransport();
54842
+ if ("stderr" in transport && transport.stderr) {
54843
+ const stderrStream = transport.stderr;
54844
+ if (typeof stderrStream.on === "function") {
54845
+ stderrStream.on("error", (err) => {
54846
+ logger.debug(`MCP transport stderr error (${transportName}): ${err.message}`);
54847
+ });
54848
+ }
54849
+ }
53825
54850
  try {
53826
54851
  let timeoutId;
53827
54852
  try {
@@ -54178,7 +55203,7 @@ async function interactivePrompt(options) {
54178
55203
  } catch {
54179
55204
  }
54180
55205
  if (multiline) {
54181
- rl = readline3.createInterface({
55206
+ rl = readline2.createInterface({
54182
55207
  input: process.stdin,
54183
55208
  output: process.stdout,
54184
55209
  terminal: true
@@ -54291,7 +55316,7 @@ async function interactivePrompt(options) {
54291
55316
  }
54292
55317
  async function simplePrompt(prompt) {
54293
55318
  return new Promise((resolve17) => {
54294
- const rl = readline3.createInterface({
55319
+ const rl = readline2.createInterface({
54295
55320
  input: process.stdin,
54296
55321
  output: process.stdout
54297
55322
  });
@@ -54310,11 +55335,11 @@ async function simplePrompt(prompt) {
54310
55335
  });
54311
55336
  });
54312
55337
  }
54313
- var readline3, activePrompt, waiters;
55338
+ var readline2, activePrompt, waiters;
54314
55339
  var init_interactive_prompt = __esm({
54315
55340
  "src/utils/interactive-prompt.ts"() {
54316
55341
  "use strict";
54317
- readline3 = __toESM(require("readline"));
55342
+ readline2 = __toESM(require("readline"));
54318
55343
  activePrompt = false;
54319
55344
  waiters = [];
54320
55345
  }
@@ -54531,7 +55556,7 @@ var init_stdin_reader = __esm({
54531
55556
  });
54532
55557
 
54533
55558
  // src/providers/human-input-check-provider.ts
54534
- var fs24, path25, HumanInputCheckProvider;
55559
+ var fs23, path25, HumanInputCheckProvider;
54535
55560
  var init_human_input_check_provider = __esm({
54536
55561
  "src/providers/human-input-check-provider.ts"() {
54537
55562
  "use strict";
@@ -54540,7 +55565,7 @@ var init_human_input_check_provider = __esm({
54540
55565
  init_prompt_state();
54541
55566
  init_liquid_extensions();
54542
55567
  init_stdin_reader();
54543
- fs24 = __toESM(require("fs"));
55568
+ fs23 = __toESM(require("fs"));
54544
55569
  path25 = __toESM(require("path"));
54545
55570
  HumanInputCheckProvider = class _HumanInputCheckProvider extends CheckProvider {
54546
55571
  liquid;
@@ -54722,12 +55747,12 @@ var init_human_input_check_provider = __esm({
54722
55747
  return null;
54723
55748
  }
54724
55749
  try {
54725
- await fs24.promises.access(normalizedPath, fs24.constants.R_OK);
54726
- const stats = await fs24.promises.stat(normalizedPath);
55750
+ await fs23.promises.access(normalizedPath, fs23.constants.R_OK);
55751
+ const stats = await fs23.promises.stat(normalizedPath);
54727
55752
  if (!stats.isFile()) {
54728
55753
  return null;
54729
55754
  }
54730
- const content = await fs24.promises.readFile(normalizedPath, "utf-8");
55755
+ const content = await fs23.promises.readFile(normalizedPath, "utf-8");
54731
55756
  return content.trim();
54732
55757
  } catch {
54733
55758
  return null;
@@ -55784,6 +56809,11 @@ var init_script_check_provider = __esm({
55784
56809
  env,
55785
56810
  stderr: "pipe"
55786
56811
  });
56812
+ if (transport.stderr) {
56813
+ transport.stderr.on("error", (err) => {
56814
+ logger.debug(`MCP transport stderr error: ${err.message}`);
56815
+ });
56816
+ }
55787
56817
  await Promise.race([
55788
56818
  client.connect(transport),
55789
56819
  new Promise(
@@ -55881,16 +56911,48 @@ var init_script_check_provider = __esm({
55881
56911
  }
55882
56912
  });
55883
56913
 
56914
+ // src/utils/child-process-error-handler.ts
56915
+ function isChildProcessIOError(error) {
56916
+ if (!(error instanceof Error)) return false;
56917
+ const errno = error.code;
56918
+ if (errno && TRANSIENT_IO_CODES.has(errno)) return true;
56919
+ const msg = error.message;
56920
+ if (msg.includes("read EIO") || msg.includes("write EPIPE")) return true;
56921
+ if (msg.includes("ERR_STREAM_DESTROYED")) return true;
56922
+ return false;
56923
+ }
56924
+ var TRANSIENT_IO_CODES, GUARD;
56925
+ var init_child_process_error_handler = __esm({
56926
+ "src/utils/child-process-error-handler.ts"() {
56927
+ "use strict";
56928
+ init_logger();
56929
+ TRANSIENT_IO_CODES = /* @__PURE__ */ new Set(["EIO", "EPIPE", "ECONNRESET"]);
56930
+ GUARD = Symbol.for("visor.childProcessErrorHandler");
56931
+ if (!globalThis[GUARD]) {
56932
+ globalThis[GUARD] = true;
56933
+ process.on("uncaughtException", (error, origin) => {
56934
+ if (isChildProcessIOError(error)) {
56935
+ logger.warn(
56936
+ `[child-process-error-handler] Suppressed transient I/O error (${origin}): ${error.message}`
56937
+ );
56938
+ return;
56939
+ }
56940
+ });
56941
+ }
56942
+ }
56943
+ });
56944
+
55884
56945
  // src/utils/worktree-manager.ts
55885
- var fs25, fsp, path26, crypto4, WorktreeManager, worktreeManager;
56946
+ var fs24, fsp, path26, crypto4, WorktreeManager, worktreeManager;
55886
56947
  var init_worktree_manager = __esm({
55887
56948
  "src/utils/worktree-manager.ts"() {
55888
56949
  "use strict";
55889
- fs25 = __toESM(require("fs"));
56950
+ fs24 = __toESM(require("fs"));
55890
56951
  fsp = __toESM(require("fs/promises"));
55891
56952
  path26 = __toESM(require("path"));
55892
56953
  crypto4 = __toESM(require("crypto"));
55893
56954
  init_command_executor();
56955
+ init_child_process_error_handler();
55894
56956
  init_logger();
55895
56957
  WorktreeManager = class _WorktreeManager {
55896
56958
  static instance;
@@ -55941,12 +57003,12 @@ var init_worktree_manager = __esm({
55941
57003
  }
55942
57004
  const reposDir = this.getReposDir();
55943
57005
  const worktreesDir = this.getWorktreesDir();
55944
- if (!fs25.existsSync(reposDir)) {
55945
- fs25.mkdirSync(reposDir, { recursive: true });
57006
+ if (!fs24.existsSync(reposDir)) {
57007
+ fs24.mkdirSync(reposDir, { recursive: true });
55946
57008
  logger.debug(`Created repos directory: ${reposDir}`);
55947
57009
  }
55948
- if (!fs25.existsSync(worktreesDir)) {
55949
- fs25.mkdirSync(worktreesDir, { recursive: true });
57010
+ if (!fs24.existsSync(worktreesDir)) {
57011
+ fs24.mkdirSync(worktreesDir, { recursive: true });
55950
57012
  logger.debug(`Created worktrees directory: ${worktreesDir}`);
55951
57013
  }
55952
57014
  }
@@ -55980,7 +57042,7 @@ var init_worktree_manager = __esm({
55980
57042
  const reposDir = this.getReposDir();
55981
57043
  const repoName = repository.replace(/\//g, "-");
55982
57044
  const bareRepoPath = path26.join(reposDir, `${repoName}.git`);
55983
- if (fs25.existsSync(bareRepoPath)) {
57045
+ if (fs24.existsSync(bareRepoPath)) {
55984
57046
  logger.debug(`Bare repository already exists: ${bareRepoPath}`);
55985
57047
  const verifyResult = await this.verifyBareRepoRemote(bareRepoPath, repoUrl);
55986
57048
  if (verifyResult === "timeout") {
@@ -56049,6 +57111,7 @@ var init_worktree_manager = __esm({
56049
57111
  const result = await this.executeGitCommand(updateCmd, { timeout: 9e4 });
56050
57112
  if (result.exitCode === 0) {
56051
57113
  logger.debug(`Successfully updated bare repository`);
57114
+ await this.gcBareRepo(bareRepoPath);
56052
57115
  return;
56053
57116
  }
56054
57117
  logger.warn(`Bare repo update attempt ${attempt}/2 failed: ${result.stderr}`);
@@ -56062,6 +57125,36 @@ var init_worktree_manager = __esm({
56062
57125
  }
56063
57126
  logger.warn(`Failed to update bare repository after 2 attempts (will rely on per-ref fetch)`);
56064
57127
  }
57128
+ /**
57129
+ * Consolidate pack files after fetch to prevent unbounded pack accumulation.
57130
+ * Each `git fetch` creates a new pack file; without periodic repacking these
57131
+ * pile up and waste disk (especially on COW filesystems like BTRFS).
57132
+ */
57133
+ async gcBareRepo(bareRepoPath) {
57134
+ try {
57135
+ const packDir = path26.join(bareRepoPath, "objects", "pack");
57136
+ if (!fs24.existsSync(packDir)) return;
57137
+ const packFiles = fs24.readdirSync(packDir).filter((f) => f.endsWith(".pack"));
57138
+ if (packFiles.length <= 3) return;
57139
+ if (packFiles.length > 10) {
57140
+ logger.info(`Repacking bare repo (${packFiles.length} packs): ${bareRepoPath}`);
57141
+ const repackCmd = `git -C ${this.escapeShellArg(bareRepoPath)} repack -a -d --unpack-unreachable=now`;
57142
+ const result = await this.executeGitCommand(repackCmd, { timeout: 3e5 });
57143
+ if (result.exitCode === 0) {
57144
+ logger.info(`Repack complete for ${bareRepoPath}`);
57145
+ const pruneCmd = `git -C ${this.escapeShellArg(bareRepoPath)} prune --expire=now`;
57146
+ await this.executeGitCommand(pruneCmd, { timeout: 6e4 });
57147
+ } else {
57148
+ logger.warn(`Repack failed: ${result.stderr}`);
57149
+ }
57150
+ } else {
57151
+ const gcCmd = `git -C ${this.escapeShellArg(bareRepoPath)} gc --auto`;
57152
+ await this.executeGitCommand(gcCmd, { timeout: 12e4 });
57153
+ }
57154
+ } catch (error) {
57155
+ logger.debug(`gc/repack non-fatal error: ${error}`);
57156
+ }
57157
+ }
56065
57158
  /**
56066
57159
  * Verify that a bare repository has the correct remote URL.
56067
57160
  * This prevents reusing corrupted repos that were cloned from a different repository.
@@ -56154,7 +57247,7 @@ var init_worktree_manager = __esm({
56154
57247
  worktreePath = this.validatePath(options.workingDirectory);
56155
57248
  }
56156
57249
  let refreshFailedNeedsRecreate = false;
56157
- if (fs25.existsSync(worktreePath)) {
57250
+ if (fs24.existsSync(worktreePath)) {
56158
57251
  logger.debug(`Worktree already exists: ${worktreePath}`);
56159
57252
  const metadata2 = await this.loadMetadata(worktreePath);
56160
57253
  if (metadata2) {
@@ -56483,9 +57576,13 @@ var init_worktree_manager = __esm({
56483
57576
  * Remove a worktree
56484
57577
  */
56485
57578
  async removeWorktree(worktreeId) {
56486
- const metadata = this.activeWorktrees.get(worktreeId);
57579
+ let metadata = this.activeWorktrees.get(worktreeId);
57580
+ if (!metadata) {
57581
+ const worktreePath = path26.join(this.getWorktreesDir(), worktreeId);
57582
+ metadata = await this.loadMetadata(worktreePath) || void 0;
57583
+ }
56487
57584
  if (!metadata) {
56488
- logger.warn(`Worktree not found in active list: ${worktreeId}`);
57585
+ logger.warn(`Worktree metadata not found: ${worktreeId}`);
56489
57586
  return;
56490
57587
  }
56491
57588
  const { bare_repo_path, worktree_path } = metadata;
@@ -56494,15 +57591,15 @@ var init_worktree_manager = __esm({
56494
57591
  const result = await this.executeGitCommand(removeCmd, { timeout: 3e4 });
56495
57592
  if (result.exitCode !== 0) {
56496
57593
  logger.warn(`Failed to remove worktree via git: ${result.stderr}`);
56497
- if (fs25.existsSync(worktree_path)) {
57594
+ if (fs24.existsSync(worktree_path)) {
56498
57595
  logger.debug(`Manually removing worktree directory`);
56499
- fs25.rmSync(worktree_path, { recursive: true, force: true });
57596
+ fs24.rmSync(worktree_path, { recursive: true, force: true });
56500
57597
  }
56501
57598
  }
56502
57599
  const metadataPath = this.getMetadataPath(worktree_path);
56503
57600
  try {
56504
- if (fs25.existsSync(metadataPath)) {
56505
- fs25.unlinkSync(metadataPath);
57601
+ if (fs24.existsSync(metadataPath)) {
57602
+ fs24.unlinkSync(metadataPath);
56506
57603
  }
56507
57604
  } catch {
56508
57605
  }
@@ -56522,7 +57619,7 @@ var init_worktree_manager = __esm({
56522
57619
  */
56523
57620
  async saveMetadata(worktreePath, metadata) {
56524
57621
  const metadataPath = this.getMetadataPath(worktreePath);
56525
- fs25.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf8");
57622
+ fs24.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf8");
56526
57623
  }
56527
57624
  /**
56528
57625
  * Load worktree metadata
@@ -56530,12 +57627,12 @@ var init_worktree_manager = __esm({
56530
57627
  async loadMetadata(worktreePath) {
56531
57628
  const metadataPath = this.getMetadataPath(worktreePath);
56532
57629
  const legacyPath = path26.join(worktreePath, ".visor-metadata.json");
56533
- const pathToRead = fs25.existsSync(metadataPath) ? metadataPath : fs25.existsSync(legacyPath) ? legacyPath : null;
57630
+ const pathToRead = fs24.existsSync(metadataPath) ? metadataPath : fs24.existsSync(legacyPath) ? legacyPath : null;
56534
57631
  if (!pathToRead) {
56535
57632
  return null;
56536
57633
  }
56537
57634
  try {
56538
- const content = fs25.readFileSync(pathToRead, "utf8");
57635
+ const content = fs24.readFileSync(pathToRead, "utf8");
56539
57636
  return JSON.parse(content);
56540
57637
  } catch (error) {
56541
57638
  logger.warn(`Failed to load metadata: ${error}`);
@@ -56547,10 +57644,10 @@ var init_worktree_manager = __esm({
56547
57644
  */
56548
57645
  async listWorktrees() {
56549
57646
  const worktreesDir = this.getWorktreesDir();
56550
- if (!fs25.existsSync(worktreesDir)) {
57647
+ if (!fs24.existsSync(worktreesDir)) {
56551
57648
  return [];
56552
57649
  }
56553
- const entries = fs25.readdirSync(worktreesDir, { withFileTypes: true });
57650
+ const entries = fs24.readdirSync(worktreesDir, { withFileTypes: true });
56554
57651
  const worktrees = [];
56555
57652
  for (const entry of entries) {
56556
57653
  if (!entry.isDirectory()) continue;
@@ -56638,6 +57735,10 @@ var init_worktree_manager = __esm({
56638
57735
  process.exit(143);
56639
57736
  });
56640
57737
  process.on("uncaughtException", async (error) => {
57738
+ if (isChildProcessIOError(error)) {
57739
+ logger.warn(`Ignoring transient child-process I/O error: ${error.message}`);
57740
+ return;
57741
+ }
56641
57742
  logger.error(`Uncaught exception, cleaning up worktrees: ${error}`);
56642
57743
  await this.cleanupProcessWorktrees();
56643
57744
  process.exit(1);
@@ -60138,6 +61239,36 @@ async function executeCheckWithForEachItems2(checkId, forEachParent, forEachItem
60138
61239
  wave: state.wave
60139
61240
  },
60140
61241
  async (span) => {
61242
+ if (checkConfig.debounce && checkConfig.debounce > 0) {
61243
+ const debounceKey = checkConfig.debounce_key || checkId;
61244
+ const debounceResult = await getDebounceManager().enqueue(
61245
+ debounceKey,
61246
+ checkConfig.debounce,
61247
+ async () => {
61248
+ const r = await executeWithSandboxRouting(
61249
+ checkId,
61250
+ checkConfig,
61251
+ context2,
61252
+ prInfo,
61253
+ dependencyResults,
61254
+ checkConfig.timeout || checkConfig.ai?.timeout || 18e5,
61255
+ () => provider.execute(prInfo, providerConfig, dependencyResults, executionContext)
61256
+ );
61257
+ try {
61258
+ captureCheckOutput(span, r.output);
61259
+ } catch {
61260
+ }
61261
+ return r;
61262
+ }
61263
+ );
61264
+ if (debounceResult.outcome === "debounced") {
61265
+ logger.info(
61266
+ `[LevelDispatch] ${checkId}: debounced (superseded by later invocation)`
61267
+ );
61268
+ return { issues: [], output: { debounced: true } };
61269
+ }
61270
+ return debounceResult.result;
61271
+ }
60141
61272
  const res = await executeWithSandboxRouting(
60142
61273
  checkId,
60143
61274
  checkConfig,
@@ -61289,6 +62420,34 @@ async function executeSingleCheck2(checkId, context2, state, emitEvent, transiti
61289
62420
  wave: state.wave
61290
62421
  },
61291
62422
  async (span) => {
62423
+ if (checkConfig2.debounce && checkConfig2.debounce > 0) {
62424
+ const debounceKey = checkConfig2.debounce_key || checkId;
62425
+ const debounceResult = await getDebounceManager().enqueue(
62426
+ debounceKey,
62427
+ checkConfig2.debounce,
62428
+ async () => {
62429
+ const r = await executeWithSandboxRouting(
62430
+ checkId,
62431
+ checkConfig2,
62432
+ context2,
62433
+ prInfo,
62434
+ dependencyResults,
62435
+ checkConfig2.timeout || checkConfig2.ai?.timeout || 18e5,
62436
+ () => provider.execute(prInfo, providerConfig, dependencyResults, executionContext)
62437
+ );
62438
+ try {
62439
+ captureCheckOutput(span, r.output);
62440
+ } catch {
62441
+ }
62442
+ return r;
62443
+ }
62444
+ );
62445
+ if (debounceResult.outcome === "debounced") {
62446
+ logger.info(`[LevelDispatch] ${checkId}: debounced (superseded by later invocation)`);
62447
+ return { issues: [], output: { debounced: true } };
62448
+ }
62449
+ return debounceResult.result;
62450
+ }
61292
62451
  const res = await executeWithSandboxRouting(
61293
62452
  checkId,
61294
62453
  checkConfig2,
@@ -61422,13 +62581,10 @@ async function executeSingleCheck2(checkId, context2, state, emitEvent, transiti
61422
62581
  }
61423
62582
  let isForEach = result.isForEach;
61424
62583
  let forEachItems2 = result.forEachItems;
61425
- logger.info(
61426
- `[LevelDispatch][DEBUG] After execution ${checkId}: checkConfig.forEach=${checkConfig2.forEach}, output type=${typeof result.output}, isArray=${Array.isArray(result.output)}`
61427
- );
61428
62584
  if (checkConfig2.forEach === true) {
61429
62585
  const output = result.output;
61430
- logger.info(
61431
- `[LevelDispatch][DEBUG] Processing forEach=true for ${checkId}, output=${JSON.stringify(output)?.substring(0, 200)}`
62586
+ logger.debug(
62587
+ `[LevelDispatch] Processing forEach=true for ${checkId}, output=${JSON.stringify(output)?.substring(0, 200)}`
61432
62588
  );
61433
62589
  if (output === void 0) {
61434
62590
  logger.error(`[LevelDispatch] forEach check "${checkId}" produced undefined output`);
@@ -62026,6 +63182,7 @@ var init_level_dispatch = __esm({
62026
63182
  init_workflow_inputs();
62027
63183
  init_sandbox_routing();
62028
63184
  init_policy_gate();
63185
+ init_debounce_manager();
62029
63186
  init_liquid_extensions();
62030
63187
  }
62031
63188
  });
@@ -62573,12 +63730,12 @@ var init_runner = __esm({
62573
63730
  });
62574
63731
 
62575
63732
  // src/utils/file-exclusion.ts
62576
- var import_ignore, fs26, path27, DEFAULT_EXCLUSION_PATTERNS, FileExclusionHelper;
63733
+ var import_ignore, fs25, path27, DEFAULT_EXCLUSION_PATTERNS, FileExclusionHelper;
62577
63734
  var init_file_exclusion = __esm({
62578
63735
  "src/utils/file-exclusion.ts"() {
62579
63736
  "use strict";
62580
63737
  import_ignore = __toESM(require("ignore"));
62581
- fs26 = __toESM(require("fs"));
63738
+ fs25 = __toESM(require("fs"));
62582
63739
  path27 = __toESM(require("path"));
62583
63740
  DEFAULT_EXCLUSION_PATTERNS = [
62584
63741
  "dist/",
@@ -62624,8 +63781,8 @@ var init_file_exclusion = __esm({
62624
63781
  if (additionalPatterns && additionalPatterns.length > 0) {
62625
63782
  this.gitignore.add(additionalPatterns);
62626
63783
  }
62627
- if (fs26.existsSync(gitignorePath)) {
62628
- const rawContent = fs26.readFileSync(gitignorePath, "utf8");
63784
+ if (fs25.existsSync(gitignorePath)) {
63785
+ const rawContent = fs25.readFileSync(gitignorePath, "utf8");
62629
63786
  const gitignoreContent = rawContent.replace(/[\r\n]+/g, "\n").replace(/[\x00-\x09\x0B-\x1F\x7F]/g, "").split("\n").filter((line) => line.length < 1e3).join("\n").trim();
62630
63787
  this.gitignore.add(gitignoreContent);
62631
63788
  if (process.env.VISOR_DEBUG === "true") {
@@ -62657,13 +63814,13 @@ var git_repository_analyzer_exports = {};
62657
63814
  __export(git_repository_analyzer_exports, {
62658
63815
  GitRepositoryAnalyzer: () => GitRepositoryAnalyzer
62659
63816
  });
62660
- var import_simple_git2, path28, fs27, MAX_PATCH_SIZE, GitRepositoryAnalyzer;
63817
+ var import_simple_git2, path28, fs26, MAX_PATCH_SIZE, GitRepositoryAnalyzer;
62661
63818
  var init_git_repository_analyzer = __esm({
62662
63819
  "src/git-repository-analyzer.ts"() {
62663
63820
  "use strict";
62664
63821
  import_simple_git2 = require("simple-git");
62665
63822
  path28 = __toESM(require("path"));
62666
- fs27 = __toESM(require("fs"));
63823
+ fs26 = __toESM(require("fs"));
62667
63824
  init_file_exclusion();
62668
63825
  MAX_PATCH_SIZE = 50 * 1024;
62669
63826
  GitRepositoryAnalyzer = class {
@@ -62959,7 +64116,7 @@ ${file.patch}`).join("\n\n");
62959
64116
  let content;
62960
64117
  let truncated = false;
62961
64118
  try {
62962
- if (includeContext && status !== "added" && fs27.existsSync(filePath)) {
64119
+ if (includeContext && status !== "added" && fs26.existsSync(filePath)) {
62963
64120
  const diff = await this.git.diff(["--", filename]).catch(() => "");
62964
64121
  if (diff) {
62965
64122
  const result = this.truncatePatch(diff, filename);
@@ -62969,7 +64126,7 @@ ${file.patch}`).join("\n\n");
62969
64126
  additions = lines.filter((line) => line.startsWith("+")).length;
62970
64127
  deletions = lines.filter((line) => line.startsWith("-")).length;
62971
64128
  }
62972
- } else if (status !== "added" && fs27.existsSync(filePath)) {
64129
+ } else if (status !== "added" && fs26.existsSync(filePath)) {
62973
64130
  const diff = await this.git.diff(["--", filename]).catch(() => "");
62974
64131
  if (diff) {
62975
64132
  const lines = diff.split("\n");
@@ -62977,17 +64134,17 @@ ${file.patch}`).join("\n\n");
62977
64134
  deletions = lines.filter((line) => line.startsWith("-")).length;
62978
64135
  }
62979
64136
  }
62980
- if (status === "added" && fs27.existsSync(filePath)) {
64137
+ if (status === "added" && fs26.existsSync(filePath)) {
62981
64138
  try {
62982
- const stats = fs27.statSync(filePath);
64139
+ const stats = fs26.statSync(filePath);
62983
64140
  if (stats.isFile() && stats.size < 1024 * 1024) {
62984
64141
  if (includeContext) {
62985
- content = fs27.readFileSync(filePath, "utf8");
64142
+ content = fs26.readFileSync(filePath, "utf8");
62986
64143
  const result = this.truncatePatch(content, filename);
62987
64144
  patch = result.patch;
62988
64145
  truncated = result.truncated;
62989
64146
  }
62990
- const fileContent = includeContext ? content : fs27.readFileSync(filePath, "utf8");
64147
+ const fileContent = includeContext ? content : fs26.readFileSync(filePath, "utf8");
62991
64148
  additions = fileContent.split("\n").length;
62992
64149
  }
62993
64150
  } catch {
@@ -63078,10 +64235,11 @@ function shellEscape(str) {
63078
64235
  function sanitizePathComponent(name) {
63079
64236
  return name.replace(/\.\./g, "").replace(/[\/\\]/g, "-").replace(/^\.+/, "").trim() || "unnamed";
63080
64237
  }
63081
- var fsp2, path29, WorkspaceManager;
64238
+ var fs27, fsp2, path29, WorkspaceManager;
63082
64239
  var init_workspace_manager = __esm({
63083
64240
  "src/utils/workspace-manager.ts"() {
63084
64241
  "use strict";
64242
+ fs27 = __toESM(require("fs"));
63085
64243
  fsp2 = __toESM(require("fs/promises"));
63086
64244
  path29 = __toESM(require("path"));
63087
64245
  init_command_executor();
@@ -63284,12 +64442,7 @@ var init_workspace_manager = __esm({
63284
64442
  }
63285
64443
  }
63286
64444
  try {
63287
- const entries = await fsp2.readdir(this.workspacePath, { withFileTypes: true });
63288
- for (const entry of entries) {
63289
- if (entry.name !== mainProjectName) {
63290
- this.usedNames.add(entry.name);
63291
- }
63292
- }
64445
+ await this.loadExistingProjects(mainProjectName);
63293
64446
  } catch {
63294
64447
  }
63295
64448
  this.registerCleanupHandlers();
@@ -63434,6 +64587,41 @@ var init_workspace_manager = __esm({
63434
64587
  return false;
63435
64588
  }
63436
64589
  }
64590
+ /**
64591
+ * Rehydrate existing symlinked projects in a persisted workspace so future
64592
+ * runs update them in place instead of allocating project-2, project-3, etc.
64593
+ */
64594
+ async loadExistingProjects(mainProjectName) {
64595
+ const entries = await fsp2.readdir(this.workspacePath, { withFileTypes: true });
64596
+ for (const entry of entries) {
64597
+ if (entry.name === mainProjectName) {
64598
+ continue;
64599
+ }
64600
+ const entryPath = path29.join(this.workspacePath, entry.name);
64601
+ this.usedNames.add(entry.name);
64602
+ if (!entry.isSymbolicLink()) {
64603
+ continue;
64604
+ }
64605
+ try {
64606
+ const targetPath = await fsp2.realpath(entryPath);
64607
+ const metadataPath = `${targetPath.replace(/\/?$/, "")}.metadata.json`;
64608
+ if (!fs27.existsSync(metadataPath)) {
64609
+ continue;
64610
+ }
64611
+ const metadata = JSON.parse(await fsp2.readFile(metadataPath, "utf8"));
64612
+ if (!metadata.repository) {
64613
+ continue;
64614
+ }
64615
+ this.projects.set(entry.name, {
64616
+ name: entry.name,
64617
+ path: entryPath,
64618
+ worktreePath: metadata.worktree_path || targetPath,
64619
+ repository: metadata.repository
64620
+ });
64621
+ } catch {
64622
+ }
64623
+ }
64624
+ }
63437
64625
  /**
63438
64626
  * Clean up stale workspace directories older than maxAge.
63439
64627
  * Call periodically (e.g. at socket-runner startup) to prevent disk bloat.
@@ -63449,6 +64637,17 @@ var init_workspace_manager = __esm({
63449
64637
  try {
63450
64638
  const stat2 = await fsp2.stat(dirPath);
63451
64639
  if (now - stat2.mtimeMs > maxAgeMs) {
64640
+ const topGitPath = path29.join(dirPath, ".git");
64641
+ try {
64642
+ const topGitStat = await fsp2.stat(topGitPath);
64643
+ if (topGitStat.isDirectory()) {
64644
+ logger.warn(
64645
+ `[Workspace] Skipping ${dirPath} \u2014 it is a real git repository, not a visor workspace`
64646
+ );
64647
+ continue;
64648
+ }
64649
+ } catch {
64650
+ }
63452
64651
  try {
63453
64652
  const subdirs = await fsp2.readdir(dirPath, { withFileTypes: true });
63454
64653
  for (const sub of subdirs) {
@@ -63460,7 +64659,19 @@ var init_workspace_manager = __esm({
63460
64659
  const match = gitContent.match(/gitdir:\s*(.+)/);
63461
64660
  if (match) {
63462
64661
  const worktreeGitDir = match[1].trim();
64662
+ if (!worktreeGitDir.includes("/.git/worktrees/")) {
64663
+ logger.warn(
64664
+ `[Workspace] Skipping worktree removal for ${subPath} \u2014 gitdir does not point to a worktree`
64665
+ );
64666
+ continue;
64667
+ }
63463
64668
  const repoGitDir = worktreeGitDir.replace(/\/\.git\/worktrees\/.*$/, "");
64669
+ if (!repoGitDir.startsWith(basePath)) {
64670
+ logger.warn(
64671
+ `[Workspace] Skipping worktree removal for ${subPath} \u2014 parent repo ${repoGitDir} is outside workspace`
64672
+ );
64673
+ continue;
64674
+ }
63464
64675
  await commandExecutor.execute(
63465
64676
  `git -C ${shellEscape(repoGitDir)} worktree remove ${shellEscape(subPath)} --force`,
63466
64677
  { timeout: 1e4 }
@@ -65792,8 +67003,11 @@ var init_client = __esm({
65792
67003
  console.warn("Slack auth.test failed (non-fatal); bot user id unavailable");
65793
67004
  return "UNKNOWN_BOT";
65794
67005
  }
67006
+ if (resp.bot_id) this._botId = String(resp.bot_id);
65795
67007
  return String(resp.user_id);
65796
67008
  }
67009
+ /** The bot's bot_id (from auth.test), used to detect own bot_message events */
67010
+ _botId;
65797
67011
  /**
65798
67012
  * Fetch user info from Slack API.
65799
67013
  * Returns user profile including guest status flags, email, display name, and timezone.
@@ -65829,6 +67043,31 @@ var init_client = __esm({
65829
67043
  * Open a DM channel with a user.
65830
67044
  * Returns the DM channel ID.
65831
67045
  */
67046
+ /**
67047
+ * Resolve a channel name (e.g. "function-cve-alerts") to a channel ID.
67048
+ * Uses conversations.list with pagination. Results are cached for the process lifetime.
67049
+ */
67050
+ channelNameCache = /* @__PURE__ */ new Map();
67051
+ async resolveChannelName(name) {
67052
+ const normalized = name.startsWith("#") ? name.slice(1) : name;
67053
+ if (this.channelNameCache.has(normalized)) return this.channelNameCache.get(normalized);
67054
+ let cursor;
67055
+ do {
67056
+ const params = {
67057
+ types: "public_channel,private_channel",
67058
+ limit: 200
67059
+ };
67060
+ if (cursor) params.cursor = cursor;
67061
+ const resp = await this.api("conversations.list", params);
67062
+ if (!resp?.ok) break;
67063
+ for (const ch of resp.channels || []) {
67064
+ this.channelNameCache.set(ch.name, ch.id);
67065
+ if (ch.name === normalized) return ch.id;
67066
+ }
67067
+ cursor = resp.response_metadata?.next_cursor;
67068
+ } while (cursor);
67069
+ return void 0;
67070
+ }
65832
67071
  async openDM(userId) {
65833
67072
  try {
65834
67073
  const resp = await this.api("conversations.open", { users: userId });
@@ -66007,6 +67246,7 @@ var init_slack_frontend = __esm({
66007
67246
  init_lazy_otel();
66008
67247
  init_task_live_updates();
66009
67248
  init_logger();
67249
+ init_user_facing_error();
66010
67250
  SlackFrontend = class {
66011
67251
  name = "slack";
66012
67252
  subs = [];
@@ -66256,7 +67496,7 @@ var init_slack_frontend = __esm({
66256
67496
  if (checkId) text += `
66257
67497
  Check: ${checkId}`;
66258
67498
  if (message) text += `
66259
- ${message}`;
67499
+ ${formatUserFacingExecutionMessage(message)}`;
66260
67500
  if (this.isTelemetryEnabled(ctx)) {
66261
67501
  const suffix = this.getExecutionReferenceSuffix();
66262
67502
  if (suffix) {
@@ -66443,17 +67683,13 @@ ${suffix}`;
66443
67683
  text = (text || "") + "\n\n" + out._rawOutput.trim();
66444
67684
  }
66445
67685
  if (!text) {
66446
- const issues = result?.issues || [];
66447
- const errorIssues = issues.filter(
66448
- (i) => i.severity === "error" && (i.ruleId?.startsWith("system/") || i.ruleId?.endsWith("/error"))
66449
- );
66450
- if (errorIssues.length > 0) {
66451
- const errorMessages = errorIssues.map((i) => i.message).join("\n");
67686
+ const errorMessages = summarizeUserFacingIssues(result?.issues || []);
67687
+ if (errorMessages) {
66452
67688
  text = `:warning: Something went wrong while processing your request:
66453
67689
  ${errorMessages}`;
66454
67690
  this.errorNotified = true;
66455
67691
  ctx.logger.warn(
66456
- `[slack-frontend] posting error fallback for ${checkId}: ${errorIssues.length} system error(s)`
67692
+ `[slack-frontend] posting error fallback for ${checkId}: issue-derived error message`
66457
67693
  );
66458
67694
  }
66459
67695
  }
@@ -67222,6 +68458,7 @@ var init_telegram_frontend = __esm({
67222
68458
  init_client2();
67223
68459
  init_markdown2();
67224
68460
  init_task_live_updates();
68461
+ init_user_facing_error();
67225
68462
  TelegramFrontend = class {
67226
68463
  name = "telegram";
67227
68464
  subs = [];
@@ -67385,7 +68622,7 @@ var init_telegram_frontend = __esm({
67385
68622
  if (checkId) text += `
67386
68623
  Check: ${checkId}`;
67387
68624
  if (message) text += `
67388
- ${message}`;
68625
+ ${formatUserFacingExecutionMessage(message)}`;
67389
68626
  await telegram.sendMessage({
67390
68627
  chat_id: chatId,
67391
68628
  text,
@@ -67437,6 +68674,13 @@ ${message}`;
67437
68674
  if (out && typeof out._rawOutput === "string" && out._rawOutput.trim().length > 0) {
67438
68675
  text = (text || "") + "\n\n" + out._rawOutput.trim();
67439
68676
  }
68677
+ if (!text) {
68678
+ const issueSummary = summarizeUserFacingIssues(result?.issues || []);
68679
+ if (issueSummary) {
68680
+ text = issueSummary;
68681
+ this.errorNotified = true;
68682
+ }
68683
+ }
67440
68684
  if (!text) {
67441
68685
  ctx.logger.info(
67442
68686
  `[telegram-frontend] skip posting reply for ${checkId}: no renderable text`
@@ -75950,6 +77194,7 @@ var init_email_frontend = __esm({
75950
77194
  "use strict";
75951
77195
  init_client3();
75952
77196
  init_markdown3();
77197
+ init_user_facing_error();
75953
77198
  EmailFrontend = class {
75954
77199
  name = "email";
75955
77200
  subs = [];
@@ -76028,12 +77273,14 @@ var init_email_frontend = __esm({
76028
77273
  if (checkId) text += `
76029
77274
  Check: ${checkId}`;
76030
77275
  if (message) text += `
76031
- ${message}`;
77276
+ ${formatUserFacingExecutionMessage(message)}`;
76032
77277
  const sendOpts = {
76033
77278
  to: ev.from,
76034
77279
  subject: ev.subject ? addRePrefix(ev.subject) : `Visor: ${title}`,
76035
77280
  text,
76036
- html: wrapInEmailTemplate(`<p><strong>${title}</strong></p><p>${message}</p>`)
77281
+ html: wrapInEmailTemplate(
77282
+ `<p><strong>${title}</strong></p><p>${formatUserFacingExecutionMessage(message)}</p>`
77283
+ )
76037
77284
  };
76038
77285
  if (ev.messageId) {
76039
77286
  sendOpts.inReplyTo = ev.messageId;
@@ -76083,6 +77330,13 @@ ${message}`;
76083
77330
  if (out && typeof out._rawOutput === "string" && out._rawOutput.trim().length > 0) {
76084
77331
  text = (text || "") + "\n\n" + out._rawOutput.trim();
76085
77332
  }
77333
+ if (!text) {
77334
+ const issueSummary = summarizeUserFacingIssues(result?.issues || []);
77335
+ if (issueSummary) {
77336
+ text = issueSummary;
77337
+ this.errorNotified = true;
77338
+ }
77339
+ }
76086
77340
  if (!text) {
76087
77341
  ctx.logger.info(`[email-frontend] skip posting reply for ${checkId}: no renderable text`);
76088
77342
  return;
@@ -76392,6 +77646,7 @@ var init_whatsapp_frontend = __esm({
76392
77646
  "use strict";
76393
77647
  init_client4();
76394
77648
  init_markdown4();
77649
+ init_user_facing_error();
76395
77650
  WhatsAppFrontend = class {
76396
77651
  name = "whatsapp";
76397
77652
  subs = [];
@@ -76470,7 +77725,7 @@ var init_whatsapp_frontend = __esm({
76470
77725
  if (checkId) text += `
76471
77726
  Check: ${checkId}`;
76472
77727
  if (message) text += `
76473
- ${message}`;
77728
+ ${formatUserFacingExecutionMessage(message)}`;
76474
77729
  await whatsapp.sendMessage({
76475
77730
  to: from,
76476
77731
  text,
@@ -76522,6 +77777,13 @@ ${message}`;
76522
77777
  if (out && typeof out._rawOutput === "string" && out._rawOutput.trim().length > 0) {
76523
77778
  text = (text || "") + "\n\n" + out._rawOutput.trim();
76524
77779
  }
77780
+ if (!text) {
77781
+ const issueSummary = summarizeUserFacingIssues(result?.issues || []);
77782
+ if (issueSummary) {
77783
+ text = issueSummary;
77784
+ this.errorNotified = true;
77785
+ }
77786
+ }
76525
77787
  if (!text) {
76526
77788
  ctx.logger.info(
76527
77789
  `[whatsapp-frontend] skip posting reply for ${checkId}: no renderable text`
@@ -76720,6 +77982,7 @@ var init_teams_frontend = __esm({
76720
77982
  init_client5();
76721
77983
  init_markdown5();
76722
77984
  init_task_live_updates();
77985
+ init_user_facing_error();
76723
77986
  TeamsFrontend = class {
76724
77987
  name = "teams";
76725
77988
  subs = [];
@@ -76799,7 +78062,7 @@ var init_teams_frontend = __esm({
76799
78062
  if (_checkId) text += `
76800
78063
  Check: ${_checkId}`;
76801
78064
  if (message) text += `
76802
- ${message}`;
78065
+ ${formatUserFacingExecutionMessage(message)}`;
76803
78066
  await teams.sendMessage({
76804
78067
  conversationReference: conversationRef,
76805
78068
  text,
@@ -76852,6 +78115,13 @@ ${message}`;
76852
78115
  if (out && typeof out._rawOutput === "string" && out._rawOutput.trim().length > 0) {
76853
78116
  text = (text || "") + "\n\n" + out._rawOutput.trim();
76854
78117
  }
78118
+ if (!text) {
78119
+ const issueSummary = summarizeUserFacingIssues(result?.issues || []);
78120
+ if (issueSummary) {
78121
+ text = issueSummary;
78122
+ this.errorNotified = true;
78123
+ }
78124
+ }
76855
78125
  if (!text) {
76856
78126
  ctx.logger.info(`[teams-frontend] skip posting reply for ${checkId}: no renderable text`);
76857
78127
  return;
@@ -78799,7 +80069,7 @@ var init_state_machine_execution_engine = __esm({
78799
80069
  const gitAnalyzer = new GitRepositoryAnalyzer2(options.workingDirectory);
78800
80070
  logger.info("Analyzing local git repository...");
78801
80071
  const repositoryInfo = await gitAnalyzer.analyzeRepository();
78802
- const hasConversation = !!this.executionContext?.conversation;
80072
+ const hasConversation = !!this.executionContext?.conversation || this.webhookContextHasConversation(options?.webhookContext);
78803
80073
  if (!repositoryInfo.isGitRepository && !hasConversation) {
78804
80074
  return this.createErrorResult(
78805
80075
  repositoryInfo,
@@ -79198,7 +80468,7 @@ var init_state_machine_execution_engine = __esm({
79198
80468
  try {
79199
80469
  const { SessionRegistry: SessionRegistry2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
79200
80470
  const sessionRegistry = SessionRegistry2.getInstance();
79201
- sessionRegistry.clearAllSessions();
80471
+ await sessionRegistry.clearAllSessions();
79202
80472
  } catch (error) {
79203
80473
  logger.debug(`[StateMachine] Failed to cleanup sessions: ${error}`);
79204
80474
  }
@@ -79387,6 +80657,20 @@ var init_state_machine_execution_engine = __esm({
79387
80657
  const { convertToReviewSummary: convertToReviewSummary2 } = (init_summary(), __toCommonJS(summary_exports));
79388
80658
  return convertToReviewSummary2(groupedResults, statistics);
79389
80659
  }
80660
+ webhookContextHasConversation(webhookContext) {
80661
+ try {
80662
+ const webhookData = webhookContext?.webhookData;
80663
+ if (!(webhookData instanceof Map)) return false;
80664
+ for (const payload of webhookData.values()) {
80665
+ const value = payload;
80666
+ if (value?.conversation || value?.slack_conversation || value?.mcp_conversation || value?.telegram_conversation || value?.teams_conversation || value?.whatsapp_conversation || value?.email_conversation) {
80667
+ return true;
80668
+ }
80669
+ }
80670
+ } catch {
80671
+ }
80672
+ return false;
80673
+ }
79390
80674
  /**
79391
80675
  * Evaluate failure conditions for a check result
79392
80676
  *