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
@@ -0,0 +1,508 @@
1
+ /**
2
+ * channel-pickers.mjs — the Channel setup picker cluster.
3
+ *
4
+ * Extracted from App.jsx behavior-preservingly as a dependency-injection
5
+ * factory. Every function body is the original App logic verbatim, with closure
6
+ * identifiers threaded through the factory argument. Cross-references between
7
+ * the three openers stay inside this factory; the onboarding-only dep
8
+ * (onboardingWarnReopen) threads as a lazy getter wrapper so it resolves the
9
+ * live opener at call time.
10
+ */
11
+ import { theme } from '../theme.mjs';
12
+
13
+ export function createChannelPickers({
14
+ store,
15
+ setPicker,
16
+ setProviderPrompt,
17
+ setChannelPrompt,
18
+ setHookPrompt,
19
+ setSettingsPrompt,
20
+ setContextPanel,
21
+ onboardingWarnReopen,
22
+ }) {
23
+ const openChannelTypeActionsPicker = (backend, options = {}) => {
24
+ const parentReturn = typeof options.returnTo === 'function'
25
+ ? options.returnTo
26
+ : () => openChannelSettingTypePicker();
27
+ setProviderPrompt(null);
28
+ setHookPrompt(null);
29
+ setSettingsPrompt(null);
30
+ setContextPanel(null);
31
+ let setup;
32
+ try {
33
+ setup = store.getChannelSetup();
34
+ } catch (e) {
35
+ store.pushNotice(`channels failed: ${e?.message || e}`, 'error');
36
+ return;
37
+ }
38
+ const isTelegram = backend === 'telegram';
39
+ const activeBackend = setup.backend || 'discord';
40
+ const tokenDescription = isTelegram
41
+ ? `${setup.telegram?.status ?? 'Off'}${setup.telegram?.problem ? ' · Invalid' : ''}`
42
+ : `${setup.discord.status}${setup.discord.problem ? ' · Invalid' : ''}`;
43
+ const mainEntry = setup.channel || {};
44
+ const mainTarget = isTelegram
45
+ ? (mainEntry?.telegramChatId || (activeBackend === 'telegram' ? mainEntry?.channelId : ''))
46
+ : (mainEntry?.discordChannelId || (activeBackend === 'discord' ? mainEntry?.channelId : ''));
47
+ const mainDescription = isTelegram
48
+ ? (mainTarget ? `Chat ID ${mainTarget}` : 'Not set · Enter Telegram chat ID')
49
+ : (mainTarget ? `Channel ID ${mainTarget}` : 'Not set · Enter Discord channel ID');
50
+
51
+ const openChannelPrompt = (prompt) => {
52
+ setPicker(null);
53
+ setContextPanel(null);
54
+ setChannelPrompt({
55
+ ...prompt,
56
+ afterSave: () => openChannelTypeActionsPicker(backend, options),
57
+ });
58
+ };
59
+
60
+ setPicker({
61
+ title: isTelegram ? 'Telegram' : 'Discord',
62
+ description: activeBackend === backend
63
+ ? 'Active channel type · token and main target'
64
+ : 'Token and main target settings',
65
+ help: '↑/↓ Select · Enter Edit · Esc Back',
66
+ indexMode: 'always',
67
+ labelWidth: 18,
68
+ items: [
69
+ {
70
+ value: 'token',
71
+ label: 'Bot token',
72
+ description: tokenDescription,
73
+ _action: isTelegram ? 'telegram-token' : 'discord-token',
74
+ },
75
+ {
76
+ value: 'main',
77
+ label: isTelegram ? 'Main chat' : 'Main channel',
78
+ description: mainDescription,
79
+ _action: 'main-target',
80
+ },
81
+ ],
82
+ onSelect: (_value, item) => {
83
+ try {
84
+ if (item._action === 'discord-token') {
85
+ openChannelPrompt({
86
+ kind: 'discord-token',
87
+ label: 'Discord bot token',
88
+ hint: 'Paste the Discord bot token. It is stored in the OS keychain.',
89
+ });
90
+ return;
91
+ }
92
+ if (item._action === 'telegram-token') {
93
+ openChannelPrompt({
94
+ kind: 'telegram-token',
95
+ label: 'Telegram bot token',
96
+ hint: 'Paste the Telegram bot token from @BotFather. Stored in the OS keychain.',
97
+ });
98
+ return;
99
+ }
100
+ if (item._action === 'main-target') {
101
+ openChannelPrompt({
102
+ kind: 'channel-add',
103
+ backend,
104
+ label: isTelegram ? 'Main chat' : 'Main channel',
105
+ hint: isTelegram
106
+ ? 'Paste the Telegram chat ID.'
107
+ : 'Paste the Discord channel ID.',
108
+ });
109
+ }
110
+ } catch (e) {
111
+ store.pushNotice(`channels update failed: ${e?.message || e}`, 'error');
112
+ }
113
+ },
114
+ onCancel: () => {
115
+ parentReturn();
116
+ },
117
+ });
118
+ };
119
+
120
+ const openChannelSettingTypePicker = (options = {}) => {
121
+ const returnTo = typeof options.returnTo === 'function' ? options.returnTo : () => {};
122
+ setProviderPrompt(null);
123
+ setChannelPrompt(null);
124
+ setHookPrompt(null);
125
+ setSettingsPrompt(null);
126
+ setContextPanel(null);
127
+ let setup;
128
+ try {
129
+ setup = store.getChannelSetup();
130
+ } catch (e) {
131
+ store.pushNotice(`channels failed: ${e?.message || e}`, 'error');
132
+ return;
133
+ }
134
+ const activeBackend = setup.backend || 'discord';
135
+ const mainEntry = setup.channel || {};
136
+ const typeDescription = (backend) => {
137
+ const selected = activeBackend === backend;
138
+ const hasToken = backend === 'telegram'
139
+ ? setup.telegram?.authenticated === true
140
+ : setup.discord?.authenticated === true;
141
+ const hasTarget = backend === 'telegram'
142
+ ? Boolean(mainEntry?.telegramChatId || (activeBackend === 'telegram' && mainEntry?.channelId))
143
+ : Boolean(mainEntry?.discordChannelId || (activeBackend === 'discord' && mainEntry?.channelId));
144
+ const needs = [
145
+ ...(hasToken ? [] : ['token']),
146
+ ...(hasTarget ? [] : [backend === 'telegram' ? 'chat ID' : 'channel ID']),
147
+ ];
148
+ return [
149
+ ...(selected ? ['Selected'] : []),
150
+ needs.length ? `Needs ${needs.join(' + ')}` : 'Ready',
151
+ ].join(' · ');
152
+ };
153
+ setPicker({
154
+ title: 'Channel Type Settings',
155
+ description: 'Choose a type. Selected is the active backend; Ready means token and main target are set.',
156
+ help: '↑/↓ Select · Enter Open · Esc Back',
157
+ indexMode: 'always',
158
+ labelWidth: 18,
159
+ items: [
160
+ {
161
+ value: 'discord',
162
+ label: 'Discord',
163
+ description: typeDescription('discord'),
164
+ _backend: 'discord',
165
+ },
166
+ {
167
+ value: 'telegram',
168
+ label: 'Telegram',
169
+ description: typeDescription('telegram'),
170
+ _backend: 'telegram',
171
+ },
172
+ ],
173
+ onSelect: (value, item) => {
174
+ const backend = item?._backend || (value === 'telegram' ? 'telegram' : value === 'discord' ? 'discord' : null);
175
+ if (!backend) return;
176
+ setPicker(null);
177
+ openChannelTypeActionsPicker(backend, {
178
+ returnTo: () => openChannelSettingTypePicker(options),
179
+ });
180
+ },
181
+ onCancel: () => {
182
+ setPicker(null);
183
+ returnTo();
184
+ },
185
+ });
186
+ };
187
+
188
+ const openChannelSetupPicker = async (focus = 'all', options = {}) => {
189
+ setProviderPrompt(null);
190
+ setChannelPrompt(null);
191
+ setHookPrompt(null);
192
+ setSettingsPrompt(null);
193
+ setContextPanel(null);
194
+ let setup;
195
+ try {
196
+ setup = store.getChannelSetup();
197
+ } catch (e) {
198
+ store.pushNotice(`channels failed: ${e?.message || e}`, 'error');
199
+ return;
200
+ }
201
+
202
+ const openChannelPrompt = (prompt) => {
203
+ setPicker(null);
204
+ setContextPanel(null);
205
+ setChannelPrompt(prompt);
206
+ };
207
+
208
+ const channelRemoteEnabled = store.isRemoteEnabled?.() === true;
209
+
210
+ if (focus === 'schedules') {
211
+ const schedules = setup.schedules || [];
212
+ const items = [
213
+ ...(schedules.length ? schedules.map((schedule) => {
214
+ const enabled = schedule.enabled !== false;
215
+ return {
216
+ value: `schedule:${schedule.name}`,
217
+ label: schedule.name,
218
+ marker: enabled ? '●' : '○',
219
+ markerColor: channelRemoteEnabled ? (enabled ? theme.success : theme.inactive) : theme.inactive,
220
+ description: `${schedule.time || '(no cron)'} · ${schedule.route}${schedule.model ? ` · ${schedule.model}` : ''}${channelRemoteEnabled ? '' : ' · channel off'}`,
221
+ _action: 'schedule-toggle',
222
+ _name: schedule.name,
223
+ _enabled: enabled,
224
+ };
225
+ }) : [{
226
+ value: 'empty',
227
+ label: 'No schedules',
228
+ description: 'no schedules configured',
229
+ _action: 'noop',
230
+ }]),
231
+ ];
232
+ const toggleSchedule = (item) => {
233
+ if (item._action !== 'schedule-toggle') return;
234
+ if (!channelRemoteEnabled) {
235
+ store.pushNotice('enable channel first', 'warn');
236
+ return;
237
+ }
238
+ try {
239
+ store.setScheduleEnabled?.(item._name, !item._enabled);
240
+ void openChannelSetupPicker('schedules', { highlightValue: `schedule:${item._name}` });
241
+ } catch (e) {
242
+ store.pushNotice(`schedule toggle failed: ${e?.message || e}`, 'error');
243
+ }
244
+ };
245
+ setPicker({
246
+ title: 'Schedules',
247
+ description: channelRemoteEnabled ? 'Enable or disable cron schedules.' : 'Enable channel to toggle schedules.',
248
+ initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options.highlightValue)),
249
+ items,
250
+ onSelect: (_value, item) => toggleSchedule(item),
251
+ onLeft: (item) => toggleSchedule(item),
252
+ onRight: (item) => toggleSchedule(item),
253
+ onCancel: () => {
254
+ setPicker(null);
255
+ },
256
+ });
257
+ return;
258
+ }
259
+
260
+ if (focus === 'webhook-endpoint') {
261
+ const returnTo = typeof options.returnTo === 'function'
262
+ ? options.returnTo
263
+ : () => setPicker(null);
264
+ const domain = setup.webhook?.ngrokDomain || setup.webhook?.domain || '';
265
+ const items = [
266
+ {
267
+ value: 'endpoint-domain',
268
+ label: 'ngrok domain',
269
+ description: domain ? domain : 'Not set · Enter ngrok domain',
270
+ _action: 'endpoint-domain',
271
+ },
272
+ {
273
+ value: 'endpoint-authtoken',
274
+ label: 'authtoken',
275
+ description: setup.webhook?.authenticated === true ? 'Set' : 'Not set · Enter authtoken',
276
+ _action: 'endpoint-authtoken',
277
+ },
278
+ ];
279
+ setPicker({
280
+ title: 'Webhook endpoint',
281
+ description: 'ngrok domain and authtoken. Toggle individual webhooks in /webhooks.',
282
+ help: '↑/↓ Select · Enter Edit · Esc Back',
283
+ indexMode: 'always',
284
+ labelWidth: 18,
285
+ items,
286
+ onSelect: (_value, item) => {
287
+ try {
288
+ if (item._action === 'endpoint-domain') {
289
+ openChannelPrompt({
290
+ kind: 'webhook-domain',
291
+ label: 'ngrok domain',
292
+ hint: 'Paste the reserved ngrok domain (e.g. my-app.ngrok-free.app).',
293
+ afterSave: () => void openChannelSetupPicker('webhook-endpoint', options),
294
+ });
295
+ return;
296
+ }
297
+ if (item._action === 'endpoint-authtoken') {
298
+ openChannelPrompt({
299
+ kind: 'webhook-token',
300
+ label: 'Webhook/ngrok authtoken',
301
+ hint: 'Paste the webhook/ngrok authtoken. It is stored in the OS keychain.',
302
+ afterSave: () => void openChannelSetupPicker('webhook-endpoint', options),
303
+ });
304
+ }
305
+ } catch (e) {
306
+ store.pushNotice(`webhook endpoint failed: ${e?.message || e}`, 'error');
307
+ }
308
+ },
309
+ onCancel: () => {
310
+ setPicker(null);
311
+ returnTo();
312
+ },
313
+ });
314
+ return;
315
+ }
316
+
317
+ if (focus === 'webhooks') {
318
+ const hooks = setup.webhooks || [];
319
+ const items = [
320
+ ...(hooks.length ? hooks.map((hook) => {
321
+ const enabled = hook.enabled !== false;
322
+ return {
323
+ value: `webhook:${hook.name}`,
324
+ label: hook.name,
325
+ marker: enabled ? '●' : '○',
326
+ markerColor: channelRemoteEnabled ? (enabled ? theme.success : theme.inactive) : theme.inactive,
327
+ description: `${hook.parser || 'github'} · ${hook.route} · secret:${hook.secretSet ? 'set' : 'missing'}${channelRemoteEnabled ? '' : ' · channel off'}`,
328
+ _action: 'webhook-toggle',
329
+ _name: hook.name,
330
+ _enabled: enabled,
331
+ };
332
+ }) : [{
333
+ value: 'empty',
334
+ label: 'No webhooks',
335
+ description: 'no webhook endpoints configured',
336
+ _action: 'noop',
337
+ }]),
338
+ ];
339
+ const toggleWebhook = (item) => {
340
+ if (item._action !== 'webhook-toggle') return;
341
+ if (!channelRemoteEnabled) {
342
+ store.pushNotice('enable channel first', 'warn');
343
+ return;
344
+ }
345
+ try {
346
+ store.setWebhookEnabled?.(item._name, !item._enabled);
347
+ void openChannelSetupPicker('webhooks', { highlightValue: `webhook:${item._name}` });
348
+ } catch (e) {
349
+ store.pushNotice(`webhook toggle failed: ${e?.message || e}`, 'error');
350
+ }
351
+ };
352
+ setPicker({
353
+ title: 'Webhooks',
354
+ description: channelRemoteEnabled ? 'Enable or disable inbound webhook endpoints.' : 'Enable channel to toggle webhooks.',
355
+ initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options.highlightValue)),
356
+ items,
357
+ onSelect: (_value, item) => toggleWebhook(item),
358
+ onLeft: (item) => toggleWebhook(item),
359
+ onRight: (item) => toggleWebhook(item),
360
+ onCancel: () => {
361
+ setPicker(null);
362
+ },
363
+ });
364
+ return;
365
+ }
366
+
367
+ const worker = store.getChannelWorkerStatus?.();
368
+ const activeBackend = setup.backend === 'telegram' ? 'telegram' : 'discord';
369
+ const backendLabel = activeBackend === 'telegram' ? 'Telegram' : 'Discord';
370
+ const remoteEnabled = store.isRemoteEnabled?.() === true;
371
+ const boolLabel = (enabled) => enabled ? 'On' : 'Off';
372
+ // Onboarding Step 5 reuses this root picker with a ConfirmBar. Reopens after
373
+ // a toggle must carry the onboarding context (confirmBar + flag) forward so
374
+ // Back/Finish stay wired and the general /channels path keeps its own opts.
375
+ const reopenRoot = (extra = {}) => {
376
+ const preserved = options.onboarding
377
+ ? { onboarding: true, confirmBar: options.confirmBar || null }
378
+ : {};
379
+ void openChannelSetupPicker('all', { ...preserved, ...extra });
380
+ };
381
+ const applyRemoteRuntime = (highlightValue = 'remote-runtime') => {
382
+ const enabled = store.toggleRemote?.() === true;
383
+ store.pushNotice(enabled ? 'Remote mode ON' : 'Remote mode OFF', 'info');
384
+ reopenRoot({ highlightValue });
385
+ };
386
+ const cycleChannelBackend = (direction = 1, highlightValue = 'channel-backend') => {
387
+ const backends = ['discord', 'telegram'];
388
+ const currentIndex = Math.max(0, backends.indexOf(activeBackend));
389
+ const chosen = backends[(currentIndex + direction + backends.length) % backends.length];
390
+ if (chosen === activeBackend) {
391
+ reopenRoot({ highlightValue, backendOverride: activeBackend });
392
+ return;
393
+ }
394
+ try {
395
+ store.setBackend(chosen);
396
+ const label = chosen === 'telegram' ? 'Telegram' : 'Discord';
397
+ const restartHint = (store.isRemoteEnabled?.() === true || worker?.running)
398
+ ? `Channel set to ${label}. Restart remote to apply.`
399
+ : `Channel set to ${label}.`;
400
+ store.pushNotice(restartHint, 'info');
401
+ } catch (e) {
402
+ store.pushNotice(`channel backend failed: ${e?.message || e}`, 'error');
403
+ }
404
+ reopenRoot({ highlightValue, backendOverride: chosen });
405
+ };
406
+ const items = [
407
+ {
408
+ value: 'remote-runtime',
409
+ label: 'Remote Runtime',
410
+ meta: boolLabel(remoteEnabled),
411
+ description: worker?.running ? `Running · pid ${worker.pid}` : 'Stopped',
412
+ _action: 'remote-runtime',
413
+ },
414
+ {
415
+ value: 'channel-backend',
416
+ label: 'Channel',
417
+ meta: backendLabel,
418
+ description: 'Select Discord or Telegram',
419
+ _action: 'channel-backend',
420
+ },
421
+ {
422
+ value: 'channel-setting',
423
+ label: 'Setting',
424
+ description: 'Token and main target',
425
+ _action: 'channel-setting',
426
+ },
427
+ {
428
+ value: 'webhook-endpoint',
429
+ label: 'Webhook endpoint',
430
+ meta: (() => {
431
+ const hasDomain = Boolean(setup.webhook?.ngrokDomain || setup.webhook?.domain);
432
+ const hasAuth = setup.webhook?.authenticated === true;
433
+ return (hasDomain && hasAuth) ? 'On' : 'Off';
434
+ })(),
435
+ description: (() => {
436
+ const hasDomain = Boolean(setup.webhook?.ngrokDomain || setup.webhook?.domain);
437
+ const hasAuth = setup.webhook?.authenticated === true;
438
+ const needs = [
439
+ ...(hasDomain ? [] : ['domain']),
440
+ ...(hasAuth ? [] : ['authtoken']),
441
+ ];
442
+ return needs.length ? `Needs ${needs.join(' + ')}` : 'ngrok domain and authtoken set';
443
+ })(),
444
+ _action: 'webhook-endpoint',
445
+ },
446
+ ];
447
+
448
+ setPicker({
449
+ title: 'Channels',
450
+ description: 'Remote access and channel setup.',
451
+ // Onboarding overlays a ConfirmBar (←/→ drive Back/Finish, not toggles),
452
+ // so drop the ←/→ Change hint there and use the Picker's ConfirmBar help.
453
+ help: options.confirmBar ? undefined : '↑/↓ Select · ←/→ Change · Enter Choose/Toggle · Esc Back',
454
+ indexMode: 'always',
455
+ labelWidth: 18,
456
+ metaWidth: 12,
457
+ pickerKey: `channels:${activeBackend}:${remoteEnabled ? 'on' : 'off'}:${options.highlightValue || 'root'}`,
458
+ initialIndex: Math.max(0, items.findIndex((entry) => entry.value === options.highlightValue)),
459
+ items,
460
+ confirmBar: options.confirmBar || null,
461
+ onLeft: options.confirmBar ? undefined : (item) => {
462
+ if (item?._action === 'remote-runtime') applyRemoteRuntime(item.value);
463
+ else if (item?._action === 'channel-backend') cycleChannelBackend(-1, item.value);
464
+ },
465
+ onRight: options.confirmBar ? undefined : (item) => {
466
+ if (item?._action === 'remote-runtime') applyRemoteRuntime(item.value);
467
+ else if (item?._action === 'channel-backend') cycleChannelBackend(1, item.value);
468
+ },
469
+ onSelect: (_value, item) => {
470
+ try {
471
+ if (item._action === 'remote-runtime') {
472
+ applyRemoteRuntime(item.value);
473
+ return;
474
+ }
475
+ if (item._action === 'channel-backend') {
476
+ cycleChannelBackend(1, item.value);
477
+ return;
478
+ }
479
+ if (item._action === 'channel-setting') {
480
+ openChannelSettingTypePicker({
481
+ returnTo: () => reopenRoot({ highlightValue: 'channel-setting' }),
482
+ });
483
+ return;
484
+ }
485
+ if (item._action === 'webhook-endpoint') {
486
+ void openChannelSetupPicker('webhook-endpoint', {
487
+ ...(options.onboarding ? { onboarding: true, confirmBar: options.confirmBar || null } : {}),
488
+ returnTo: () => reopenRoot({ highlightValue: 'webhook-endpoint' }),
489
+ });
490
+ }
491
+ } catch (e) {
492
+ store.pushNotice(`channels update failed: ${e?.message || e}`, 'error');
493
+ }
494
+ },
495
+ onCancel: () => {
496
+ setPicker(null);
497
+ // Onboarding Step 5 Esc mirrors the other steps' reopen-next-launch warning.
498
+ if (options.onboarding) onboardingWarnReopen();
499
+ },
500
+ });
501
+ };
502
+
503
+ return {
504
+ openChannelTypeActionsPicker,
505
+ openChannelSettingTypePicker,
506
+ openChannelSetupPicker,
507
+ };
508
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * clipboard.mjs — OS-clipboard write helpers extracted verbatim from App.jsx.
3
+ * Pure module functions (spawn/process/Buffer only); no React, no App state.
4
+ */
5
+ import { Buffer } from 'node:buffer';
6
+ import { spawn } from 'node:child_process';
7
+
8
+ export function osc52ClipboardSequence(text) {
9
+ const b64 = Buffer.from(String(text ?? ''), 'utf8').toString('base64');
10
+ const raw = `\x1b]52;c;${b64}\x07`;
11
+ if (!process.env.TMUX) return raw;
12
+ return `\x1bPtmux;${raw.replaceAll('\x1b', '\x1b\x1b')}\x1b\\`;
13
+ }
14
+
15
+ export function writeOsc52Clipboard(text) {
16
+ try {
17
+ process.stdout.write(osc52ClipboardSequence(text));
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ export function nativeClipboardCommand(text) {
25
+ if (process.platform === 'win32') {
26
+ return {
27
+ cmd: 'powershell.exe',
28
+ args: [
29
+ '-NoLogo',
30
+ '-NoProfile',
31
+ '-NonInteractive',
32
+ '-Command',
33
+ '$b=[Console]::In.ReadToEnd();$t=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b));Set-Clipboard -Value $t',
34
+ ],
35
+ input: Buffer.from(String(text ?? ''), 'utf8').toString('base64'),
36
+ };
37
+ }
38
+ if (process.platform === 'darwin') return { cmd: 'pbcopy', args: [], input: text };
39
+ if (process.env.WAYLAND_DISPLAY) return { cmd: 'wl-copy', args: [], input: text };
40
+ return { cmd: 'xclip', args: ['-selection', 'clipboard'], input: text };
41
+ }
42
+
43
+ export function copyToClipboard(text) {
44
+ const value = String(text ?? '');
45
+ const wroteOsc52 = writeOsc52Clipboard(value);
46
+ return new Promise((resolve, reject) => {
47
+ const { cmd, args, input } = nativeClipboardCommand(value);
48
+ let child;
49
+ try {
50
+ child = spawn(cmd, args, { stdio: ['pipe', 'ignore', 'ignore'], windowsHide: true });
51
+ } catch (e) {
52
+ if (wroteOsc52) resolve();
53
+ else reject(e);
54
+ return;
55
+ }
56
+ child.on('error', (e) => {
57
+ if (wroteOsc52) resolve();
58
+ else reject(e);
59
+ });
60
+ child.on('close', (code) => {
61
+ if (code === 0 || wroteOsc52) resolve();
62
+ else reject(new Error(`${cmd} exited with code ${code}`));
63
+ });
64
+ child.stdin.on('error', () => { /* ignore EPIPE if the helper closed early */ });
65
+ child.stdin.end(input);
66
+ });
67
+ }