mixdog 1.2.5 → 1.2.6

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 (290) hide show
  1. package/package.json +1 -1
  2. package/scripts/lib/gate-scheduler.mjs +2 -2
  3. package/scripts/lib/run-node-tests.mjs +5 -14
  4. package/scripts/provider-pingpong-live.mjs +9 -9
  5. package/src/agents/advisor/AGENT.md +4 -4
  6. package/src/agents/advisor/agent.json +1 -1
  7. package/src/agents/reviewer/AGENT.md +2 -3
  8. package/src/agents/reviewer/agent.json +1 -1
  9. package/src/defaults/shipped-history.json +10 -0
  10. package/src/defaults/skills/browser-use/SKILL.md +2 -1
  11. package/src/defaults/skills/computer-use/SKILL.md +3 -1
  12. package/src/defaults/skills/visualize/SKILL.md +3 -2
  13. package/src/lib/rules-builder.cjs +1 -1
  14. package/src/output-styles/common.md +22 -6
  15. package/src/output-styles/detailed.md +7 -5
  16. package/src/output-styles/minimal.md +7 -3
  17. package/src/output-styles/simple.md +2 -3
  18. package/src/rules/agent/AGENT.md +3 -1
  19. package/src/rules/lead/LEAD.md +4 -3
  20. package/src/rules/shared/10-tool-workflow.md +2 -1
  21. package/src/rules/shared/60-verification.md +4 -5
  22. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
  23. package/src/runtime/agent/orchestrator/config.mjs +6 -0
  24. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +1 -1
  25. package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
  26. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +53 -60
  27. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  28. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
  29. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +40 -19
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
  32. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
  34. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
  37. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +1 -2
  38. package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
  39. package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
  40. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
  41. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
  42. package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
  43. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
  44. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
  45. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  46. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
  47. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
  51. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
  56. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
  57. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
  58. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
  64. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
  65. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
  66. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
  67. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
  68. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
  69. package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
  70. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +2 -3
  71. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +3 -8
  72. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
  73. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
  74. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
  75. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
  76. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
  77. package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
  78. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
  79. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +9 -19
  80. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -3
  81. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -0
  82. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
  83. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
  84. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
  85. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
  86. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
  87. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
  88. package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
  89. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
  90. package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
  91. package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
  92. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
  93. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
  94. package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
  95. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
  96. package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
  97. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +258 -43
  98. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +306 -0
  99. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +70 -21
  100. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
  101. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
  102. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
  103. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
  104. package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
  105. package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
  106. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
  107. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
  108. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
  109. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
  110. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
  111. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
  112. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
  113. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
  114. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
  115. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
  117. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
  119. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
  120. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
  121. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
  122. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
  123. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
  124. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
  125. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
  126. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
  127. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
  129. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
  130. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
  131. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
  132. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
  133. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
  134. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
  135. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
  136. package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
  137. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +119 -32
  138. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
  139. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
  140. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
  141. package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
  142. package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
  143. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
  144. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
  145. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
  146. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  147. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
  148. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +40 -2
  149. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -2
  150. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
  151. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -1
  153. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
  154. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +35 -4
  155. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
  156. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
  157. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
  158. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  159. package/src/runtime/attachments/office-extract.mjs +11 -9
  160. package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
  161. package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
  162. package/src/runtime/memory/lib/tool-call-handler.mjs +7 -5
  163. package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
  164. package/src/runtime/office/core/tabular.mjs +1 -1
  165. package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
  166. package/src/runtime/office/portable/docx-formatting.mjs +1 -1
  167. package/src/runtime/office/portable/docx-runs.mjs +1 -1
  168. package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
  169. package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
  170. package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
  171. package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
  172. package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
  173. package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
  174. package/src/runtime/office/portable/portable-opc.mjs +27 -13
  175. package/src/runtime/office/portable/portable-pivot.mjs +3 -3
  176. package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
  177. package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
  178. package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
  179. package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
  180. package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
  181. package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
  182. package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
  183. package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
  184. package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
  185. package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
  186. package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
  187. package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
  188. package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
  189. package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
  190. package/src/runtime/office/portable/portable-xml.mjs +1 -1
  191. package/src/runtime/shared/atomic-file.mjs +22 -13
  192. package/src/runtime/shared/background-tasks.mjs +60 -20
  193. package/src/runtime/shared/child-spawn-gate.mjs +26 -16
  194. package/src/runtime/shared/config.mjs +0 -10
  195. package/src/runtime/shared/file-lock.mjs +7 -1
  196. package/src/runtime/shared/file-permissions-worker.mjs +38 -0
  197. package/src/runtime/shared/file-permissions.d.mts +19 -4
  198. package/src/runtime/shared/file-permissions.mjs +310 -4
  199. package/src/runtime/shared/git-runner-worker.mjs +6 -78
  200. package/src/runtime/shared/llm/http-agent.mjs +7 -3
  201. package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
  202. package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
  203. package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
  204. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
  205. package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
  206. package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
  207. package/src/runtime/shared/memory-snapshot.mjs +5 -1
  208. package/src/runtime/shared/notify-trace.mjs +8 -1
  209. package/src/runtime/shared/pristine-execution-contract.json +5 -1
  210. package/src/runtime/shared/process-runner-worker.mjs +7 -0
  211. package/src/runtime/shared/process-runner.mjs +38 -0
  212. package/src/runtime/shared/provider-accounts.mjs +2 -1
  213. package/src/runtime/shared/provider-api-key.mjs +0 -1
  214. package/src/runtime/shared/read-row-numbers.mjs +1 -1
  215. package/src/runtime/shared/resource-admission.mjs +60 -18
  216. package/src/runtime/shared/run-child.mjs +84 -0
  217. package/src/runtime/shared/session-activity-signal.mjs +23 -0
  218. package/src/runtime/shared/shell-output-summary.mjs +23 -0
  219. package/src/runtime/shared/singleton-owner.mjs +126 -42
  220. package/src/runtime/shared/tool-output-limit.mjs +4 -2
  221. package/src/runtime/shared/tool-output-marker.mjs +58 -0
  222. package/src/runtime/shared/user-data-guard.mjs +28 -9
  223. package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
  224. package/src/runtime/shared/worktrees/git-head.mjs +20 -2
  225. package/src/runtime/shared/worktrees/lock.mjs +3 -2
  226. package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
  227. package/src/runtime/tidy/runners/biome.mjs +3 -1
  228. package/src/runtime/tidy/shadow.mjs +2 -1
  229. package/src/runtime/web-search/index.mjs +5 -7
  230. package/src/runtime/web-search/lib/formatter.mjs +42 -6
  231. package/src/runtime/web-search/tool-defs.mjs +6 -1
  232. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
  233. package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -4
  234. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
  235. package/src/session-runtime/lifecycle/shared.mjs +19 -2
  236. package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
  237. package/src/session-runtime/lifecycle/teardown.mjs +5 -2
  238. package/src/session-runtime/lifecycle-api.mjs +3 -1
  239. package/src/session-runtime/provider-auth/login.mjs +10 -6
  240. package/src/session-runtime/provider-auth/setup.mjs +19 -2
  241. package/src/session-runtime/provider-catalog-cache.mjs +101 -25
  242. package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
  243. package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
  244. package/src/session-runtime/provider-models.mjs +76 -10
  245. package/src/session-runtime/provider-readiness.mjs +4 -0
  246. package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
  247. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
  248. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
  249. package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
  250. package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
  251. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
  252. package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
  253. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
  254. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
  255. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
  256. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
  257. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
  258. package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
  259. package/src/session-runtime/services/agent-tool.mjs +20 -0
  260. package/src/session-runtime/services/provider-admin.mjs +92 -20
  261. package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
  262. package/src/session-runtime/services/usage-dashboard.mjs +27 -69
  263. package/src/session-runtime/turn/session-ops.mjs +2 -2
  264. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  265. package/src/session-runtime/usage-stats-api.mjs +6 -1
  266. package/src/standalone/channel-transport/http-routes.mjs +10 -9
  267. package/src/standalone/daemon-desktop-runtime.mjs +20 -7
  268. package/src/standalone/daemon-owner.mjs +117 -25
  269. package/src/standalone/daemon.mjs +43 -13
  270. package/src/standalone/session-call-cache.mjs +6 -0
  271. package/src/standalone/session-protocol.mjs +1 -1
  272. package/src/standalone/session-service/retention/busy.mjs +7 -2
  273. package/src/standalone/session-transport/routes.mjs +7 -6
  274. package/src/tui/app/model-options.mjs +39 -2
  275. package/src/tui/app/model-picker.mjs +16 -2
  276. package/src/tui/app/provider-setup-picker.mjs +1 -2
  277. package/src/tui/dist/index.mjs +385 -104
  278. package/src/tui/session/notice-text.mjs +0 -1
  279. package/src/tui/session/session-api/intake/submission.mjs +7 -13
  280. package/src/tui/session/session-api/integrations.mjs +1 -5
  281. package/src/tui/session/session-flow/drain.mjs +8 -0
  282. package/src/tui/session/session-flow/steering.mjs +46 -0
  283. package/src/tui/session/session-flow.mjs +2 -0
  284. package/src/tui/session/turn-stream.mjs +33 -0
  285. package/src/tui/session/turn.mjs +30 -3
  286. package/src/ui/statusline.mjs +1 -6
  287. package/src/ui/streaming-markdown-heal.mjs +24 -7
  288. package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
  289. package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
  290. package/src/workflows/default/WORKFLOW.md +9 -13
@@ -0,0 +1,134 @@
1
+ // agent-tool/restart-notify.mjs
2
+ // Agent tasks live in process memory, so a daemon restart ends every in-flight
3
+ // one without a word: the successor only sees worker rows left `failed`
4
+ // (process-restart). This tells each task's owner, exactly once.
5
+ //
6
+ // Order is what makes it safe: the failure notification is enqueued in the
7
+ // owner's persisted pending queue under a stable id (`restart:<taskId>`) and
8
+ // flushed to the spool FIRST; only then is the row stamped `restartNotified`.
9
+ // A crash or a throw before the stamp leaves the row retryable, and the retry
10
+ // re-enqueues the same id — the queue is id-idempotent, so the owner still
11
+ // sees it once.
12
+ import {
13
+ acknowledgeBackgroundTaskCompletion,
14
+ completeBackgroundTask,
15
+ getBackgroundTask,
16
+ registerBackgroundTask,
17
+ } from '../../../runtime/shared/background-tasks.mjs';
18
+ import { renderAgentCompletionEnvelope } from '../../../runtime/shared/task-notification-envelope.mjs';
19
+ import { modelVisibleToolCompletionMessage } from '../../../runtime/shared/tool-execution-contract.mjs';
20
+ import { clean } from './helpers.mjs';
21
+
22
+ const RESTART_FAILURE_ERROR = 'process restart: the daemon restarted while this agent task was running';
23
+ const SETTLE_TIMEOUT_MS = 5000;
24
+
25
+ /** Stable dedup id of one lost task's notification. A row that never recorded
26
+ * a task id falls back to its session and settle time. */
27
+ export function restartNotificationId(row) {
28
+ return `restart:${clean(row.task_id) || `${clean(row.sessionId)}:${clean(row.finishedAt)}`}`;
29
+ }
30
+
31
+ /** `pendingRestartFailures()` lists rows whose owner is not yet told;
32
+ * `markRestartNotified(rows)` stamps them. `mgr` supplies the persisted
33
+ * pending queue (enqueuePendingMessage / markCompletionEntry /
34
+ * settlePendingMessageWrites). `notifySessionCompletion` is only the live,
35
+ * best-effort emit to attached surfaces after the durable enqueue. */
36
+ export function createRestartFailureNotifier({
37
+ pendingRestartFailures,
38
+ markRestartNotified,
39
+ mgr,
40
+ notifySessionCompletion,
41
+ }) {
42
+ function enqueueDurably(row) {
43
+ const owner = clean(row.ownerSessionId);
44
+ const executionId = restartNotificationId(row);
45
+ const meta = {
46
+ type: 'agent_task_result',
47
+ execution_surface: 'agent',
48
+ execution_id: executionId,
49
+ status: 'failed',
50
+ caller_session_id: owner,
51
+ };
52
+ const text = renderAgentCompletionEnvelope({
53
+ id: clean(row.task_id) || executionId,
54
+ tag: row.tag,
55
+ status: 'failed',
56
+ error: RESTART_FAILURE_ERROR,
57
+ });
58
+ const entry = mgr.markCompletionEntry(modelVisibleToolCompletionMessage(text, meta), { executionId, meta });
59
+ return { owner, text, meta, entry };
60
+ }
61
+
62
+ // Lets `agent status <id>` resolve after the restart; best-effort.
63
+ function registerFailedTask(row) {
64
+ const taskId = clean(row.task_id);
65
+ if (!taskId || getBackgroundTask(taskId)) return;
66
+ const owner = clean(row.ownerSessionId);
67
+ registerBackgroundTask({
68
+ taskId,
69
+ surface: 'agent',
70
+ operation: 'restart',
71
+ label: row.tag,
72
+ input: { type: 'restart', tag: row.tag, sessionId: row.sessionId, agent: row.agent || null },
73
+ context: { callerSessionId: owner, ownerSessionId: owner, clientHostPid: row.clientHostPid || null },
74
+ meta: {
75
+ tag: row.tag,
76
+ sessionId: row.sessionId,
77
+ agent: row.agent || null,
78
+ provider: row.provider || null,
79
+ model: row.model || null,
80
+ preset: row.preset || null,
81
+ effort: row.effort || null,
82
+ },
83
+ resultType: 'agent_task_result',
84
+ });
85
+ completeBackgroundTask(taskId, {
86
+ status: 'failed',
87
+ error: RESTART_FAILURE_ERROR,
88
+ terminalReason: 'process-restart',
89
+ notify: false,
90
+ });
91
+ acknowledgeBackgroundTaskCompletion(taskId);
92
+ }
93
+
94
+ async function notifyRow(row) {
95
+ const { owner, text, meta, entry } = enqueueDurably(row);
96
+ if (!(mgr.enqueuePendingMessage(owner, entry) > 0)) throw new Error(`restart notification refused for ${owner}`);
97
+ if (!(await mgr.settlePendingMessageWrites({ timeoutMs: SETTLE_TIMEOUT_MS }))) {
98
+ throw new Error(`restart notification for ${owner} did not reach the spool`);
99
+ }
100
+ markRestartNotified([row]);
101
+ try {
102
+ registerFailedTask(row);
103
+ } catch {
104
+ /* status lookup only */
105
+ }
106
+ try {
107
+ // Same execution id as the queued entry: the queue dedups the mirror.
108
+ notifySessionCompletion?.(owner, text, meta);
109
+ } catch {
110
+ /* the durable queue is authoritative */
111
+ }
112
+ }
113
+
114
+ let inFlight = null;
115
+ async function run() {
116
+ for (const row of pendingRestartFailures()) {
117
+ try {
118
+ await notifyRow(row);
119
+ } catch (err) {
120
+ // Not stamped: the next pass or restart retries this row.
121
+ process.stderr.write(`[agent-tool] restart notification retry pending: ${err?.message || err}\n`);
122
+ }
123
+ }
124
+ }
125
+
126
+ return function notifyRestartFailures() {
127
+ inFlight ||= run()
128
+ .catch((err) => process.stderr.write(`[agent-tool] restart notification pass failed: ${err?.message || err}\n`))
129
+ .finally(() => {
130
+ inFlight = null;
131
+ });
132
+ return inFlight;
133
+ };
134
+ }
@@ -98,6 +98,8 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
98
98
  registry.upsertWorkerSessionDeferred(session, tag, {
99
99
  status: 'running',
100
100
  stage: 'running',
101
+ // The send's own task: a restart that ends this turn reports it by id.
102
+ task_id: job?.taskId,
101
103
  turnStartedAt: new Date().toISOString(),
102
104
  });
103
105
  let handoffMsgStart = 0;
@@ -224,5 +226,26 @@ export function createSendFlow({ mgr, defaultCwd, sessionSurface, canUseSessionS
224
226
  return renderResult({ ...extras, ...views.renderJob(job, false) });
225
227
  }
226
228
 
227
- return { prepareSend, dispatchToExistingSession };
229
+ // A background-task completion was queued for this worker. A busy worker
230
+ // takes it at its next tool boundary; an idle one runs a follow-up turn on
231
+ // an empty prompt, which drains the queued completion (never an empty user
232
+ // turn). Its answer reaches the owner like any send result.
233
+ function wakeIdleWorker(sessionId) {
234
+ const id = clean(sessionId);
235
+ if (!id || views.isSessionBusy(id)) return false;
236
+ const session = mgr.getSession(id);
237
+ if (!session || session.closed) return false;
238
+ const owner = clean(session.ownerSessionId || session.parentSessionId);
239
+ const notifyContext = owner
240
+ ? { callerSessionId: owner, ownerSessionId: owner, clientHostPid: session.clientHostPid || null }
241
+ : null;
242
+ void dispatchToExistingSession({ args: {}, session, sessionId: id, prompt: '' }, notifyContext).catch((error) => {
243
+ try {
244
+ process.stderr.write(`[agent] completion wake failed session=${id}: ${error?.message || error}\n`);
245
+ } catch {}
246
+ });
247
+ return true;
248
+ }
249
+
250
+ return { prepareSend, dispatchToExistingSession, wakeIdleWorker };
228
251
  }
@@ -5,6 +5,7 @@
5
5
  * flip to running, and the cancellation checks around the turn.
6
6
  */
7
7
  import { nonNegativeInt, clearAgentStatuslineRoute } from '../helpers.mjs';
8
+ import { closeManagedSession } from '../../../lifecycle/shared.mjs';
8
9
 
9
10
  export function createDeferredSpawn({
10
11
  mgr,
@@ -22,7 +23,7 @@ export function createDeferredSpawn({
22
23
  function closePreparedSpawn(prepared, reason = 'agent-task-cancel') {
23
24
  if (!prepared?.session?.id) return;
24
25
  try {
25
- Promise.resolve(mgr.closeSession(prepared.session.id, reason)).catch(() => {});
26
+ Promise.resolve(closeManagedSession(mgr, prepared.session.id, reason)).catch(() => {});
26
27
  } catch {}
27
28
  try {
28
29
  clearAgentStatuslineRoute(prepared.session.id);
@@ -7,6 +7,7 @@ import { sanitizeTaskMeta, startBackgroundTask } from '../../../../runtime/share
7
7
  import { resourceAdmission } from '../../../../runtime/shared/resource-admission.mjs';
8
8
  import { clean, terminalPidForContext } from '../helpers.mjs';
9
9
  import { renderResult } from '../render.mjs';
10
+ import { closeManagedSession } from '../../../lifecycle/shared.mjs';
10
11
 
11
12
  /** The completion callback that hands a worker's result back to its caller session; null without a handoff. */
12
13
  function handoffNotifier(handoffSessionId, notifySessionCompletion) {
@@ -53,9 +54,9 @@ export function createJobStarter({ mgr, notifyStatusChange, notifySessionComplet
53
54
  const currentMeta = task?.meta || jobMeta;
54
55
  if (currentMeta?.sessionId) {
55
56
  try {
56
- Promise.resolve(mgr.closeSession(currentMeta.sessionId, 'agent-task-cancel', { tombstone: false })).catch(
57
- () => {}
58
- );
57
+ Promise.resolve(
58
+ closeManagedSession(mgr, currentMeta.sessionId, 'agent-task-cancel', { tombstone: false })
59
+ ).catch(() => {});
59
60
  } catch {}
60
61
  }
61
62
  setImmediate(notifyStatusChange);
@@ -39,9 +39,13 @@ export function createSpawnRunner({
39
39
  emitSubagentEvent,
40
40
  sessionSurface = null,
41
41
  createTurnReviewCollector,
42
+ onWorkerCompletionQueued = null,
42
43
  }) {
43
44
  // Owner/worker completion notification lives in ../notify.mjs.
44
- const { workerNotifyFn, notifyOwnerAgentCompletionEarly } = createNotify(mgr, { notifySessionCompletion });
45
+ const { workerNotifyFn, notifyOwnerAgentCompletionEarly } = createNotify(mgr, {
46
+ notifySessionCompletion,
47
+ onWorkerCompletionQueued,
48
+ });
45
49
 
46
50
  // Idle-progress watchdogs (shared sweep timer) live in
47
51
  // agent-watchdog-registry.mjs.
@@ -32,14 +32,16 @@ export function createTagLookup({ tags, mgr, readWorkerRows, reaper, getLiveSess
32
32
  const rows = [];
33
33
  const seen = new Set();
34
34
  const tombstones = reaper.tagTombstoneIndex();
35
- const add = (session, fallbackTag = '') => {
35
+ // `loaded`: `session` is getLiveSession's own answer (not a catalog
36
+ // summary), so consumers may reuse it instead of loading it again.
37
+ const add = (session, fallbackTag = '', loaded = false) => {
36
38
  const tag = agentTagOf(session) || clean(fallbackTag);
37
39
  if (!tag || !session?.id || session.closed === true) return;
38
40
  if (isLeadPoolAgent(session.agent)) return;
39
41
  if (!sessionMatchesContext(session, context)) return;
40
42
  if (seen.has(session.id)) return;
41
43
  seen.add(session.id);
42
- rows.push({ tag, session });
44
+ rows.push(loaded ? { tag, session, loaded: true } : { tag, session });
43
45
  };
44
46
  const addIndexRow = (row) => {
45
47
  const tag = clean(row?.tag);
@@ -70,9 +72,12 @@ export function createTagLookup({ tags, mgr, readWorkerRows, reaper, getLiveSess
70
72
  }
71
73
  }
72
74
  for (const [tag, sessionId] of tags.entries()) {
75
+ // add() drops an id already listed; loading it first (a whole-session
76
+ // read for an idle worker) was pure cost on every status pulse.
77
+ if (seen.has(sessionId)) continue;
73
78
  const session = getLiveSession(sessionId);
74
79
  if (session && reaper.tombstoneBlocksScan(session, tag, tombstones)) continue;
75
- add(session, tag);
80
+ add(session, tag, true);
76
81
  }
77
82
  return rows;
78
83
  }
@@ -112,6 +112,9 @@ export function createTagRegistry({ dataDir, cfgMod, mgr }) {
112
112
  readWorkerRows,
113
113
  writeWorkerRows: index.writeWorkerRows,
114
114
  flushWorkerIndexMutations: index.flushWorkerIndexMutations,
115
+ recoverStaleWorkerRows: index.recoverStaleWorkerRows,
116
+ pendingRestartFailures: index.pendingRestartFailures,
117
+ markRestartNotified: index.markRestartNotified,
115
118
  upsertWorkerSession: index.upsertWorkerSession,
116
119
  upsertWorkerSessionDeferred,
117
120
  upsertLeadSession,
@@ -55,6 +55,9 @@ export function normalizeWorkerRows(value) {
55
55
  // Why a `failed` row failed (row-liveness, a watchdog stop). Always
56
56
  // present, so a later whole-row write clears a stale reason.
57
57
  failureReason: clean(row.failureReason) || null,
58
+ // When the owner was told this row's turn died with a process restart;
59
+ // always present, so new work on the row starts unnotified.
60
+ restartNotified: clean(row.restartNotified) || null,
58
61
  permission: clean(row.permission) || null,
59
62
  toolPermission: clean(row.toolPermission) || null,
60
63
  messages: positiveInt(row.messages) || 0,
@@ -112,6 +115,7 @@ export function workerRowFromSession(session, fallbackTag = '', extra = {}, getS
112
115
  task_id: clean(extra.task_id || extra.taskId) || null,
113
116
  error: clean(extra.error) || null,
114
117
  failureReason: clean(extra.failureReason) || null,
118
+ restartNotified: clean(extra.restartNotified) || null,
115
119
  permission: clean(session?.permission) || null,
116
120
  toolPermission: clean(session?.toolPermission) || null,
117
121
  messages: Array.isArray(session?.messages) ? session.messages.length : 0,
@@ -161,14 +161,19 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
161
161
  });
162
162
  }
163
163
 
164
- // Startup recovery: stale active rows settle — as failed when the process
165
- // that ran their turn is gone (the turn never finished), otherwise to idle —
166
- // and an approval wait left behind by a runtime that is gone (daemon
167
- // restart) is cleared.
168
- function recoverStaleWorkerRows() {
164
+ // Startup recovery: stale active rows settle — as failed when another
165
+ // process ran their turn (it is gone, or is the predecessor still shutting
166
+ // down: either way the turn never finished), otherwise to idle — and an
167
+ // approval wait left behind by a runtime that is gone (daemon restart) is
168
+ // cleared. `deadOnly` (later sweeps) settles only rows whose runtime is
169
+ // provably dead, never a live process's quiet turn.
170
+ function recoverStaleWorkerRows({ deadOnly = false } = {}) {
169
171
  const now = Date.now();
170
172
  const rows = store.readAll();
171
- const staleActive = rows.filter((row) => liveness.isStaleActive(row, now));
173
+ const isStale = deadOnly
174
+ ? (row) => isActiveWorkerRow(row) && runtimeDied(row)
175
+ : (row) => liveness.isStaleActive(row, now);
176
+ const staleActive = rows.filter(isStale);
172
177
  const staleWaits = rows.some(isStaleApprovalWait);
173
178
  if (!staleActive.length && !staleWaits) return false;
174
179
  if (staleActive.length) {
@@ -176,12 +181,11 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
176
181
  for (const row of staleActive) {
177
182
  const key = workerRowKey(row);
178
183
  const current = byKey.get(key);
179
- if (!current || !liveness.isStaleActive(current, now)) continue;
184
+ if (!current || !isStale(current)) continue;
185
+ const ranElsewhere = positiveInt(current.runtimePid) && positiveInt(current.runtimePid) !== process.pid;
180
186
  byKey.set(
181
187
  key,
182
- runtimeDied(current)
183
- ? liveness.failedRow(current, now, PROCESS_RESTART_FAILURE)
184
- : liveness.idleRow(current, now)
188
+ ranElsewhere ? liveness.failedRow(current, now, PROCESS_RESTART_FAILURE) : liveness.idleRow(current, now)
185
189
  );
186
190
  }
187
191
  });
@@ -190,6 +194,34 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
190
194
  return true;
191
195
  }
192
196
 
197
+ const isUnnotifiedRestartFailure = (row) =>
198
+ row.status === 'failed' &&
199
+ row.failureReason === PROCESS_RESTART_FAILURE &&
200
+ Boolean(clean(row.ownerSessionId)) &&
201
+ !row.restartNotified;
202
+
203
+ /** Rows whose turn a process restart ended and whose owner has not been told
204
+ * yet — whoever settled the row (this process at startup, or the
205
+ * predecessor's exit flush). Read-only: the caller enqueues the owner's
206
+ * notification durably, THEN calls `markRestartNotified`, so a crash in
207
+ * between retries (the notification's stable id makes the retry exactly-once). */
208
+ function pendingRestartFailures() {
209
+ return store.readAll().filter(isUnnotifiedRestartFailure);
210
+ }
211
+
212
+ /** Stamp these rows as told, once their notification is durably queued. */
213
+ function markRestartNotified(rows) {
214
+ const stamp = new Date().toISOString();
215
+ store.write((byKey) => {
216
+ for (const row of rows) {
217
+ const key = workerRowKey(row);
218
+ const current = byKey.get(key);
219
+ if (!current || !isUnnotifiedRestartFailure(current)) continue;
220
+ byKey.set(key, { ...current, restartNotified: stamp });
221
+ }
222
+ });
223
+ }
224
+
193
225
  function flushWorkerIndexOnExit() {
194
226
  batch.flush();
195
227
  flushActiveWorkerRows();
@@ -285,5 +317,7 @@ export function createWorkerIndex({ dataDir, cfgMod, mgr, tags, tagAgents, tagCw
285
317
  flushActiveWorkerRows,
286
318
  flushWorkerIndexOnExit,
287
319
  recoverStaleWorkerRows,
320
+ pendingRestartFailures,
321
+ markRestartNotified,
288
322
  };
289
323
  }
@@ -9,6 +9,7 @@ import { liveAgentStatusRow } from './agent-tool/job-views/session-progress.mjs'
9
9
  import { createSpawnFlow } from './agent-tool/spawn-flow.mjs';
10
10
  import { createSendFlow } from './agent-tool/send-flow.mjs';
11
11
  import { createCloseFlow } from './agent-tool/close-flow.mjs';
12
+ import { createRestartFailureNotifier } from './agent-tool/restart-notify.mjs';
12
13
  import { createTerminalTrace } from './agent-tool/terminal-trace.mjs';
13
14
  import { createAgentExecute } from './agent-tool/execute.mjs';
14
15
  import { createTurnReviewCollector } from './agent-tool/turn-review.mjs';
@@ -70,8 +71,24 @@ export function createStandaloneAgent({
70
71
  }
71
72
 
72
73
  const registry = createTagRegistry({ dataDir, cfgMod, mgr });
74
+ // Tasks the previous process was running died with it: tell their owners.
75
+ // Later sweeps also catch rows the predecessor settled as it shut down.
76
+ const notifyRestartFailures = createRestartFailureNotifier({
77
+ pendingRestartFailures: registry.pendingRestartFailures,
78
+ markRestartNotified: registry.markRestartNotified,
79
+ mgr,
80
+ notifySessionCompletion,
81
+ });
82
+ const sweepRestartFailures = () => {
83
+ registry.recoverStaleWorkerRows({ deadOnly: true });
84
+ notifyRestartFailures();
85
+ };
86
+ notifyRestartFailures();
73
87
  const views = createJobViews({ ...registry, mgr, reg, cfgMod, DEFAULT_SPAWN_PREP_TIMEOUT_MS });
88
+ // Late-bound: the send flow that wakes an idle worker is built after spawn.
89
+ let wakeIdleWorker = null;
74
90
  const spawnFlow = createSpawnFlow({
91
+ onWorkerCompletionQueued: (sessionId) => wakeIdleWorker?.(sessionId),
75
92
  ...registry,
76
93
  ...views,
77
94
  mgr,
@@ -96,6 +113,7 @@ export function createStandaloneAgent({
96
113
  views,
97
114
  spawnFlow,
98
115
  });
116
+ wakeIdleWorker = sendFlow.wakeIdleWorker;
99
117
  const closeFlow = createCloseFlow({ mgr, registry, views });
100
118
  const terminalTrace = createTerminalTrace({ registry });
101
119
  const execute = createAgentExecute({
@@ -116,6 +134,7 @@ export function createStandaloneAgent({
116
134
  void awaitKeychainPrewarm();
117
135
  return { workers: [], jobs: [], scope: null };
118
136
  }
137
+ sweepRestartFailures();
119
138
  const scopedContext = agentScope({}, context);
120
139
  const ownerSession = callerSessionForContext(scopedContext);
121
140
  const pid = terminalPidForContext(scopedContext);
@@ -134,6 +153,7 @@ export function createStandaloneAgent({
134
153
  void awaitKeychainPrewarm();
135
154
  return [];
136
155
  }
156
+ sweepRestartFailures();
137
157
  const scopedContext = agentScope({ recover: true }, context);
138
158
  registry.refreshTagsFromSessions({ scanSessions: true, context: scopedContext });
139
159
  return views.list({ scanSessions: false, context: scopedContext });
@@ -64,6 +64,15 @@ import {
64
64
  beginOAuthLogin as beginCommandCodeOAuthLogin,
65
65
  loginOAuth as loginCommandCodeOAuth,
66
66
  } from '../../runtime/agent/orchestrator/providers/command-code-oauth-login.mjs';
67
+ import {
68
+ describeOpenCodeGoOAuthCredentials,
69
+ forgetOpenCodeGoOAuthCredentials,
70
+ hasOpenCodeGoOAuthCredentials,
71
+ } from '../../runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs';
72
+ import {
73
+ beginOAuthLogin as beginOpenCodeGoOAuthLogin,
74
+ loginOAuth as loginOpenCodeGoOAuth,
75
+ } from '../../runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs';
67
76
  import {
68
77
  beginCursorOAuthLogin,
69
78
  describeCursorOAuthCredentials,
@@ -101,7 +110,6 @@ import {
101
110
  } from '../../runtime/agent/orchestrator/providers/custom-provider-config.mjs';
102
111
 
103
112
  const API_PROVIDERS = Object.freeze([
104
- Object.freeze({ id: 'opencode-go', name: 'OpenCode Go API', env: 'OPENCODE_API_KEY', url: 'https://opencode.ai' }),
105
113
  Object.freeze({ id: 'openrouter', name: 'OpenRouter', env: 'OPENROUTER_API_KEY', url: 'https://openrouter.ai/keys' }),
106
114
  Object.freeze({
107
115
  id: 'openai',
@@ -176,6 +184,16 @@ const OAUTH_PROVIDERS = Object.freeze([
176
184
  begin: beginCommandCodeOAuthLogin,
177
185
  login: loginCommandCodeOAuth,
178
186
  }),
187
+ Object.freeze({
188
+ id: 'opencode-go-oauth',
189
+ name: 'OpenCode Go OAuth',
190
+ desc: 'Sign in with your OpenCode account (OpenCode Go subscription)',
191
+ has: hasOpenCodeGoOAuthCredentials,
192
+ describe: describeOpenCodeGoOAuthCredentials,
193
+ forget: forgetOpenCodeGoOAuthCredentials,
194
+ begin: beginOpenCodeGoOAuthLogin,
195
+ login: loginOpenCodeGoOAuth,
196
+ }),
179
197
  Object.freeze({
180
198
  id: 'mimo-oauth',
181
199
  name: 'MiMo',
@@ -381,7 +399,7 @@ export function isCustomProviderConfig(entry) {
381
399
  return Boolean(entry) && typeof entry === 'object' && entry.type === 'custom';
382
400
  }
383
401
 
384
- export function isCustomProviderId(id) {
402
+ function isCustomProviderId(id) {
385
403
  return CUSTOM_ID_RE.test(String(id || ''));
386
404
  }
387
405
 
@@ -552,7 +570,11 @@ async function modelProxyProviderSetup(options) {
552
570
  // Signed in but switched off on the desktop.
553
571
  else if (!enabled && authState === 'present') status = 'Off';
554
572
  return {
555
- id, name, group, type, enabled,
573
+ id,
574
+ name,
575
+ group,
576
+ type,
577
+ enabled,
556
578
  authenticated,
557
579
  usable,
558
580
  ...(group === 'local' ? { detected: enabled } : {}),
@@ -847,6 +869,10 @@ export function listProviderAccounts(provider) {
847
869
  let identity = null;
848
870
  if (typeof auth.email === 'string' && auth.email.trim()) identity = auth.email.trim();
849
871
  else if (typeof auth.accountId === 'string' && auth.accountId.trim()) identity = auth.accountId.trim();
872
+ // An organization-scoped login names the organization it acts for.
873
+ if (typeof auth.orgName === 'string' && auth.orgName.trim()) {
874
+ identity = identity ? `${identity} · ${auth.orgName.trim()}` : auth.orgName.trim();
875
+ }
850
876
  return {
851
877
  id: row.id,
852
878
  label: row.label,
@@ -1128,6 +1154,69 @@ export async function updateCustomProviderModel(cfgMod, rawId, rawModelId, patch
1128
1154
  return customProviderRow(id, saved);
1129
1155
  }
1130
1156
 
1157
+ const MODEL_PREFERENCE_KEYS = new Set(['model', 'hidden', 'favorite']);
1158
+ export const MODEL_PREFERENCE_CHANGES_MAX = 5_000;
1159
+
1160
+ function modelPreferenceChanges(raw) {
1161
+ if (!Array.isArray(raw) || raw.length > MODEL_PREFERENCE_CHANGES_MAX) {
1162
+ throw new TypeError('model preference changes are invalid');
1163
+ }
1164
+ return raw.map((change) => {
1165
+ plainPatch(change, MODEL_PREFERENCE_KEYS, 'model preference change');
1166
+ const model = typeof change.model === 'string' ? change.model.trim() : '';
1167
+ if (!model) throw new TypeError('model id is required');
1168
+ for (const key of ['hidden', 'favorite']) {
1169
+ if (change[key] !== undefined && typeof change[key] !== 'boolean') {
1170
+ throw new TypeError(`model preference ${key} is invalid`);
1171
+ }
1172
+ }
1173
+ return { model, hidden: change.hidden, favorite: change.favorite };
1174
+ });
1175
+ }
1176
+
1177
+ /**
1178
+ * The model management card's save for one provider: whether each model is
1179
+ * listed in /model (`hidden`) and whether it is a favorite. Both live in the
1180
+ * shared per-model store `modelSettings["provider/model"]` — except a custom
1181
+ * provider's visibility, which stays its own `modelOverrides[id].hidden`, the
1182
+ * store its edit dialog, the runtime catalog and SSH servers already honor.
1183
+ * `hidden: false` is an explicit choice (it also lists a model the family
1184
+ * limit would leave out); an absent field keeps the stored value.
1185
+ */
1186
+ export function saveModelPreferences(cfgMod, rawProvider, rawChanges) {
1187
+ const provider = String(rawProvider || '').trim();
1188
+ const changes = modelPreferenceChanges(rawChanges);
1189
+ const full = cfgMod.loadConfig();
1190
+ if (!isKnownProvider(provider, full)) throw new TypeError(`unknown provider "${provider}"`);
1191
+ const custom = isCustomProviderId(provider);
1192
+ const modelSettings = { ...(full.modelSettings || {}) };
1193
+ for (const { model, hidden, favorite } of changes) {
1194
+ const key = `${provider}/${model}`;
1195
+ const setting = { ...(modelSettings[key] || {}) };
1196
+ if (hidden !== undefined && !custom) setting.hidden = hidden;
1197
+ if (favorite === true) setting.favorite = true;
1198
+ else if (favorite === false) delete setting.favorite;
1199
+ if (Object.keys(setting).length) modelSettings[key] = setting;
1200
+ else delete modelSettings[key];
1201
+ }
1202
+ const next = { ...full, modelSettings };
1203
+ const customHidden = custom ? changes.filter((change) => change.hidden !== undefined) : [];
1204
+ if (customHidden.length) {
1205
+ requireLocalProviderManagement();
1206
+ const entry = full.providers[provider];
1207
+ const current = normalizeStoredCustomConfig(entry);
1208
+ const overrides = new Map(Object.entries(current.modelOverrides || {}));
1209
+ for (const { model, hidden } of customHidden) overrides.set(model, { ...overrides.get(model), hidden });
1210
+ const config = normalizeStoredCustomConfig({ ...current, modelOverrides: Object.fromEntries(overrides) });
1211
+ next.providers = {
1212
+ ...full.providers,
1213
+ [provider]: { ...config, type: 'custom', enabled: entry.enabled !== false },
1214
+ };
1215
+ }
1216
+ cfgMod.saveConfig(next, { baseConfig: full });
1217
+ return { provider, changed: changes.length, customCatalogChanged: customHidden.length > 0 };
1218
+ }
1219
+
1131
1220
  // Drops every stored model setting and route that names the provider.
1132
1221
  function withoutProviderReferences(config, id) {
1133
1222
  const next = { ...config };
@@ -1235,23 +1324,6 @@ export async function saveOpenAIUsageSessionKey(cfgMod, secret) {
1235
1324
  return { provider: 'openai', type: 'usage-auth', authenticated: true };
1236
1325
  }
1237
1326
 
1238
- export async function saveOpenCodeGoUsageAuth(cfgMod, { apiKey } = {}) {
1239
- requireLocalProviderManagement();
1240
- const secret = String(apiKey || '').trim();
1241
- if (!secret) throw new Error('OpenCode console API key is required for usage lookup');
1242
- // Subscription meters are read with a console service-account key holding the
1243
- // `all` permission. The console accepts no other credential for them, so
1244
- // rejecting anything else here keeps unusable auth out of the keychain.
1245
- if (!/^(?:oc_sk_|sk-)/.test(secret))
1246
- throw new Error('OpenCode usage auth must be a console API key (oc_sk_… or sk-…) with all permissions');
1247
- await saveSecretAsync(SECRET_ACCOUNTS.opencodeGoConsoleKey, secret);
1248
- // Usage auth may be a different key from the inference key. Only flip the
1249
- // model provider on when a model key actually exists; otherwise usage-only
1250
- // setups get routed to the provider with apiKey 'no-key' and fail with 401s.
1251
- if (getAgentApiKey('opencode-go')) updateConfigProvider(cfgMod, 'opencode-go', { enabled: true });
1252
- return { provider: 'opencode-go', type: 'usage-auth', authenticated: true };
1253
- }
1254
-
1255
1327
  export async function forgetProviderAuth(cfgModOrProvider, maybeProvider, requestedAccountId) {
1256
1328
  requireLocalProviderManagement();
1257
1329
  const cfgMod = maybeProvider === undefined ? null : cfgModOrProvider;
@@ -242,7 +242,7 @@ function providerDescription(id, group) {
242
242
  return 'MiMo Token Plan subscription quota';
243
243
  case 'antigravity-oauth':
244
244
  return 'Antigravity subscription quota';
245
- case 'opencode-go':
245
+ case 'opencode-go-oauth':
246
246
  return 'OpenCode Go subscription quota';
247
247
  case 'openai':
248
248
  return 'OpenAI API billing';
@@ -349,7 +349,7 @@ const PROVIDER_RANKS = {
349
349
  'mimo-oauth': 34,
350
350
  'cursor-oauth': 35,
351
351
  'antigravity-oauth': 36,
352
- 'opencode-go': 40,
352
+ 'opencode-go-oauth': 38,
353
353
  openai: 50,
354
354
  anthropic: 60,
355
355
  deepseek: 70,