synabun 2.0.5 → 2.0.7

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 (233) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +2 -1
  3. package/THIRD-PARTY-LICENSES.md +13 -0
  4. package/docs/whatsapp.md +151 -16
  5. package/hooks/claude-code/shared.mjs +6 -3
  6. package/lib/child-path.js +303 -0
  7. package/lib/claude-hooks.js +2 -1
  8. package/lib/data-home-migration.js +2 -0
  9. package/lib/desktop-app.js +265 -0
  10. package/lib/external-tools.js +7 -3
  11. package/lib/log-redact.js +102 -0
  12. package/lib/packaged-runtime.js +138 -11
  13. package/mcp-server/dist/http.d.ts +3 -1
  14. package/mcp-server/dist/http.d.ts.map +1 -1
  15. package/mcp-server/dist/http.js +15 -4
  16. package/mcp-server/dist/http.js.map +1 -1
  17. package/mcp-server/dist/index.d.ts.map +1 -1
  18. package/mcp-server/dist/index.js +9 -6
  19. package/mcp-server/dist/index.js.map +1 -1
  20. package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
  21. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
  22. package/mcp-server/dist/services/budget-judgments.js +153 -0
  23. package/mcp-server/dist/services/budget-judgments.js.map +1 -0
  24. package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
  25. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  26. package/mcp-server/dist/services/codex-config-heal.js +41 -4
  27. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  28. package/mcp-server/dist/services/identity.d.ts +11 -0
  29. package/mcp-server/dist/services/identity.d.ts.map +1 -1
  30. package/mcp-server/dist/services/identity.js +24 -0
  31. package/mcp-server/dist/services/identity.js.map +1 -1
  32. package/mcp-server/dist/services/neural-interface.d.ts +13 -0
  33. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  34. package/mcp-server/dist/services/neural-interface.js +34 -2
  35. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  36. package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
  37. package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
  38. package/mcp-server/dist/services/typesafe-config.js +4 -1
  39. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  40. package/mcp-server/dist/tools/agents.d.ts +10 -7
  41. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  42. package/mcp-server/dist/tools/agents.js +34 -13
  43. package/mcp-server/dist/tools/agents.js.map +1 -1
  44. package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
  45. package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
  46. package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
  47. package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
  48. package/mcp-server/dist/tui.js +2 -1
  49. package/mcp-server/dist/tui.js.map +1 -1
  50. package/mcp-server/dist/version.d.ts +5 -0
  51. package/mcp-server/dist/version.d.ts.map +1 -0
  52. package/mcp-server/dist/version.js +21 -0
  53. package/mcp-server/dist/version.js.map +1 -0
  54. package/mcp-server/package-lock.json +2 -2
  55. package/mcp-server/package.json +1 -1
  56. package/mcp-server/run.mjs +15 -9
  57. package/mcp-server/src/http.ts +18 -3
  58. package/mcp-server/src/index.ts +9 -6
  59. package/mcp-server/src/services/budget-judgments.ts +183 -0
  60. package/mcp-server/src/services/codex-config-heal.ts +35 -4
  61. package/mcp-server/src/services/identity.ts +27 -0
  62. package/mcp-server/src/services/neural-interface.ts +34 -2
  63. package/mcp-server/src/services/typesafe-config.ts +4 -1
  64. package/mcp-server/src/tools/agents.ts +33 -14
  65. package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
  66. package/mcp-server/src/tui.ts +2 -1
  67. package/mcp-server/src/version.ts +22 -0
  68. package/neural-interface/i18n/en.json +627 -57
  69. package/neural-interface/i18n/pt-BR.json +626 -56
  70. package/neural-interface/lib/account-usage.js +163 -0
  71. package/neural-interface/lib/assistant-api.js +255 -52
  72. package/neural-interface/lib/assistant-brains/claude.js +15 -2
  73. package/neural-interface/lib/assistant-brains/codex.js +3 -0
  74. package/neural-interface/lib/assistant-brains/opencode.js +17 -4
  75. package/neural-interface/lib/assistant-budget-planner.js +390 -0
  76. package/neural-interface/lib/assistant-budget-split.js +408 -0
  77. package/neural-interface/lib/assistant-budget.js +94 -95
  78. package/neural-interface/lib/assistant-config.js +62 -14
  79. package/neural-interface/lib/assistant-dispatch.js +432 -125
  80. package/neural-interface/lib/assistant-jev.js +26 -3
  81. package/neural-interface/lib/assistant-persona.js +68 -11
  82. package/neural-interface/lib/assistant-plan-permissions.js +8 -1
  83. package/neural-interface/lib/assistant-route-accounts.js +14 -0
  84. package/neural-interface/lib/assistant-route-gate.js +19 -3
  85. package/neural-interface/lib/assistant-router.js +373 -42
  86. package/neural-interface/lib/assistant-runtime.js +307 -47
  87. package/neural-interface/lib/assistant-user-writer.js +4 -2
  88. package/neural-interface/lib/augmented-path.js +317 -53
  89. package/neural-interface/lib/claude-accounts.js +342 -31
  90. package/neural-interface/lib/claude-agent-bridge.js +90 -3
  91. package/neural-interface/lib/claude-executable.js +6 -1
  92. package/neural-interface/lib/claude-model-catalog.js +3 -3
  93. package/neural-interface/lib/claude-panel-auth.js +221 -0
  94. package/neural-interface/lib/claude-profile-api-key.js +62 -0
  95. package/neural-interface/lib/cli-install-api.js +137 -0
  96. package/neural-interface/lib/cli-install.js +797 -0
  97. package/neural-interface/lib/cli-path.js +130 -0
  98. package/neural-interface/lib/cli-resolver.js +744 -0
  99. package/neural-interface/lib/cli-update-planner.js +143 -7
  100. package/neural-interface/lib/codex-account-auth.js +178 -0
  101. package/neural-interface/lib/codex-accounts.js +489 -0
  102. package/neural-interface/lib/codex-capabilities.js +2 -1
  103. package/neural-interface/lib/codex-compat-rpc.js +8 -2
  104. package/neural-interface/lib/codex-mcp-approvals.js +5 -1
  105. package/neural-interface/lib/codex-panel-auth.js +62 -0
  106. package/neural-interface/lib/codex-session-settings.js +1 -1
  107. package/neural-interface/lib/codex-settings-schema.js +120 -0
  108. package/neural-interface/lib/desktop/config.js +3 -0
  109. package/neural-interface/lib/git-bash/bzip2.js +339 -0
  110. package/neural-interface/lib/git-bash/claude-settings.js +73 -0
  111. package/neural-interface/lib/git-bash/extract-worker.js +34 -0
  112. package/neural-interface/lib/git-bash/install.js +529 -0
  113. package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
  114. package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
  115. package/neural-interface/lib/git-bash/manager.js +268 -0
  116. package/neural-interface/lib/git-bash/pins.js +206 -0
  117. package/neural-interface/lib/git-bash/pins.json +34 -0
  118. package/neural-interface/lib/git-bash/resolver.js +194 -0
  119. package/neural-interface/lib/git-read.js +34 -2
  120. package/neural-interface/lib/install-details.js +145 -0
  121. package/neural-interface/lib/mcp-installer.js +14 -2
  122. package/neural-interface/lib/native-loop-providers.js +16 -3
  123. package/neural-interface/lib/native-loop-runtime.js +9 -4
  124. package/neural-interface/lib/os-path.js +319 -0
  125. package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
  126. package/neural-interface/lib/provider-panel-terminal-auth.js +95 -0
  127. package/neural-interface/lib/pty-host/core.js +2 -1
  128. package/neural-interface/lib/pty-host/manager.js +27 -2
  129. package/neural-interface/lib/remote-policy.js +20 -11
  130. package/neural-interface/lib/session-title-generator.js +4 -3
  131. package/neural-interface/lib/system-about.js +340 -0
  132. package/neural-interface/lib/system-activity.js +66 -0
  133. package/neural-interface/lib/terminal-env.js +11 -3
  134. package/neural-interface/lib/terminal-path.js +55 -0
  135. package/neural-interface/lib/virtual-socket.js +12 -1
  136. package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
  137. package/neural-interface/lib/whatsapp/bridge.js +78 -34
  138. package/neural-interface/lib/whatsapp/commands.js +13 -14
  139. package/neural-interface/lib/whatsapp/config.js +134 -20
  140. package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
  141. package/neural-interface/lib/whatsapp/host-core.js +168 -35
  142. package/neural-interface/lib/whatsapp/inbound.js +5 -3
  143. package/neural-interface/lib/whatsapp/local-file.js +524 -0
  144. package/neural-interface/lib/whatsapp/manager.js +92 -32
  145. package/neural-interface/lib/whatsapp/paths.js +11 -1
  146. package/neural-interface/lib/whatsapp/protocol.js +65 -5
  147. package/neural-interface/lib/whatsapp/redact.js +63 -0
  148. package/neural-interface/lib/whatsapp/service.js +343 -91
  149. package/neural-interface/lib/whatsapp/transcription.js +463 -0
  150. package/neural-interface/package-lock.json +2 -2
  151. package/neural-interface/package.json +1 -1
  152. package/neural-interface/public/index.html +2 -0
  153. package/neural-interface/public/index2d.html +2 -0
  154. package/neural-interface/public/offline.html +16 -1
  155. package/neural-interface/public/onboarding.html +96 -2
  156. package/neural-interface/public/shared/account-i18n.js +7 -0
  157. package/neural-interface/public/shared/account-menu.js +539 -0
  158. package/neural-interface/public/shared/account-usage.js +19 -0
  159. package/neural-interface/public/shared/account-view.js +335 -0
  160. package/neural-interface/public/shared/agent-runtime-options.js +28 -8
  161. package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
  162. package/neural-interface/public/shared/assistant/asst-api.js +32 -2
  163. package/neural-interface/public/shared/assistant/asst-brain-picker.js +124 -25
  164. package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
  165. package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
  166. package/neural-interface/public/shared/assistant/asst-menu.js +15 -3
  167. package/neural-interface/public/shared/assistant/asst-panel.js +48 -14
  168. package/neural-interface/public/shared/assistant/asst-render.js +38 -7
  169. package/neural-interface/public/shared/assistant/asst-route.js +72 -14
  170. package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
  171. package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
  172. package/neural-interface/public/shared/assistant/asst-state.js +1 -1
  173. package/neural-interface/public/shared/assistant/asst-styles.js +99 -17
  174. package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
  175. package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
  176. package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
  177. package/neural-interface/public/shared/cdx/cdx-panel.js +48 -107
  178. package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
  179. package/neural-interface/public/shared/cdx/cdx-styles.js +128 -107
  180. package/neural-interface/public/shared/cdx/cdx-tabs.js +261 -664
  181. package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
  182. package/neural-interface/public/shared/claude-account-view.js +3 -0
  183. package/neural-interface/public/shared/cli-install-view.js +392 -0
  184. package/neural-interface/public/shared/cli-status.js +282 -22
  185. package/neural-interface/public/shared/cp/cp-events.js +2 -1
  186. package/neural-interface/public/shared/cp/cp-styles.js +46 -0
  187. package/neural-interface/public/shared/desktop-bridge.js +39 -0
  188. package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
  189. package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
  190. package/neural-interface/public/shared/desktop-titlebar.js +153 -0
  191. package/neural-interface/public/shared/desktop-update-view.js +462 -0
  192. package/neural-interface/public/shared/editor/editor-panel.js +1 -1
  193. package/neural-interface/public/shared/explorer/fx-main.js +1 -1
  194. package/neural-interface/public/shared/git-bash-view.js +145 -0
  195. package/neural-interface/public/shared/html-shell.js +1 -0
  196. package/neural-interface/public/shared/i18n.js +19 -1
  197. package/neural-interface/public/shared/install-details-view.js +86 -0
  198. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +32 -0
  199. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
  200. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +118 -0
  201. package/neural-interface/public/shared/pwa-install-page.js +3 -1
  202. package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
  203. package/neural-interface/public/shared/settings/settings-about.js +254 -0
  204. package/neural-interface/public/shared/settings/settings-ia.js +15 -0
  205. package/neural-interface/public/shared/showcase-privacy.js +489 -0
  206. package/neural-interface/public/shared/storage.js +30 -3
  207. package/neural-interface/public/shared/styles.css +278 -31
  208. package/neural-interface/public/shared/synabun-mascot.js +48 -2
  209. package/neural-interface/public/shared/ui-claude-panel.js +212 -213
  210. package/neural-interface/public/shared/ui-cli-banner.js +355 -0
  211. package/neural-interface/public/shared/ui-git-bash.js +124 -0
  212. package/neural-interface/public/shared/ui-judgments.js +10 -1
  213. package/neural-interface/public/shared/ui-loading.js +30 -6
  214. package/neural-interface/public/shared/ui-menubar.js +13 -3
  215. package/neural-interface/public/shared/ui-navbar.js +3 -1
  216. package/neural-interface/public/shared/ui-provider-auth.js +104 -0
  217. package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
  218. package/neural-interface/public/shared/ui-sessions.js +9 -3
  219. package/neural-interface/public/shared/ui-settings.js +55 -3
  220. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
  221. package/neural-interface/public/shared/ui-sync.js +6 -0
  222. package/neural-interface/public/shared/ui-terminal.js +17 -0
  223. package/neural-interface/public/shared/ui-tool-updates.js +5 -0
  224. package/neural-interface/public/shared/ui-update.js +594 -5
  225. package/neural-interface/public/shared/ui-whatsapp.js +21 -6
  226. package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
  227. package/neural-interface/public/shared/worker-end.js +2 -0
  228. package/neural-interface/public/sw.js +1 -1
  229. package/neural-interface/public/variant/2d/main.js +10 -1
  230. package/neural-interface/public/variant/3d/main.js +10 -1
  231. package/neural-interface/server.js +1219 -608
  232. package/package.json +4 -1
  233. package/setup.js +9 -8
@@ -19,6 +19,8 @@ import {
19
19
  getCachedCodexAccounts,
20
20
  getCachedMcpProfilePresets,
21
21
  getCachedOpencodeAgents,
22
+ getClaudeWorkerAccountId,
23
+ getCodexWorkerAccountId,
22
24
  getEffortLevelsForModel,
23
25
  getModelsForProfile,
24
26
  modelEffortIds,
@@ -27,8 +29,11 @@ import {
27
29
  import { getHiddenModels, HIDDEN_MODELS_EVENT, hiddenModelMatch } from '../ocp-hidden-models.js';
28
30
  import { getProviderMeta } from '../provider-icons.js';
29
31
  import { on } from '../state.js';
30
- import { fetchAssistantCatalog, fetchProjects } from './asst-api.js';
32
+ import { fetchAssistantCatalog, fetchProjects, patchClaudeAccount, patchCodexAccount, setClaudeWorkerAccount, setCodexWorkerAccount } from './asst-api.js';
33
+ import { accountBadge, accountCompactMode, accountIdentity, accountName, accountUsable, modeChoices } from '../account-view.js';
34
+ import { accountUsageText, autoAccountLabel } from '../account-usage.js';
31
35
  import { closeMenu, currentMenu, isMenuOpen, openMenu } from './asst-menu.js';
36
+ import { createMascotControl } from './asst-mascot.js';
32
37
  import {
33
38
  ACCOUNT_PROVIDERS,
34
39
  brainRequiresSwitch,
@@ -438,6 +443,9 @@ export async function openModelMenu(anchor, opts = {}) {
438
443
  if (!anchor.isConnected || (loading && currentMenu() !== loading.el)) return null;
439
444
  const metadata = modelCatalogStatus();
440
445
  const items = [];
446
+ if (Array.isArray(opts.accountItems) && opts.accountItems.length) {
447
+ items.push({ kind: 'header', label: tr('assistant.brain.account', 'Account profile') }, ...opts.accountItems, { kind: 'separator' });
448
+ }
441
449
  if (includeHere) {
442
450
  items.push({ kind: 'radio', id: '__here', label: hereLabel || tr('assistant.routes.brainDecides', 'Let the brain decide'), selected: !current, onSelect: () => onSelect?.({ here: true }) });
443
451
  }
@@ -498,7 +506,7 @@ export async function openModelMenu(anchor, opts = {}) {
498
506
  role: 'menu',
499
507
  className: `asst-model-menu${opts.routingEditor ? ' asst-route-model-menu' : ''}`,
500
508
  zIndex: opts.zIndex ?? null,
501
- repositionOnResize: opts.routingEditor === true,
509
+ repositionOnResize: true,
502
510
  filterEmptyLabel: tr('assistant.routes.menu.noMatches', 'No matching models. Check Models for hidden choices.'),
503
511
  });
504
512
  if (opts.routingEditor && menu) {
@@ -582,6 +590,7 @@ export function createBrainPicker(host, hooks = {}) {
582
590
  let loading = null;
583
591
  // The empty state's "Choose a project" (mountEmptyProject): the project field's second button.
584
592
  let heroProject = null;
593
+ let heroProjectMascot = null;
585
594
 
586
595
  function currentBrain() {
587
596
  return normalizeBrain(hooks.getBrain?.());
@@ -621,9 +630,16 @@ export function createBrainPicker(host, hooks = {}) {
621
630
  return accounts.find(a => a.id === brain.accountId) || null;
622
631
  }
623
632
 
633
+ const accountProvider = (brain) => (brain.provider === 'codex' ? 'codex' : 'claude');
624
634
  function accountLabel(brain) {
625
635
  const acc = accountOf(brain);
626
- return acc ? (acc.label || acc.email || acc.id) : (brain.accountId || t('assistant.brain.accountDefault', 'Default'));
636
+ const name = acc ? accountName(acc, accountProvider(brain)) : (brain.accountId || t('assistant.brain.accountDefault', 'Default'));
637
+ // An account says what it runs on (its subscription or its API key), or that it is signed out.
638
+ if (acc && acc.mode !== undefined) {
639
+ const badge = accountBadge(acc, accountProvider(brain));
640
+ return badge.text ? `${name} · ${badge.text}` : name;
641
+ }
642
+ return name;
627
643
  }
628
644
 
629
645
  function modelLabel(brain = currentBrain()) {
@@ -679,6 +695,13 @@ export function createBrainPicker(host, hooks = {}) {
679
695
  const v = values(brain);
680
696
  const modelTip = [v.meta.label, brain.model || t('assistant.brain.modelDefault', 'Default model'), v.account ? t('assistant.brain.asAccount', 'as {account}', { account: v.account }) : ''].filter(Boolean).join(' · ');
681
697
  setBtn('model', { label: v.model, icon: v.meta.icon, color: v.meta.color, tooltip: modelTip, aria: `${t('assistant.brain.model', 'Model')}: ${modelTip}` });
698
+ if (ACCOUNT_PROVIDERS.has(brain.provider)) {
699
+ const acc = accountOf(brain);
700
+ const label = buttons.model.querySelector('.asst-dd-label');
701
+ label.classList.add('asst-model-account-label');
702
+ const mode = acc ? accountCompactMode(acc, accountProvider(brain)) : t('accounts.checking', 'Checking…');
703
+ label.replaceChildren(el('span', 'asst-model-name', v.model), el('span', '', '·'), el('span', 'asst-model-account', acc ? accountName(acc, accountProvider(brain)) : brain.accountId || t('assistant.brain.accountDefault', 'Default')), el('span', '', '·'), el('span', 'asst-model-mode', mode));
704
+ } else buttons.model.querySelector('.asst-dd-label').classList.remove('asst-model-account-label');
682
705
  // A display name ("Sonnet 5") reads in sans; a raw id nothing names ("opus[1m]") stays mono.
683
706
  buttons.model?.toggleAttribute('data-raw', /^[a-z0-9][\w.\-[\]:/]*$/.test(String(v.model || '')));
684
707
 
@@ -688,9 +711,12 @@ export function createBrainPicker(host, hooks = {}) {
688
711
  setBtn('effort', { label: v.effort, icon: ICON_EFFORT, hidden: !v.effortSupported, tooltip: `${t('assistant.brain.effort', 'Thinking effort')}: ${v.effort}` });
689
712
  }
690
713
  setBtn('variant', { label: v.variant, icon: ICON_EFFORT, hidden: !v.variantSupported, tooltip: `${t('assistant.brain.variant', 'Model variant')}: ${v.variant}` });
691
- // Inline only when there is a choice to make; the "⋯" menu always has it (add / manage).
692
- const accountChoices = ACCOUNT_PROVIDERS.has(brain.provider) ? (brain.provider === 'codex' ? getCachedCodexAccounts() : getCachedClaudeAccounts()).length : 0;
693
- setBtn('account', { label: v.account, icon: ICON_ACCOUNT, hidden: accountChoices < 2, tooltip: `${t('assistant.brain.account', 'Account profile')}: ${v.account}` });
714
+ // A Claude or Codex brain always shows it: which account and which credential (its subscription or
715
+ // its API key) it runs on, or that the account is signed out.
716
+ const brainAcc = ACCOUNT_PROVIDERS.has(brain.provider) ? accountOf(brain) : null;
717
+ const accountTip = [v.account, brainAcc ? accountIdentity(brainAcc) : ''].filter(Boolean).join(' · ');
718
+ setBtn('account', { label: v.account, icon: ICON_ACCOUNT, hidden: true, tooltip: `${t('assistant.brain.account', 'Account profile')}: ${accountTip}` });
719
+ buttons.account?.classList.toggle('asst-dd-warn', !!brainAcc && brainAcc.signedIn === false);
694
720
  setBtn('project', { label: v.project, icon: ICON_FOLDER, tooltip: brain.project || t('assistant.brain.projectNoneTip', 'Working project: home directory'), aria: `${t('assistant.brain.project', 'Working project')}: ${brain.project || v.project}` });
695
721
  setBtn('mode', { label: v.mode, icon: ICON_SHIELD, tooltip: `${t('assistant.brain.mode', 'Permission mode')}: ${v.mode}` });
696
722
  const tests = testDensity();
@@ -721,10 +747,13 @@ export function createBrainPicker(host, hooks = {}) {
721
747
  * sends focus back to it. A new slot replaces the last one; null removes it.
722
748
  */
723
749
  function mountEmptyProject(slot) {
750
+ if (heroProject && isMenuOpen(heroProject)) closeMenu();
751
+ heroProjectMascot?.destroy();
752
+ heroProjectMascot = null;
724
753
  heroProject?.remove();
725
754
  heroProject = null;
726
755
  if (destroyed || !slot || typeof slot.appendChild !== 'function') return null;
727
- const btn = el('button', 'asst-dd asst-empty-project-btn');
756
+ const btn = el('button', 'asst-dd asst-empty-control-btn asst-empty-project-btn');
728
757
  btn.type = 'button';
729
758
  btn.dataset.field = 'project';
730
759
  btn.setAttribute('aria-haspopup', 'menu');
@@ -743,6 +772,7 @@ export function createBrainPicker(host, hooks = {}) {
743
772
  });
744
773
  slot.appendChild(btn);
745
774
  heroProject = btn;
775
+ heroProjectMascot = createMascotControl(btn, { director: hooks.mascot, pose: 'project' });
746
776
  renderEmptyProject();
747
777
  return btn;
748
778
  }
@@ -818,12 +848,14 @@ export function createBrainPicker(host, hooks = {}) {
818
848
  const anchor = anchorFor(field, anchorOverride);
819
849
  const brain = currentBrain();
820
850
  if (field === 'model') {
851
+ if (ACCOUNT_PROVIDERS.has(brain.provider)) await ensureLoaded(brain.provider);
821
852
  await openModelMenu(anchor, {
822
853
  brain,
823
854
  t: hooks.t,
824
855
  placement,
825
856
  current: { provider: brain.provider, model: brain.model },
826
857
  warnSwitch: true,
858
+ accountItems: ACCOUNT_PROVIDERS.has(brain.provider) ? accountItems(brain, { composer: true }) : [],
827
859
  onToast: hooks.onToast,
828
860
  onManageModels: hooks.onManageModels,
829
861
  onSelect: (target) => {
@@ -888,29 +920,23 @@ export function createBrainPicker(host, hooks = {}) {
888
920
 
889
921
  if (field === 'account') {
890
922
  if (!ACCOUNT_PROVIDERS.has(brain.provider)) return;
891
- const accounts = brain.provider === 'codex' ? getCachedCodexAccounts() : getCachedClaudeAccounts();
892
- const items = accounts.map((a) => ({
893
- kind: 'radio',
894
- id: a.id,
895
- label: a.label || a.email || a.id,
896
- desc: a.email && a.email !== a.label ? a.email : (a.isDefault ? t('assistant.accounts.ambient', 'ambient config') : ''),
897
- selected: a.id === brain.accountId,
898
- onSelect: () => commit({ accountId: a.id }, 'account'),
899
- }));
900
- items.push({ kind: 'separator' });
901
- items.push({ id: '__add', label: t('assistant.accounts.add', 'Add account…'), onSelect: () => hooks.onAddAccount?.(brain.provider) });
902
- items.push({ id: '__manage', label: t('assistant.accounts.manage', 'Manage…'), onSelect: () => hooks.onManageAccounts?.(brain.provider) });
903
- menu(t('assistant.brain.account', 'Account profile'), items);
923
+ menu(t('assistant.brain.account', 'Account profile'), accountItems(brain), { width: 300 });
904
924
  return;
905
925
  }
906
926
 
907
927
  if (field === 'project') {
928
+ const actor = anchor === heroProject ? heroProjectMascot : null;
929
+ const pick = project => {
930
+ const changed = (currentBrain().project || null) !== (project || null);
931
+ commit({ project }, 'project');
932
+ actor?.commit(changed ? 'hop' : 'nod');
933
+ };
908
934
  const projects = _projectsCache || [];
909
- const items = [{ kind: 'radio', id: '', label: t('assistant.brain.projectNone', 'Home directory'), desc: t('assistant.brain.projectNoneDesc', 'no project'), selected: !brain.project, restoreFocus, onSelect: () => commit({ project: null }, 'project') }];
910
- for (const p of projects) items.push({ kind: 'radio', id: p.path, label: p.label || basename(p.path), desc: p.path, selected: p.path === brain.project, restoreFocus, onSelect: () => commit({ project: p.path }, 'project') });
935
+ const items = [{ kind: 'radio', id: '', label: t('assistant.brain.projectNone', 'Home directory'), desc: t('assistant.brain.projectNoneDesc', 'no project'), selected: !brain.project, restoreFocus, onSelect: () => pick(null) }];
936
+ for (const p of projects) items.push({ kind: 'radio', id: p.path, label: p.label || basename(p.path), desc: p.path, selected: p.path === brain.project, restoreFocus, onSelect: () => pick(p.path) });
911
937
  items.push({ kind: 'separator' });
912
- items.push({ kind: 'input', placeholder: t('assistant.brain.projectCustom', 'Type a directory path…'), value: brain.project && !projects.some(p => p.path === brain.project) ? brain.project : '', restoreFocus, onSubmit: (v) => commit({ project: v }, 'project') });
913
- menu(t('assistant.brain.project', 'Working project'), items, { width: 280, filter: projects.length > 8, filterPlaceholder: t('assistant.brain.filterProjects', 'Filter projects…') });
938
+ items.push({ kind: 'input', placeholder: t('assistant.brain.projectCustom', 'Type a directory path…'), value: brain.project && !projects.some(p => p.path === brain.project) ? brain.project : '', restoreFocus, onSubmit: pick });
939
+ menu(t('assistant.brain.project', 'Working project'), items, { width: 280, filter: projects.length > 8, filterPlaceholder: t('assistant.brain.filterProjects', 'Filter projects…'), onOpen: node => actor?.open(node), onClose: () => actor?.close() });
914
940
  return;
915
941
  }
916
942
 
@@ -953,6 +979,69 @@ export function createBrainPicker(host, hooks = {}) {
953
979
  }
954
980
  }
955
981
 
982
+ // The brain's account menu (Claude or Codex): every account with what it runs on and who it is (a
983
+ // signed-out one is listed, not offered), the mode of the brain's account when it holds both credentials,
984
+ // and the account dispatched workers of that provider use when a dispatch names none.
985
+ function accountItems(brain, { composer = false } = {}) {
986
+ const codex = brain.provider === 'codex';
987
+ const p = codex ? 'codex' : 'claude';
988
+ const accounts = codex ? getCachedCodexAccounts() : getCachedClaudeAccounts();
989
+ const refetch = () => (codex ? fetchCodexAccounts(true) : fetchClaudeAccounts(true));
990
+ const items = accounts.map((a) => {
991
+ const badge = accountBadge(a, p);
992
+ const usable = accountUsable(a);
993
+ return {
994
+ kind: 'radio', id: a.id, label: accountName(a, p),
995
+ desc: [accountIdentity(a), accountUsageText(a, hooks.t), !usable ? t('assistant.accounts.signInFirst', 'Signed out: sign it in from Manage…') : composer && a.id !== brain.accountId ? t('assistant.brain.newConversation', 'starts a new conversation') : ''].filter(Boolean).join(' · '),
996
+ tag: badge.text, disabled: !usable,
997
+ selected: a.id === brain.accountId,
998
+ onSelect: () => commit({ accountId: a.id }, 'account'),
999
+ };
1000
+ });
1001
+ items.unshift({ kind: 'radio', id: 'auto', label: autoAccountLabel(hooks.t), desc: t('assistant.accountUsage.composerHint', 'Pick the subscription with most usage left and start a new conversation.'), onSelect: () => commit({ accountId: 'auto' }, 'account') });
1002
+ if (composer) for (const a of accounts.filter(a => !accountUsable(a))) items.push({ id: `signin:${a.id}`, label: t('assistant.accounts.signInAccount', 'Sign in to {label}…', { label: accountName(a, p) }), onSelect: () => hooks.onManageAccounts?.(brain.provider, a.id) });
1003
+ const current = accounts.find((a) => a.id === brain.accountId);
1004
+ const modes = modeChoices(current, p);
1005
+ if (modes.length) {
1006
+ items.push({ kind: 'separator' });
1007
+ items.push({ kind: 'header', label: t('assistant.accounts.usesHeader', '{label} uses (every session on it)', { label: accountName(current, p) }) });
1008
+ for (const m of modes) {
1009
+ items.push({
1010
+ kind: 'radio', id: `mode:${m.mode}`, label: m.label, desc: [m.detail, !m.current ? t('assistant.brain.newConversation', 'starts a new conversation') : ''].filter(Boolean).join(' · '), selected: m.current, disabled: hooks.getRunning?.() === true,
1011
+ onSelect: async () => {
1012
+ if (m.current) return;
1013
+ try {
1014
+ await (codex ? patchCodexAccount : patchClaudeAccount)(current.id, { mode: m.mode }); await refetch();
1015
+ hooks.onChange?.(currentBrain(), { requiresSwitch: true, restartBrain: true, field: 'accountMode', previous: brain }); render();
1016
+ }
1017
+ catch (err) { hooks.onToast?.(err?.message || String(err)); }
1018
+ },
1019
+ });
1020
+ }
1021
+ }
1022
+ const worker = codex ? getCodexWorkerAccountId() : getClaudeWorkerAccountId();
1023
+ if (!composer) {
1024
+ items.push({ kind: 'separator' });
1025
+ items.push({ kind: 'header', label: codex ? t('assistant.accounts.codexWorkersHeader', 'Codex workers use') : t('assistant.accounts.workersHeader', 'Claude Code workers use') });
1026
+ for (const a of accounts) {
1027
+ const usable = accountUsable(a);
1028
+ items.push({
1029
+ kind: 'radio', id: `worker:${a.id}`, label: accountName(a, p), tag: accountBadge(a, p).text,
1030
+ disabled: !usable && a.id !== worker, selected: a.id === worker,
1031
+ onSelect: async () => {
1032
+ if (a.id === worker) return;
1033
+ try { await (codex ? setCodexWorkerAccount : setClaudeWorkerAccount)(a.id); await refetch(); render(); }
1034
+ catch (err) { hooks.onToast?.(err?.message || String(err)); }
1035
+ },
1036
+ });
1037
+ }
1038
+ }
1039
+ items.push({ kind: 'separator' });
1040
+ items.push({ id: '__add', label: t('assistant.accounts.add', 'Add account…'), onSelect: () => hooks.onAddAccount?.(brain.provider) });
1041
+ items.push({ id: '__manage', label: t('assistant.accounts.manage', 'Manage…'), onSelect: () => hooks.onManageAccounts?.(brain.provider) });
1042
+ return items;
1043
+ }
1044
+
956
1045
  async function switchProvider(provider, model = '') {
957
1046
  const before = currentBrain();
958
1047
  if (provider === before.provider) return;
@@ -976,6 +1065,16 @@ export function createBrainPicker(host, hooks = {}) {
976
1065
 
977
1066
  render();
978
1067
  refresh().catch(() => {});
1068
+ // Any window changed a Claude or Codex account (signed in or out, a key, its mode, the worker choice,
1069
+ // added, removed): its list is read again and the picker re-drawn, so it never shows a stale state.
1070
+ const accountsChanged = (provider) => (msg) => {
1071
+ const which = provider || (msg?.provider === 'codex' ? 'codex' : msg?.provider === 'claude-code' ? 'claude-code' : null);
1072
+ const jobs = [];
1073
+ if (!which || which === 'claude-code') jobs.push(fetchClaudeAccounts(true));
1074
+ if (!which || which === 'codex') jobs.push(fetchCodexAccounts(true));
1075
+ Promise.all(jobs).then(() => { if (!destroyed) render(); }).catch(() => {});
1076
+ };
1077
+ const offAccounts = [on('sync:claude:accounts-changed', accountsChanged('claude-code')), on('sync:codex:accounts-changed', accountsChanged('codex')), on('sync:assistant:accounts-changed', accountsChanged(null))];
979
1078
 
980
1079
  return {
981
1080
  el: root,
@@ -989,6 +1088,6 @@ export function createBrainPicker(host, hooks = {}) {
989
1088
  mountEmptyProject,
990
1089
  setBrain() { render(); },
991
1090
  close: closeMenu,
992
- destroy() { destroyed = true; closeMenu(); Object.values(buttons).forEach((b) => b.remove()); heroProject?.remove(); heroProject = null; root.remove(); },
1091
+ destroy() { destroyed = true; for (const off of offAccounts) { try { off?.(); } catch {} } closeMenu(); heroProjectMascot?.destroy(); Object.values(buttons).forEach((b) => b.remove()); heroProject?.remove(); heroProject = null; root.remove(); },
993
1092
  };
994
1093
  }