mixdog 0.9.2 → 0.9.4

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 (417) hide show
  1. package/package.json +8 -3
  2. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  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/review-tasks.json +20 -0
  7. package/scripts/bench/round-codex.json +114 -0
  8. package/scripts/bench/round-mixdog-lead-r3.json +269 -0
  9. package/scripts/bench/round-mixdog-lead.json +269 -0
  10. package/scripts/bench/round-mixdog.json +126 -0
  11. package/scripts/bench/round-r10-bigsample.json +679 -0
  12. package/scripts/bench/round-r11-codexalign.json +257 -0
  13. package/scripts/bench/round-r4-codex.json +114 -0
  14. package/scripts/bench/round-r4-mixed.json +225 -0
  15. package/scripts/bench/round-r5-gpt-lead.json +259 -0
  16. package/scripts/bench/round-r6-codex.json +114 -0
  17. package/scripts/bench/round-r6-solo.json +257 -0
  18. package/scripts/bench/round-r7-full.json +254 -0
  19. package/scripts/bench/round-r8-fulldefault.json +255 -0
  20. package/scripts/bench-run.mjs +215 -29
  21. package/scripts/build-tui.mjs +13 -1
  22. package/scripts/explore-bench.mjs +124 -0
  23. package/scripts/freevar-smoke.mjs +95 -0
  24. package/scripts/hook-bus-test.mjs +191 -0
  25. package/scripts/internal-comms-bench.mjs +1 -0
  26. package/scripts/internal-comms-smoke.mjs +10 -9
  27. package/scripts/mouse-probe.mjs +45 -0
  28. package/scripts/output-style-bench.mjs +13 -6
  29. package/scripts/output-style-smoke.mjs +4 -4
  30. package/scripts/path-suffix-test.mjs +57 -0
  31. package/scripts/provider-toolcall-test.mjs +7 -3
  32. package/scripts/recall-bench.mjs +207 -0
  33. package/scripts/recall-usecase-cases.json +18 -0
  34. package/scripts/recall-usecase-probe.json +6 -0
  35. package/scripts/session-bench.mjs +152 -6
  36. package/scripts/tool-smoke.mjs +30 -67
  37. package/scripts/tui-render-smoke.mjs +90 -0
  38. package/scripts/webhook-smoke.mjs +208 -0
  39. package/src/agents/debugger/AGENT.md +5 -2
  40. package/src/agents/heavy-worker/AGENT.md +21 -11
  41. package/src/agents/maintainer/AGENT.md +4 -0
  42. package/src/agents/reviewer/AGENT.md +3 -2
  43. package/src/agents/worker/AGENT.md +21 -11
  44. package/src/lib/rules-builder.cjs +4 -0
  45. package/src/mixdog-session-runtime.mjs +933 -3731
  46. package/src/output-styles/default.md +34 -9
  47. package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
  48. package/src/output-styles/minimal.md +4 -1
  49. package/src/output-styles/simple.md +22 -7
  50. package/src/repl.mjs +5 -5
  51. package/src/rules/agent/00-common.md +2 -0
  52. package/src/rules/agent/30-explorer.md +8 -11
  53. package/src/rules/lead/lead-brief.md +12 -0
  54. package/src/rules/lead/lead-tool.md +2 -9
  55. package/src/rules/shared/01-tool.md +11 -5
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
  60. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
  61. package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
  62. package/src/runtime/agent/orchestrator/config.mjs +69 -2
  63. package/src/runtime/agent/orchestrator/context/collect.mjs +51 -0
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +63 -21
  66. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +97 -1343
  70. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
  71. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +78 -10
  72. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +1 -13
  73. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
  75. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
  76. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
  77. package/src/runtime/agent/orchestrator/providers/gemini.mjs +44 -1014
  78. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +18 -4
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  80. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
  81. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
  82. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  83. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
  84. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +19 -12
  85. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
  86. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +41 -1142
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +303 -2119
  92. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +140 -995
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
  94. package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
  95. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
  96. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
  97. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +38 -12
  99. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +7 -8
  100. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
  101. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
  102. package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
  103. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
  107. package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
  108. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  109. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +274 -0
  110. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
  111. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  112. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  113. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  114. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +47 -0
  115. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
  116. package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
  117. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  118. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
  120. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  121. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
  122. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  123. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  124. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  125. package/src/runtime/agent/orchestrator/session/loop.mjs +409 -1304
  126. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
  127. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +230 -0
  128. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  129. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +149 -0
  130. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  131. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
  132. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
  133. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  134. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
  135. package/src/runtime/agent/orchestrator/session/manager.mjs +226 -2114
  136. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
  137. package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
  138. package/src/runtime/agent/orchestrator/stall-policy.mjs +3 -3
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
  141. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +33 -42
  142. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  143. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  144. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
  145. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  146. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +1 -1
  147. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  148. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
  149. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
  150. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
  151. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -87
  152. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
  153. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
  154. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +78 -304
  155. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -6
  156. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  157. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  158. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  159. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +551 -0
  160. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  161. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  162. package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
  163. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  164. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  165. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  166. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1080 -0
  167. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  168. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  169. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  170. package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
  171. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  172. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
  173. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +36 -4277
  174. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
  175. package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
  176. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
  177. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
  178. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
  179. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
  180. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
  181. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
  182. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
  183. package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
  184. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -23
  185. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +10 -74
  186. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
  187. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  188. package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
  189. package/src/runtime/channels/backends/discord-access.mjs +32 -0
  190. package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
  191. package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
  192. package/src/runtime/channels/backends/discord.mjs +12 -292
  193. package/src/runtime/channels/index.mjs +241 -894
  194. package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
  195. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  196. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  197. package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
  198. package/src/runtime/channels/lib/event-queue.mjs +63 -4
  199. package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
  200. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  201. package/src/runtime/channels/lib/output-forwarder.mjs +9 -1
  202. package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
  203. package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
  204. package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
  205. package/src/runtime/channels/lib/session-discovery.mjs +56 -4
  206. package/src/runtime/channels/lib/telegram-format.mjs +19 -22
  207. package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
  208. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  209. package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
  210. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
  211. package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
  212. package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
  213. package/src/runtime/channels/lib/webhook/log.mjs +42 -0
  214. package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
  215. package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
  216. package/src/runtime/channels/lib/webhook.mjs +36 -570
  217. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  218. package/src/runtime/channels/tool-defs.mjs +11 -130
  219. package/src/runtime/memory/index.mjs +258 -2050
  220. package/src/runtime/memory/lib/core-memory-store.mjs +351 -1
  221. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
  222. package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
  223. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  224. package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
  225. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  226. package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
  227. package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
  228. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
  229. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
  230. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
  231. package/src/runtime/memory/lib/memory-cycle2.mjs +72 -837
  232. package/src/runtime/memory/lib/memory-embed.mjs +149 -0
  233. package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
  234. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  235. package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
  236. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  237. package/src/runtime/memory/lib/memory.mjs +20 -0
  238. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
  239. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  240. package/src/runtime/memory/lib/query-handlers.mjs +780 -0
  241. package/src/runtime/memory/lib/recall-format.mjs +238 -0
  242. package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
  243. package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
  244. package/src/runtime/memory/tool-defs.mjs +6 -14
  245. package/src/runtime/search/lib/http-fetch.mjs +274 -0
  246. package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
  247. package/src/runtime/search/lib/web-tools.mjs +24 -602
  248. package/src/runtime/shared/abort-controller.mjs +1 -1
  249. package/src/runtime/shared/atomic-file.mjs +26 -1
  250. package/src/runtime/shared/background-tasks.mjs +2 -3
  251. package/src/runtime/shared/buffered-appender.mjs +149 -0
  252. package/src/runtime/shared/launcher-control.mjs +2 -2
  253. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  254. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  255. package/src/runtime/shared/tool-primitives.mjs +308 -0
  256. package/src/runtime/shared/tool-result-summary.mjs +515 -0
  257. package/src/runtime/shared/tool-surface.mjs +80 -898
  258. package/src/runtime/shared/transcript-writer.mjs +52 -2
  259. package/src/runtime/shared/update-checker.mjs +7 -4
  260. package/src/session-runtime/config-helpers.mjs +291 -0
  261. package/src/session-runtime/config-lifecycle.mjs +232 -0
  262. package/src/session-runtime/cwd-plugins.mjs +226 -0
  263. package/src/session-runtime/effort.mjs +128 -0
  264. package/src/session-runtime/fs-utils.mjs +10 -0
  265. package/src/session-runtime/mcp-glue.mjs +177 -0
  266. package/src/session-runtime/model-capabilities.mjs +130 -0
  267. package/src/session-runtime/model-recency.mjs +111 -0
  268. package/src/session-runtime/native-search.mjs +247 -0
  269. package/src/session-runtime/output-styles.mjs +126 -0
  270. package/src/session-runtime/plugin-mcp.mjs +114 -0
  271. package/src/session-runtime/prewarm.mjs +142 -0
  272. package/src/session-runtime/provider-models.mjs +278 -0
  273. package/src/session-runtime/provider-usage.mjs +120 -0
  274. package/src/session-runtime/quick-model-rows.mjs +170 -0
  275. package/src/session-runtime/quick-search-models.mjs +46 -0
  276. package/src/session-runtime/session-hooks.mjs +93 -0
  277. package/src/session-runtime/session-text.mjs +100 -0
  278. package/src/session-runtime/settings-api.mjs +319 -0
  279. package/src/session-runtime/statusline-route.mjs +35 -0
  280. package/src/session-runtime/tool-catalog.mjs +720 -0
  281. package/src/session-runtime/tool-defs.mjs +84 -0
  282. package/src/session-runtime/warmup-schedulers.mjs +201 -0
  283. package/src/session-runtime/workflow.mjs +358 -0
  284. package/src/standalone/agent-tool/helpers.mjs +237 -0
  285. package/src/standalone/agent-tool/notify.mjs +107 -0
  286. package/src/standalone/agent-tool/provider-init.mjs +143 -0
  287. package/src/standalone/agent-tool/render.mjs +152 -0
  288. package/src/standalone/agent-tool/tool-def.mjs +55 -0
  289. package/src/standalone/agent-tool.mjs +155 -677
  290. package/src/standalone/channel-worker.mjs +7 -9
  291. package/src/standalone/explore-tool.mjs +40 -12
  292. package/src/standalone/hook-bus/config.mjs +207 -0
  293. package/src/standalone/hook-bus/constants.mjs +90 -0
  294. package/src/standalone/hook-bus/handlers.mjs +481 -0
  295. package/src/standalone/hook-bus/payload.mjs +31 -0
  296. package/src/standalone/hook-bus/rules.mjs +77 -0
  297. package/src/standalone/hook-bus.mjs +110 -746
  298. package/src/standalone/memory-runtime-proxy.mjs +7 -0
  299. package/src/standalone/opencode-go-login.mjs +125 -0
  300. package/src/standalone/provider-admin.mjs +15 -19
  301. package/src/standalone/usage-dashboard.mjs +3 -1
  302. package/src/tui/App.jsx +1163 -7571
  303. package/src/tui/app/app-format.mjs +206 -0
  304. package/src/tui/app/channel-pickers.mjs +510 -0
  305. package/src/tui/app/clipboard.mjs +67 -0
  306. package/src/tui/app/core-memory-picker.mjs +210 -0
  307. package/src/tui/app/extension-pickers.mjs +506 -0
  308. package/src/tui/app/input-parsers.mjs +193 -0
  309. package/src/tui/app/maintenance-pickers.mjs +324 -0
  310. package/src/tui/app/model-options.mjs +330 -0
  311. package/src/tui/app/model-picker.mjs +365 -0
  312. package/src/tui/app/onboarding-steps.mjs +400 -0
  313. package/src/tui/app/project-picker.mjs +247 -0
  314. package/src/tui/app/provider-setup-picker.mjs +580 -0
  315. package/src/tui/app/resume-picker.mjs +55 -0
  316. package/src/tui/app/route-pickers.mjs +419 -0
  317. package/src/tui/app/settings-picker.mjs +490 -0
  318. package/src/tui/app/slash-commands.mjs +101 -0
  319. package/src/tui/app/slash-dispatch.mjs +427 -0
  320. package/src/tui/app/text-layout.mjs +46 -0
  321. package/src/tui/app/theme-effort-pickers.mjs +154 -0
  322. package/src/tui/app/transcript-window.mjs +671 -0
  323. package/src/tui/app/use-mouse-input.mjs +460 -0
  324. package/src/tui/app/use-prompt-handlers.mjs +310 -0
  325. package/src/tui/app/use-transcript-scroll.mjs +510 -0
  326. package/src/tui/app/use-transcript-window.mjs +589 -0
  327. package/src/tui/components/ConfirmBar.jsx +1 -1
  328. package/src/tui/components/Picker.jsx +32 -4
  329. package/src/tui/components/PromptInput.jsx +259 -80
  330. package/src/tui/components/SlashCommandPalette.jsx +8 -1
  331. package/src/tui/components/StatusLine.jsx +63 -12
  332. package/src/tui/components/TextEntryPanel.jsx +11 -0
  333. package/src/tui/components/ToolExecution.jsx +56 -588
  334. package/src/tui/components/TranscriptItem.jsx +105 -0
  335. package/src/tui/components/UsagePanel.jsx +18 -4
  336. package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
  337. package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
  338. package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
  339. package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
  340. package/src/tui/components/tool-execution/text-format.mjs +161 -0
  341. package/src/tui/components/tool-output-format.mjs +2 -2
  342. package/src/tui/display-width.mjs +20 -3
  343. package/src/tui/dist/index.mjs +18034 -17188
  344. package/src/tui/engine/agent-envelope.mjs +296 -0
  345. package/src/tui/engine/agent-job-feed.mjs +133 -0
  346. package/src/tui/engine/boot-profile.mjs +21 -0
  347. package/src/tui/engine/labels.mjs +67 -0
  348. package/src/tui/engine/notice-text.mjs +112 -0
  349. package/src/tui/engine/notification-plan.mjs +76 -0
  350. package/src/tui/engine/queue-helpers.mjs +161 -0
  351. package/src/tui/engine/render-timing.mjs +17 -0
  352. package/src/tui/engine/session-stats.mjs +46 -0
  353. package/src/tui/engine/tool-approval.mjs +94 -0
  354. package/src/tui/engine/tool-call-fields.mjs +23 -0
  355. package/src/tui/engine/tool-card-results.mjs +234 -0
  356. package/src/tui/engine/tool-result-status.mjs +135 -0
  357. package/src/tui/engine/tool-result-text.mjs +126 -0
  358. package/src/tui/engine.mjs +405 -1385
  359. package/src/tui/figures.mjs +5 -0
  360. package/src/tui/index.jsx +105 -0
  361. package/src/tui/input-editing.mjs +60 -10
  362. package/src/tui/keyboard-protocol.mjs +2 -2
  363. package/src/tui/lib/voice-recorder.mjs +35 -19
  364. package/src/tui/markdown/format-token.mjs +11 -9
  365. package/src/tui/paste-attachments.mjs +38 -0
  366. package/src/tui/statusline-ansi-bridge.mjs +11 -3
  367. package/src/tui/theme.mjs +6 -0
  368. package/src/tui/themes/base.mjs +2 -2
  369. package/src/tui/themes/kanagawa.mjs +4 -4
  370. package/src/tui/themes/teal.mjs +4 -5
  371. package/src/tui/themes/utils.mjs +1 -1
  372. package/src/ui/statusline-agents.mjs +213 -0
  373. package/src/ui/statusline-format.mjs +146 -0
  374. package/src/ui/statusline-segments.mjs +148 -0
  375. package/src/ui/statusline.mjs +77 -462
  376. package/src/ui/tool-card.mjs +0 -1
  377. package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
  378. package/src/workflows/default/WORKFLOW.md +16 -9
  379. package/src/workflows/sequential/WORKFLOW.md +16 -11
  380. package/src/workflows/solo/WORKFLOW.md +5 -1
  381. package/vendor/ink/build/display-width.js +19 -3
  382. package/vendor/ink/build/ink.js +103 -6
  383. package/vendor/ink/build/log-update.js +17 -3
  384. package/vendor/ink/build/wrap-text.js +125 -0
  385. package/scripts/_test-folder-dialog.mjs +0 -30
  386. package/scripts/fix-brief-fn.mjs +0 -35
  387. package/scripts/fix-format-tool-surface.mjs +0 -24
  388. package/scripts/fix-tool-exec-visible.mjs +0 -42
  389. package/scripts/patch-agent-brief.mjs +0 -48
  390. package/scripts/patch-app.mjs +0 -21
  391. package/scripts/patch-app2.mjs +0 -18
  392. package/scripts/patch-dist-brief.mjs +0 -96
  393. package/scripts/patch-tool-exec.mjs +0 -70
  394. package/src/examples/schedules/SCHEDULE.example.md +0 -32
  395. package/src/examples/webhooks/WEBHOOK.example.md +0 -40
  396. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
  397. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
  398. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
  399. package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
  400. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
  401. package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
  402. package/src/standalone/agent-task-status.test.mjs +0 -76
  403. package/src/tui/components/tool-output-format.test.mjs +0 -399
  404. package/src/tui/display-width.test.mjs +0 -35
  405. package/src/tui/engine-runtime-notification.test.mjs +0 -115
  406. package/src/tui/engine-tool-result-text.test.mjs +0 -75
  407. package/src/tui/markdown/format-token.test.mjs +0 -354
  408. package/src/tui/markdown/render-ansi.test.mjs +0 -108
  409. package/src/tui/markdown/stream-fence.test.mjs +0 -26
  410. package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
  411. package/src/tui/prompt-history-store.test.mjs +0 -52
  412. package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
  413. package/src/tui/transcript-tool-failures.test.mjs +0 -111
  414. package/src/ui/markdown.test.mjs +0 -70
  415. package/src/ui/statusline-context-label.test.mjs +0 -15
  416. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
  417. package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
@@ -1,5 +1,4 @@
1
- // Per-tool live-status message builder (ported from a reference agent's progress
2
- // generalization / getActivityDescription). Dependency-light and
1
+ // Per-tool live-status message builder. Dependency-light and
3
2
  // side-effect-free: given a tool name + its raw args, return a short
4
3
  // human-readable "what's happening now" string. Used by the central dispatch
5
4
  // path to emit a single start-of-tool progress notification. Every tool gets a
@@ -37,9 +36,6 @@ export function formatToolStartProgress(name, args = {}) {
37
36
  ? 'listing background tasks'
38
37
  : `${action === 'wait' ? 'waiting for' : action} task ${_t(a.task_id)}`;
39
38
  }
40
- case 'diagnostics':
41
- return `running diagnostics${a.path ? ` for ${_t(a.path)}` : ''}`;
42
-
43
39
  // ── builtin / code_graph: search & navigation ────────────────────
44
40
  case 'grep':
45
41
  return Array.isArray(a.pattern) ? `searching for ${_plural(a.pattern.length, 'pattern')}` : `searching for ${_t(a.pattern)}`;
@@ -83,26 +79,8 @@ export function formatToolStartProgress(name, args = {}) {
83
79
  // ── channels module ──────────────────────────────────────────────
84
80
  case 'reply':
85
81
  return 'replying';
86
- case 'react':
87
- return 'reacting';
88
- case 'edit_message':
89
- return 'editing message';
90
- case 'download_attachment':
91
- return 'downloading attachment';
92
82
  case 'fetch':
93
83
  return 'fetching messages';
94
- case 'schedule_status':
95
- return 'checking schedules';
96
- case 'trigger_schedule':
97
- return a.name ? `triggering ${_t(a.name)}` : 'triggering schedule';
98
- case 'schedule_control':
99
- return 'scheduling';
100
- case 'activate_channel_bridge':
101
- return 'activating channel';
102
- case 'reload_config':
103
- return 'reloading config';
104
- case 'inject_command':
105
- return 'injecting command';
106
84
 
107
85
  // ── host_input / cwd ─────────────────────────────────────────────
108
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
@@ -53,8 +62,7 @@ const SHELL_OUTPUT_DISK_CAP = 100 * 1024 * 1024;
53
62
 
54
63
  // Background-task disk watchdog cadence. The size guard polls the spilled
55
64
  // stdout/stderr files every interval and SIGKILLs the child once the
56
- // combined size exceeds SHELL_OUTPUT_DISK_CAP. 5 s matches reference agent
57
- // upstream cadence — short enough that a runaway loop is caught within a
65
+ // combined size exceeds SHELL_OUTPUT_DISK_CAP short enough that a runaway loop is caught within a
58
66
  // few seconds, long enough that the stat overhead is negligible.
59
67
  const SIZE_WATCHDOG_INTERVAL_MS = 1_000;
60
68
 
@@ -416,78 +424,6 @@ export class ExecResult {
416
424
  }
417
425
  }
418
426
 
419
- // On Windows, nested `powershell -Command "<inline>"` invocations can be
420
- // mangled by an outer shell quoting layer before powershell.exe sees
421
- // automatic variables (`$_`, `$args`, `$($_.Line)`, etc.). Rewrite
422
- // `powershell -Command "<inline>"` / `pwsh -Command "<inline>"` to
423
- // `-EncodedCommand <utf16le-base64>` so the payload stays opaque to the
424
- // outer shell. Other shells / non-Windows are no-op pass-through.
425
- // Match -Command "<body>" where the body may contain escaped quotes
426
- // (`\"` or `""`). Stops at the first unescaped closing quote so nested
427
- // patterns like `"... \"inner\" ..."` survive intact. Common PowerShell
428
- // flags (NoProfile, NonInteractive, WindowStyle, ExecutionPolicy, Sta,
429
- // Mta, NoLogo, NoExit) are recognised so they don't break the match.
430
- // Single-quoted -Command '<body>' is also covered.
431
- const _POWERSHELL_FLAGS_RE = /\s+-(?:NoProfile|NonInteractive|WindowStyle\s+\S+|ExecutionPolicy\s+\S+|Sta|Mta|NoLogo|NoExit)/.source;
432
- const _POWERSHELL_DOUBLE_RE = new RegExp(
433
- '\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:' + _POWERSHELL_FLAGS_RE + ')*)\\s+(?:-Command|-c)\\s+"((?:[^"\\\\]|\\\\.|"")+?)"(?=\\s|$|;|&&|\\|\\|)',
434
- 'gi',
435
- );
436
- const _POWERSHELL_SINGLE_RE = new RegExp(
437
- "\\b(powershell(?:\\.exe)?|pwsh(?:\\.exe)?)((?:" + _POWERSHELL_FLAGS_RE + ")*)\\s+(?:-Command|-c)\\s+'((?:[^'\\\\]|\\\\.|'')+?)'(?=\\s|$|;|&&|\\|\\|)",
438
- 'gi',
439
- );
440
-
441
- export function _maybeEncodePowerShellCommand(command) {
442
- if (process.platform !== 'win32') return command;
443
- if (typeof command !== 'string' || command.length === 0) return command;
444
- const replaceFn = (match, exe, flags, body) => {
445
- try {
446
- // Unescape doubled-up quotes the caller used to embed " / ' inside
447
- // the -Command literal. We're handing the body to powershell as
448
- // base64 so the outer-shell escaping is no longer needed.
449
- // Unescape both PowerShell-style doubled quotes (`""` / `''`) AND
450
- // bash-style backslash-escaped quotes (`\"` / `\'`) since POSIX
451
- // outer-shell wrappers commonly use backslash form. Without
452
- // backslash unescape, `pwsh -Command "Get-Process \"foo\""` would
453
- // base64-encode the literal backslash, breaking inside PowerShell.
454
- const unescaped = body
455
- .replace(/""/g, '"')
456
- .replace(/''/g, "'")
457
- .replace(/\\"/g, '"')
458
- .replace(/\\'/g, "'");
459
- const encoded = Buffer.from(unescaped, 'utf16le').toString('base64');
460
- const trimmedFlags = (flags || '').replace(/\s+/g, ' ').trim();
461
- return `${exe}${trimmedFlags ? ' ' + trimmedFlags : ''} -EncodedCommand ${encoded}`;
462
- } catch {
463
- return match;
464
- }
465
- };
466
- return command.replace(_POWERSHELL_DOUBLE_RE, replaceFn).replace(_POWERSHELL_SINGLE_RE, replaceFn);
467
- }
468
-
469
- function _unescapePowerShellCommandBody(body) {
470
- return String(body || '')
471
- .replace(/""/g, '"')
472
- .replace(/''/g, "'")
473
- .replace(/\\"/g, '"')
474
- .replace(/\\'/g, "'");
475
- }
476
-
477
- // Extract inline `powershell -Command "…"` bodies for policy scan parity
478
- // with hard-block normalization (encoded payloads use decodePowerShellEncodedCommand).
479
- export function extractPowerShellCommandInner(command) {
480
- if (typeof command !== 'string' || command.length === 0) return [];
481
- const out = [];
482
- const push = (body) => {
483
- const unescaped = _unescapePowerShellCommandBody(body);
484
- if (unescaped.trim()) out.push(unescaped);
485
- };
486
- for (const m of command.matchAll(_POWERSHELL_DOUBLE_RE)) push(m[3]);
487
- for (const m of command.matchAll(_POWERSHELL_SINGLE_RE)) push(m[3]);
488
- return out;
489
- }
490
-
491
427
  // One-shot async shell runner. abortSignal optional (session-scoped abort
492
428
  // from getAbortSignalForSession in builtin.mjs). Timeout implemented via
493
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
+ }
@@ -8,8 +8,7 @@
8
8
  // pyenv / mise / asdf / direnv setup the user gets in their interactive
9
9
  // terminal — without paying a fresh login-shell startup on every call.
10
10
  //
11
- // Mirrors upstream shell snapshot pattern (reference ShellSnapshot.ts:413,
12
- // createAndSaveSnapshot). Simpler scope: bash and zsh only, no embedded
11
+ // Scope: bash and zsh only, no embedded
13
12
  // search-tool injection (mixdog ships its own grep/glob helpers).
14
13
 
15
14
  import { spawn } from 'node:child_process';
@@ -34,8 +33,7 @@ const _cache = new Map();
34
33
  // command. Cleared on process exit (process-scoped Set).
35
34
  const _failedShells = new Set();
36
35
 
37
- // Mirrors reference cleanupRegistry pattern (utils/
38
- // cleanupRegistry.ts + ShellSnapshot.ts:534-545). Snapshot files are
36
+ // Snapshot files are
39
37
  // session-scoped and must be unlinked on graceful shutdown — otherwise
40
38
  // they pile up forever (each rc-file mtime change creates a new file).
41
39
  const _activeSnapshots = new Set();
@@ -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 };