@songsid/agend 2.2.0-beta.2 → 2.2.0-beta.20

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 (389) hide show
  1. package/README.md +40 -24
  2. package/README.zh-TW.md +40 -24
  3. package/dist/agent-cli-instructions.md +6 -2
  4. package/dist/agent-cli.js +15 -7
  5. package/dist/agent-cli.js.map +1 -1
  6. package/dist/agent-endpoint.js +3 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-whats-new.js +16 -4
  9. package/dist/agent-whats-new.js.map +1 -1
  10. package/dist/auth-api.d.ts +3 -1
  11. package/dist/auth-api.js +17 -1
  12. package/dist/auth-api.js.map +1 -1
  13. package/dist/backend/antigravity.d.ts +33 -0
  14. package/dist/backend/antigravity.js +126 -18
  15. package/dist/backend/antigravity.js.map +1 -1
  16. package/dist/backend/claude-carry.d.ts +22 -0
  17. package/dist/backend/claude-carry.js +170 -0
  18. package/dist/backend/claude-carry.js.map +1 -0
  19. package/dist/backend/claude-code.d.ts +65 -2
  20. package/dist/backend/claude-code.js +226 -33
  21. package/dist/backend/claude-code.js.map +1 -1
  22. package/dist/backend/codex-metadata.d.ts +33 -0
  23. package/dist/backend/codex-metadata.js +122 -18
  24. package/dist/backend/codex-metadata.js.map +1 -1
  25. package/dist/backend/codex.d.ts +3 -0
  26. package/dist/backend/codex.js +17 -1
  27. package/dist/backend/codex.js.map +1 -1
  28. package/dist/backend/credential-profile.d.ts +40 -3
  29. package/dist/backend/credential-profile.js +113 -5
  30. package/dist/backend/credential-profile.js.map +1 -1
  31. package/dist/backend/effort-metadata.d.ts +37 -1
  32. package/dist/backend/effort-metadata.js +86 -15
  33. package/dist/backend/effort-metadata.js.map +1 -1
  34. package/dist/backend/grok.d.ts +4 -0
  35. package/dist/backend/grok.js +14 -0
  36. package/dist/backend/grok.js.map +1 -1
  37. package/dist/backend/kiro.d.ts +27 -1
  38. package/dist/backend/kiro.js +63 -10
  39. package/dist/backend/kiro.js.map +1 -1
  40. package/dist/backend/launch-effort.d.ts +61 -0
  41. package/dist/backend/launch-effort.js +170 -0
  42. package/dist/backend/launch-effort.js.map +1 -0
  43. package/dist/backend/muse.d.ts +84 -0
  44. package/dist/backend/muse.js +449 -53
  45. package/dist/backend/muse.js.map +1 -1
  46. package/dist/backend/types.d.ts +48 -2
  47. package/dist/backend/types.js +12 -1
  48. package/dist/backend/types.js.map +1 -1
  49. package/dist/backend-command.d.ts +91 -0
  50. package/dist/backend-command.js +122 -0
  51. package/dist/backend-command.js.map +1 -0
  52. package/dist/background-work-ended.d.ts +18 -0
  53. package/dist/background-work-ended.js +30 -0
  54. package/dist/background-work-ended.js.map +1 -0
  55. package/dist/cache-service.d.ts +2 -0
  56. package/dist/cache-service.js +2 -1
  57. package/dist/cache-service.js.map +1 -1
  58. package/dist/channel/adapters/discord.d.ts +26 -1
  59. package/dist/channel/adapters/discord.js +103 -3
  60. package/dist/channel/adapters/discord.js.map +1 -1
  61. package/dist/channel/adapters/telegram.d.ts +35 -3
  62. package/dist/channel/adapters/telegram.js +122 -23
  63. package/dist/channel/adapters/telegram.js.map +1 -1
  64. package/dist/channel/mcp-tools.js +5 -3
  65. package/dist/channel/mcp-tools.js.map +1 -1
  66. package/dist/channel/message-edit-errors.d.ts +5 -0
  67. package/dist/channel/message-edit-errors.js +24 -0
  68. package/dist/channel/message-edit-errors.js.map +1 -0
  69. package/dist/channel/telegram-markdown.d.ts +6 -0
  70. package/dist/channel/telegram-markdown.js +104 -0
  71. package/dist/channel/telegram-markdown.js.map +1 -0
  72. package/dist/channel/tool-router.d.ts +2 -0
  73. package/dist/channel/tool-router.js +6 -5
  74. package/dist/channel/tool-router.js.map +1 -1
  75. package/dist/channel/types.d.ts +46 -4
  76. package/dist/cli.js +184 -34
  77. package/dist/cli.js.map +1 -1
  78. package/dist/command-table.d.ts +2 -2
  79. package/dist/command-table.js +5 -2
  80. package/dist/command-table.js.map +1 -1
  81. package/dist/compact-watch.d.ts +86 -0
  82. package/dist/compact-watch.js +288 -0
  83. package/dist/compact-watch.js.map +1 -0
  84. package/dist/config-validator.js +55 -5
  85. package/dist/config-validator.js.map +1 -1
  86. package/dist/config.d.ts +1 -0
  87. package/dist/config.js +2 -1
  88. package/dist/config.js.map +1 -1
  89. package/dist/cross-instance-envelope.js +2 -0
  90. package/dist/cross-instance-envelope.js.map +1 -1
  91. package/dist/daemon.d.ts +129 -1
  92. package/dist/daemon.js +754 -94
  93. package/dist/daemon.js.map +1 -1
  94. package/dist/delivery-outbox.d.ts +64 -8
  95. package/dist/delivery-outbox.js +315 -24
  96. package/dist/delivery-outbox.js.map +1 -1
  97. package/dist/delivery-reconciliation.d.ts +18 -1
  98. package/dist/delivery-reconciliation.js +67 -2
  99. package/dist/delivery-reconciliation.js.map +1 -1
  100. package/dist/doctor.js +7 -0
  101. package/dist/doctor.js.map +1 -1
  102. package/dist/fleet-context.d.ts +12 -1
  103. package/dist/fleet-manager.d.ts +392 -19
  104. package/dist/fleet-manager.js +2030 -275
  105. package/dist/fleet-manager.js.map +1 -1
  106. package/dist/forged-envelope-scan.d.ts +4 -3
  107. package/dist/forged-envelope-scan.js +4 -3
  108. package/dist/forged-envelope-scan.js.map +1 -1
  109. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +84 -16
  110. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +16 -1
  111. package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
  112. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +4 -0
  113. package/dist/host-memory.d.ts +10 -0
  114. package/dist/host-memory.js +33 -1
  115. package/dist/host-memory.js.map +1 -1
  116. package/dist/human-delivery-receipts.d.ts +83 -0
  117. package/dist/human-delivery-receipts.js +194 -0
  118. package/dist/human-delivery-receipts.js.map +1 -0
  119. package/dist/install-lock.d.ts +18 -1
  120. package/dist/install-lock.js +30 -3
  121. package/dist/install-lock.js.map +1 -1
  122. package/dist/instance-avatar.d.ts +5 -4
  123. package/dist/instance-avatar.js +12 -8
  124. package/dist/instance-avatar.js.map +1 -1
  125. package/dist/instance-config-impact.js +1 -0
  126. package/dist/instance-config-impact.js.map +1 -1
  127. package/dist/instance-lifecycle.d.ts +25 -6
  128. package/dist/instance-lifecycle.js +76 -27
  129. package/dist/instance-lifecycle.js.map +1 -1
  130. package/dist/instructions.d.ts +1 -1
  131. package/dist/instructions.js +3 -3
  132. package/dist/instructions.js.map +1 -1
  133. package/dist/kiro-compact-pane.d.ts +38 -0
  134. package/dist/kiro-compact-pane.js +57 -0
  135. package/dist/kiro-compact-pane.js.map +1 -0
  136. package/dist/kiro-log-gate.d.ts +42 -0
  137. package/dist/kiro-log-gate.js +39 -0
  138. package/dist/kiro-log-gate.js.map +1 -0
  139. package/dist/launchd-handoff.d.ts +43 -0
  140. package/dist/launchd-handoff.js +56 -0
  141. package/dist/launchd-handoff.js.map +1 -0
  142. package/dist/locale.js +268 -42
  143. package/dist/locale.js.map +1 -1
  144. package/dist/log-service.d.ts +99 -0
  145. package/dist/log-service.js +243 -0
  146. package/dist/log-service.js.map +1 -0
  147. package/dist/log-steps.d.ts +169 -0
  148. package/dist/log-steps.js +858 -0
  149. package/dist/log-steps.js.map +1 -0
  150. package/dist/login-controller.d.ts +20 -3
  151. package/dist/login-controller.js +71 -19
  152. package/dist/login-controller.js.map +1 -1
  153. package/dist/login-flows.d.ts +5 -2
  154. package/dist/login-flows.js +5 -4
  155. package/dist/login-flows.js.map +1 -1
  156. package/dist/login-shell.d.ts +21 -0
  157. package/dist/login-shell.js +98 -0
  158. package/dist/login-shell.js.map +1 -0
  159. package/dist/memory-pressure.d.ts +13 -1
  160. package/dist/memory-pressure.js +50 -10
  161. package/dist/memory-pressure.js.map +1 -1
  162. package/dist/needs-you-hub.js +2 -0
  163. package/dist/needs-you-hub.js.map +1 -1
  164. package/dist/needs-you-live.d.ts +6 -0
  165. package/dist/needs-you-live.js +23 -3
  166. package/dist/needs-you-live.js.map +1 -1
  167. package/dist/outbound-handlers.d.ts +100 -0
  168. package/dist/outbound-handlers.js +277 -93
  169. package/dist/outbound-handlers.js.map +1 -1
  170. package/dist/outbound-schemas.d.ts +30 -15
  171. package/dist/outbound-schemas.js +23 -5
  172. package/dist/outbound-schemas.js.map +1 -1
  173. package/dist/provider-probe.d.ts +29 -0
  174. package/dist/provider-probe.js +198 -2
  175. package/dist/provider-probe.js.map +1 -1
  176. package/dist/public-link-progress.d.ts +1 -1
  177. package/dist/public-link-progress.js +8 -2
  178. package/dist/public-link-progress.js.map +1 -1
  179. package/dist/public-preview.d.ts +101 -0
  180. package/dist/public-preview.js +236 -0
  181. package/dist/public-preview.js.map +1 -0
  182. package/dist/public-web-gateway.d.ts +2 -0
  183. package/dist/public-web-gateway.js +20 -6
  184. package/dist/public-web-gateway.js.map +1 -1
  185. package/dist/public-web-link.d.ts +24 -1
  186. package/dist/public-web-link.js +63 -9
  187. package/dist/public-web-link.js.map +1 -1
  188. package/dist/quickstart-api.d.ts +1 -1
  189. package/dist/quickstart-api.js +8 -6
  190. package/dist/quickstart-api.js.map +1 -1
  191. package/dist/quickstart.d.ts +12 -0
  192. package/dist/quickstart.js +36 -6
  193. package/dist/quickstart.js.map +1 -1
  194. package/dist/reply-buttons-controller.d.ts +89 -5
  195. package/dist/reply-buttons-controller.js +273 -8
  196. package/dist/reply-buttons-controller.js.map +1 -1
  197. package/dist/reply-buttons.d.ts +48 -3
  198. package/dist/reply-buttons.js +105 -10
  199. package/dist/reply-buttons.js.map +1 -1
  200. package/dist/reply-polls.d.ts +85 -0
  201. package/dist/reply-polls.js +154 -0
  202. package/dist/reply-polls.js.map +1 -0
  203. package/dist/restart-guard.d.ts +6 -3
  204. package/dist/restart-guard.js +6 -3
  205. package/dist/restart-guard.js.map +1 -1
  206. package/dist/restart-progress.d.ts +33 -2
  207. package/dist/restart-progress.js +30 -2
  208. package/dist/restart-progress.js.map +1 -1
  209. package/dist/sd-notify.d.ts +22 -1
  210. package/dist/sd-notify.js +63 -10
  211. package/dist/sd-notify.js.map +1 -1
  212. package/dist/service-activation.d.ts +15 -7
  213. package/dist/service-activation.js +42 -26
  214. package/dist/service-activation.js.map +1 -1
  215. package/dist/service-installer.d.ts +55 -0
  216. package/dist/service-installer.js +90 -3
  217. package/dist/service-installer.js.map +1 -1
  218. package/dist/service-plan.d.ts +11 -0
  219. package/dist/service-plan.js +23 -0
  220. package/dist/service-plan.js.map +1 -1
  221. package/dist/settings-api.js +14 -4
  222. package/dist/settings-api.js.map +1 -1
  223. package/dist/settings-change.js +4 -2
  224. package/dist/settings-change.js.map +1 -1
  225. package/dist/settings-effect.js +2 -0
  226. package/dist/settings-effect.js.map +1 -1
  227. package/dist/spawn-gate.js +5 -1
  228. package/dist/spawn-gate.js.map +1 -1
  229. package/dist/spawn-hold-reply.d.ts +12 -0
  230. package/dist/spawn-hold-reply.js +39 -0
  231. package/dist/spawn-hold-reply.js.map +1 -0
  232. package/dist/systemd-peer-bus.d.ts +95 -0
  233. package/dist/systemd-peer-bus.js +430 -0
  234. package/dist/systemd-peer-bus.js.map +1 -0
  235. package/dist/systemd-runtime.d.ts +27 -1
  236. package/dist/systemd-runtime.js +69 -7
  237. package/dist/systemd-runtime.js.map +1 -1
  238. package/dist/tips.js +1 -1
  239. package/dist/tips.js.map +1 -1
  240. package/dist/tool-permissions.js +3 -3
  241. package/dist/tool-permissions.js.map +1 -1
  242. package/dist/tool-progress.js +3 -2
  243. package/dist/tool-progress.js.map +1 -1
  244. package/dist/topic-commands.d.ts +71 -4
  245. package/dist/topic-commands.js +167 -34
  246. package/dist/topic-commands.js.map +1 -1
  247. package/dist/transcript-delta-reader.d.ts +3 -3
  248. package/dist/transcript-delta-reader.js +8 -8
  249. package/dist/transcript-delta-reader.js.map +1 -1
  250. package/dist/transcript-jsonl.d.ts +85 -3
  251. package/dist/transcript-jsonl.js +156 -13
  252. package/dist/transcript-jsonl.js.map +1 -1
  253. package/dist/transcript-monitor.d.ts +11 -0
  254. package/dist/transcript-monitor.js +61 -13
  255. package/dist/transcript-monitor.js.map +1 -1
  256. package/dist/transcript-sources.d.ts +24 -0
  257. package/dist/transcript-sources.js +211 -56
  258. package/dist/transcript-sources.js.map +1 -1
  259. package/dist/tunnel/cloudflared.d.ts +7 -15
  260. package/dist/tunnel/cloudflared.js +36 -1
  261. package/dist/tunnel/cloudflared.js.map +1 -1
  262. package/dist/tunnel/purpose-lane.d.ts +5 -1
  263. package/dist/tunnel/purpose-lane.js +14 -7
  264. package/dist/tunnel/purpose-lane.js.map +1 -1
  265. package/dist/tunnel/types.d.ts +5 -1
  266. package/dist/tunnel/types.js.map +1 -1
  267. package/dist/turn-resume.d.ts +2 -0
  268. package/dist/turn-resume.js +1 -1
  269. package/dist/turn-resume.js.map +1 -1
  270. package/dist/types.d.ts +22 -0
  271. package/dist/ui/chat-commands.js +1 -0
  272. package/dist/ui/chat-mentions.js +63 -0
  273. package/dist/ui/chat-render.js +80 -10
  274. package/dist/ui/chat-store.js +70 -6
  275. package/dist/ui/chat-strings.js +62 -32
  276. package/dist/ui/chat-thread.js +199 -24
  277. package/dist/ui/chat-time.js +80 -0
  278. package/dist/ui/chat-tour.js +17 -14
  279. package/dist/ui/panel-chat.js +45 -21
  280. package/dist/ui/panel-details.js +2 -0
  281. package/dist/ui/panel-fleet.js +213 -58
  282. package/dist/ui/panel-log.js +428 -0
  283. package/dist/ui/panel-needs.js +14 -10
  284. package/dist/ui/panel-settings.js +82 -17
  285. package/dist/ui/preview-panel.js +55 -15
  286. package/dist/ui/preview-status.js +28 -0
  287. package/dist/ui/preview.js +44 -3
  288. package/dist/ui/settings-dialogs.js +11 -6
  289. package/dist/ui/settings-model.js +3 -1
  290. package/dist/ui/settings-strings.js +32 -12
  291. package/dist/ui/settings-token.js +8 -1
  292. package/dist/ui/settings-wizard.js +5 -3
  293. package/dist/ui/shared/app-i18n.js +41 -15
  294. package/dist/ui/shared/app-prefs.js +250 -0
  295. package/dist/ui/shared/app-route.js +6 -1
  296. package/dist/ui/shared/app-shell.js +165 -8
  297. package/dist/ui/shared/app-store.js +5 -1
  298. package/dist/ui/shared/app-stream.js +26 -5
  299. package/dist/ui/shared/app.css +170 -30
  300. package/dist/ui/shared/app.js +11 -1
  301. package/dist/ui/shared/fonts/README.md +1 -1
  302. package/dist/ui/shared/header-tools.js +83 -13
  303. package/dist/ui/shared/instance-nav.js +178 -51
  304. package/dist/ui/shared/instance-switch.js +8 -5
  305. package/dist/ui/shared/panel-view.js +21 -4
  306. package/dist/ui/shared/signin.js +15 -9
  307. package/dist/ui/shared/theme.js +39 -7
  308. package/dist/ui/shared/tokens.css +1 -1
  309. package/dist/ui/shared/ui-icons.js +2 -0
  310. package/dist/ui/shared/usage-dialog.js +23 -5
  311. package/dist/ui/shared/view-strings.js +40 -4
  312. package/dist/ui/signin.html +8 -3
  313. package/dist/unrecognised-prompt.d.ts +23 -0
  314. package/dist/unrecognised-prompt.js +74 -0
  315. package/dist/unrecognised-prompt.js.map +1 -0
  316. package/dist/update-check.d.ts +31 -0
  317. package/dist/update-check.js +47 -0
  318. package/dist/update-check.js.map +1 -1
  319. package/dist/update-install.d.ts +13 -1
  320. package/dist/update-install.js +16 -4
  321. package/dist/update-install.js.map +1 -1
  322. package/dist/update-launch.d.ts +48 -1
  323. package/dist/update-launch.js +159 -5
  324. package/dist/update-launch.js.map +1 -1
  325. package/dist/update-marker.d.ts +51 -0
  326. package/dist/update-marker.js +61 -0
  327. package/dist/update-marker.js.map +1 -1
  328. package/dist/usage/i18n-keys.d.ts +1 -1
  329. package/dist/usage/i18n-keys.js +1 -1
  330. package/dist/usage/i18n-keys.js.map +1 -1
  331. package/dist/usage/providers.d.ts +62 -13
  332. package/dist/usage/providers.js +256 -68
  333. package/dist/usage/providers.js.map +1 -1
  334. package/dist/usage/statusline-usage.d.ts +6 -2
  335. package/dist/usage/statusline-usage.js +10 -4
  336. package/dist/usage/statusline-usage.js.map +1 -1
  337. package/dist/view-api.js +31 -9
  338. package/dist/view-api.js.map +1 -1
  339. package/dist/web-api.d.ts +82 -4
  340. package/dist/web-api.js +632 -88
  341. package/dist/web-api.js.map +1 -1
  342. package/dist/web-auth.js +5 -2
  343. package/dist/web-auth.js.map +1 -1
  344. package/dist/web-chat-history.d.ts +26 -1
  345. package/dist/web-chat-history.js +47 -8
  346. package/dist/web-chat-history.js.map +1 -1
  347. package/dist/web-commands.d.ts +41 -5
  348. package/dist/web-commands.js +25 -7
  349. package/dist/web-commands.js.map +1 -1
  350. package/dist/web-login.d.ts +14 -0
  351. package/dist/web-login.js +4 -2
  352. package/dist/web-login.js.map +1 -1
  353. package/dist/web-mentions.d.ts +44 -0
  354. package/dist/web-mentions.js +75 -0
  355. package/dist/web-mentions.js.map +1 -0
  356. package/dist/web-prefs.d.ts +81 -0
  357. package/dist/web-prefs.js +297 -0
  358. package/dist/web-prefs.js.map +1 -0
  359. package/dist/web-preview.d.ts +14 -1
  360. package/dist/web-preview.js +35 -13
  361. package/dist/web-preview.js.map +1 -1
  362. package/dist/web-profile.d.ts +32 -0
  363. package/dist/web-profile.js +132 -0
  364. package/dist/web-profile.js.map +1 -0
  365. package/dist/web-reply-files.d.ts +45 -0
  366. package/dist/web-reply-files.js +223 -0
  367. package/dist/web-reply-files.js.map +1 -0
  368. package/dist/web-schedules.d.ts +12 -0
  369. package/dist/web-schedules.js +34 -0
  370. package/dist/web-schedules.js.map +1 -0
  371. package/dist/web-server-drain.d.ts +10 -0
  372. package/dist/web-server-drain.js +50 -0
  373. package/dist/web-server-drain.js.map +1 -0
  374. package/dist/web-session.d.ts +14 -0
  375. package/dist/web-session.js +11 -1
  376. package/dist/web-session.js.map +1 -1
  377. package/dist/web-settings-patch.d.ts +3 -1
  378. package/dist/web-settings-patch.js +4 -1
  379. package/dist/web-settings-patch.js.map +1 -1
  380. package/dist/web-shell-routes.d.ts +4 -0
  381. package/dist/web-shell-routes.js +9 -0
  382. package/dist/web-shell-routes.js.map +1 -1
  383. package/dist/web-upload.d.ts +135 -7
  384. package/dist/web-upload.js +315 -51
  385. package/dist/web-upload.js.map +1 -1
  386. package/launcher/install-admission.cjs +61 -6
  387. package/package.json +3 -3
  388. package/templates/launchd.plist.ejs +3 -1
  389. package/templates/systemd.service.ejs +2 -1
@@ -90,6 +90,27 @@ const MUSE_STATUS_BAR = /^\s{1,4}[^\s·]+(?:\s+·\s+[^·\s][^·]*)+$/;
90
90
  const MUSE_DIALOG_CHROME = /Allow this (?:tool|command)|Approve this (?:tool|command)|^\s*[>❯]?\s*1\s+(?:Allow|Approve|Trust)\b|Use Up\/Down|Esc (?:quits|to cancel)|↓↑ to select|·\s*Enter to choose\b/i;
91
91
  /** The status bar is one row; a long cwd in a narrow pane wraps it onto a few more. */
92
92
  const MUSE_MAX_STATUS_ROWS = 4;
93
+ /**
94
+ * #1717: extended bottom-region depth, but ONLY for a terminals-panel-headed
95
+ * block (header + running tasks + bar). A long wrapped bar still fails closed
96
+ * past MUSE_MAX_STATUS_ROWS (#958): the extension below accepts a deeper
97
+ * separator only when the row right after it is the panel header.
98
+ */
99
+ const MUSE_MAX_PANEL_DEPTH = 8;
100
+ /**
101
+ * #1717: while a background task runs, muse draws a terminals panel between
102
+ * the input box's bottom separator and the status bar (live 1.4.6,
103
+ * tests/fixtures/muse-1.4.6/prod-background-work.pane.txt):
104
+ * main · ↓ to select
105
+ * └ ◆ Wait in the background running 11s
106
+ * The header ends in `↓ to select` — note the single arrow, which is NOT the
107
+ * dialog chrome's `↓↑ to select`. The timer redraws every second, which is
108
+ * exactly the periodic-redraw case. The composer above stays free, so the
109
+ * panel must not fail the idle proof — but only this shape passes: anything
110
+ * else under the separator still fails closed.
111
+ */
112
+ const MUSE_TERMINALS_HEADER = /^.+· ↓ to select\s*$/;
113
+ const MUSE_TERMINAL_TASK_ROW = /^[└├]\s+.*\brunning\b\s+(?:\d+(?:\.\d+)?[hms]\s*)+\s*$/;
93
114
  /** The working line's timer: `◇ Thinking (2s · esc to interrupt)`. */
94
115
  const MUSE_BUSY = /\(\s*(?:\d+(?:\.\d+)?[hms]\s*)+·\s*esc to interrupt\s*\)/;
95
116
  const MUSE_MAX_INPUT_ROWS = 200;
@@ -98,10 +119,30 @@ const MUSE_MAX_INPUT_ROWS = 200;
98
119
  * first row in the bar's grammar, the rest a wrapped continuation of it. A
99
120
  * picker, a dialog, a busy line or a second separator there means the box above
100
121
  * is not the live input (#829 review: a chooser under an old box).
122
+ *
123
+ * #1717: a terminals panel may sit between the separator and the bar (a
124
+ * `· ↓ to select` header plus `running` task rows). It is stripped first and
125
+ * each of its rows must match the panel grammar — a header with no task row,
126
+ * or any other shape, still fails closed.
101
127
  */
102
128
  function museStatusFooter(rows, bottomSeparator, busy) {
103
- const status = rows.slice(bottomSeparator + 1);
104
- if (status.length === 0 || !MUSE_STATUS_BAR.test(status[0]))
129
+ let status = rows.slice(bottomSeparator + 1);
130
+ if (status.length === 0)
131
+ return false;
132
+ if (MUSE_TERMINALS_HEADER.test(status[0])) {
133
+ let end = 1;
134
+ while (end < status.length && MUSE_TERMINAL_TASK_ROW.test(status[end]))
135
+ end++;
136
+ if (end === 1)
137
+ return false;
138
+ const panel = status.slice(0, end);
139
+ if (!panel.every(row => !MUSE_SEPARATOR.test(row.trim()) && !busy.test(row) && !MUSE_DIALOG_CHROME.test(row)))
140
+ return false;
141
+ status = status.slice(end);
142
+ if (status.length === 0)
143
+ return false;
144
+ }
145
+ if (!MUSE_STATUS_BAR.test(status[0]))
105
146
  return false;
106
147
  if (!status.every(row => /^\s+\S/.test(row) && !MUSE_SEPARATOR.test(row.trim()) && !busy.test(row) && !MUSE_DIALOG_CHROME.test(row)))
107
148
  return false;
@@ -109,6 +150,85 @@ function museStatusFooter(rows, bottomSeparator, busy) {
109
150
  // selection cursor under the bar is a picker, not the bar.
110
151
  return !status.slice(1).some(row => /^\s*[>❯›]?\s*\d+[.)]?\s+\S/.test(row) || /^\s*[>›]/.test(row));
111
152
  }
153
+ /**
154
+ * The structural core of the idle proof, shared with the quit-menu matcher
155
+ * (#1730 P2-1): true when the CURRENT screen — anchored to the bottom
156
+ * separator — shows the live, empty input box. A transcript quoting an older
157
+ * screen above never satisfies it.
158
+ */
159
+ function museFreeComposerLayout(rows, busyPattern) {
160
+ // The approval prompt is auto-answered rather than input-blocking, so the
161
+ // idle proof itself must refuse a frame that shows dialog chrome.
162
+ const dialogChrome = MUSE_DIALOG_CHROME;
163
+ // Everything after the bottom separator must be the status bar (#1717:
164
+ // optionally a terminals panel first).
165
+ const bottomSeparator = museBottomSeparator(rows);
166
+ if (bottomSeparator < 2)
167
+ return false;
168
+ if (!museStatusFooter(rows, bottomSeparator, busyPattern))
169
+ return false;
170
+ // Directly above it, the EMPTY live prompt. A `❯` with text is a draft
171
+ // (or, higher up, a transcript echo) and never proves idle.
172
+ if (!/^❯\s*$/.test(rows[bottomSeparator - 1]))
173
+ return false;
174
+ const topSeparator = bottomSeparator - 2;
175
+ if (!MUSE_SEPARATOR.test(rows[topSeparator]))
176
+ return false;
177
+ // No busy indicator and no dialog just above the input box. The working
178
+ // line (`◇ Thinking (2s · esc to interrupt)`) sits immediately above the
179
+ // top separator; check 8 rows up to be safe.
180
+ const above = rows.slice(Math.max(0, topSeparator - 8), topSeparator);
181
+ if (above.some(r => busyPattern.test(r) || dialogChrome.test(r)))
182
+ return false;
183
+ // Arrow-key pickers (`/model`) are refused by their component chrome
184
+ // above, not by guessing from answer text that happens to list model ids.
185
+ // A `›` selection cursor is refused too; `❯` is not a cursor here — muse
186
+ // echoes submitted messages with it.
187
+ return !above.some(r => /^\s*›\s+\S/.test(r));
188
+ }
189
+ /**
190
+ * Nearest bottom separator, with the #1717 panel extension: the plain search
191
+ * stays capped at MUSE_MAX_STATUS_ROWS (a 5-row wrapped bar still fails
192
+ * closed, #958); a deeper separator is accepted only when the row right
193
+ * after it is the terminals-panel header. Nearest-first, so a stale
194
+ * separator in scrollback can never win over the live one.
195
+ */
196
+ function museBottomSeparator(rows) {
197
+ for (let i = rows.length - 2; i >= Math.max(0, rows.length - 1 - MUSE_MAX_STATUS_ROWS); i--) {
198
+ if (MUSE_SEPARATOR.test(rows[i]))
199
+ return i;
200
+ }
201
+ for (let i = Math.max(0, rows.length - 1 - MUSE_MAX_STATUS_ROWS) - 1; i >= Math.max(0, rows.length - 1 - MUSE_MAX_PANEL_DEPTH); i--) {
202
+ if (MUSE_SEPARATOR.test(rows[i])) {
203
+ return MUSE_TERMINALS_HEADER.test(rows[i + 1] ?? "") ? i : -1;
204
+ }
205
+ }
206
+ return -1;
207
+ }
208
+ /**
209
+ * #1717: true when the pane shows muse's terminals panel with at least one
210
+ * running task — the "local work still active" state. Anchored to the last
211
+ * separator, where the live panel sits under the input box: a transcript
212
+ * quoting an old panel above a separator never counts. The idle proof checks
213
+ * the same shape positionally via museStatusFooter; this boolean feeds the
214
+ * pause gate below.
215
+ */
216
+ export function museBackgroundWorkActive(pane) {
217
+ const rows = pane.replace(/\r/g, "").split("\n");
218
+ while (rows.length && !rows[rows.length - 1].trim())
219
+ rows.pop();
220
+ let sep = -1;
221
+ for (let i = rows.length - 1; i >= 0; i--) {
222
+ if (MUSE_SEPARATOR.test(rows[i])) {
223
+ sep = i;
224
+ break;
225
+ }
226
+ }
227
+ if (sep < 0)
228
+ return false;
229
+ const after = rows.slice(sep + 1);
230
+ return after.length > 1 && MUSE_TERMINALS_HEADER.test(after[0]) && MUSE_TERMINAL_TASK_ROW.test(after[1]);
231
+ }
112
232
  /**
113
233
  * The rows of muse's live input box, or null when the bottom of the pane is not
114
234
  * muse's layout (#829). The box is the LAST thing above the status bar:
@@ -127,13 +247,7 @@ export function museInputBox(pane, busy = MUSE_BUSY) {
127
247
  const rows = pane.replace(/\r/g, "").split("\n");
128
248
  while (rows.length && !rows[rows.length - 1].trim())
129
249
  rows.pop();
130
- let bottom = -1;
131
- for (let i = rows.length - 2; i >= Math.max(0, rows.length - 1 - MUSE_MAX_STATUS_ROWS); i--) {
132
- if (MUSE_SEPARATOR.test(rows[i])) {
133
- bottom = i;
134
- break;
135
- }
136
- }
250
+ const bottom = museBottomSeparator(rows);
137
251
  if (bottom < 2 || !museStatusFooter(rows, bottom, busy))
138
252
  return null;
139
253
  for (let top = bottom - 1, n = 0; top >= 1 && n < MUSE_MAX_INPUT_ROWS; top--, n++) {
@@ -234,6 +348,288 @@ const MUSE_LOGIN_MENU = {
234
348
  inputBlocked: true,
235
349
  description: "Muse login menu — waiting for a human to log in (never answered)",
236
350
  };
351
+ /**
352
+ * #1716: `/quit` while a background task runs is answered with a confirmation
353
+ * instead of an exit (live 1.4.6,
354
+ * tests/fixtures/muse-1.4.6/prod-quit-with-local-work.pane.txt):
355
+ * ❯
356
+ * Local work is still active
357
+ * The following will stop when you exit:
358
+ * background task · tool.bash
359
+ * ❯ 1. Exit anyway
360
+ * 2. Stay
361
+ * Enter to confirm · Esc to cancel
362
+ * ──────────
363
+ * main · ↓ to select
364
+ * └ ◇ Wait in the background running 15s
365
+ * mock-model · high · ~/work · Launch overrides
366
+ * Unlike Claude's prompt this menu is NOT bottom-anchored — the terminals
367
+ * panel sits under it — so the match is anchored to the hint row instead:
368
+ * the header must be within 12 rows above it with both options in between.
369
+ * Enter would take the selected option ("Exit anyway"), so like #1217 this is
370
+ * recognised whole and never answered here: the stop flow cancels it.
371
+ */
372
+ /**
373
+ * #1715: with `skip_permissions: false` muse asks before running a command
374
+ * needing approval (live 1.4.6,
375
+ * tests/fixtures/muse-1.4.6/approval-escalated.pane.txt and
376
+ * approval-untrusted-staged.pane.txt):
377
+ * Would you like to run the following command?
378
+ * $ echo perm-check
379
+ * › 1. Yes, proceed (y)
380
+ * 2. No, and tell Muse Code what to do differently (esc)
381
+ * Change approval defaults in /permissions
382
+ * `--approval-mode untrusted` (new in 1.4.6) stages the command first
383
+ * (`Stage 1/1`, `Current argv: [...]`) and offers three options (allow once /
384
+ * always allow in this workspace / abort). 1.4.4 shows the same prompt with a
385
+ * `›` marker and no footer line. Enter would take the preselected allow
386
+ * option, so like #1716 this is recognised whole and never answered here.
387
+ * 1.4.7 draws the selection cursor as `⟩` on this prompt and on the #1716
388
+ * quit menu (tests/fixtures/muse-1.4.7); the other rows of those blocks are
389
+ * unchanged.
390
+ */
391
+ export const MUSE_APPROVAL_PROMPT = /^[ \t]*Would you like to run the following command\?[ \t]*$/m;
392
+ const MUSE_APPROVAL_STAGE = /^[ \t]*Stage \d+\/\d+[ \t]*$/;
393
+ const MUSE_APPROVAL_ARGV = /^[ \t]*Current argv: \[.*\][ \t]*$/;
394
+ const MUSE_APPROVAL_OPT = /^[ \t]*[❯›⟩]?[ \t]*\d\.[ \t]+/;
395
+ const MUSE_APPROVAL_FOOTER = /^[ \t]*Change approval defaults in \/permissions[ \t]*$/;
396
+ /**
397
+ * #1715: which request the approval prompt is showing — its `$ command` row.
398
+ * Stable across the `Calling tools (Ns)` timer redraws, so the episode can
399
+ * confirm; a whole-pane hash never would. Derived from the SAME selected
400
+ * block as the predicate (#1715 r2): a scrollback quote's command must never
401
+ * become the current request's identity.
402
+ */
403
+ export function museApprovalRequestIdentity(pane) {
404
+ const s = museApprovalScreen(pane);
405
+ if (s === null)
406
+ return null;
407
+ return museApprovalBlock(s.screen, s.top, s.bottom)?.command ?? null;
408
+ }
409
+ /**
410
+ * #1715 r2: the selected live approval block — its header row index and `$`
411
+ * command text. Headers are tried newest-first inside the live input block so
412
+ * a scrollback quote never shadows the current prompt, and the identity comes
413
+ * from the SAME block the predicate accepts (never a separate global search).
414
+ */
415
+ function museApprovalBlock(screen, top, bottom) {
416
+ const window = [];
417
+ for (let i = bottom - 1; i > top; i--) {
418
+ if (screen[i].trim())
419
+ window.unshift({ row: screen[i], index: i });
420
+ }
421
+ for (let h = window.length - 1; h >= 0; h--) {
422
+ if (!MUSE_APPROVAL_PROMPT.test(window[h].row))
423
+ continue;
424
+ let k = h + 1;
425
+ const commandRow = k < window.length ? window[k].row : "";
426
+ const command = /^[ \t]*\$ (.+?)[ \t]*$/.exec(commandRow)?.[1]?.trim();
427
+ if (!command)
428
+ continue;
429
+ k++;
430
+ if (k < window.length && MUSE_APPROVAL_STAGE.test(window[k].row))
431
+ k++;
432
+ if (k < window.length && MUSE_APPROVAL_ARGV.test(window[k].row))
433
+ k++;
434
+ const last = window[window.length - 1]?.row;
435
+ const options = (last !== undefined && MUSE_APPROVAL_FOOTER.test(last) ? window.slice(k, -1) : window.slice(k))
436
+ .map(entry => entry.row);
437
+ if (options.length !== 2 && options.length !== 3)
438
+ continue;
439
+ if (!options.every(row => MUSE_APPROVAL_OPT.test(row)))
440
+ continue;
441
+ const first = (options[0] ?? "").replace(/^[ \t]*[❯›⟩]?[ \t]*/, "");
442
+ const second = options[1] ?? "";
443
+ if (options.length === 2) {
444
+ if (!/^1\. Yes, proceed \(y\)[ \t]*$/.test(first))
445
+ continue;
446
+ if (!/^[ \t]*[❯›⟩]?[ \t]*2\. No, and tell Muse Code what to do differently \(esc\)[ \t]*$/.test(second))
447
+ continue;
448
+ }
449
+ else {
450
+ if (!/^1\. Allow this stage once \(y\)[ \t]*$/.test(first))
451
+ continue;
452
+ if (!/^[ \t]*[❯›⟩]?[ \t]*2\. Always allow in this workspace: .+ \(p\)[ \t]*$/.test(second))
453
+ continue;
454
+ if (!/^[ \t]*[❯›⟩]?[ \t]*3\. Abort the entire command \(esc\)[ \t]*$/.test(options[2] ?? ""))
455
+ continue;
456
+ }
457
+ return { head: window[h].index, command };
458
+ }
459
+ return null;
460
+ }
461
+ function museApprovalScreen(pane) {
462
+ const screen = pane.replace(/\r/g, "").split("\n");
463
+ while (screen.length && !screen[screen.length - 1].trim())
464
+ screen.pop();
465
+ const bottom = museBottomSeparator(screen);
466
+ if (bottom < 0)
467
+ return null;
468
+ // Same ownership as the quit menu (#1730 P2-1): the prompt must live in the
469
+ // current input block, and a free composer means any prompt text is history.
470
+ let top = -1;
471
+ for (let i = bottom - 1; i >= 0; i--) {
472
+ if (MUSE_SEPARATOR.test(screen[i])) {
473
+ top = i;
474
+ break;
475
+ }
476
+ }
477
+ if (museFreeComposerLayout(screen, MUSE_BUSY))
478
+ return null;
479
+ // #1715 r3: any readable composer means prompt text is a quote, not the
480
+ // live block — the live approval panes carry no ❯ composer row at all
481
+ // (inputDraft null), while a quoting draft does, even one starting with a
482
+ // blank first row. A null draft (no readable box) carries nothing to judge,
483
+ // so it still proceeds.
484
+ if (museInputBox(pane, MUSE_BUSY) !== null)
485
+ return null;
486
+ return { screen, top, bottom };
487
+ }
488
+ export function museApprovalPromptActive(pane) {
489
+ const s = museApprovalScreen(pane);
490
+ return s !== null && museApprovalBlock(s.screen, s.top, s.bottom) !== null;
491
+ }
492
+ export const MUSE_LOCAL_WORK_QUIT = /^[ \t]*Local work is still active[ \t]*$/m;
493
+ const MUSE_QUIT_HINT = /^[ \t]*Enter to confirm · Esc to cancel[ \t]*$/;
494
+ const MUSE_QUIT_OPT_1 = /^[ \t]*[❯›⟩]?[ \t]*1\.[ \t]+Exit anyway[ \t]*$/m;
495
+ const MUSE_QUIT_OPT_2 = /^[ \t]*[❯›⟩]?[ \t]*2\.[ \t]+Stay[ \t]*$/m;
496
+ export function museLocalWorkQuitActive(pane) {
497
+ const screen = pane.replace(/\r/g, "").split("\n");
498
+ while (screen.length && !screen[screen.length - 1].trim())
499
+ screen.pop();
500
+ const bottom = museBottomSeparator(screen);
501
+ if (bottom < 0)
502
+ return false;
503
+ // #1730 P2-1 (refined): the menu must belong to the CURRENT input block —
504
+ // between the bottom separator and the nearest separator above it. Menu
505
+ // text quoted in an older screen is history even when the current composer
506
+ // is busy with a draft, not just when it is free.
507
+ let top = -1;
508
+ for (let i = bottom - 1; i >= 0; i--) {
509
+ if (MUSE_SEPARATOR.test(screen[i])) {
510
+ top = i;
511
+ break;
512
+ }
513
+ }
514
+ // A free composer on the current screen means any menu text is history.
515
+ if (museFreeComposerLayout(screen, MUSE_BUSY))
516
+ return false;
517
+ // #1730 P2-1 r3: a populated ❯ line means any menu text is a quote, not the
518
+ // live chrome. The menu only ever answers a submitted /quit, which clears
519
+ // the box — the live capture's ❯ row is empty with the menu as chrome under
520
+ // it — while every quoting draft (r2's "cancel me", r3a's hint-carrying
521
+ // draft, r3b's menu-quoting draft) holds text on the ❯ line itself. A null
522
+ // draft (no readable box) carries nothing to judge, so it still proceeds.
523
+ // Residual: an empty ❯ followed by a verbatim menu paste is byte-identical
524
+ // to the live chrome and cannot be separated here.
525
+ const draft = museInputBox(pane, MUSE_BUSY);
526
+ if (draft !== null && draft.rows.length > 0 && draft.rows[0].trim() !== "")
527
+ return false;
528
+ // Candidates newest-first, so a stale quote above never shadows the live
529
+ // block below (or passes as live itself).
530
+ for (let h = screen.length - 1; h >= 0; h--) {
531
+ if (!MUSE_QUIT_HINT.test(screen[h]))
532
+ continue;
533
+ if (!(h > top && h < bottom))
534
+ continue;
535
+ // The block above, skipping padding blanks (the same 12-non-blank
536
+ // window as before — the live menu fits in 9), with screen indices so the
537
+ // WHOLE block is fenced to the live input box, not just the hint: the
538
+ // backward scan must not borrow the header or options from above top.
539
+ const window = [];
540
+ for (let i = h - 1; i >= 0 && window.length < 12; i--) {
541
+ if (screen[i].trim())
542
+ window.unshift({ row: screen[i], index: i });
543
+ }
544
+ if (window.length < 4)
545
+ continue;
546
+ // #1730 P2-1 r4: headers newest-first, eligible only — a scrollback header
547
+ // above top must not shadow the live header inside the box. The first
548
+ // (oldest) match can sit above top while the current menu is intact below.
549
+ for (let head = window.length - 1; head >= 0; head--) {
550
+ if (!MUSE_LOCAL_WORK_QUIT.test(window[head].row))
551
+ continue;
552
+ if (window[head].index <= top)
553
+ continue;
554
+ const block = window.slice(head).map(entry => entry.row).join("\n");
555
+ if (!MUSE_QUIT_OPT_1.test(block) || !MUSE_QUIT_OPT_2.test(block))
556
+ continue;
557
+ return true;
558
+ }
559
+ }
560
+ return false;
561
+ }
562
+ /**
563
+ * #1796: the fallback `/model` set, used only when muse's on-disk provider
564
+ * catalog is missing or unreadable. It mirrors what the 1.4.7 picker shows
565
+ * (visible rows only — no `-contributor` ids); the live catalog remains the
566
+ * source of truth whenever it can be read.
567
+ */
568
+ const MUSE_MODEL_FALLBACK = [
569
+ { id: "muse-wonder-1.0", label: "muse-wonder-1.0" },
570
+ { id: "muse-spark-1.3", label: "muse-spark-1.3" },
571
+ { id: "muse-spark-1.2", label: "muse-spark-1.2" },
572
+ ];
573
+ /**
574
+ * #1796: read the provider catalog muse caches on disk
575
+ * (`<state>/model-catalog/<provider>__<profile>.json`). Bounded (at most 16
576
+ * files, 256 KiB each) and best-effort — anything unexpected yields the
577
+ * fallback. Listed rows only: `visibility: "visible"` is what the 1.4.7
578
+ * picker shows (the `-contributor` siblings and the image model are hidden),
579
+ * and a null `context_limit` additionally marks a non-chat model. File order
580
+ * is kept and ids deduped.
581
+ *
582
+ * `stateDir` is injectable for tests; production passes nothing and reads the
583
+ * host state dir (the fleet never isolates XDG_DATA_HOME, mirroring the
584
+ * session lookup).
585
+ */
586
+ export function readMuseModelCatalog(stateDir) {
587
+ // Root resolution mirrors the binary, not XDG_STATE_HOME: 1.4.7 builds its
588
+ // state dir as `$XDG_DATA_HOME/muse` or `$HOME/.local/share/muse`
589
+ // (MUSE_SESSIONS uses the same root).
590
+ const base = process.env.XDG_DATA_HOME
591
+ ? join(process.env.XDG_DATA_HOME, "muse")
592
+ : join(homedir(), ".local", "share", "muse");
593
+ const dir = join(stateDir ?? base, "model-catalog");
594
+ try {
595
+ const files = readdirSync(dir).filter(f => f.endsWith(".json")).sort().slice(0, 16);
596
+ if (files.length === 0)
597
+ return [...MUSE_MODEL_FALLBACK];
598
+ const seen = new Set();
599
+ const out = [];
600
+ for (const file of files) {
601
+ const path = join(dir, file);
602
+ try {
603
+ if (statSync(path).size > 262_144)
604
+ continue;
605
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
606
+ const rows = parsed.rows;
607
+ if (!Array.isArray(rows))
608
+ continue;
609
+ for (const row of rows.slice(0, 256)) {
610
+ const r = row;
611
+ if (typeof r.model_id !== "string" || r.model_id === "" || r.visibility !== "visible")
612
+ continue;
613
+ // A `-contributor` suffix is a legacy spelling of contributor mode,
614
+ // not a model — excluded even if some profile ever marks it visible.
615
+ if (r.model_id.endsWith("-contributor"))
616
+ continue;
617
+ if (r.context_limit == null)
618
+ continue;
619
+ if (seen.has(r.model_id))
620
+ continue;
621
+ seen.add(r.model_id);
622
+ out.push({ id: r.model_id, label: typeof r.display_label === "string" && r.display_label !== "" ? r.display_label : r.model_id });
623
+ }
624
+ }
625
+ catch { /* one bad file never hides the rest */ }
626
+ }
627
+ return out.length > 0 ? out : [...MUSE_MODEL_FALLBACK];
628
+ }
629
+ catch {
630
+ return [...MUSE_MODEL_FALLBACK];
631
+ }
632
+ }
237
633
  /**
238
634
  * Meta Muse Code — the `muse` CLI (v1.3.0 verified).
239
635
  *
@@ -559,41 +955,7 @@ export class MuseBackend {
559
955
  const rows = pane.replace(/\r/g, "").split("\n");
560
956
  while (rows.length && !rows[rows.length - 1].trim())
561
957
  rows.pop();
562
- const separator = MUSE_SEPARATOR;
563
- const busyPattern = this.getBusyPattern();
564
- // The approval prompt is auto-answered rather than input-blocking, so the
565
- // idle proof itself must refuse a frame that shows dialog chrome.
566
- const dialogChrome = MUSE_DIALOG_CHROME;
567
- // Everything after the bottom separator must be the status bar.
568
- let bottomSeparator = -1;
569
- for (let i = rows.length - 2; i >= Math.max(0, rows.length - 1 - MUSE_MAX_STATUS_ROWS); i--) {
570
- if (separator.test(rows[i])) {
571
- bottomSeparator = i;
572
- break;
573
- }
574
- }
575
- if (bottomSeparator < 2)
576
- return false;
577
- if (!museStatusFooter(rows, bottomSeparator, busyPattern))
578
- return false;
579
- // Directly above it, the EMPTY live prompt. A `❯` with text is a draft
580
- // (or, higher up, a transcript echo) and never proves idle.
581
- if (!/^❯\s*$/.test(rows[bottomSeparator - 1]))
582
- return false;
583
- const topSeparator = bottomSeparator - 2;
584
- if (!separator.test(rows[topSeparator]))
585
- return false;
586
- // No busy indicator and no dialog just above the input box. The working
587
- // line (`◇ Thinking (2s · esc to interrupt)`) sits immediately above the
588
- // top separator; check 8 rows up to be safe.
589
- const above = rows.slice(Math.max(0, topSeparator - 8), topSeparator);
590
- if (above.some(r => busyPattern.test(r) || dialogChrome.test(r)))
591
- return false;
592
- // Arrow-key pickers (`/model`) are refused by their component chrome
593
- // above, not by guessing from answer text that happens to list model ids.
594
- // A `›` selection cursor is refused too; `❯` is not a cursor here — muse
595
- // echoes submitted messages with it.
596
- return !above.some(r => /^\s*›\s+\S/.test(r));
958
+ return museFreeComposerLayout(rows, this.getBusyPattern());
597
959
  }
598
960
  getErrorPatterns() {
599
961
  // NOTE: an Escape-interrupted run prints "interrupting run" in the status
@@ -659,6 +1021,31 @@ export class MuseBackend {
659
1021
  keys: ["1", "Enter"],
660
1022
  description: "Muse tool approval — allow",
661
1023
  },
1024
+ {
1025
+ // #1715: never answered here. Enter would take the preselected allow
1026
+ // option, so the human must choose. The prompt replaces the input
1027
+ // box (no draft to clear, unlike #829); deliveries simply hold.
1028
+ pattern: MUSE_APPROVAL_PROMPT,
1029
+ isActive: museApprovalPromptActive,
1030
+ requestIdentity: museApprovalRequestIdentity,
1031
+ interactionKind: "permission",
1032
+ keys: [],
1033
+ holdOnly: true,
1034
+ blocksDelivery: true,
1035
+ inputBlocked: true,
1036
+ description: "Muse approval prompt — waiting for a human choice, never auto-approving",
1037
+ },
1038
+ {
1039
+ // #1716: never answered here. A delivery's Enter would pick
1040
+ // "Exit anyway"; the stop flow cancels it (Escape, then SIGTERM).
1041
+ pattern: MUSE_LOCAL_WORK_QUIT,
1042
+ isActive: museLocalWorkQuitActive,
1043
+ keys: [],
1044
+ holdOnly: true,
1045
+ blocksDelivery: true,
1046
+ inputBlocked: true,
1047
+ description: "Muse \"Local work is still active\" quit menu — holding, never answering with Enter",
1048
+ },
662
1049
  ];
663
1050
  }
664
1051
  getContextUsage() {
@@ -726,6 +1113,19 @@ export class MuseBackend {
726
1113
  getQuitCommand() { return "/quit"; }
727
1114
  getQuitKey() { return "C-c"; }
728
1115
  getQuitKeyPresses() { return 2; }
1116
+ /** #1716: `/quit` with a background task running stops at the "Local work is still active" menu. */
1117
+ quitBlockedByDialog(pane) {
1118
+ return museLocalWorkQuitActive(pane);
1119
+ }
1120
+ /**
1121
+ * #1717: the terminals panel with a `running` task — the pause flow defers
1122
+ * an idle pause while this holds, so an AgEnD-initiated silent pause never
1123
+ * kills the user's running background work. Explicit stop/restart still
1124
+ * proceed via quitBlockedByDialog above.
1125
+ */
1126
+ backgroundWorkRunning(pane) {
1127
+ return museBackgroundWorkActive(pane);
1128
+ }
729
1129
  getCompactCommand() { return "/compact"; }
730
1130
  getClearCommand() { return "/clear"; }
731
1131
  /**
@@ -773,15 +1173,11 @@ export class MuseBackend {
773
1173
  async listModels() {
774
1174
  // muse has no models subcommand (the command table is: resume, exec,
775
1175
  // config, export, trace, skills, plugins, sandbox, schema, serve,
776
- // session-message, mcp, auth, login, logout, init). This set was read off
777
- // the live `/model` picker; the `-contributor` variants are the same models
778
- // with content sharing enabled, which the picker spells out.
779
- return [
780
- { id: "muse-spark-1.3", label: "muse-spark-1.3" },
781
- { id: "muse-spark-1.3-contributor", label: "muse-spark-1.3-contributor (shares content)" },
782
- { id: "muse-spark-1.2", label: "muse-spark-1.2" },
783
- { id: "muse-spark-1.2-contributor", label: "muse-spark-1.2-contributor (shares content)" },
784
- ];
1176
+ // session-message, mcp, auth, login, logout, init). The live `/model`
1177
+ // picker reads the provider catalog muse caches on disk (#1796), so this
1178
+ // reads the same files — bounded and best-effort, falling back to the
1179
+ // last known set when the cache is missing or unreadable.
1180
+ return readMuseModelCatalog();
785
1181
  }
786
1182
  async probeCLIEnv() {
787
1183
  const { probeCliVersion } = await import("./types.js");