mixdog 0.9.3 → 0.9.5

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 (382) hide show
  1. package/README.md +112 -38
  2. package/package.json +10 -3
  3. package/scripts/bench/lead-review-tasks-r3.json +20 -0
  4. package/scripts/bench/lead-review-tasks.json +20 -0
  5. package/scripts/bench/r4-mixed-tasks.json +20 -0
  6. package/scripts/bench/r5-orchestrated-task.json +7 -0
  7. package/scripts/bench/review-tasks.json +20 -0
  8. package/scripts/bench/round-codex.json +114 -0
  9. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  10. package/scripts/bench/round-mixdog-lead.json +269 -0
  11. package/scripts/bench/round-mixdog.json +126 -0
  12. package/scripts/bench/round-r10-bigsample.json +679 -0
  13. package/scripts/bench/round-r11-codexalign.json +257 -0
  14. package/scripts/bench/round-r13-clientmeta.json +464 -0
  15. package/scripts/bench/round-r14-betafeatures.json +466 -0
  16. package/scripts/bench/round-r15-fulldefault.json +462 -0
  17. package/scripts/bench/round-r16-sessionid.json +466 -0
  18. package/scripts/bench/round-r17-wirebytes.json +456 -0
  19. package/scripts/bench/round-r18-prewarm.json +468 -0
  20. package/scripts/bench/round-r19-clean.json +472 -0
  21. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  22. package/scripts/bench/round-r21-delta-retry.json +473 -0
  23. package/scripts/bench/round-r22-full-probe.json +693 -0
  24. package/scripts/bench/round-r23-itemprobe.json +701 -0
  25. package/scripts/bench/round-r24-shapefix.json +677 -0
  26. package/scripts/bench/round-r25-serial.json +464 -0
  27. package/scripts/bench/round-r26-parallel3.json +671 -0
  28. package/scripts/bench/round-r27-parallel10.json +894 -0
  29. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  30. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  31. package/scripts/bench/round-r30-instid.json +253 -0
  32. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  33. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  34. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  35. package/scripts/bench/round-r34-orchestrated.json +120 -0
  36. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  37. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  38. package/scripts/bench/round-r4-codex.json +114 -0
  39. package/scripts/bench/round-r4-mixed.json +225 -0
  40. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  41. package/scripts/bench/round-r6-codex.json +114 -0
  42. package/scripts/bench/round-r6-solo.json +257 -0
  43. package/scripts/bench/round-r7-full.json +254 -0
  44. package/scripts/bench/round-r8-fulldefault.json +255 -0
  45. package/scripts/bench-run.mjs +251 -32
  46. package/scripts/freevar-smoke.mjs +95 -0
  47. package/scripts/internal-comms-bench.mjs +3 -4
  48. package/scripts/internal-comms-smoke.mjs +10 -9
  49. package/scripts/model-catalog-audit.mjs +209 -0
  50. package/scripts/model-list-sanitize-test.mjs +37 -0
  51. package/scripts/mouse-probe.mjs +45 -0
  52. package/scripts/output-style-bench.mjs +13 -6
  53. package/scripts/output-style-smoke.mjs +4 -4
  54. package/scripts/provider-toolcall-test.mjs +7 -3
  55. package/scripts/recall-bench.mjs +76 -13
  56. package/scripts/recall-quality-cases.json +12 -0
  57. package/scripts/recall-usecase-cases.json +18 -0
  58. package/scripts/session-bench.mjs +152 -6
  59. package/scripts/tool-smoke.mjs +25 -65
  60. package/scripts/tui-render-smoke.mjs +90 -0
  61. package/scripts/webhook-smoke.mjs +208 -0
  62. package/src/agents/debugger/AGENT.md +4 -1
  63. package/src/agents/heavy-worker/AGENT.md +9 -8
  64. package/src/agents/maintainer/AGENT.md +4 -0
  65. package/src/agents/reviewer/AGENT.md +2 -1
  66. package/src/agents/scheduler-task/AGENT.md +2 -3
  67. package/src/agents/webhook-handler/AGENT.md +2 -3
  68. package/src/agents/worker/AGENT.md +10 -7
  69. package/src/app.mjs +12 -1
  70. package/src/headless-role.mjs +7 -1
  71. package/src/lib/rules-builder.cjs +4 -0
  72. package/src/mixdog-session-runtime.mjs +647 -2056
  73. package/src/output-styles/default.md +30 -9
  74. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  75. package/src/output-styles/minimal.md +8 -6
  76. package/src/output-styles/simple.md +21 -7
  77. package/src/rules/agent/00-common.md +6 -3
  78. package/src/rules/agent/30-explorer.md +16 -5
  79. package/src/rules/lead/01-general.md +5 -5
  80. package/src/rules/lead/lead-brief.md +15 -0
  81. package/src/rules/lead/lead-tool.md +6 -15
  82. package/src/rules/shared/01-tool.md +17 -21
  83. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  84. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  85. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  86. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  87. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  88. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  89. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  90. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  91. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
  92. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  93. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
  95. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  96. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
  97. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  99. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  100. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  101. package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
  102. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
  103. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +105 -11
  104. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +356 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  109. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
  110. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +740 -0
  111. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  112. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +349 -2131
  113. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +143 -1002
  114. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +229 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +465 -0
  117. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  119. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  120. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  121. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  122. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  123. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  124. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  125. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  126. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  127. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  128. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
  129. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
  131. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +275 -0
  132. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  133. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  134. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  135. package/src/runtime/agent/orchestrator/session/loop.mjs +278 -402
  136. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  137. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
  138. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
  139. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  140. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  141. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  142. package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
  143. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  144. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  145. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  146. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  147. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  148. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +35 -44
  149. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  152. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  155. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  156. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
  157. package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
  158. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
  159. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
  161. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  163. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  164. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  165. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  166. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  167. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  168. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  169. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  170. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  171. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  172. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  173. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
  174. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
  175. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  176. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  177. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  178. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  179. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  180. package/src/runtime/channels/backends/discord.mjs +27 -318
  181. package/src/runtime/channels/backends/telegram.mjs +8 -12
  182. package/src/runtime/channels/index.mjs +247 -701
  183. package/src/runtime/channels/lib/backend-dispatch.mjs +46 -0
  184. package/src/runtime/channels/lib/config.mjs +37 -149
  185. package/src/runtime/channels/lib/event-pipeline.mjs +22 -5
  186. package/src/runtime/channels/lib/event-queue.mjs +78 -13
  187. package/src/runtime/channels/lib/inbound-routing.mjs +74 -0
  188. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  189. package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
  190. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  191. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  192. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  193. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  194. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  195. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  196. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  197. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  198. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  199. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  200. package/src/runtime/channels/lib/webhook/deliveries.mjs +313 -0
  201. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  202. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  203. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  204. package/src/runtime/channels/lib/webhook.mjs +43 -616
  205. package/src/runtime/channels/tool-defs.mjs +11 -130
  206. package/src/runtime/memory/index.mjs +210 -1948
  207. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  208. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  209. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  210. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  211. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  212. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  213. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  214. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  215. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  216. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  217. package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
  218. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  219. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  220. package/src/runtime/memory/lib/memory-recall-store.mjs +69 -12
  221. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  222. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  223. package/src/runtime/memory/lib/query-handlers.mjs +802 -0
  224. package/src/runtime/memory/lib/recall-format.mjs +55 -0
  225. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  226. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  227. package/src/runtime/memory/tool-defs.mjs +5 -13
  228. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  229. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  230. package/src/runtime/search/lib/web-tools.mjs +24 -602
  231. package/src/runtime/shared/atomic-file.mjs +26 -1
  232. package/src/runtime/shared/config.mjs +14 -4
  233. package/src/runtime/shared/launcher-control.mjs +2 -2
  234. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  235. package/src/runtime/shared/schedules-store.mjs +13 -3
  236. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  237. package/src/runtime/shared/tool-primitives.mjs +308 -0
  238. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  239. package/src/runtime/shared/tool-surface.mjs +80 -898
  240. package/src/runtime/shared/transcript-writer.mjs +23 -0
  241. package/src/runtime/shared/update-checker.mjs +7 -4
  242. package/src/session-runtime/config-helpers.mjs +119 -2
  243. package/src/session-runtime/config-lifecycle.mjs +232 -0
  244. package/src/session-runtime/cwd-plugins.mjs +226 -0
  245. package/src/session-runtime/mcp-glue.mjs +177 -0
  246. package/src/session-runtime/model-recency.mjs +111 -0
  247. package/src/session-runtime/native-search.mjs +247 -0
  248. package/src/session-runtime/output-styles.mjs +11 -9
  249. package/src/session-runtime/prewarm.mjs +142 -0
  250. package/src/session-runtime/provider-models.mjs +278 -0
  251. package/src/session-runtime/provider-usage.mjs +120 -0
  252. package/src/session-runtime/quick-model-rows.mjs +205 -0
  253. package/src/session-runtime/quick-search-models.mjs +47 -0
  254. package/src/session-runtime/session-hooks.mjs +93 -0
  255. package/src/session-runtime/settings-api.mjs +352 -0
  256. package/src/session-runtime/tool-catalog.mjs +29 -29
  257. package/src/session-runtime/tool-defs.mjs +84 -0
  258. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  259. package/src/session-runtime/workflow.mjs +1 -1
  260. package/src/standalone/agent-tool/helpers.mjs +237 -0
  261. package/src/standalone/agent-tool/notify.mjs +107 -0
  262. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  263. package/src/standalone/agent-tool/render.mjs +152 -0
  264. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  265. package/src/standalone/agent-tool.mjs +138 -669
  266. package/src/standalone/channel-admin.mjs +102 -90
  267. package/src/standalone/channel-worker.mjs +4 -7
  268. package/src/standalone/explore-tool.mjs +64 -14
  269. package/src/standalone/hook-bus/config.mjs +207 -0
  270. package/src/standalone/hook-bus/constants.mjs +90 -0
  271. package/src/standalone/hook-bus/handlers.mjs +481 -0
  272. package/src/standalone/hook-bus/payload.mjs +31 -0
  273. package/src/standalone/hook-bus/rules.mjs +77 -0
  274. package/src/standalone/hook-bus.mjs +77 -870
  275. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  276. package/src/standalone/opencode-go-login.mjs +5 -1
  277. package/src/standalone/provider-admin.mjs +1 -16
  278. package/src/standalone/usage-dashboard.mjs +3 -1
  279. package/src/tui/App.jsx +1059 -8110
  280. package/src/tui/app/app-format.mjs +213 -0
  281. package/src/tui/app/channel-pickers.mjs +508 -0
  282. package/src/tui/app/clipboard.mjs +67 -0
  283. package/src/tui/app/core-memory-picker.mjs +210 -0
  284. package/src/tui/app/extension-pickers.mjs +506 -0
  285. package/src/tui/app/input-parsers.mjs +193 -0
  286. package/src/tui/app/maintenance-pickers.mjs +356 -0
  287. package/src/tui/app/model-options.mjs +334 -0
  288. package/src/tui/app/model-picker.mjs +365 -0
  289. package/src/tui/app/onboarding-steps.mjs +400 -0
  290. package/src/tui/app/project-picker.mjs +247 -0
  291. package/src/tui/app/provider-setup-picker.mjs +580 -0
  292. package/src/tui/app/resume-picker.mjs +55 -0
  293. package/src/tui/app/route-pickers.mjs +419 -0
  294. package/src/tui/app/settings-picker.mjs +489 -0
  295. package/src/tui/app/slash-commands.mjs +101 -0
  296. package/src/tui/app/slash-dispatch.mjs +427 -0
  297. package/src/tui/app/text-layout.mjs +46 -0
  298. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  299. package/src/tui/app/transcript-window.mjs +677 -0
  300. package/src/tui/app/use-mouse-input.mjs +460 -0
  301. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  302. package/src/tui/app/use-transcript-scroll.mjs +512 -0
  303. package/src/tui/app/use-transcript-window.mjs +607 -0
  304. package/src/tui/components/ConfirmBar.jsx +10 -7
  305. package/src/tui/components/Picker.jsx +64 -15
  306. package/src/tui/components/PromptInput.jsx +33 -102
  307. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  308. package/src/tui/components/StatusLine.jsx +69 -15
  309. package/src/tui/components/TextEntryPanel.jsx +11 -0
  310. package/src/tui/components/ToolExecution.jsx +52 -594
  311. package/src/tui/components/TranscriptItem.jsx +105 -0
  312. package/src/tui/components/UsagePanel.jsx +18 -4
  313. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  314. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  315. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  316. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  317. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  318. package/src/tui/display-width.mjs +20 -3
  319. package/src/tui/dist/index.mjs +13553 -12384
  320. package/src/tui/engine/agent-job-feed.mjs +133 -0
  321. package/src/tui/engine/notification-plan.mjs +76 -0
  322. package/src/tui/engine/render-timing.mjs +17 -0
  323. package/src/tui/engine/tool-approval.mjs +94 -0
  324. package/src/tui/engine/tool-card-results.mjs +234 -0
  325. package/src/tui/engine/tool-result-status.mjs +135 -0
  326. package/src/tui/engine.mjs +170 -574
  327. package/src/tui/figures.mjs +5 -0
  328. package/src/tui/index.jsx +65 -1
  329. package/src/tui/input-editing.mjs +2 -2
  330. package/src/tui/markdown/format-token.mjs +4 -1
  331. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  332. package/src/tui/theme.mjs +6 -0
  333. package/src/ui/statusline-agents.mjs +213 -0
  334. package/src/ui/statusline-format.mjs +146 -0
  335. package/src/ui/statusline-segments.mjs +148 -0
  336. package/src/ui/statusline.mjs +77 -501
  337. package/src/ui/tool-card.mjs +0 -1
  338. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  339. package/src/workflows/default/WORKFLOW.md +16 -18
  340. package/src/workflows/sequential/WORKFLOW.md +16 -18
  341. package/vendor/ink/build/display-width.js +19 -3
  342. package/vendor/ink/build/ink.js +112 -7
  343. package/vendor/ink/build/log-update.js +17 -3
  344. package/vendor/ink/build/wrap-text.js +125 -0
  345. package/scripts/_test-folder-dialog.mjs +0 -30
  346. package/scripts/fix-brief-fn.mjs +0 -35
  347. package/scripts/fix-format-tool-surface.mjs +0 -24
  348. package/scripts/fix-tool-exec-visible.mjs +0 -42
  349. package/scripts/patch-agent-brief.mjs +0 -48
  350. package/scripts/patch-app.mjs +0 -21
  351. package/scripts/patch-app2.mjs +0 -18
  352. package/scripts/patch-dist-brief.mjs +0 -96
  353. package/scripts/patch-tool-exec.mjs +0 -70
  354. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  355. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  356. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  357. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  358. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  359. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
  360. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  361. package/src/runtime/channels/lib/holidays.mjs +0 -138
  362. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  363. package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
  364. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  365. package/src/standalone/agent-task-status.test.mjs +0 -76
  366. package/src/tui/components/tool-output-format.test.mjs +0 -399
  367. package/src/tui/display-width.test.mjs +0 -35
  368. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  369. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  370. package/src/tui/input-editing.selection.test.mjs +0 -75
  371. package/src/tui/markdown/format-token.test.mjs +0 -354
  372. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  373. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  374. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  375. package/src/tui/paste-fix.test.mjs +0 -119
  376. package/src/tui/prompt-history-store.test.mjs +0 -52
  377. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  378. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  379. package/src/ui/markdown.test.mjs +0 -70
  380. package/src/ui/statusline-context-label.test.mjs +0 -15
  381. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  382. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -36,9 +36,6 @@ export function formatToolStartProgress(name, args = {}) {
36
36
  ? 'listing background tasks'
37
37
  : `${action === 'wait' ? 'waiting for' : action} task ${_t(a.task_id)}`;
38
38
  }
39
- case 'diagnostics':
40
- return `running diagnostics${a.path ? ` for ${_t(a.path)}` : ''}`;
41
-
42
39
  // ── builtin / code_graph: search & navigation ────────────────────
43
40
  case 'grep':
44
41
  return Array.isArray(a.pattern) ? `searching for ${_plural(a.pattern.length, 'pattern')}` : `searching for ${_t(a.pattern)}`;
@@ -82,26 +79,8 @@ export function formatToolStartProgress(name, args = {}) {
82
79
  // ── channels module ──────────────────────────────────────────────
83
80
  case 'reply':
84
81
  return 'replying';
85
- case 'react':
86
- return 'reacting';
87
- case 'edit_message':
88
- return 'editing message';
89
- case 'download_attachment':
90
- return 'downloading attachment';
91
82
  case 'fetch':
92
83
  return 'fetching messages';
93
- case 'schedule_status':
94
- return 'checking schedules';
95
- case 'trigger_schedule':
96
- return a.name ? `triggering ${_t(a.name)}` : 'triggering schedule';
97
- case 'schedule_control':
98
- return 'scheduling';
99
- case 'activate_channel_bridge':
100
- return 'activating channel';
101
- case 'reload_config':
102
- return 'reloading config';
103
- case 'inject_command':
104
- return 'injecting command';
105
84
 
106
85
  // ── host_input / cwd ─────────────────────────────────────────────
107
86
  case 'inject_input':
@@ -38,6 +38,15 @@ import { startChildGuardian } from '../../../shared/child-guardian.mjs';
38
38
  // a static cycle — safe because neither binding is touched at module-eval
39
39
  // time, only when the respective functions actually run.
40
40
  import { adoptForegroundShellJob } from './builtin/shell-jobs.mjs';
41
+ import {
42
+ _maybeEncodePowerShellCommand,
43
+ extractPowerShellCommandInner,
44
+ } from './shell-powershell.mjs';
45
+
46
+ export {
47
+ _maybeEncodePowerShellCommand,
48
+ extractPowerShellCommandInner,
49
+ } from './shell-powershell.mjs';
41
50
 
42
51
  // Inline cap. Output above this size is spilled to disk and the caller
43
52
  // renders a path marker instead of pasting the tail. Matches the
@@ -415,78 +424,6 @@ export class ExecResult {
415
424
  }
416
425
  }
417
426
 
418
- // On Windows, nested `powershell -Command "<inline>"` invocations can be
419
- // mangled by an outer shell quoting layer before powershell.exe sees
420
- // automatic variables (`$_`, `$args`, `$($_.Line)`, etc.). Rewrite
421
- // `powershell -Command "<inline>"` / `pwsh -Command "<inline>"` to
422
- // `-EncodedCommand <utf16le-base64>` so the payload stays opaque to the
423
- // outer shell. Other shells / non-Windows are no-op pass-through.
424
- // Match -Command "<body>" where the body may contain escaped quotes
425
- // (`\"` or `""`). Stops at the first unescaped closing quote so nested
426
- // patterns like `"... \"inner\" ..."` survive intact. Common PowerShell
427
- // flags (NoProfile, NonInteractive, WindowStyle, ExecutionPolicy, Sta,
428
- // Mta, NoLogo, NoExit) are recognised so they don't break the match.
429
- // Single-quoted -Command '<body>' is also covered.
430
- const _POWERSHELL_FLAGS_RE = /\s+-(?:NoProfile|NonInteractive|WindowStyle\s+\S+|ExecutionPolicy\s+\S+|Sta|Mta|NoLogo|NoExit)/.source;
431
- const _POWERSHELL_DOUBLE_RE = new RegExp(
432
- '\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:' + _POWERSHELL_FLAGS_RE + ')*)\\s+(?:-Command|-c)\\s+"((?:[^"\\\\]|\\\\.|"")+?)"(?=\\s|$|;|&&|\\|\\|)',
433
- 'gi',
434
- );
435
- const _POWERSHELL_SINGLE_RE = new RegExp(
436
- "\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:" + _POWERSHELL_FLAGS_RE + ")*)\\s+(?:-Command|-c)\\s+'((?:[^'\\\\]|\\\\.|'')+?)'(?=\\s|$|;|&&|\\|\\|)",
437
- 'gi',
438
- );
439
-
440
- export function _maybeEncodePowerShellCommand(command) {
441
- if (process.platform !== 'win32') return command;
442
- if (typeof command !== 'string' || command.length === 0) return command;
443
- const replaceFn = (match, exe, flags, body) => {
444
- try {
445
- // Unescape doubled-up quotes the caller used to embed " / ' inside
446
- // the -Command literal. We're handing the body to powershell as
447
- // base64 so the outer-shell escaping is no longer needed.
448
- // Unescape both PowerShell-style doubled quotes (`""` / `''`) AND
449
- // bash-style backslash-escaped quotes (`\"` / `\'`) since POSIX
450
- // outer-shell wrappers commonly use backslash form. Without
451
- // backslash unescape, `pwsh -Command "Get-Process \"foo\""` would
452
- // base64-encode the literal backslash, breaking inside PowerShell.
453
- const unescaped = body
454
- .replace(/""/g, '"')
455
- .replace(/''/g, "'")
456
- .replace(/\\"/g, '"')
457
- .replace(/\\'/g, "'");
458
- const encoded = Buffer.from(unescaped, 'utf16le').toString('base64');
459
- const trimmedFlags = (flags || '').replace(/\s+/g, ' ').trim();
460
- return `${exe}${trimmedFlags ? ' ' + trimmedFlags : ''} -EncodedCommand ${encoded}`;
461
- } catch {
462
- return match;
463
- }
464
- };
465
- return command.replace(_POWERSHELL_DOUBLE_RE, replaceFn).replace(_POWERSHELL_SINGLE_RE, replaceFn);
466
- }
467
-
468
- function _unescapePowerShellCommandBody(body) {
469
- return String(body || '')
470
- .replace(/""/g, '"')
471
- .replace(/''/g, "'")
472
- .replace(/\\"/g, '"')
473
- .replace(/\\'/g, "'");
474
- }
475
-
476
- // Extract inline `powershell -Command "…"` bodies for policy scan parity
477
- // with hard-block normalization (encoded payloads use decodePowerShellEncodedCommand).
478
- export function extractPowerShellCommandInner(command) {
479
- if (typeof command !== 'string' || command.length === 0) return [];
480
- const out = [];
481
- const push = (body) => {
482
- const unescaped = _unescapePowerShellCommandBody(body);
483
- if (unescaped.trim()) out.push(unescaped);
484
- };
485
- for (const m of command.matchAll(_POWERSHELL_DOUBLE_RE)) push(m[3]);
486
- for (const m of command.matchAll(_POWERSHELL_SINGLE_RE)) push(m[3]);
487
- return out;
488
- }
489
-
490
427
  // One-shot async shell runner. abortSignal optional (session-scoped abort
491
428
  // from getAbortSignalForSession in builtin.mjs). Timeout implemented via
492
429
  // treeKill so forked grandchildren also come down. Output streams capture
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+ // PowerShell inline-command normalization + policy-scan extraction. Extracted
3
+ // verbatim from shell-command.mjs (behavior-preserving). shell-command.mjs
4
+ // re-exports _maybeEncodePowerShellCommand / extractPowerShellCommandInner so
5
+ // the module's public export surface is unchanged.
6
+ //
7
+ // On Windows, nested `powershell -Command "<inline>"` invocations can be
8
+ // mangled by an outer shell quoting layer before powershell.exe sees
9
+ // automatic variables (`$_`, `$args`, `$($_.Line)`, etc.). Rewrite
10
+ // `powershell -Command "<inline>"` / `pwsh -Command "<inline>"` to
11
+ // `-EncodedCommand <utf16le-base64>` so the payload stays opaque to the
12
+ // outer shell. Other shells / non-Windows are no-op pass-through.
13
+ // Match -Command "<body>" where the body may contain escaped quotes
14
+ // (`\"` or `""`). Stops at the first unescaped closing quote so nested
15
+ // patterns like `"... \"inner\" ..."` survive intact. Common PowerShell
16
+ // flags (NoProfile, NonInteractive, WindowStyle, ExecutionPolicy, Sta,
17
+ // Mta, NoLogo, NoExit) are recognised so they don't break the match.
18
+ // Single-quoted -Command '<body>' is also covered.
19
+ const _POWERSHELL_FLAGS_RE = /\s+-(?:NoProfile|NonInteractive|WindowStyle\s+\S+|ExecutionPolicy\s+\S+|Sta|Mta|NoLogo|NoExit)/.source;
20
+ const _POWERSHELL_DOUBLE_RE = new RegExp(
21
+ '\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:' + _POWERSHELL_FLAGS_RE + ')*)\\s+(?:-Command|-c)\\s+"((?:[^"\\\\]|\\\\.|"")+?)"(?=\\s|$|;|&&|\\|\\|)',
22
+ 'gi',
23
+ );
24
+ const _POWERSHELL_SINGLE_RE = new RegExp(
25
+ "\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:" + _POWERSHELL_FLAGS_RE + ")*)\\s+(?:-Command|-c)\\s+'((?:[^'\\\\]|\\\\.|'')+?)'(?=\\s|$|;|&&|\\|\\|)",
26
+ 'gi',
27
+ );
28
+
29
+ export function _maybeEncodePowerShellCommand(command) {
30
+ if (process.platform !== 'win32') return command;
31
+ if (typeof command !== 'string' || command.length === 0) return command;
32
+ const replaceFn = (match, exe, flags, body) => {
33
+ try {
34
+ // Unescape doubled-up quotes the caller used to embed " / ' inside
35
+ // the -Command literal. We're handing the body to powershell as
36
+ // base64 so the outer-shell escaping is no longer needed.
37
+ // Unescape both PowerShell-style doubled quotes (`""` / `''`) AND
38
+ // bash-style backslash-escaped quotes (`\"` / `\'`) since POSIX
39
+ // outer-shell wrappers commonly use backslash form. Without
40
+ // backslash unescape, `pwsh -Command "Get-Process \"foo\""` would
41
+ // base64-encode the literal backslash, breaking inside PowerShell.
42
+ const unescaped = body
43
+ .replace(/""/g, '"')
44
+ .replace(/''/g, "'")
45
+ .replace(/\\"/g, '"')
46
+ .replace(/\\'/g, "'");
47
+ const encoded = Buffer.from(unescaped, 'utf16le').toString('base64');
48
+ const trimmedFlags = (flags || '').replace(/\s+/g, ' ').trim();
49
+ return `${exe}${trimmedFlags ? ' ' + trimmedFlags : ''} -EncodedCommand ${encoded}`;
50
+ } catch {
51
+ return match;
52
+ }
53
+ };
54
+ return command.replace(_POWERSHELL_DOUBLE_RE, replaceFn).replace(_POWERSHELL_SINGLE_RE, replaceFn);
55
+ }
56
+
57
+ function _unescapePowerShellCommandBody(body) {
58
+ return String(body || '')
59
+ .replace(/""/g, '"')
60
+ .replace(/''/g, "'")
61
+ .replace(/\\"/g, '"')
62
+ .replace(/\\'/g, "'");
63
+ }
64
+
65
+ // Extract inline `powershell -Command "…"` bodies for policy scan parity
66
+ // with hard-block normalization (encoded payloads use decodePowerShellEncodedCommand).
67
+ export function extractPowerShellCommandInner(command) {
68
+ if (typeof command !== 'string' || command.length === 0) return [];
69
+ const out = [];
70
+ const push = (body) => {
71
+ const unescaped = _unescapePowerShellCommandBody(body);
72
+ if (unescaped.trim()) out.push(unescaped);
73
+ };
74
+ for (const m of command.matchAll(_POWERSHELL_DOUBLE_RE)) push(m[3]);
75
+ for (const m of command.matchAll(_POWERSHELL_SINGLE_RE)) push(m[3]);
76
+ return out;
77
+ }
@@ -0,0 +1,154 @@
1
+ 'use strict';
2
+ // Claude Code-style shell state carry-over for the one-shot shell tool.
3
+ //
4
+ // No live shell process is kept. Instead, after each sync command we chain a
5
+ // trailing cwd probe that writes the final working directory to a per-session
6
+ // state file. The next shell call (without an explicit cwd arg) reads that file
7
+ // and starts there — mimicking a persistent terminal's cwd without a PTY.
8
+ //
9
+ // Style mirrors shell-snapshot.mjs: files live under getPluginData(), one file
10
+ // per session key, with a stale-file sweep on module init.
11
+
12
+ import { existsSync, mkdirSync, readFileSync, statSync, utimesSync } from 'node:fs';
13
+ import { readdir as readdirAsync, stat as statAsync, unlink as unlinkAsync } from 'node:fs/promises';
14
+ import { join } from 'node:path';
15
+ import { createHash } from 'node:crypto';
16
+ import { getPluginData } from '../config.mjs';
17
+
18
+ function _stateDir() {
19
+ return join(getPluginData(), 'shell-state');
20
+ }
21
+
22
+ // One file per session key. Hash the key so arbitrary session ids (which may
23
+ // contain path-hostile chars) map to a safe, collision-resistant filename.
24
+ function _keyFor(sessionKey) {
25
+ const raw = String(sessionKey == null || sessionKey === '' ? '__default__' : sessionKey);
26
+ return createHash('sha1').update(raw).digest('hex').slice(0, 16);
27
+ }
28
+
29
+ function _stateFileFor(sessionKey) {
30
+ return join(_stateDir(), `cwd-${_keyFor(sessionKey)}.txt`);
31
+ }
32
+
33
+ // Stale-file sweep on module init. Mirrors shell-snapshot's _sweepStaleSnapshots:
34
+ // state files older than the cutoff belong to sessions that are long gone.
35
+ const STATE_STALE_MS = 24 * 60 * 60 * 1000;
36
+ async function _sweepStaleState() {
37
+ const dir = _stateDir();
38
+ if (!existsSync(dir)) return;
39
+ const cutoff = Date.now() - STATE_STALE_MS;
40
+ let names;
41
+ try { names = await readdirAsync(dir); } catch { return; }
42
+ await Promise.all(names.map(async (name) => {
43
+ if (!name.startsWith('cwd-') || !name.endsWith('.txt')) return;
44
+ const p = join(dir, name);
45
+ try {
46
+ const st = await statAsync(p);
47
+ if (st.mtimeMs < cutoff) {
48
+ // Tolerate the stat→unlink race: a concurrent write may have
49
+ // refreshed mtime, or another sweeper may have already removed
50
+ // it. ENOENT and any transient error are non-fatal.
51
+ try { await unlinkAsync(p); } catch (err) { if (err && err.code !== 'ENOENT') { /* swallow */ } }
52
+ }
53
+ } catch {}
54
+ }));
55
+ }
56
+ _sweepStaleState();
57
+
58
+ // Resolve the effective cwd for a shell call. Explicit cwd always wins (the
59
+ // caller resolved/validated it already). Otherwise, if a stored cwd exists and
60
+ // still points at a live directory, use it; else fall back to defaultCwd.
61
+ export function resolveSessionCwd(sessionKey, explicitCwd, defaultCwd) {
62
+ if (explicitCwd) return explicitCwd;
63
+ // No nonblank session key → no carry-over (mirrors stateFilePath). Without a
64
+ // real key every caller would share the `__default__` file and read each
65
+ // other's cwd, so skip the store entirely and use the default.
66
+ if (sessionKey == null || String(sessionKey).trim() === '') return defaultCwd;
67
+ try {
68
+ const file = _stateFileFor(sessionKey);
69
+ if (!existsSync(file)) return defaultCwd;
70
+ const stored = readFileSync(file, 'utf-8').trim();
71
+ if (!stored) return defaultCwd;
72
+ const st = statSync(stored);
73
+ if (st.isDirectory()) return stored;
74
+ } catch {}
75
+ return defaultCwd;
76
+ }
77
+
78
+ // Path to the state file for this session (created lazily). Returned so the
79
+ // caller can embed it in the probe chain. Returns null if the dir can't be made
80
+ // OR when there is no nonblank session key — without a real key all callers
81
+ // would collapse onto a single shared `__default__` file and cross-contaminate
82
+ // each other's cwd, so we skip carry-over entirely in that case.
83
+ export function stateFilePath(sessionKey) {
84
+ if (sessionKey == null || String(sessionKey).trim() === '') return null;
85
+ const dir = _stateDir();
86
+ try { mkdirSync(dir, { recursive: true }); } catch { return null; }
87
+ const file = _stateFileFor(sessionKey);
88
+ // Refresh mtime on every write so an old-but-live session file is not
89
+ // reaped by the stale sweep mid-session. `touch`-equivalent: bump atime/
90
+ // mtime to now if the file already exists; a fresh file gets its mtime on
91
+ // first write by the probe redirection.
92
+ try {
93
+ if (existsSync(file)) {
94
+ const now = new Date();
95
+ utimesSync(file, now, now);
96
+ }
97
+ } catch {}
98
+ return file;
99
+ }
100
+
101
+ // PowerShell single-quote escape ('' escapes a literal quote inside '...').
102
+ function _psQuote(s) {
103
+ return `'${String(s).replace(/'/g, "''")}'`;
104
+ }
105
+
106
+ // POSIX single-quote escape.
107
+ function _shQuote(s) {
108
+ return `'${String(s).replace(/'/g, "'\\''")}'`;
109
+ }
110
+
111
+ // Append a trailing cwd probe so the final working directory is persisted after
112
+ // the user command runs. The probe is joined with `;` (statement separator, not
113
+ // `&&`) so it runs regardless of the command's exit status, and it does not
114
+ // touch the exit code the runtime reports: the runtime reads the child's exit
115
+ // code, and neither `Out-File` nor a redirection changes `$?`/`$LASTEXITCODE`
116
+ // semantics observed by the *process* exit — the shell's exit status is the
117
+ // last statement's, but see wrapWithCwdProbe callers which capture the code
118
+ // BEFORE the probe. For PowerShell we snapshot $LASTEXITCODE/$? and restore the
119
+ // process exit via `exit` so the probe cannot mask a non-zero command.
120
+ export function wrapPowerShellWithCwdProbe(command, stateFile) {
121
+ if (!stateFile) return command;
122
+ const qFile = _psQuote(stateFile);
123
+ // $?-driven, bash-like exit semantics. $LASTEXITCODE persists the last
124
+ // *native* process's code indefinitely, so a prologue reset cannot cover
125
+ // intra-command staleness (`cmd /c exit 5; Write-Output ok` leaves the
126
+ // trailing cmdlet succeeding but $LASTEXITCODE still 5). Instead we mirror
127
+ // bash: the exit code is the LAST statement's success. $? immediately after
128
+ // the command reflects that statement — false only when it failed, and for
129
+ // a failing native call $LASTEXITCODE holds its real code (case3 → 7).
130
+ //
131
+ // The `if (-not $?)` check MUST be the first statement after the command;
132
+ // any intervening statement resets $?. Terminating errors (`throw`) skip to
133
+ // catch → $__ec = 1. The cwd probe runs in finally regardless, and we exit
134
+ // with $__ec so the probe never masks the observed code.
135
+ return '$global:LASTEXITCODE = 0\n'
136
+ + '$__ec = 0\n'
137
+ + 'try {\n'
138
+ + `${command}\n`
139
+ + ' if (-not $?) { $__ec = if ($LASTEXITCODE) { $LASTEXITCODE } else { 1 } }\n'
140
+ + '} catch { $__ec = 1 }\n'
141
+ + `finally { (Get-Location).Path | Out-File -Encoding utf8 ${qFile} }\n`
142
+ + 'exit $__ec';
143
+ }
144
+
145
+ export function wrapBashWithCwdProbe(command, stateFile) {
146
+ if (!stateFile) return command;
147
+ const qFile = _shQuote(stateFile);
148
+ // Capture $? before the probe, run the probe unconditionally (`;`), then
149
+ // re-exit with the saved status so `pwd`/redirection cannot mask it.
150
+ return `${command}\n`
151
+ + '__mixdog_ec=$?;\n'
152
+ + `pwd -P >| ${qFile} 2>/dev/null || true;\n`
153
+ + 'exit $__mixdog_ec';
154
+ }
@@ -0,0 +1,32 @@
1
+ // Access-config helpers extracted from discord.mjs. Pure functions with no
2
+ // dependency on the backend instance; behavior is identical to the originals.
3
+
4
+ function defaultAccess() {
5
+ return {
6
+ dmPolicy: "allowlist",
7
+ allowFrom: [],
8
+ channels: {}
9
+ };
10
+ }
11
+ function normalizeAccess(parsed) {
12
+ const defaults = defaultAccess();
13
+ return {
14
+ // Legacy "pairing" policy was removed (its approval flow was never
15
+ // completable); normalize it to "allowlist" on load.
16
+ dmPolicy: parsed?.dmPolicy === "pairing" ? "allowlist" : (parsed?.dmPolicy ?? defaults.dmPolicy),
17
+ allowFrom: parsed?.allowFrom ?? defaults.allowFrom,
18
+ channels: parsed?.channels ?? defaults.channels,
19
+ mentionPatterns: parsed?.mentionPatterns,
20
+ // Setup UI historically saved a boolean toggle; runtime needs an emoji
21
+ // string for msg.react(). true → default emoji, non-string → off.
22
+ ackReaction: parsed?.ackReaction === true
23
+ ? "✅"
24
+ : (typeof parsed?.ackReaction === "string" && parsed.ackReaction ? parsed.ackReaction : undefined),
25
+ replyToMode: parsed?.replyToMode,
26
+ textChunkLimit: parsed?.textChunkLimit,
27
+ };
28
+ }
29
+ function safeAttName(att) {
30
+ return (att.name ?? att.id).replace(/[\[\]\r\n;]/g, "_");
31
+ }
32
+ export { defaultAccess, normalizeAccess, safeAttName };
@@ -0,0 +1,65 @@
1
+ // Attachment download + streaming size-guard extracted from discord.mjs.
2
+ // Standalone function; takes inboxDir explicitly instead of `this`.
3
+ import { writeFileSync, mkdirSync, realpathSync } from "fs";
4
+ import { join, sep } from "path";
5
+
6
+ const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
7
+
8
+ async function downloadSingleAttachment(att, inboxDir) {
9
+ if (att.size > MAX_ATTACHMENT_BYTES) {
10
+ throw new Error(
11
+ `attachment too large: ${(att.size / 1024 / 1024).toFixed(1)}MB, max ${MAX_ATTACHMENT_BYTES / 1024 / 1024}MB`
12
+ );
13
+ }
14
+ const res = await fetch(att.url, { signal: AbortSignal.timeout(180_000) });
15
+ if (!res.ok) {
16
+ throw new Error(`attachment download failed: HTTP ${res.status}`);
17
+ }
18
+ if (!res.body) {
19
+ throw new Error(`attachment download returned empty body: ${att.name ?? att.id}`);
20
+ }
21
+ // Stream the response so an oversized payload is aborted before the
22
+ // full body lands in memory. Buffering via arrayBuffer() first would
23
+ // already exceed MAX_ATTACHMENT_BYTES by the time we checked length.
24
+ const reader = res.body.getReader();
25
+ const chunks = [];
26
+ let received = 0;
27
+ try {
28
+ while (true) {
29
+ const { done, value } = await reader.read();
30
+ if (done) break;
31
+ if (!value) continue;
32
+ received += value.byteLength;
33
+ if (received > MAX_ATTACHMENT_BYTES) {
34
+ try { await reader.cancel(); } catch {}
35
+ throw new Error(
36
+ `attachment payload too large: exceeded ${MAX_ATTACHMENT_BYTES / 1024 / 1024}MB while streaming (${att.name ?? att.id})`
37
+ );
38
+ }
39
+ chunks.push(value);
40
+ }
41
+ } finally {
42
+ try { reader.releaseLock(); } catch {}
43
+ }
44
+ if (received === 0) {
45
+ throw new Error(`attachment download returned empty buffer: ${att.name ?? att.id}`);
46
+ }
47
+ const buf = Buffer.concat(chunks.map((c) => Buffer.from(c.buffer, c.byteOffset, c.byteLength)), received);
48
+ if (att.size > 0 && buf.length !== att.size) {
49
+ process.stderr.write(`mixdog discord: attachment size mismatch: expected ${att.size} got ${buf.length} (${att.name ?? att.id})\n`);
50
+ }
51
+ const name = att.name ?? `${att.id}`;
52
+ const rawExt = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1) : "bin";
53
+ const ext = rawExt.replace(/[^a-zA-Z0-9]/g, "") || "bin";
54
+ const safeId = String(att.id).replace(/[^a-zA-Z0-9_-]/g, "_");
55
+ mkdirSync(inboxDir, { recursive: true });
56
+ const candidate = join(inboxDir, `${Date.now()}-${safeId}.${ext}`);
57
+ const resolvedInbox = realpathSync(inboxDir);
58
+ if (!candidate.startsWith(resolvedInbox + sep)) {
59
+ throw new Error(`attachment path traversal rejected: ${candidate}`);
60
+ }
61
+ writeFileSync(candidate, buf);
62
+ return candidate;
63
+ }
64
+
65
+ export { MAX_ATTACHMENT_BYTES, downloadSingleAttachment };