synabun 2.0.0 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +46 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +2 -2
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +20 -0
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/lib/start-launcher.js +88 -1
  14. package/mcp-server/package-lock.json +2 -2
  15. package/mcp-server/package.json +1 -3
  16. package/neural-interface/i18n/en.json +1500 -153
  17. package/neural-interface/i18n/pt-BR.json +1498 -152
  18. package/neural-interface/lib/assistant-api.js +62 -6
  19. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  20. package/neural-interface/lib/assistant-catalog.js +179 -27
  21. package/neural-interface/lib/assistant-config.js +31 -2
  22. package/neural-interface/lib/assistant-dispatch.js +62 -13
  23. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  24. package/neural-interface/lib/assistant-memory.js +24 -10
  25. package/neural-interface/lib/assistant-persona.js +6 -2
  26. package/neural-interface/lib/assistant-pricing.js +21 -6
  27. package/neural-interface/lib/assistant-runtime.js +181 -12
  28. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  29. package/neural-interface/lib/assistant-test-density.js +142 -0
  30. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  31. package/neural-interface/lib/assistant-user-context.js +908 -0
  32. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  33. package/neural-interface/lib/assistant-user-learn.js +404 -0
  34. package/neural-interface/lib/assistant-user-threads.js +226 -0
  35. package/neural-interface/lib/assistant-user-writer.js +181 -0
  36. package/neural-interface/lib/browser-background-tab.js +134 -0
  37. package/neural-interface/lib/browser-screenshot.js +678 -61
  38. package/neural-interface/lib/claude-agent-bridge.js +33 -6
  39. package/neural-interface/lib/claude-model-catalog.js +30 -2
  40. package/neural-interface/lib/claude-temporary.js +46 -7
  41. package/neural-interface/lib/cloudflared/install.js +533 -0
  42. package/neural-interface/lib/cloudflared/manager.js +545 -0
  43. package/neural-interface/lib/cloudflared/pins.js +115 -0
  44. package/neural-interface/lib/cloudflared/pins.json +63 -0
  45. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  46. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  47. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  48. package/neural-interface/lib/editor-completion.js +714 -0
  49. package/neural-interface/lib/editor-config.js +149 -0
  50. package/neural-interface/lib/editor-files-api.js +268 -0
  51. package/neural-interface/lib/editor-git-api.js +134 -0
  52. package/neural-interface/lib/file-explorer-api.js +1039 -0
  53. package/neural-interface/lib/file-explorer-system.js +355 -0
  54. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  55. package/neural-interface/lib/file-explorer-watch.js +239 -0
  56. package/neural-interface/lib/git-diff-api.js +103 -0
  57. package/neural-interface/lib/git-read.js +202 -0
  58. package/neural-interface/lib/greeting/api.js +89 -0
  59. package/neural-interface/lib/greeting/budgets.js +90 -0
  60. package/neural-interface/lib/greeting/engine.js +461 -0
  61. package/neural-interface/lib/greeting/facts.js +320 -0
  62. package/neural-interface/lib/greeting/sources.js +252 -0
  63. package/neural-interface/lib/greeting/writer.js +139 -0
  64. package/neural-interface/lib/http-guards.js +189 -13
  65. package/neural-interface/lib/machine-names.js +88 -0
  66. package/neural-interface/lib/memory-api.js +31 -7
  67. package/neural-interface/lib/native-loop-providers.js +10 -5
  68. package/neural-interface/lib/plugin-installer.js +4 -2
  69. package/neural-interface/lib/project-storage.js +5 -7
  70. package/neural-interface/lib/request-gate.js +589 -0
  71. package/neural-interface/lib/ui-blob-api.js +108 -0
  72. package/neural-interface/lib/ui-blob-store.js +68 -0
  73. package/neural-interface/package-lock.json +701 -2
  74. package/neural-interface/package.json +13 -2
  75. package/neural-interface/public/install-app.html +9 -6
  76. package/neural-interface/public/offline.html +3 -2
  77. package/neural-interface/public/onboarding.html +12 -1
  78. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  79. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  80. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  81. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  82. package/neural-interface/public/shared/assistant/asst-mascot.js +18 -58
  83. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  84. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  85. package/neural-interface/public/shared/assistant/asst-render.js +162 -19
  86. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  87. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  88. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  89. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  90. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  91. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  92. package/neural-interface/public/shared/assistant/asst-styles.js +158 -4
  93. package/neural-interface/public/shared/blob-store.js +111 -22
  94. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  95. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  96. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  97. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  98. package/neural-interface/public/shared/constants.js +6 -1
  99. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  100. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  101. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  102. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  103. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  104. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  105. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  106. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  107. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  108. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  109. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  110. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  111. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  112. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  113. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  114. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  115. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  116. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  117. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  118. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  119. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  120. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  121. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  122. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  123. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  124. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  125. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  126. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  127. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  128. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  129. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  130. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  131. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  132. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  133. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  134. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  135. package/neural-interface/public/shared/file-links.js +339 -0
  136. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  137. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  138. package/neural-interface/public/shared/html-shell.js +160 -165
  139. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  140. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  141. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  142. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  143. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  144. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  145. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  146. package/neural-interface/public/shared/pwa-install-page.js +29 -1
  147. package/neural-interface/public/shared/pwa-install.css +3 -3
  148. package/neural-interface/public/shared/pwa-install.js +100 -1
  149. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  150. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  151. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  152. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  153. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  154. package/neural-interface/public/shared/start-bridge.js +238 -19
  155. package/neural-interface/public/shared/styles.css +1414 -721
  156. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  157. package/neural-interface/public/shared/tunnel-view.js +167 -0
  158. package/neural-interface/public/shared/ui-claude-panel.js +157 -16
  159. package/neural-interface/public/shared/ui-detail.js +15 -0
  160. package/neural-interface/public/shared/ui-explorer.js +22 -0
  161. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  162. package/neural-interface/public/shared/ui-file-links.js +103 -0
  163. package/neural-interface/public/shared/ui-help.js +26 -0
  164. package/neural-interface/public/shared/ui-invite.js +126 -116
  165. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  166. package/neural-interface/public/shared/ui-loading.js +159 -15
  167. package/neural-interface/public/shared/ui-menubar.js +12 -30
  168. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  169. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  170. package/neural-interface/public/shared/ui-navbar.js +45 -21
  171. package/neural-interface/public/shared/ui-settings.js +455 -488
  172. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  173. package/neural-interface/public/shared/ui-sync.js +16 -0
  174. package/neural-interface/public/shared/ui-terminal.js +23 -5
  175. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  176. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  177. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  178. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  179. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  180. package/neural-interface/public/shared/worker-end.js +251 -0
  181. package/neural-interface/public/sw.js +2 -2
  182. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  183. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  184. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  185. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  186. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  187. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  188. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  189. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  190. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  191. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  192. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  193. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  194. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  195. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  196. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  197. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  198. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  199. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  200. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  201. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  202. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  203. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  204. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  205. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  206. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  207. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  208. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  209. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  210. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  211. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  212. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  309. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  310. package/neural-interface/scripts/build-vendor.mjs +78 -2
  311. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  312. package/neural-interface/server.js +873 -1028
  313. package/neural-interface/templates/rulesets/README.md +1 -1
  314. package/package.json +2 -8
  315. package/setup.js +44 -7
  316. package/benchmarks/browser-live-results.json +0 -79
  317. package/benchmarks/browser-v2-results.json +0 -258
  318. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  319. package/neural-interface/public/category-logos/claude.svg +0 -1
  320. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  321. package/neural-interface/public/category-logos/openai.svg +0 -1
  322. package/neural-interface/public/category-logos/synabun.png +0 -0
  323. package/neural-interface/public/claude.svg +0 -1
  324. package/neural-interface/public/gallery.svg +0 -6
  325. package/neural-interface/public/leonardoai.svg +0 -1
  326. package/neural-interface/public/mcp.svg +0 -4
  327. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  328. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  329. package/neural-interface/public/sessionicon.svg +0 -8
  330. package/neural-interface/public/toolbar-album.svg +0 -8
  331. package/neural-interface/public/toolbar-question.svg +0 -6
  332. package/neural-interface/public/toolbar-sessions.svg +0 -6
  333. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  334. package/skills/synabun/synabunicon.png +0 -0
@@ -1,9 +1,15 @@
1
1
  import { mountMemoryMaintenance } from './ui-memory-maintenance.js';
2
2
  import { buildJudgmentsTab, wireJudgmentsTab } from './ui-judgments.js';
3
3
  import { buildWhatsAppTab, wireWhatsAppTab } from './ui-whatsapp.js';
4
+ import { buildCodeEditorTab, wireCodeEditorTab } from './settings/settings-code-editor-pane.js';
4
5
  import { SETTINGS_GROUPS, SETTINGS_PAGES, DEFAULT_SETTINGS_PAGE, pageKeys, getSettingsPage, resolveSettingsTarget, homeOfControl } from './settings/settings-ia.js';
5
6
  import * as kit from './settings/settings-kit.js';
6
7
  import { RULES_HOST_LABELS, describeRulesHost, describeRulesInstall, describeRulesInstallAll } from './settings/settings-rules.js';
8
+ import { readUserContextResponse, describeUserContext, describeUserContextSaveFailure } from './settings/settings-user-context.js';
9
+ import { readGreetingStatus, describeGreeting, describeGreetingSaveFailure } from './settings/settings-greeting.js';
10
+ import { renderGreeting, clearGreeting } from './greeting/greeting-card.js';
11
+ import { createTunnelCard, drawInto, mcpKeyNeededText, readTunnel } from './ui-tunnel.js';
12
+ import { remoteMcpAddress } from './tunnel-view.js';
7
13
  import { initI18n, isReady as i18nReady, SUPPORTED_LOCALES, LOCALE_NAMES, SYSTEM_LOCALE, getLocale, getLocaleChoice, setLocaleChoice, getSystemLocale, englishTranslator, loadEnglishTranslator } from './i18n.js';
8
14
  import { wireSettingsShell, wireSettingsSearch, collectSearchRows, revealSettingsTarget, followSettingsTarget, onSettingsTakeover, applyStoredSections, storeSectionState } from './settings/settings-shell.js';
9
15
  import { buildSettingsSearchIndex } from './settings/settings-search.js';
@@ -12,7 +18,7 @@ import { buildSettingsSearchIndex } from './settings/settings-search.js';
12
18
  // ═══════════════════════════════════════════
13
19
  //
14
20
  // The most complex shared UI module. Builds a floating settings panel with
15
- // eleven task pages in five groups (./settings/settings-ia.js). A page is a
21
+ // twelve task pages in five groups (./settings/settings-ia.js). A page is a
16
22
  // title, a one-line purpose and section cards (./settings/settings-kit.js);
17
23
  // each builder below owns one pane of a page. Variant-registered tabs (the 2D
18
24
  // Graphics tab) join the Appearance page. Old tab ids still open the right page.
@@ -62,6 +68,7 @@ const TAB_ICONS = {
62
68
  discord: '<svg viewBox="0 0 24 24"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>',
63
69
  whatsapp: '<svg viewBox="0 0 24 24"><path d="M3.5 20.5l1.3-3.9A8.5 8.5 0 1 1 8 19.6z"/><path d="M9.5 8.5c.4 2.6 2.9 5.2 5.5 5.6l1-1.2 1.6.8c-.3 1-1.1 1.6-2.1 1.5-2.9-.3-5.8-3.2-6.1-6.1-.1-1 .5-1.8 1.5-2.1l.8 1.6z"/></svg>',
64
70
  morelogin: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"><path d="M12 11.5a1 1 0 0 0-1 1c0 1.8-.3 3.6-1.2 5.2"/><path d="M7.8 14.5a8 8 0 0 0-.6 4.3"/><path d="M12 7.5a4.5 4.5 0 0 1 4.5 4.5c0 .6 0 1.2-.1 1.8"/><path d="M5 9.2a7 7 0 0 1 11-1.9"/><path d="M15 13a3 3 0 0 1-3 3"/></svg>',
71
+ editor: '<svg viewBox="0 0 24 24"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/><line x1="13.5" y1="5" x2="10.5" y2="19"/></svg>',
65
72
  };
66
73
 
67
74
  // ── Shared icon constants ──
@@ -1721,7 +1728,163 @@ function buildPermissionsTab(toolCategories, toolPermissions) {
1721
1728
  </div>`;
1722
1729
  }
1723
1730
 
1724
- function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo, openclawBridge, greetingConfig, codexGreetingConfig, opencodeGreetingConfig, toolPermissions, toolCategories) {
1731
+ // ── Automation behavior: the Assistant's user context ──
1732
+ // settings/settings-user-context.js reads the server's answer and decides what is true; these draw it. The
1733
+ // same markup is built with the page and redrawn after a save, so a state never looks different the second time.
1734
+
1735
+ /** What GET /api/assistant/user-context answered, as a reading (never throws: no answer is a reading too). */
1736
+ // ── The greeting (Settings → Automations → Greeting) ──
1737
+ // settings/settings-greeting.js reads the server's answers and decides what is true; these ask and draw.
1738
+ async function loadGreetingReading() {
1739
+ try {
1740
+ const res = await fetch('/api/greeting');
1741
+ const body = await res.json().catch(() => null);
1742
+ return readGreetingStatus({ status: res.status, body });
1743
+ } catch (err) {
1744
+ return readGreetingStatus({ status: 0, error: err?.message });
1745
+ }
1746
+ }
1747
+
1748
+ /** What a new session would open with right now. `write`: ask the writer for the sentence now (one small call). */
1749
+ async function fetchGreetingPreview({ write = false } = {}) {
1750
+ try {
1751
+ const res = await fetch('/api/greeting/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(write ? { write: true } : {}) });
1752
+ const body = await res.json().catch(() => null);
1753
+ if (res.ok && body?.ok === true && body.preview && typeof body.preview === 'object') return body.preview;
1754
+ return { failed: String(body?.reason || body?.error || `HTTP ${res.status}`) };
1755
+ } catch (err) {
1756
+ return { failed: err?.message || 'no answer' };
1757
+ }
1758
+ }
1759
+
1760
+ /** The section after an answer: the note, the switch, what stands in its way, and the preview (the card every panel shows). */
1761
+ function drawGreetingSection(overlay, view) {
1762
+ const q = (selector) => overlay.querySelector(selector);
1763
+ const note = q('#gr-status-note');
1764
+ if (note) { note.hidden = !view.note; note.textContent = view.note ? view.note.text : ''; note.dataset.grState = view.state; }
1765
+ const row = q('[data-gr-row]');
1766
+ if (row) { row.classList.toggle('enabled', view.on); row.classList.toggle('uc-disabled', view.disabled); }
1767
+ const toggle = q('[data-gr-toggle]');
1768
+ if (toggle) { toggle.classList.toggle('on', view.on); toggle.setAttribute('aria-pressed', view.on ? 'true' : 'false'); toggle.disabled = view.disabled; }
1769
+ const notes = q('#gr-notes');
1770
+ if (notes) notes.innerHTML = userContextNotesHTML(view.notes);
1771
+ const box = q('#gr-preview');
1772
+ if (!box) return;
1773
+ const preview = view.preview;
1774
+ box.hidden = !preview.shown;
1775
+ box.dataset.grPreviewState = preview.state;
1776
+ const project = q('#gr-preview-project');
1777
+ if (project) project.textContent = preview.project;
1778
+ const lead = q('#gr-preview-lead');
1779
+ if (lead) { lead.textContent = preview.lead || preview.empty; lead.hidden = !(preview.lead || preview.empty); }
1780
+ const card = q('#gr-preview-card');
1781
+ if (card) {
1782
+ if (preview.greeting) renderGreeting(card, preview.greeting, { t: kit.tx, onOpenMemory: (id) => { if (id) emit('memory:open', { id }); } });
1783
+ else clearGreeting(card);
1784
+ }
1785
+ const spend = q('#gr-preview-spend');
1786
+ if (spend) { spend.textContent = preview.spend; spend.hidden = !preview.spend; }
1787
+ const write = q('#gr-write');
1788
+ if (write) write.hidden = !preview.canWrite;
1789
+ }
1790
+
1791
+ async function loadUserContextReading() {
1792
+ try {
1793
+ const res = await fetch('/api/assistant/user-context');
1794
+ const body = await res.json().catch(() => null);
1795
+ return readUserContextResponse({ status: res.status, body });
1796
+ } catch (err) {
1797
+ return readUserContextResponse({ status: 0, error: err?.message });
1798
+ }
1799
+ }
1800
+
1801
+ /** What stands in the way of a layer: one line each, with the Settings page that fixes it when there is one. */
1802
+ const userContextNotesHTML = (notes = []) => notes.map(note => `<div class="uc-layer-note" data-tone="${note.tone === 'warn' ? 'warn' : 'info'}">${kit.esc(note.text)}${
1803
+ note.link ? ` <button type="button" class="cc-copy-btn stg-text-btn uc-note-link" data-uc-goto="${kit.esc(JSON.stringify(note.link.target))}">${kit.esc(note.link.label)}</button>` : ''
1804
+ }</div>`).join('');
1805
+
1806
+ /** The digest's lines, or the one sentence that stands in for them (nothing known yet, the layer is off). */
1807
+ const userContextDigestHTML = (digest) => (digest.rows.length
1808
+ ? digest.rows.map(row => `<button type="button" class="uc-digest-row" role="listitem" data-uc-memory="${kit.esc(row.id)}" title="${kit.te('settings.redesign.hooks.uc.openMemory')}"><span class="uc-digest-statement">${kit.esc(row.statement)}</span><span class="uc-digest-facet">${kit.esc(row.facet)}${row.seed ? ` · ${kit.te('settings.redesign.hooks.uc.seed')}` : ''}</span></button>`).join('')
1809
+ : `<div class="stg-help uc-digest-message" role="listitem" style="margin:0">${kit.esc(digest.message)}</div>`);
1810
+
1811
+ /** Put a view (describeUserContext) on the section: the note, each switch and its notes, the digest. */
1812
+ function drawUserContext(overlay, view) {
1813
+ const section = overlay.querySelector('#stg-sec-automation-behavior');
1814
+ if (!section) return;
1815
+ const note = section.querySelector('#uc-status-note');
1816
+ if (note) { note.hidden = !view.note; note.textContent = view.note?.text || ''; note.dataset.ucState = view.state; }
1817
+ const layers = section.querySelector('#uc-layers');
1818
+ if (layers) { layers.dataset.ucReady = view.ready ? '1' : '0'; layers.dataset.ucState = view.state; }
1819
+ for (const [key, row] of Object.entries(view.layers)) {
1820
+ const toggle = section.querySelector(`.cc-toggle[data-uc-layer="${key}"]`);
1821
+ if (!toggle) continue;
1822
+ toggle.classList.toggle('on', row.on);
1823
+ toggle.setAttribute('aria-pressed', row.on ? 'true' : 'false');
1824
+ toggle.disabled = row.disabled;
1825
+ toggle.setAttribute('aria-describedby', `uc-layer-${key}-help uc-layer-${key}-notes${view.note ? ' uc-status-note' : ''}`);
1826
+ const item = toggle.closest('.cc-integration-item');
1827
+ item?.classList.toggle('enabled', row.on);
1828
+ item?.classList.toggle('uc-disabled', row.disabled);
1829
+ const notes = section.querySelector(`#uc-layer-${key}-notes`);
1830
+ if (notes) notes.innerHTML = userContextNotesHTML(row.notes);
1831
+ }
1832
+ const applies = section.querySelector('#uc-applies');
1833
+ if (applies) { applies.hidden = !view.applies; applies.textContent = view.applies; }
1834
+ const digest = section.querySelector('#uc-digest');
1835
+ if (digest) {
1836
+ digest.hidden = !view.digest.shown;
1837
+ digest.dataset.ucDigestState = view.digest.state;
1838
+ const count = digest.querySelector('#uc-digest-count');
1839
+ if (count) count.textContent = view.digest.count;
1840
+ const rows = digest.querySelector('#uc-digest-rows');
1841
+ if (rows) rows.innerHTML = view.digest.shown ? userContextDigestHTML(view.digest) : '';
1842
+ const more = digest.querySelector('#uc-digest-more');
1843
+ if (more) { more.hidden = !view.digest.omitted; more.textContent = view.digest.omitted; }
1844
+ }
1845
+ }
1846
+
1847
+ /**
1848
+ * Does the server that answered refuse MCP from outside while no access key is set? One that brings its own
1849
+ * tunnel program does (lib/request-gate.js mcpWithoutKey); one started before that change, still running until
1850
+ * it is restarted, leaves the endpoint open to whoever has the address. Its tunnel status tells the two apart,
1851
+ * and the words about a missing key say what is true for the server that is running.
1852
+ */
1853
+ function closesKeylessMcp(tunnelStatus) {
1854
+ return !!tunnelStatus?.cloudflared;
1855
+ }
1856
+
1857
+ /**
1858
+ * Settings → Automations → Online access, the tunnel, for a reading of tunnel-view.js (the same reading the Share
1859
+ * menu draws). Two pieces, each drawn again by itself when the reading changes: the row (the state as a word
1860
+ * with its dot, the address while one is up, the switch), and what belongs under it (a download's progress; the
1861
+ * sentence about the state; after a failure its reason and a way to try again; for a running tunnel the remote
1862
+ * MCP address to copy, or what is missing for one, an access key, and which cloudflared runs).
1863
+ */
1864
+ function tunnelRowHtml(view) {
1865
+ const tone = { ok: 'ok', busy: 'busy', err: 'err', warn: 'warn' }[view.tone] || '';
1866
+ return `
1867
+ <div class="cc-integration-info">
1868
+ <div class="cc-integration-label" id="cc-tunnel-label"><span class="stg-pill"${tone ? ` data-state="${tone}"` : ''} role="status">${escapeHtml(view.label)}${view.progress ? ` · ${escapeHtml(view.progress.text)}` : ''}</span></div>
1869
+ ${view.kind === 'running' ? `<div class="cc-integration-path" id="cc-tunnel-url">${escapeHtml(view.url)}</div>` : ''}
1870
+ </div>
1871
+ ${view.action ? `<button type="button" class="cc-toggle${view.on ? ' on' : ''}" id="cc-tunnel-toggle" role="switch" aria-checked="${view.on ? 'true' : 'false'}" ${kit.inv('AUT049')} aria-label="${kit.L('AUT049')}" title="${escapeHtml(view.actionLabel)}"></button>` : ''}`;
1872
+ }
1873
+
1874
+ function tunnelExtraHtml(view, mcpKeyInfo) {
1875
+ const running = view.kind === 'running';
1876
+ const address = running ? remoteMcpAddress(view.url, mcpKeyInfo?.key) : null;
1877
+ return [
1878
+ view.progress ? `<div class="stg-tunnel-progress" role="progressbar" aria-label="${escapeHtml(view.label)}" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${view.progress.percent}"><span style="width:${view.progress.percent}%"></span></div>` : '',
1879
+ !running && view.detail ? `<div class="stg-help" id="cc-tunnel-detail"${view.kind === 'failed' ? ' role="alert"' : ''}>${escapeHtml(view.detail)}</div>` : '',
1880
+ view.kind === 'failed' ? `<button type="button" class="cc-copy-btn" id="cc-tunnel-retry">${escapeHtml(view.actionLabel)}</button>` : '',
1881
+ address ? `<button type="button" class="cc-copy-btn" id="cc-tunnel-copy-url" ${kit.inv('AUT050')} title="${kit.H('AUT050')}">${COPY_ICON} ${kit.te('settings.redesign.hooks.copyMcpUrl')}</button>` : '',
1882
+ running && !address ? `<div class="stg-help" id="cc-tunnel-key-needed">${escapeHtml(mcpKeyNeededText())}</div>` : '',
1883
+ running && view.copy ? `<div class="stg-help" id="cc-tunnel-copy">${escapeHtml(view.copy)}${view.copyNote ? ` ${escapeHtml(view.copyNote)}` : ''}</div>` : '',
1884
+ ].filter(Boolean).join('');
1885
+ }
1886
+
1887
+ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo, openclawBridge, greeting, toolPermissions, toolCategories, userContext = null) {
1725
1888
  const gh = ccIntegrations.global.hooks || {};
1726
1889
  const ssOn = !!gh.SessionStart;
1727
1890
  const psOn = !!gh.UserPromptSubmit;
@@ -1733,7 +1896,6 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
1733
1896
  const subStopOn = !!gh.SubagentStop;
1734
1897
  const hf = ccIntegrations.hookFeatures || {};
1735
1898
  const cmOn = hf.conversationMemory !== false;
1736
- const grOn = hf.greeting === true;
1737
1899
  const ulOn = hf.userLearning !== false;
1738
1900
  const ulThreshold = hf.userLearningThreshold || 8;
1739
1901
  const ulMaxNudges = hf.userLearningMaxNudges || 3;
@@ -1766,7 +1928,7 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
1766
1928
  { label: 'Gemini Web', on: false },
1767
1929
  ];
1768
1930
  const openaiItems = [
1769
- { label: 'Codex CLI', on: true },
1931
+ { label: 'Codex CLI', on: compat.codex !== false },
1770
1932
  { label: 'Cursor', on: false },
1771
1933
  { label: 'ChatGPT Web', on: false },
1772
1934
  ];
@@ -1797,91 +1959,35 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
1797
1959
  </div>`;
1798
1960
  };
1799
1961
 
1800
- // One greeting editor. `ids` are the inventory ids of its controls, in the order below.
1801
- function buildGreetingSection({ kind, enabled, config, prefix, bodyId, toggleAttrs, ids }) {
1802
- const [idToggle, idProject, idTemplate, idShowReminders, idLastSession, idAdd, idSave] = ids;
1803
- const gc = config || { defaults: {}, projects: {}, global: {} };
1804
- const greetingProjects = Object.keys(gc.projects || {});
1805
- const firstKey = greetingProjects[0] || 'global';
1806
- const projectKeys = [...greetingProjects, 'global'];
1807
- const firstCfg = firstKey === 'global'
1808
- ? { ...gc.defaults, ...gc.global }
1809
- : { ...gc.defaults, ...(gc.projects[firstKey] || {}) };
1810
- const projectLabel = (key) => (key === 'global' ? h('greeting.global') : escapeHtml((gc.projects[key] || {}).label || key));
1811
- const buildReminderRow = (reminder) => `
1812
- <div class="cc-greeting-reminder-row">
1813
- <span class="cc-greeting-drag-handle" title="${h('dragToReorder')}">&#8942;&#8942;</span>
1814
- <input class="cc-greeting-reminder-input label" placeholder="${h('label')}" aria-label="${h('label')}" value="${escapeHtml(reminder.label || '')}">
1815
- <input class="cc-greeting-reminder-input cmd" placeholder="${h('command')}" aria-label="${h('command')}" value="${escapeHtml(reminder.command || '')}">
1816
- <button type="button" class="cc-greeting-reminder-remove" title="${h('remove')}" aria-label="${h('remove')}">&times;</button>
1817
- </div>`;
1818
- const remindersHTML = (firstCfg.reminders || []).map(buildReminderRow).join('');
1819
- const variable = (name, id) => `<tr><td><code>{${name}}</code></td><td id="${prefix}-cs-${id}"></td></tr>`;
1820
-
1821
- return kit.card({
1822
- id: `${prefix}-greeting-config`, level: 'h4', collapsible: true, collapsed: true,
1823
- title: h(`greeting.title.${kind}`), purpose: h(`greeting.purpose.${kind}`),
1824
- status: `<button type="button" class="cc-toggle${enabled ? ' on' : ''}" ${toggleAttrs} ${kit.inv(idToggle)} aria-label="${kit.L(idToggle)}" title="${kit.H(idToggle)}"></button>`,
1825
- body: `
1826
- <div id="${bodyId}" style="display:${enabled ? 'block' : 'none'}">
1827
- <div class="stg-field" ${kit.inv(idProject)}>
1828
- <label id="${prefix}-greeting-project-caption">${kit.L(idProject)}</label>
1829
- <div class="cc-dropdown" id="${prefix}-greeting-project-dropdown" role="group" aria-labelledby="${prefix}-greeting-project-caption">
1830
- <button class="cc-dropdown-trigger" type="button" aria-haspopup="listbox" aria-describedby="${prefix}-greeting-project-help">
1831
- <span class="cc-dropdown-value" id="${prefix}-greeting-project-label">${projectLabel(firstKey)}</span>
1832
- <svg class="cc-dropdown-arrow" viewBox="0 0 24 24" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>
1833
- </button>
1834
- <div class="cc-dropdown-menu" id="${prefix}-greeting-project-menu">
1835
- ${projectKeys.map((key) => `<div class="cc-dropdown-item${key === firstKey ? ' active' : ''}" data-value="${key}">${projectLabel(key)}</div>`).join('')}
1836
- </div>
1837
- <input type="hidden" id="${prefix}-greeting-project" value="${firstKey}">
1838
- </div>
1839
- <div class="stg-help" id="${prefix}-greeting-project-help">${kit.H(idProject)}</div>
1840
- </div>
1841
- <div class="cc-greeting-field stg-field" ${kit.inv(idTemplate)}>
1842
- <label class="cc-greeting-label" for="${prefix}-greeting-template">${kit.L(idTemplate)}</label>
1843
- <textarea class="cc-greeting-textarea" id="${prefix}-greeting-template" placeholder="${h('greeting.templatePlaceholder')}" aria-describedby="${prefix}-greeting-template-help">${escapeHtml(firstCfg.greetingTemplate || '')}</textarea>
1844
- <div class="stg-help" id="${prefix}-greeting-template-help">${kit.H(idTemplate)}</div>
1845
- <div class="cc-greeting-cheatsheet-toggle" id="${prefix}-greeting-cheatsheet-toggle" ${kit.inv('AUT047')} role="button" tabindex="0">
1846
- <svg viewBox="0 0 24 24" aria-hidden="true" style="width:11px;height:11px;fill:none;stroke:currentColor;stroke-width:2;vertical-align:-1px"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
1847
- ${kit.L('AUT047')}
1848
- </div>
1849
- <div class="cc-greeting-cheatsheet" id="${prefix}-greeting-cheatsheet" style="display:none">
1850
- <table class="cc-cheatsheet-table">
1851
- <thead><tr><th>${h('greeting.placeholder')}</th><th>${h('greeting.showsAs')}</th></tr></thead>
1852
- <tbody>
1853
- ${variable('time_greeting', 'time')}
1854
- ${variable('project_label', 'label')}
1855
- ${variable('project_name', 'name')}
1856
- ${variable('branch', 'branch')}
1857
- ${variable('date', 'date')}
1858
- </tbody>
1859
- </table>
1860
- <div class="cc-cheatsheet-example">
1861
- <div class="cc-greeting-label" style="margin-bottom:3px">${h('greeting.example')}</div>
1862
- <div class="cc-cheatsheet-preview" id="${prefix}-cs-preview"></div>
1962
+ // The greeting (lib/greeting/): one switch for every agent, and what a new session would open with right now.
1963
+ // `greeting` is the view of what the server answered (describeGreeting). Without the engine (a server started
1964
+ // before it existed, one where it did not start, no answer) the switch shows off and disabled under a line
1965
+ // that says which of those it is. The preview is filled in once Settings is open (drawGreetingSection).
1966
+ const gr = greeting || describeGreeting(null, null, { tx: kit.tx });
1967
+ const greetingBlock = `
1968
+ <div class="stg-help uc-status-note" id="gr-status-note" role="status" data-gr-state="${gr.state}" data-stg-data${gr.note ? '' : ' hidden'}>${gr.note ? kit.esc(gr.note.text) : ''}</div>
1969
+ <div class="cc-hook-toggles" id="gr-switch" data-gr-ready="${gr.ready ? '1' : '0'}">
1970
+ <div class="cc-integration-item${gr.on ? ' enabled' : ''}${gr.disabled ? ' uc-disabled' : ''}" data-gr-row>
1971
+ <div class="cc-integration-info">
1972
+ <div class="cc-integration-label" id="gr-enabled-label">${kit.L('AUT058')}</div>
1973
+ <div class="cc-integration-path" id="gr-enabled-help">${kit.H('AUT058')}</div>
1974
+ <div class="uc-layer-notes" id="gr-notes" data-stg-data>${userContextNotesHTML(gr.notes)}</div>
1863
1975
  </div>
1976
+ <button type="button" class="cc-toggle${gr.on ? ' on' : ''}" data-gr-toggle ${kit.inv('AUT058')} aria-pressed="${gr.on ? 'true' : 'false'}" aria-labelledby="gr-enabled-label" aria-describedby="gr-enabled-help gr-notes gr-status-note"${gr.disabled ? ' disabled' : ''}></button>
1864
1977
  </div>
1978
+ </div>
1979
+ <div class="stg-help uc-save-status" id="gr-save-status" role="alert" data-stg-data hidden></div>
1980
+ <div class="uc-digest gr-preview" id="gr-preview" data-gr-preview-state="${gr.preview.state}" ${kit.inv('AUT059')}${gr.preview.shown ? '' : ' hidden'}>
1981
+ <div class="uc-digest-head">
1982
+ <span class="uc-digest-title" id="gr-preview-title">${kit.L('AUT059')}</span>
1983
+ <span class="uc-digest-count" id="gr-preview-project" data-stg-data>${kit.esc(gr.preview.project)}</span>
1865
1984
  </div>
1866
- <div class="cc-greeting-checkboxes">
1867
- ${kit.toggleField(idShowReminders, kit.switchInput(`${prefix}-greeting-show-reminders`, { checked: !!firstCfg.showReminders }), { forId: `${prefix}-greeting-show-reminders` })}
1868
- ${kit.toggleField(idLastSession, kit.switchInput(`${prefix}-greeting-show-last-session`, { checked: !!firstCfg.showLastSession }), { forId: `${prefix}-greeting-show-last-session` })}
1869
- </div>
1870
- <div class="cc-greeting-field stg-field" style="margin-top:12px">
1871
- <label class="cc-greeting-label" id="${prefix}-greeting-reminders-label">${h('greeting.reminders')}</label>
1872
- <div class="cc-greeting-reminder-list" id="${prefix}-greeting-reminders" role="group" aria-labelledby="${prefix}-greeting-reminders-label">${remindersHTML}</div>
1873
- <div class="stg-help">${h('greeting.remindersHelp')}</div>
1874
- <button type="button" class="conn-add-btn" id="${prefix}-greeting-add-reminder" ${kit.inv(idAdd)} title="${kit.H(idAdd)}" style="margin-top:6px">
1875
- <svg viewBox="0 0 24 24" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
1876
- ${kit.L(idAdd)}
1877
- </button>
1878
- </div>
1879
- <button type="button" class="conn-add-btn stg-save-btn" id="${prefix}-greeting-save" ${kit.inv(idSave)} title="${kit.H(idSave)}" style="width:100%;margin-top:4px;border-style:solid;background:rgba(79,195,247,0.08);border-color:rgba(79,195,247,0.25);color:rgba(79,195,247,0.9)">
1880
- ${kit.L(idSave)}
1881
- </button>
1882
- </div>`,
1883
- });
1884
- }
1985
+ <div class="stg-help gr-preview-help" style="margin:2px 0 0">${kit.H('AUT059')}</div>
1986
+ <div class="stg-help gr-preview-lead" id="gr-preview-lead" role="status" data-stg-data>${kit.esc(gr.preview.lead || gr.preview.empty)}</div>
1987
+ <div class="gr-preview-card" id="gr-preview-card" data-stg-data></div>
1988
+ <div class="stg-help gr-preview-spend" id="gr-preview-spend" data-stg-data${gr.preview.spend ? '' : ' hidden'}>${kit.esc(gr.preview.spend)}</div>
1989
+ <button type="button" class="conn-add-btn" id="gr-write" ${kit.inv('AUT060')} title="${kit.H('AUT060')}" style="margin:0;width:auto;padding:4px 10px;border-style:solid"${gr.preview.canWrite ? '' : ' hidden'}>${kit.L('AUT060')}</button>
1990
+ </div>`;
1885
1991
 
1886
1992
  const feature = (invId, key, on, extra = '') => `
1887
1993
  <div class="cc-integration-item${on ? ' enabled' : ''}" data-feature="${key}">
@@ -1892,26 +1998,65 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
1892
1998
  <button type="button" class="cc-toggle${on ? ' on' : ''}" data-cc-feature="${key}" ${kit.inv(invId)} aria-labelledby="cc-feature-${key}-label" aria-describedby="cc-feature-${key}-help"></button>
1893
1999
  </div>`;
1894
2000
 
2001
+ // The Assistant's user context: three layers and the digest a new conversation starts with.
2002
+ // `userContext` is the view of what the server answered (describeUserContext). Without the engine (a server
2003
+ // started before it existed, one where it did not start, no answer) the rows show switched off and disabled
2004
+ // under a line that says which of those it is: public/ is served live, lib/ only after a restart.
2005
+ const uc = userContext || describeUserContext(null, { tx: kit.tx });
2006
+ const layer = (invId, key) => {
2007
+ const row = uc.layers[key];
2008
+ return `
2009
+ <div class="cc-integration-item${row.on ? ' enabled' : ''}${row.disabled ? ' uc-disabled' : ''}" data-uc-layer-row="${key}">
2010
+ <div class="cc-integration-info">
2011
+ <div class="cc-integration-label" id="uc-layer-${key}-label">${kit.L(invId)}</div>
2012
+ <div class="cc-integration-path" id="uc-layer-${key}-help">${kit.H(invId)}</div>
2013
+ <div class="uc-layer-notes" id="uc-layer-${key}-notes" data-stg-data>${userContextNotesHTML(row.notes)}</div>
2014
+ </div>
2015
+ <button type="button" class="cc-toggle${row.on ? ' on' : ''}" data-uc-layer="${key}" ${kit.inv(invId)} aria-pressed="${row.on ? 'true' : 'false'}" aria-labelledby="uc-layer-${key}-label" aria-describedby="uc-layer-${key}-help uc-layer-${key}-notes${uc.note ? ' uc-status-note' : ''}"${row.disabled ? ' disabled' : ''}></button>
2016
+ </div>`;
2017
+ };
2018
+ const userContextBlock = `
2019
+ <div class="stg-help uc-status-note" id="uc-status-note" role="status" data-uc-state="${uc.state}" data-stg-data${uc.note ? '' : ' hidden'}>${uc.note ? kit.esc(uc.note.text) : ''}</div>
2020
+ <div class="cc-hook-toggles" id="uc-layers" data-uc-ready="${uc.ready ? '1' : '0'}" data-uc-state="${uc.state}">
2021
+ ${layer('AUT053', 'knowledge')}
2022
+ ${layer('AUT054', 'nudges')}
2023
+ ${layer('AUT055', 'trace')}
2024
+ </div>
2025
+ <div class="stg-help uc-save-status" id="uc-save-status" role="alert" data-stg-data hidden></div>
2026
+ <div class="stg-help uc-applies" id="uc-applies"${uc.applies ? '' : ' hidden'}>${kit.esc(uc.applies)}</div>
2027
+ <div class="uc-digest" id="uc-digest" data-uc-digest-state="${uc.digest.state}" ${kit.inv('AUT056')}${uc.digest.shown ? '' : ' hidden'}>
2028
+ <div class="uc-digest-head">
2029
+ <span class="uc-digest-title" id="uc-digest-title">${kit.L('AUT056')}</span>
2030
+ <span class="uc-digest-count" id="uc-digest-count" data-stg-data>${kit.esc(uc.digest.count)}</span>
2031
+ </div>
2032
+ <div class="stg-help uc-digest-help" style="margin:2px 0 0">${kit.H('AUT056')}</div>
2033
+ <div class="uc-digest-rows" id="uc-digest-rows" role="list" aria-labelledby="uc-digest-title" data-stg-data>${uc.digest.shown ? userContextDigestHTML(uc.digest) : ''}</div>
2034
+ <div class="stg-help" id="uc-digest-more" style="margin:0 0 8px"${uc.digest.omitted ? '' : ' hidden'}>${kit.esc(uc.digest.omitted)}</div>
2035
+ <button type="button" class="conn-add-btn" id="uc-digest-all" ${kit.inv('AUT057')} title="${kit.H('AUT057')}" style="margin:0;width:auto;padding:4px 10px;border-style:solid">${kit.L('AUT057')}</button>
2036
+ </div>`;
2037
+
1895
2038
  return `
1896
2039
  <div class="stg-pane" data-stg-pane="hooks">
1897
2040
 
1898
2041
  ${kit.card({
1899
2042
  id: 'stg-sec-greetings', section: 'automations.greetings',
1900
2043
  body: `
1901
- ${buildGreetingSection({ kind: 'cc', enabled: grOn, config: greetingConfig, prefix: 'cc', bodyId: 'cc-greeting-body', toggleAttrs: 'data-cc-feature="greeting"',
1902
- ids: ['AUT001', 'AUT002', 'AUT003', 'AUT004', 'AUT005', 'AUT009', 'AUT010'] })}
1903
- ${buildGreetingSection({ kind: 'codex', enabled: codexGreetingConfig?.enabled === true, config: codexGreetingConfig, prefix: 'codex', bodyId: 'codex-greeting-body', toggleAttrs: 'data-codex-greeting-toggle="enabled"',
1904
- ids: ['AUT011', 'AUT012', 'AUT013', 'AUT014', 'AUT015', 'AUT016', 'AUT017'] })}
1905
- ${buildGreetingSection({ kind: 'opencode', enabled: opencodeGreetingConfig?.enabled === true, config: opencodeGreetingConfig, prefix: 'opencode', bodyId: 'opencode-greeting-body', toggleAttrs: 'data-opencode-greeting-toggle="enabled"',
1906
- ids: ['AUT018', 'AUT019', 'AUT020', 'AUT021', 'AUT022', 'AUT026', 'AUT027'] })}`,
2044
+ ${greetingBlock}`,
1907
2045
  })}
1908
2046
 
1909
2047
  ${kit.card({
1910
2048
  id: 'stg-sec-automation-behavior', section: 'automations.automation_behavior',
1911
- status: providerBadge({ cli: true, vscode: true, web: false, cowork: false }),
1912
2049
  body: `
1913
- <div class="stg-help" style="margin:0 0 10px">${h('claudeOnly')}</div>
1914
- <div class="cc-hook-toggles">
2050
+ ${userContextBlock}
2051
+
2052
+ ${kit.card({
2053
+ id: 'stg-sec-hooks', level: 'h4', collapsible: true, collapsed: true, advanced: true, attrs: 'data-cc-target="global"',
2054
+ title: h('hooksTitle'), purpose: h('hooksPurpose'),
2055
+ // These hooks are Claude Code's own (~/.claude/settings.json, hooks/claude-code): no other assistant runs them.
2056
+ status: `<span class="cc-hooks-badge${allOn ? ' all-on' : ''}" id="cc-hooks-badge">${onCount}/${hookRows.length}</span>${providerBadge({ cli: true, vscode: true, web: false, cowork: false, codex: false })}`,
2057
+ body: `
2058
+ <div class="stg-help" style="margin:0 0 10px">${h('claudeOnly')}</div>
2059
+ <div class="cc-hook-toggles">
1915
2060
  ${feature('AUT036', 'conversationMemory', cmOn)}
1916
2061
  ${feature('AUT039', 'userLearning', ulOn, `
1917
2062
  <div class="cc-ul-threshold" id="cc-ul-threshold" style="display:${ulOn ? 'flex' : 'none'};align-items:center;gap:6px;margin-top:5px;flex-wrap:wrap">
@@ -1924,14 +2069,8 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
1924
2069
  </div>`)}
1925
2070
  ${feature('AUT040', 'taskRecallGate', taskRecallOn)}
1926
2071
  ${feature('AUT041', 'subagentRemember', srOn)}
1927
- </div>
1928
-
1929
- ${kit.card({
1930
- id: 'stg-sec-hooks', level: 'h4', collapsible: true, collapsed: true, advanced: true, attrs: 'data-cc-target="global"',
1931
- title: h('hooksTitle'), purpose: h('hooksPurpose'),
1932
- status: `<span class="cc-hooks-badge${allOn ? ' all-on' : ''}" id="cc-hooks-badge">${onCount}/${hookRows.length}</span>${providerBadge({ cli: true, vscode: true, web: false, cowork: false })}`,
1933
- body: `
1934
- <div class="cc-hook-toggles">
2072
+ </div>
2073
+ <div class="cc-hook-toggles" style="margin-top:10px">
1935
2074
  ${hookRows.map(r => `
1936
2075
  <div class="cc-integration-item${r.on ? ' enabled' : ''}" data-hook="${r.key}">
1937
2076
  <div class="cc-integration-info">
@@ -1949,20 +2088,14 @@ function buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo,
1949
2088
  status: providerBadge({ cli: true, vscode: true, web: true, webNote: h('compat.viaAddress'), cowork: false }),
1950
2089
  body: `
1951
2090
  ${kit.subhead(h('tunnel.title'))}
1952
- <div class="cc-integration-item${tunnelStatus.running ? ' enabled' : ''}" id="cc-tunnel-row" ${kit.inv('AUT048')}>
1953
- <div class="cc-integration-info">
1954
- <div class="cc-integration-label" id="cc-tunnel-label">${tunnelStatus.running ? h('running') : tunnelStatus.available ? h('ready') : h('tunnel.notInstalled')}</div>
1955
- <div class="cc-integration-path" id="cc-tunnel-url">${tunnelStatus.url ? escapeHtml(tunnelStatus.url) : (tunnelStatus.available ? h('exposeMcpToClaudeWebVia') : h('tunnel.install'))}</div>
1956
- </div>
1957
- ${tunnelStatus.available ? `<button type="button" class="cc-toggle${tunnelStatus.running ? ' on' : ''}" id="cc-tunnel-toggle" ${kit.inv('AUT049')} aria-label="${kit.L('AUT049')}" title="${kit.H('AUT049')}"></button>` : ''}
1958
- </div>
1959
- ${tunnelStatus.url ? `<button type="button" class="cc-copy-btn" id="cc-tunnel-copy-url" ${kit.inv('AUT050')} title="${kit.H('AUT050')}" style="margin-top:4px">${copyIcon} ${h('copyMcpUrl')}</button>` : ''}
2091
+ <div class="cc-integration-item${readTunnel(tunnelStatus).on ? ' enabled' : ''}" id="cc-tunnel-row" ${kit.inv('AUT048')}>${tunnelRowHtml(readTunnel(tunnelStatus))}</div>
2092
+ <div class="stg-tunnel-extra" id="cc-tunnel-extra">${tunnelExtraHtml(readTunnel(tunnelStatus), mcpKeyInfo)}</div>
1960
2093
 
1961
2094
  <div style="margin-top:10px">
1962
2095
  <div class="cc-integration-item${mcpKeyInfo.hasKey ? ' enabled' : ''}" id="cc-apikey-row">
1963
2096
  <div class="cc-integration-info">
1964
2097
  <div class="cc-integration-label">${h('apikey.title')}</div>
1965
- <div class="cc-integration-path" id="cc-apikey-status">${mcpKeyInfo.hasKey ? escapeHtml(mcpKeyInfo.maskedKey) : h('apikey.none')}</div>
2098
+ <div class="cc-integration-path" id="cc-apikey-status">${mcpKeyInfo.hasKey ? escapeHtml(mcpKeyInfo.maskedKey) : h(closesKeylessMcp(tunnelStatus) ? 'apikey.noneClosed' : 'apikey.none')}</div>
1966
2099
  </div>
1967
2100
  <button type="button" class="conn-add-btn" id="cc-apikey-generate" ${kit.inv('AUT042')} title="${kit.H('AUT042')}" style="margin:0;padding:3px 8px;width:auto;border-style:solid;background:rgba(255,255,255,0.03)">${mcpKeyInfo.hasKey ? h('regenerate') : h('generateKey')}</button>
1968
2101
  </div>
@@ -3044,12 +3177,11 @@ export async function openSettingsModal(options = {}) {
3044
3177
  let connections = [];
3045
3178
  let ccIntegrations = { global: { installed: false }, projects: [] };
3046
3179
  let ccSkills = [];
3047
- let tunnelStatus = { available: false, running: false, url: null };
3180
+ let tunnelStatus = null; // GET /api/tunnel/status; null until it answered (tunnel-view.js reads either)
3048
3181
  let mcpKeyInfo = { hasKey: false };
3049
3182
  let _bridgeResult = null;
3050
- let greetingConfig = { defaults: {}, projects: {}, global: {} };
3051
- let codexGreetingConfig = { enabled: true, defaults: {}, projects: {}, global: {} };
3052
- let opencodeGreetingConfig = { enabled: false, defaults: {}, projects: {}, global: {} };
3183
+ // The greeting (lib/greeting/): what its endpoint answered, as a reading (no answer is a reading too).
3184
+ let greetingReading = readGreetingStatus({ status: 0 });
3053
3185
  let setupStatus = { claude: {}, gemini: {}, codex: {}, opencode: {}, paths: {} };
3054
3186
  let cliConfig = {};
3055
3187
  let toolPermissions = {};
@@ -3059,9 +3191,12 @@ export async function openSettingsModal(options = {}) {
3059
3191
  let customIconsData = { extensions: {}, filenames: {} };
3060
3192
  let youtubeConfig = { secrets: {}, configured: {}, pipeline: {} };
3061
3193
  let typesafeConfig = null;
3194
+ // The Assistant's user context (lib/assistant-user-context.js): what its endpoint answered, as a reading.
3195
+ // No answer is a reading too: the section says what is true instead of showing switches that do nothing.
3196
+ let userContextReading = readUserContextResponse({ status: 0 });
3062
3197
 
3063
3198
  try {
3064
- const [settingsRes, connRes, ccRes, skillsRes, tunnelRes, keyRes, bridgeRes, greetRes, codexGreetRes, opencodeGreetRes, setupRes, cliRes, toolPermsRes, toolCatsRes, discordRes, skinsRes, iconsRes, youtubeRes, typesafeRes] = await Promise.allSettled([
3199
+ const [settingsRes, connRes, ccRes, skillsRes, tunnelRes, keyRes, bridgeRes, greetRes, setupRes, cliRes, toolPermsRes, toolCatsRes, discordRes, skinsRes, iconsRes, youtubeRes, typesafeRes, userContextRes] = await Promise.allSettled([
3065
3200
  fetch('/api/settings').then(r => r.json()),
3066
3201
  fetch('/api/connections').then(r => r.json()),
3067
3202
  fetch('/api/claude-code/integrations').then(r => r.json()),
@@ -3069,9 +3204,7 @@ export async function openSettingsModal(options = {}) {
3069
3204
  fetch('/api/tunnel/status').then(r => r.json()),
3070
3205
  fetch('/api/mcp-key').then(r => r.json()),
3071
3206
  fetch('/api/bridges/openclaw').then(r => r.json()),
3072
- fetch('/api/greeting/config').then(r => r.json()),
3073
- fetch('/api/codex-panel/greeting/config').then(r => r.json()),
3074
- fetch('/api/opencode-panel/greeting/config').then(r => r.json()),
3207
+ loadGreetingReading(),
3075
3208
  fetch('/api/setup/status').then(r => r.json()),
3076
3209
  fetch('/api/cli/config').then(r => r.json()),
3077
3210
  fetch('/api/claude-code/tool-permissions').then(r => r.json()),
@@ -3081,8 +3214,10 @@ export async function openSettingsModal(options = {}) {
3081
3214
  fetch('/api/file-icons').then(r => r.json()),
3082
3215
  fetch('/api/youtube/config').then(r => r.json()),
3083
3216
  fetch('/api/typesafe/config').then(r => r.json()),
3217
+ loadUserContextReading(),
3084
3218
  ]);
3085
3219
  if (typesafeRes.status === 'fulfilled' && typesafeRes.value.ok) typesafeConfig = typesafeRes.value;
3220
+ if (userContextRes.status === 'fulfilled') userContextReading = userContextRes.value;
3086
3221
  if (settingsRes.status === 'fulfilled') settings = settingsRes.value;
3087
3222
  if (connRes.status === 'fulfilled' && connRes.value.connections) connections = connRes.value.connections;
3088
3223
  if (ccRes.status === 'fulfilled' && ccRes.value.ok) ccIntegrations = ccRes.value;
@@ -3090,9 +3225,7 @@ export async function openSettingsModal(options = {}) {
3090
3225
  if (tunnelRes.status === 'fulfilled' && tunnelRes.value.ok) tunnelStatus = tunnelRes.value;
3091
3226
  if (keyRes.status === 'fulfilled' && keyRes.value.ok) mcpKeyInfo = keyRes.value;
3092
3227
  if (bridgeRes.status === 'fulfilled' && bridgeRes.value.ok) _bridgeResult = bridgeRes.value;
3093
- if (greetRes.status === 'fulfilled' && greetRes.value.ok) greetingConfig = greetRes.value.config;
3094
- if (codexGreetRes.status === 'fulfilled' && codexGreetRes.value.ok) codexGreetingConfig = codexGreetRes.value.config;
3095
- if (opencodeGreetRes.status === 'fulfilled' && opencodeGreetRes.value.ok) opencodeGreetingConfig = opencodeGreetRes.value.config;
3228
+ if (greetRes.status === 'fulfilled') greetingReading = greetRes.value;
3096
3229
  if (setupRes.status === 'fulfilled' && setupRes.value.ok) setupStatus = setupRes.value;
3097
3230
  if (cliRes.status === 'fulfilled' && cliRes.value.ok) cliConfig = cliRes.value.config;
3098
3231
  if (toolPermsRes.status === 'fulfilled' && toolPermsRes.value.ok) toolPermissions = toolPermsRes.value.tools;
@@ -3153,7 +3286,7 @@ export async function openSettingsModal(options = {}) {
3153
3286
  <div class="settings-content">
3154
3287
  ${buildPagesHTML({
3155
3288
  server: buildServerTab(settings, connections),
3156
- hooks: buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo, openclawBridge, greetingConfig, codexGreetingConfig, opencodeGreetingConfig, toolPermissions, toolCategories),
3289
+ hooks: buildConnectionsTab(ccIntegrations, ccSkills, tunnelStatus, mcpKeyInfo, openclawBridge, describeGreeting(greetingReading, null, { tx: kit.tx, t: kit.tx }), toolPermissions, toolCategories, describeUserContext(userContextReading, { tx: kit.tx })),
3157
3290
  terminal: buildTerminalTab(cliConfig),
3158
3291
  opencode: buildOpencodeTab(),
3159
3292
  notifications: buildNotificationsTab(),
@@ -3169,6 +3302,7 @@ export async function openSettingsModal(options = {}) {
3169
3302
  whatsapp: buildWhatsAppTab(),
3170
3303
  morelogin: buildMoreLoginTab(),
3171
3304
  permissions: buildPermissionsTab(toolCategories, toolPermissions),
3305
+ editor: buildCodeEditorTab(),
3172
3306
  social: buildSocialTab(toolCategories, toolPermissions),
3173
3307
  youtube: buildYoutubeTab(youtubeConfig),
3174
3308
  language: buildLanguageTab(),
@@ -4261,6 +4395,9 @@ export async function openSettingsModal(options = {}) {
4261
4395
  // ── WhatsApp tab — self-loads its status (never part of the blocking fetch above) ──
4262
4396
  wireWhatsAppTab(overlay, { toast: showCCToast });
4263
4397
 
4398
+ // ── Code editor: AI autocomplete (loads and saves itself) ──
4399
+ wireCodeEditorTab(overlay);
4400
+
4264
4401
  // ── Discord tab handlers ──
4265
4402
  {
4266
4403
  // Token save
@@ -4683,6 +4820,152 @@ export async function openSettingsModal(options = {}) {
4683
4820
  toggle.addEventListener('click', () => { ccToggleHook('global', null, toggle, overlay.querySelector('.iface-section[data-cc-target="global"]'), toggle.dataset.ccHook); });
4684
4821
  });
4685
4822
 
4823
+ // ── The Assistant's user context: layers and the digest preview ──
4824
+ const ucRowsEl = overlay.querySelector('#uc-digest-rows');
4825
+ const redrawUserContext = (reading = userContextReading) => drawUserContext(overlay, describeUserContext(reading, { tx: kit.tx }));
4826
+ // What could not be saved, said next to the switches (role="alert"); empty clears it.
4827
+ const setUserContextSaveStatus = (text = '') => {
4828
+ const status = overlay.querySelector('#uc-save-status');
4829
+ if (status) { status.hidden = !text; status.textContent = text; }
4830
+ };
4831
+ // A line of the digest opens its memory (the owner corrects line 1 there and the digest follows).
4832
+ ucRowsEl?.addEventListener('click', (e) => {
4833
+ const row = e.target.closest('[data-uc-memory]');
4834
+ if (!row) return;
4835
+ const id = row.dataset.ucMemory;
4836
+ close();
4837
+ emit('memory:open', { id });
4838
+ });
4839
+ overlay.querySelector('#uc-digest-all')?.addEventListener('click', () => {
4840
+ close();
4841
+ emit('explorer:show-category', { category: 'user-knowledge', fallback: 'communication-style' });
4842
+ });
4843
+ // A note that names another Settings page (learning needs AI decisions) goes there.
4844
+ overlay.querySelector('#uc-layers')?.addEventListener('click', (e) => {
4845
+ const link = e.target.closest('[data-uc-goto]');
4846
+ if (!link) return;
4847
+ try { applySettingsOpenOptions(overlay, JSON.parse(link.dataset.ucGoto)); } catch { /* a malformed target goes nowhere */ }
4848
+ });
4849
+ // The switch moves at once and follows the server's answer: the stored settings when the save went through,
4850
+ // back where it was (with the reason next to the switches) when it did not. Saves go one after the other, in
4851
+ // the order of the clicks, so a second click made before the first was answered is kept, not dropped.
4852
+ let userContextSaves = Promise.resolve();
4853
+ const userContextWanted = {}; // layer → what its last click asked for, until that save is answered
4854
+ const userContextWaiting = {}; // layer → saves not answered yet
4855
+ const userContextBusy = () => Object.values(userContextWaiting).some(count => count > 0);
4856
+ // What the page shows while saves are on their way: the stored settings with the clicks on top.
4857
+ const userContextShown = () => (userContextReading.state === 'ready'
4858
+ ? { ...userContextReading, payload: { ...userContextReading.payload, settings: { ...userContextReading.payload.settings, ...userContextWanted } } }
4859
+ : userContextReading);
4860
+ const markUserContextWaiting = () => overlay.querySelectorAll('.cc-toggle[data-uc-layer]').forEach(other => { other.style.opacity = userContextWaiting[other.dataset.ucLayer] > 0 ? '0.4' : ''; });
4861
+ overlay.querySelectorAll('.cc-toggle[data-uc-layer]').forEach(toggle => {
4862
+ toggle.addEventListener('click', () => {
4863
+ if (toggle.disabled || userContextReading.state !== 'ready') return;
4864
+ const layerKey = toggle.dataset.ucLayer;
4865
+ const wanted = !toggle.classList.contains('on');
4866
+ const name = overlay.querySelector(`#uc-layer-${layerKey}-label`)?.textContent?.trim() || layerKey;
4867
+ userContextWanted[layerKey] = wanted;
4868
+ userContextWaiting[layerKey] = (userContextWaiting[layerKey] || 0) + 1;
4869
+ setUserContextSaveStatus('');
4870
+ redrawUserContext(userContextShown());
4871
+ markUserContextWaiting();
4872
+ userContextSaves = userContextSaves.then(async () => {
4873
+ let failure = null;
4874
+ try {
4875
+ const res = await fetch('/api/assistant/user-context', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ [layerKey]: wanted }) });
4876
+ const body = await res.json().catch(() => null);
4877
+ const reading = readUserContextResponse({ status: res.status, body });
4878
+ if (res.ok && reading.state === 'ready') userContextReading = reading;
4879
+ else failure = { status: res.status, body };
4880
+ } catch (err) {
4881
+ failure = { status: 0, error: err?.message };
4882
+ }
4883
+ userContextWaiting[layerKey] -= 1;
4884
+ // Every click on this switch was answered: it shows the stored value again (a later click still on its way keeps its wish).
4885
+ if (userContextWaiting[layerKey] <= 0) delete userContextWanted[layerKey];
4886
+ if (!overlay.isConnected) return;
4887
+ if (failure) setUserContextSaveStatus(describeUserContextSaveFailure({ name, wanted, ...failure }, { tx: kit.tx }));
4888
+ redrawUserContext(userContextShown());
4889
+ markUserContextWaiting();
4890
+ });
4891
+ });
4892
+ });
4893
+ // Switched in another window (or by a hand edit another Settings saved): this one follows.
4894
+ const offUserContextSync = on('sync:assistant:user-context-changed', async () => {
4895
+ if (userContextBusy() || userContextReading.state !== 'ready' || !overlay.isConnected) return;
4896
+ const reading = await loadUserContextReading();
4897
+ if (reading.state !== 'ready' || userContextBusy() || !overlay.isConnected) return;
4898
+ userContextReading = reading;
4899
+ redrawUserContext();
4900
+ });
4901
+ overlay.addEventListener('settings-close', () => offUserContextSync(), { once: true });
4902
+
4903
+ // ── Greeting (lib/greeting/): the one switch, and what a new session would open with right now ──
4904
+ // The switch moves at once and goes back, with the reason in the line under it, when the save fails;
4905
+ // quick clicks are saved in the order they were made. The preview is asked when Settings opens and
4906
+ // after every change; "Write it now" asks the writer for the sentence (one small call).
4907
+ let greetingPreview = null;
4908
+ let greetingSaves = Promise.resolve();
4909
+ let greetingClosed = false; // Settings was closed: an answer that arrives late draws nothing
4910
+ const drawGreeting = () => drawGreetingSection(overlay, describeGreeting(greetingReading, greetingPreview, { tx: kit.tx, t: kit.tx }));
4911
+ const refreshGreetingPreview = async ({ write = false } = {}) => {
4912
+ if (greetingReading.state !== 'ready') { drawGreeting(); return; }
4913
+ if (!greetingPreview) greetingPreview = { loading: true };
4914
+ drawGreeting();
4915
+ const answer = await fetchGreetingPreview({ write });
4916
+ if (greetingClosed) return;
4917
+ greetingPreview = answer;
4918
+ drawGreeting();
4919
+ };
4920
+ const greetingToggle = overlay.querySelector('[data-gr-toggle]');
4921
+ if (greetingToggle) {
4922
+ greetingToggle.addEventListener('click', () => {
4923
+ if (greetingToggle.disabled || greetingReading.state !== 'ready') return;
4924
+ const before = greetingReading;
4925
+ const wanted = before.payload.settings.enabled === false;
4926
+ greetingReading = { ...before, payload: { ...before.payload, settings: { ...before.payload.settings, enabled: wanted }, enabled: wanted && before.payload.killed !== true } };
4927
+ const status = overlay.querySelector('#gr-save-status');
4928
+ if (status) { status.hidden = true; status.textContent = ''; }
4929
+ drawGreeting();
4930
+ greetingSaves = greetingSaves.then(async () => {
4931
+ let res = null;
4932
+ let body = null;
4933
+ let error = null;
4934
+ try {
4935
+ res = await fetch('/api/greeting', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: wanted }) });
4936
+ body = await res.json().catch(() => null);
4937
+ } catch (err) { error = err?.message || String(err); }
4938
+ const next = readGreetingStatus({ status: res?.status || 0, body, error });
4939
+ if (next.state === 'ready') greetingReading = next;
4940
+ else {
4941
+ greetingReading = before;
4942
+ if (status) { status.textContent = describeGreetingSaveFailure({ status: res?.status || 0, body, error }, { tx: kit.tx }); status.hidden = false; }
4943
+ }
4944
+ drawGreeting();
4945
+ if (next.state === 'ready') await refreshGreetingPreview();
4946
+ });
4947
+ });
4948
+ }
4949
+ const greetingWrite = overlay.querySelector('#gr-write');
4950
+ if (greetingWrite) {
4951
+ greetingWrite.addEventListener('click', async () => {
4952
+ if (greetingWrite.disabled) return;
4953
+ const label = greetingWrite.textContent;
4954
+ greetingWrite.disabled = true;
4955
+ greetingWrite.textContent = kit.tx('settings.redesign.hooks.greet.preview.writing');
4956
+ try { await refreshGreetingPreview({ write: true }); }
4957
+ finally { greetingWrite.disabled = false; greetingWrite.textContent = label; }
4958
+ });
4959
+ }
4960
+ // Switched in another window: an open Settings follows.
4961
+ const offGreetingSync = on('sync:greeting:changed', async () => {
4962
+ greetingReading = await loadGreetingReading();
4963
+ if (greetingClosed) return;
4964
+ await refreshGreetingPreview();
4965
+ });
4966
+ overlay.addEventListener('settings-close', () => { greetingClosed = true; offGreetingSync(); }, { once: true });
4967
+ refreshGreetingPreview();
4968
+
4686
4969
  // Feature toggles
4687
4970
  overlay.querySelectorAll('.cc-toggle[data-cc-feature]').forEach(toggle => {
4688
4971
  toggle.addEventListener('click', async () => {
@@ -4693,11 +4976,6 @@ export async function openSettingsModal(options = {}) {
4693
4976
  const data = await res.json();
4694
4977
  if (data.ok) {
4695
4978
  toggle.classList.toggle('on'); const row = toggle.closest('.cc-integration-item'); if (row) row.classList.toggle('enabled', !isOn);
4696
- // Show/hide greeting body when greeting feature is toggled
4697
- if (feature === 'greeting') {
4698
- const gcBody = overlay.querySelector('#cc-greeting-body');
4699
- if (gcBody) gcBody.style.display = !isOn ? 'block' : 'none';
4700
- }
4701
4979
  // Show/hide user learning threshold when feature is toggled
4702
4980
  if (feature === 'userLearning') {
4703
4981
  const ulThreshold = overlay.querySelector('#cc-ul-threshold');
@@ -4710,64 +4988,6 @@ export async function openSettingsModal(options = {}) {
4710
4988
  });
4711
4989
  });
4712
4990
 
4713
- overlay.querySelectorAll('.cc-toggle[data-codex-greeting-toggle]').forEach(toggle => {
4714
- toggle.addEventListener('click', async () => {
4715
- const isOn = toggle.classList.contains('on');
4716
- try {
4717
- toggle.style.opacity = '0.4';
4718
- toggle.style.pointerEvents = 'none';
4719
- const res = await fetch('/api/codex-panel/greeting/config', {
4720
- method: 'PUT',
4721
- headers: { 'Content-Type': 'application/json' },
4722
- body: JSON.stringify({ enabled: !isOn }),
4723
- });
4724
- const data = await res.json();
4725
- if (!data.ok) {
4726
- alert(data.error || kit.tx('settings.redesign.hooks.failedToToggleCodexGreeting'));
4727
- return;
4728
- }
4729
- codexGreetingConfig.enabled = !isOn;
4730
- toggle.classList.toggle('on', !isOn);
4731
- const body = overlay.querySelector('#codex-greeting-body');
4732
- if (body) body.style.display = !isOn ? 'block' : 'none';
4733
- } catch (err) {
4734
- alert(kit.tx('settings.redesign.hooks.failed2', { message: err.message }));
4735
- } finally {
4736
- toggle.style.opacity = '';
4737
- toggle.style.pointerEvents = '';
4738
- }
4739
- });
4740
- });
4741
-
4742
- overlay.querySelectorAll('.cc-toggle[data-opencode-greeting-toggle]').forEach(toggle => {
4743
- toggle.addEventListener('click', async () => {
4744
- const isOn = toggle.classList.contains('on');
4745
- try {
4746
- toggle.style.opacity = '0.4';
4747
- toggle.style.pointerEvents = 'none';
4748
- const res = await fetch('/api/opencode-panel/greeting/config', {
4749
- method: 'PUT',
4750
- headers: { 'Content-Type': 'application/json' },
4751
- body: JSON.stringify({ enabled: !isOn }),
4752
- });
4753
- const data = await res.json();
4754
- if (!data.ok) {
4755
- alert(data.error || kit.tx('settings.redesign.hooks.failedToToggleOpencodeGreeting'));
4756
- return;
4757
- }
4758
- opencodeGreetingConfig.enabled = !isOn;
4759
- toggle.classList.toggle('on', !isOn);
4760
- const body = overlay.querySelector('#opencode-greeting-body');
4761
- if (body) body.style.display = !isOn ? 'block' : 'none';
4762
- } catch (err) {
4763
- alert(kit.tx('settings.redesign.hooks.failed2', { message: err.message }));
4764
- } finally {
4765
- toggle.style.opacity = '';
4766
- toggle.style.pointerEvents = '';
4767
- }
4768
- });
4769
- });
4770
-
4771
4991
  // ── Tool Permission toggles ──
4772
4992
  function updateToolBadges() {
4773
4993
  // Update per-category counts across all tabs (Automations + Social)
@@ -4874,242 +5094,6 @@ export async function openSettingsModal(options = {}) {
4874
5094
  }
4875
5095
  }
4876
5096
 
4877
- // ── Greeting editor handlers ──
4878
- function wireGreetingEditor({ prefix, configRef, saveBaseUrl, onSave }) {
4879
- const gcSelect = overlay.querySelector(`#${prefix}-greeting-project`);
4880
- const gcTemplate = overlay.querySelector(`#${prefix}-greeting-template`);
4881
- const gcShowReminders = overlay.querySelector(`#${prefix}-greeting-show-reminders`);
4882
- const gcShowLastSession = overlay.querySelector(`#${prefix}-greeting-show-last-session`);
4883
- const gcReminderList = overlay.querySelector(`#${prefix}-greeting-reminders`);
4884
- const gcAddReminder = overlay.querySelector(`#${prefix}-greeting-add-reminder`);
4885
- const gcSave = overlay.querySelector(`#${prefix}-greeting-save`);
4886
- if (!gcSelect || !gcTemplate || !gcReminderList || !gcSave) return;
4887
-
4888
- function getGreetingCfg(key) {
4889
- const gc = configRef() || { defaults: {}, projects: {}, global: {} };
4890
- if (key === 'global') return { ...gc.defaults, ...gc.global };
4891
- return { ...gc.defaults, ...(gc.projects[key] || {}) };
4892
- }
4893
-
4894
- function reminderRowHTML(reminder) {
4895
- return `<div class="cc-greeting-reminder-row">
4896
- <span class="cc-greeting-drag-handle" title="${kit.te('settings.redesign.hooks.dragToReorder')}">&#8942;&#8942;</span>
4897
- <input class="cc-greeting-reminder-input label" placeholder="${kit.te('settings.redesign.hooks.label')}" value="${escapeHtml(reminder.label || '')}">
4898
- <input class="cc-greeting-reminder-input cmd" placeholder="${kit.te('settings.redesign.hooks.command')}" value="${escapeHtml(reminder.command || '')}">
4899
- <button class="cc-greeting-reminder-remove" title="${kit.te('settings.redesign.hooks.remove')}">&times;</button>
4900
- </div>`;
4901
- }
4902
-
4903
- function wireReminderRemoveButtons() {
4904
- gcReminderList.querySelectorAll('.cc-greeting-reminder-remove').forEach((btn) => {
4905
- btn.onclick = () => {
4906
- const row = btn.closest('.cc-greeting-reminder-row');
4907
- if (!row) return;
4908
- row.style.opacity = '0';
4909
- row.style.transition = 'opacity 0.15s';
4910
- setTimeout(() => row.remove(), 150);
4911
- };
4912
- });
4913
- }
4914
-
4915
- function wireReminderDragHandles() {
4916
- gcReminderList.querySelectorAll('.cc-greeting-drag-handle').forEach((handle) => {
4917
- handle.onmousedown = (event) => {
4918
- event.preventDefault();
4919
- const row = handle.closest('.cc-greeting-reminder-row');
4920
- if (!row) return;
4921
- row.style.opacity = '0.5';
4922
- const rows = [...gcReminderList.querySelectorAll('.cc-greeting-reminder-row')];
4923
- const startY = event.clientY;
4924
- const startIdx = rows.indexOf(row);
4925
- const onMove = (moveEvent) => {
4926
- const dy = moveEvent.clientY - startY;
4927
- const rowH = row.offsetHeight + 4;
4928
- const shift = Math.round(dy / rowH);
4929
- const newIdx = Math.max(0, Math.min(rows.length - 1, startIdx + shift));
4930
- if (newIdx !== rows.indexOf(row)) {
4931
- const ref = gcReminderList.children[newIdx];
4932
- if (newIdx > rows.indexOf(row)) gcReminderList.insertBefore(row, ref?.nextSibling || null);
4933
- else gcReminderList.insertBefore(row, ref);
4934
- }
4935
- };
4936
- const onUp = () => {
4937
- row.style.opacity = '';
4938
- document.removeEventListener('mousemove', onMove);
4939
- document.removeEventListener('mouseup', onUp);
4940
- };
4941
- document.addEventListener('mousemove', onMove);
4942
- document.addEventListener('mouseup', onUp);
4943
- };
4944
- });
4945
- }
4946
-
4947
- function populateGreetingFields(key) {
4948
- const cfg = getGreetingCfg(key);
4949
- gcTemplate.value = cfg.greetingTemplate || '';
4950
- if (gcShowReminders) gcShowReminders.checked = !!cfg.showReminders;
4951
- if (gcShowLastSession) gcShowLastSession.checked = !!cfg.showLastSession;
4952
- gcReminderList.innerHTML = (cfg.reminders || []).map((reminder) => reminderRowHTML(reminder)).join('');
4953
- wireReminderRemoveButtons();
4954
- wireReminderDragHandles();
4955
- }
4956
-
4957
- const gcDropdown = overlay.querySelector(`#${prefix}-greeting-project-dropdown`);
4958
- const gcDropdownLabel = overlay.querySelector(`#${prefix}-greeting-project-label`);
4959
- const gcDropdownMenu = overlay.querySelector(`#${prefix}-greeting-project-menu`);
4960
- if (gcDropdown && gcDropdownMenu) {
4961
- gcDropdown.querySelector('.cc-dropdown-trigger')?.addEventListener('click', () => {
4962
- gcDropdown.classList.toggle('open');
4963
- });
4964
- gcDropdownMenu.querySelectorAll('.cc-dropdown-item').forEach((item) => {
4965
- item.addEventListener('click', () => {
4966
- const val = item.dataset.value;
4967
- gcSelect.value = val;
4968
- gcSelect.dispatchEvent(new Event('change'));
4969
- if (gcDropdownLabel) gcDropdownLabel.textContent = item.textContent;
4970
- gcDropdownMenu.querySelectorAll('.cc-dropdown-item').forEach((entry) => entry.classList.remove('active'));
4971
- item.classList.add('active');
4972
- gcDropdown.classList.remove('open');
4973
- populateGreetingFields(val);
4974
- });
4975
- });
4976
- document.addEventListener('click', (event) => {
4977
- if (!gcDropdown.contains(event.target)) gcDropdown.classList.remove('open');
4978
- });
4979
- }
4980
-
4981
- gcAddReminder?.addEventListener('click', () => {
4982
- const tmp = document.createElement('div');
4983
- tmp.innerHTML = reminderRowHTML({ label: '', command: '' });
4984
- const newRow = tmp.firstElementChild;
4985
- gcReminderList.appendChild(newRow);
4986
- wireReminderRemoveButtons();
4987
- wireReminderDragHandles();
4988
- newRow.querySelector('.label')?.focus();
4989
- });
4990
-
4991
- gcSave.addEventListener('click', async () => {
4992
- const project = gcSelect.value || 'global';
4993
- const reminders = [];
4994
- gcReminderList.querySelectorAll('.cc-greeting-reminder-row').forEach((row) => {
4995
- const label = row.querySelector('.label')?.value?.trim() || '';
4996
- const command = row.querySelector('.cmd')?.value?.trim() || '';
4997
- if (label || command) reminders.push({ label, command });
4998
- });
4999
- const body = {
5000
- greetingTemplate: gcTemplate.value || '',
5001
- showReminders: gcShowReminders?.checked ?? false,
5002
- showLastSession: gcShowLastSession?.checked ?? false,
5003
- reminders,
5004
- };
5005
- gcSave.style.opacity = '0.5';
5006
- gcSave.style.pointerEvents = 'none';
5007
- try {
5008
- const res = await fetch(`${saveBaseUrl}/${encodeURIComponent(project)}`, {
5009
- method: 'PUT',
5010
- headers: { 'Content-Type': 'application/json' },
5011
- body: JSON.stringify(body),
5012
- });
5013
- const data = await res.json();
5014
- if (!data.ok) {
5015
- alert(data.error || kit.tx('settings.redesign.hooks.failedToSave'));
5016
- return;
5017
- }
5018
- onSave(project, body);
5019
- showCCToast(kit.tx('settings.redesign.hooks.greetingConfigSaved'));
5020
- } catch (err) {
5021
- alert(kit.tx('settings.redesign.hooks.failed2', { message: err.message }));
5022
- } finally {
5023
- gcSave.style.opacity = '';
5024
- gcSave.style.pointerEvents = '';
5025
- }
5026
- });
5027
-
5028
- wireReminderRemoveButtons();
5029
- wireReminderDragHandles();
5030
-
5031
- const csToggle = overlay.querySelector(`#${prefix}-greeting-cheatsheet-toggle`);
5032
- const csPanel = overlay.querySelector(`#${prefix}-greeting-cheatsheet`);
5033
- if (csToggle && csPanel) {
5034
- const updateCheatsheetPreview = () => {
5035
- const hour = new Date().getHours();
5036
- const timeGreeting = hour >= 5 && hour < 12 ? kit.tx('settings.redesign.hooks.goodMorning') : hour >= 12 && hour < 17 ? kit.tx('settings.redesign.hooks.goodAfternoon') : kit.tx('settings.redesign.hooks.goodEvening');
5037
- const dateStr = new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
5038
- const key = gcSelect.value || 'global';
5039
- const gc = configRef() || {};
5040
- const projCfg = key === 'global' ? gc.global : gc.projects?.[key];
5041
- const label = projCfg?.label || key;
5042
- const name = key === 'global' ? 'global' : key;
5043
-
5044
- overlay.querySelector(`#${prefix}-cs-time`).textContent = timeGreeting;
5045
- overlay.querySelector(`#${prefix}-cs-label`).textContent = label;
5046
- overlay.querySelector(`#${prefix}-cs-name`).textContent = name;
5047
- overlay.querySelector(`#${prefix}-cs-branch`).textContent = kit.tx('settings.redesign.hooks.dev');
5048
- overlay.querySelector(`#${prefix}-cs-date`).textContent = dateStr;
5049
-
5050
- const tpl = gcTemplate.value || '{time_greeting}! Working on **{project_label}** ({branch} branch). {date}.';
5051
- const resolved = tpl
5052
- .replace(/\{time_greeting\}/g, timeGreeting)
5053
- .replace(/\{project_label\}/g, label)
5054
- .replace(/\{project_name\}/g, name)
5055
- .replace(/\{branch\}/g, 'dev')
5056
- .replace(/\{date\}/g, dateStr)
5057
- .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
5058
- .replace(/`(.+?)`/g, '<code>$1</code>');
5059
- overlay.querySelector(`#${prefix}-cs-preview`).innerHTML = resolved;
5060
- };
5061
-
5062
- csToggle.addEventListener('click', () => {
5063
- const open = csPanel.style.display !== 'none';
5064
- csPanel.style.display = open ? 'none' : 'block';
5065
- csToggle.classList.toggle('open', !open);
5066
- if (!open) updateCheatsheetPreview();
5067
- });
5068
- gcSelect.addEventListener('change', () => {
5069
- if (csPanel.style.display !== 'none') updateCheatsheetPreview();
5070
- });
5071
- gcTemplate.addEventListener('input', () => {
5072
- if (csPanel.style.display !== 'none') updateCheatsheetPreview();
5073
- });
5074
- }
5075
- }
5076
-
5077
- wireGreetingEditor({
5078
- prefix: 'cc',
5079
- configRef: () => greetingConfig,
5080
- saveBaseUrl: '/api/greeting/config',
5081
- onSave: (project, body) => {
5082
- const target = project === 'global'
5083
- ? (greetingConfig.global || (greetingConfig.global = {}))
5084
- : (greetingConfig.projects[project] || (greetingConfig.projects[project] = {}));
5085
- Object.assign(target, body);
5086
- },
5087
- });
5088
-
5089
- wireGreetingEditor({
5090
- prefix: 'codex',
5091
- configRef: () => codexGreetingConfig,
5092
- saveBaseUrl: '/api/codex-panel/greeting/config',
5093
- onSave: (project, body) => {
5094
- const target = project === 'global'
5095
- ? (codexGreetingConfig.global || (codexGreetingConfig.global = {}))
5096
- : (codexGreetingConfig.projects[project] || (codexGreetingConfig.projects[project] = {}));
5097
- Object.assign(target, body);
5098
- },
5099
- });
5100
-
5101
- wireGreetingEditor({
5102
- prefix: 'opencode',
5103
- configRef: () => opencodeGreetingConfig,
5104
- saveBaseUrl: '/api/opencode-panel/greeting/config',
5105
- onSave: (project, body) => {
5106
- const target = project === 'global'
5107
- ? (opencodeGreetingConfig.global || (opencodeGreetingConfig.global = {}))
5108
- : (opencodeGreetingConfig.projects[project] || (opencodeGreetingConfig.projects[project] = {}));
5109
- Object.assign(target, body);
5110
- },
5111
- });
5112
-
5113
5097
  // Per-project toggles
5114
5098
  overlay.querySelectorAll('.cc-enable-btn[data-cc-project-toggle]').forEach(btn => {
5115
5099
  btn.addEventListener('click', () => { const idx = btn.dataset.ccProjectToggle; const panel = overlay.querySelector(`.cc-panel[data-cc-idx="${idx}"]`); const projectPath = panel?.dataset.ccPath; if (projectPath) ccToggleHook('project', projectPath, btn, panel); });
@@ -6442,69 +6426,48 @@ export async function openSettingsModal(options = {}) {
6442
6426
 
6443
6427
  }
6444
6428
 
6445
- // ── Tunnel toggle ──
6446
- const tunnelToggle = overlay.querySelector('#cc-tunnel-toggle');
6447
- if (tunnelToggle) {
6448
- tunnelToggle.addEventListener('click', async () => {
6449
- const isOn = tunnelToggle.classList.contains('on');
6450
- const endpoint = isOn ? '/api/tunnel/stop' : '/api/tunnel/start';
6451
- try {
6452
- tunnelToggle.style.opacity = '0.4'; tunnelToggle.style.pointerEvents = 'none';
6453
- const res = await fetch(endpoint, { method: 'POST' }); const data = await res.json();
6454
- if (data.ok) {
6455
- if (isOn) {
6456
- tunnelToggle.classList.remove('on');
6457
- const row = overlay.querySelector('#cc-tunnel-row'); if (row) row.classList.remove('enabled');
6458
- const label = overlay.querySelector('#cc-tunnel-label'); if (label) label.textContent = kit.tx('settings.redesign.hooks.ready');
6459
- const urlEl = overlay.querySelector('#cc-tunnel-url'); if (urlEl) urlEl.textContent = kit.tx('settings.redesign.hooks.exposeMcpToClaudeWebVia');
6460
- } else {
6461
- tunnelToggle.classList.add('on');
6462
- const row = overlay.querySelector('#cc-tunnel-row'); if (row) row.classList.add('enabled');
6463
- const label = overlay.querySelector('#cc-tunnel-label'); if (label) label.textContent = kit.tx('settings.redesign.hooks.starting');
6464
- const urlEl = overlay.querySelector('#cc-tunnel-url'); if (urlEl) urlEl.textContent = kit.tx('settings.redesign.hooks.waitingForTunnelUrl');
6465
- let attempts = 0;
6466
- const poll = setInterval(async () => {
6467
- attempts++;
6468
- try {
6469
- const sr = await fetch('/api/tunnel/status'); const sd = await sr.json();
6470
- if (sd.url) {
6471
- clearInterval(poll);
6472
- if (label) label.textContent = kit.tx('settings.redesign.hooks.running');
6473
- if (urlEl) urlEl.textContent = sd.url;
6474
- let copyBtn = overlay.querySelector('#cc-tunnel-copy-url');
6475
- if (!copyBtn) {
6476
- const wrapper = document.createElement('div'); wrapper.style.cssText = 'margin-top:6px;display:flex;gap:6px;align-items:center';
6477
- wrapper.innerHTML = `<button type="button" class="conn-add-btn" id="cc-tunnel-copy-url" ${kit.inv('AUT050')} style="margin:0;flex:1;font-size:12px;padding:6px 10px"><svg viewBox="0 0 24 24" style="width:12px;height:12px"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> ${kit.te('settings.redesign.hooks.copyMcpUrl')}</button>`;
6478
- row.parentNode.insertBefore(wrapper, row.nextSibling);
6479
- copyBtn = wrapper.querySelector('#cc-tunnel-copy-url');
6480
- copyBtn.addEventListener('click', () => {
6481
- const tunnelMcpUrl = (urlEl?.textContent || '') + '/mcp';
6482
- navigator.clipboard.writeText(tunnelMcpUrl);
6483
- copyBtn.innerHTML = '<svg viewBox="0 0 24 24" style="width:12px;height:12px"><polyline points="20 6 9 17 4 12"/></svg> ' + kit.te('settings.redesign.hooks.copied') + '';
6484
- setTimeout(() => { copyBtn.innerHTML = '<svg viewBox="0 0 24 24" style="width:12px;height:12px"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> ' + kit.te('settings.redesign.hooks.copyMcpUrl') + ''; }, 2000);
6485
- });
6486
- }
6487
- } else if (attempts >= 30) { clearInterval(poll); if (label) label.textContent = kit.tx('settings.redesign.hooks.timeout'); if (urlEl) urlEl.textContent = kit.tx('settings.redesign.hooks.failedToGetTunnelUrlCheck'); }
6488
- } catch {}
6489
- }, 1000);
6490
- }
6491
- } else { alert(data.error || kit.tx('settings.redesign.hooks.failed')); }
6492
- } catch (err) { alert(kit.tx('settings.redesign.hooks.failed2', { message: err.message })); }
6493
- finally { tunnelToggle.style.opacity = ''; tunnelToggle.style.pointerEvents = ''; }
6494
- });
6495
- }
6496
-
6497
- // Copy tunnel MCP URL
6498
- const tunnelCopy = overlay.querySelector('#cc-tunnel-copy-url');
6499
- if (tunnelCopy) {
6500
- tunnelCopy.addEventListener('click', () => {
6501
- const urlEl = overlay.querySelector('#cc-tunnel-url'); const baseUrl = urlEl?.textContent || '';
6502
- if (!baseUrl) { alert(kit.tx('settings.redesign.hooks.tunnelUrlNotAvailable')); return; }
6503
- const tunnelMcpUrl = mcpKeyInfo.key ? baseUrl + '/mcp/' + mcpKeyInfo.key : baseUrl + '/mcp';
6504
- navigator.clipboard.writeText(tunnelMcpUrl);
6505
- tunnelCopy.innerHTML = '<svg viewBox="0 0 24 24" style="width:12px;height:12px"><polyline points="20 6 9 17 4 12"/></svg> ' + kit.te('settings.redesign.hooks.copied') + '';
6506
- setTimeout(() => { tunnelCopy.innerHTML = '<svg viewBox="0 0 24 24" style="width:12px;height:12px"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg> ' + kit.te('settings.redesign.hooks.copyMcpUrl') + ''; }, 2000);
6507
- });
6429
+ // ── Public address (the Cloudflare tunnel) ──
6430
+ // One block, drawn again whenever the server says something new: ui-tunnel.js asks again by itself while the
6431
+ // tunnel program is being downloaded or a start waits for its address, for as long as this dialog is open.
6432
+ // A start on a machine that has no cloudflared fetches it and goes on by itself: one press.
6433
+ let redrawTunnelBox = () => {};
6434
+ let latestTunnelStatus = () => tunnelStatus;
6435
+ const tunnelRow = overlay.querySelector('#cc-tunnel-row');
6436
+ const tunnelExtra = overlay.querySelector('#cc-tunnel-extra');
6437
+ if (tunnelRow && tunnelExtra) {
6438
+ const drawTunnel = (view) => {
6439
+ tunnelRow.classList.toggle('enabled', view.on);
6440
+ drawInto(tunnelRow, tunnelRowHtml(view));
6441
+ drawInto(tunnelExtra, tunnelExtraHtml(view, mcpKeyInfo));
6442
+ };
6443
+ const tunnelCard = createTunnelCard({ alive: () => tunnelRow.isConnected, onChange: drawTunnel });
6444
+ latestTunnelStatus = () => tunnelCard.status() || tunnelStatus;
6445
+ redrawTunnelBox = () => drawTunnel(tunnelCard.view());
6446
+ tunnelCard.show(tunnelStatus);
6447
+ const onTunnelClick = async (event) => {
6448
+ const button = event.target.closest('button');
6449
+ if (!button || !(tunnelRow.contains(button) || tunnelExtra.contains(button))) return;
6450
+ if (button.id === 'cc-tunnel-toggle' || button.id === 'cc-tunnel-retry') {
6451
+ const action = tunnelCard.view().action;
6452
+ if (action === 'start' || action === 'retry') {
6453
+ // Shown at once; the server answers immediately too, and then says what it is doing.
6454
+ tunnelCard.show({ ...(tunnelCard.status() || {}), state: 'starting', starting: true, url: null, error: null });
6455
+ await tunnelCard.start();
6456
+ } else if (action === 'stop' || action === 'cancel') {
6457
+ await tunnelCard.stop();
6458
+ }
6459
+ } else if (button.id === 'cc-tunnel-copy-url') {
6460
+ // The remote MCP address is the public one with the access key in it: there is none without a key.
6461
+ const address = remoteMcpAddress(tunnelCard.view().url, mcpKeyInfo?.key);
6462
+ if (!address) { redrawTunnelBox(); return; }
6463
+ navigator.clipboard.writeText(address);
6464
+ const before = button.innerHTML;
6465
+ button.innerHTML = '<svg viewBox="0 0 24 24" style="width:12px;height:12px"><polyline points="20 6 9 17 4 12"/></svg> ' + kit.te('settings.redesign.hooks.copied') + '';
6466
+ setTimeout(() => { if (button.isConnected) button.innerHTML = before; }, 2000);
6467
+ }
6468
+ };
6469
+ tunnelRow.addEventListener('click', onTunnelClick);
6470
+ tunnelExtra.addEventListener('click', onTunnelClick);
6508
6471
  }
6509
6472
 
6510
6473
  // ── API key generate / copy / revoke ──
@@ -6589,6 +6552,7 @@ export async function openSettingsModal(options = {}) {
6589
6552
  if (statusEl) statusEl.textContent = '***' + data.key.slice(-8);
6590
6553
  if (row) row.classList.add('enabled');
6591
6554
  apikeyGenBtn.textContent = kit.tx('settings.redesign.hooks.regenerate');
6555
+ redrawTunnelBox();
6592
6556
  } else { alert(data.error || kit.tx('settings.redesign.hooks.failedToGenerateKey')); }
6593
6557
  } catch (err) { alert(kit.tx('settings.redesign.hooks.failed2', { message: err.message })); }
6594
6558
  finally { apikeyGenBtn.style.opacity = ''; apikeyGenBtn.style.pointerEvents = ''; }
@@ -6606,15 +6570,18 @@ export async function openSettingsModal(options = {}) {
6606
6570
  const apikeyRevokeBtn = overlay.querySelector('#cc-apikey-revoke');
6607
6571
  if (apikeyRevokeBtn) {
6608
6572
  apikeyRevokeBtn.addEventListener('click', async () => {
6609
- if (!confirm(kit.tx('settings.redesign.hooks.revokeThisApiKeyTheMcp'))) return;
6573
+ // What a revoke leaves behind depends on the server that runs: see closesKeylessMcp.
6574
+ const closed = closesKeylessMcp(latestTunnelStatus());
6575
+ if (!confirm(kit.tx(closed ? 'settings.redesign.hooks.revokeKeyClosed' : 'settings.redesign.hooks.revokeThisApiKeyTheMcp'))) return;
6610
6576
  try {
6611
6577
  const res = await fetch('/api/mcp-key', { method: 'DELETE' }); const data = await res.json();
6612
6578
  if (data.ok) {
6613
6579
  mcpKeyInfo = { hasKey: false };
6614
6580
  const reveal = overlay.querySelector('#cc-apikey-reveal'); if (reveal) reveal.style.display = 'none';
6615
- const statusEl = overlay.querySelector('#cc-apikey-status'); if (statusEl) statusEl.textContent = kit.tx('settings.redesign.hooks.noKeyConfiguredTunnelIsOpen');
6581
+ const statusEl = overlay.querySelector('#cc-apikey-status'); if (statusEl) statusEl.textContent = kit.tx(closed ? 'settings.redesign.hooks.apikey.noneClosed' : 'settings.redesign.hooks.apikey.none');
6616
6582
  const row = overlay.querySelector('#cc-apikey-row'); if (row) row.classList.remove('enabled');
6617
6583
  const genBtn = overlay.querySelector('#cc-apikey-generate'); if (genBtn) genBtn.textContent = kit.tx('settings.redesign.hooks.generateKey');
6584
+ redrawTunnelBox();
6618
6585
  }
6619
6586
  } catch (err) { alert(kit.tx('settings.redesign.hooks.failed2', { message: err.message })); }
6620
6587
  });