@symerian/symi 3.4.30 → 3.4.31

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 (177) hide show
  1. package/dist/{agent-O3PihJOY.js → agent-ZFfYdexc.js} +10 -10
  2. package/dist/{agents-C2MpIr16.js → agents-C8naPegc.js} +5 -5
  3. package/dist/{audit-yf3H4Y1h.js → audit-DOfexJi4.js} +8 -8
  4. package/dist/{auth-choice-CyBcsQ9T.js → auth-choice-DxH-c3vK.js} +4 -4
  5. package/dist/{banner-D7__HlM-.js → banner-D7zYLyA8.js} +1 -1
  6. package/dist/{browser-cli-B_6WWHm_.js → browser-cli-MYIql1Ic.js} +3 -3
  7. package/dist/build-info.json +1 -1
  8. package/dist/bundled/boot-md/handler.js +4 -4
  9. package/dist/bundled/session-memory/handler.js +4 -4
  10. package/dist/{call-IfleM0ap.js → call-D9U2Sfhp.js} +1 -1
  11. package/dist/{channel-options-B5974x-7.js → channel-options-9PzSNQLG.js} +1 -1
  12. package/dist/{channels-cli-C6Nv9mzy.js → channels-cli-C3XnNkE5.js} +30 -30
  13. package/dist/{chrome-DT1fIVG1.js → chrome-B4P7ycw5.js} +7 -7
  14. package/dist/{chrome-DJCkCRLf.js → chrome-ClVIwINy.js} +1 -1
  15. package/dist/{cli-DZoms4qg.js → cli-DhTdzhjk.js} +26 -26
  16. package/dist/{command-registry--D98SJ6k.js → command-registry-BOwku-e8.js} +11 -11
  17. package/dist/{completion-cli-CHYvpIPw.js → completion-cli-KZYbGY4H.js} +2 -2
  18. package/dist/{config-BTSBEAnk.js → config-Byd2Y9rr.js} +5 -5
  19. package/dist/{config-cli-DstpcB33.js → config-cli-CJ1CRfG8.js} +5 -5
  20. package/dist/{config-guard-CCJrDmON.js → config-guard-zt0zhFHl.js} +2 -2
  21. package/dist/{config-validation-YCrMlM9Z.js → config-validation-CRazk6Kd.js} +1 -1
  22. package/dist/{configure-B-UvSZZ7.js → configure-CFY_XEps.js} +10 -10
  23. package/dist/{control-service-CwRG4M_O.js → control-service-DN6-IdFk.js} +4 -4
  24. package/dist/{cron-cli-DUETe3Of.js → cron-cli-fmiz43Pz.js} +3 -3
  25. package/dist/{daemon-cli-Dy5eY4M7.js → daemon-cli-BilAL40g.js} +6 -6
  26. package/dist/{daemon-runtime-ChztAKDA.js → daemon-runtime-D4KHzQq2.js} +1 -1
  27. package/dist/{deliver-59sRVaYQ.js → deliver-BkCYBlzi.js} +4 -4
  28. package/dist/{deliver-BHmK4isn.js → deliver-CnsfN7km.js} +1 -1
  29. package/dist/{devices-cli-BCZ2HHjT.js → devices-cli-D7HdOwRI.js} +2 -2
  30. package/dist/{directory-cli-BVGd0l0T.js → directory-cli-ed6QacBC.js} +1 -1
  31. package/dist/{dns-cli-DH-ksmiI.js → dns-cli-RPa1dGTQ.js} +1 -1
  32. package/dist/{docs-cli-BuX8FDdh.js → docs-cli-ZTdS_Gak.js} +2 -2
  33. package/dist/{doctor-completion-C4Sf8CFV.js → doctor-completion-DpGX8dau.js} +1 -1
  34. package/dist/{doctor-config-flow-BhmF6EAq.js → doctor-config-flow-CIrG_yW_.js} +4 -4
  35. package/dist/entry.js +3 -3
  36. package/dist/{exec-approvals-cli-CyXTXgft.js → exec-approvals-cli-65pcsa1y.js} +6 -6
  37. package/dist/{frontmatter-BH3ExkUY.js → frontmatter-B4levtVg.js} +2 -2
  38. package/dist/{gateway-cli-fT43bqlv.js → gateway-cli-CTi7Wl2k.js} +50 -50
  39. package/dist/{gateway-rpc-BEqCo2JK.js → gateway-rpc-2VPLhMhx.js} +1 -1
  40. package/dist/{glass-ui-ws-BfMKuf9v.js → glass-ui-ws-Bah_A-jn.js} +38 -38
  41. package/dist/{gmail-setup-utils-Cz9Tb30q.js → gmail-setup-utils-C9WBC6Cd.js} +1 -1
  42. package/dist/{health-DDxrkioj.js → health-B5rRzhyV.js} +3 -3
  43. package/dist/{hooks-cli-Btgk__9z.js → hooks-cli-CSDCGO-W.js} +29 -29
  44. package/dist/{hooks-status-BaV4_YaJ.js → hooks-status-OorbLcc2.js} +2 -2
  45. package/dist/index.js +0 -0
  46. package/dist/{lifecycle-core-B0tG8ERp.js → lifecycle-core-BRc-XqTk.js} +2 -2
  47. package/dist/llm-slug-generator.js +4 -4
  48. package/dist/{logs-cli-CUhZrDw7.js → logs-cli-D5phVUOT.js} +3 -3
  49. package/dist/{manager-BeNnzSJv.js → manager-BfhHvsI9.js} +1 -1
  50. package/dist/{manager-Di9qtuZF.js → manager-DzBH9uQG.js} +1 -1
  51. package/dist/{memory-BDeydRz3.js → memory-BCj0cj5v.js} +3 -3
  52. package/dist/{memory-cli-D4hZiGr5.js → memory-cli-9WUMBHol.js} +3 -3
  53. package/dist/{model-catalog-DPnCNs5N.js → model-catalog-CD2kTz4V.js} +2 -2
  54. package/dist/{model-picker-DuPommMc.js → model-picker-CVd5UuXV.js} +2 -2
  55. package/dist/{models-cli-CL5K06Wu.js → models-cli-rd4eX_6P.js} +30 -30
  56. package/dist/{models-config-CPlvO1b0.js → models-config-oxaGiRUT.js} +1 -1
  57. package/dist/{models-B0wNKA18.js → models-sbKIrJBG.js} +9 -9
  58. package/dist/{node-cli-CWWMajKF.js → node-cli-ClLXSdFL.js} +9 -9
  59. package/dist/{nodes-cli-YmE7S-JB.js → nodes-cli-DY38p5In.js} +3 -3
  60. package/dist/{onboard-HqUZVKyD.js → onboard-BAyp_UFm.js} +7 -7
  61. package/dist/{onboard-channels-BbF5-N3g.js → onboard-channels-CUpkV_IJ.js} +1 -1
  62. package/dist/{onboard-custom-63SI3jrk.js → onboard-custom-BMgLK-vo.js} +2 -2
  63. package/dist/{onboard-helpers-Cb3WFLl9.js → onboard-helpers-DIQjpQ-s.js} +2 -2
  64. package/dist/{onboard-hooks-DXXg3YH9.js → onboard-hooks-KZKmj5Dk.js} +3 -3
  65. package/dist/{onboard-remote-CqtCrqtm.js → onboard-remote-C-_NUgBl.js} +1 -1
  66. package/dist/{onboard-skills-CS3l5fhH.js → onboard-skills-CwvicFTi.js} +3 -3
  67. package/dist/{onboarding-Be_HJIFA.js → onboarding-BxCNNo75.js} +11 -11
  68. package/dist/{onboarding.finalize-dsCUTKCQ.js → onboarding.finalize-DgMPXTtO.js} +18 -18
  69. package/dist/{onboarding.gateway-config-Ctd1_QW4.js → onboarding.gateway-config-BAaWvPSX.js} +4 -4
  70. package/dist/{outbound-send-deps-DPsU_Xdy.js → outbound-send-deps-qhFaNYAY.js} +1 -1
  71. package/dist/{pairing-cli-PtlNv7Gr.js → pairing-cli-CFkiSx8t.js} +1 -1
  72. package/dist/{pi-embedded-helpers-CVKIHnFv.js → pi-embedded-helpers-CRG5LMS7.js} +1 -1
  73. package/dist/{pi-tools.policy-8_wTu7v2.js → pi-tools.policy-De6xHjVL.js} +2 -2
  74. package/dist/{plugin-registry-BG51uZOA.js → plugin-registry-BNe5jtPK.js} +2 -2
  75. package/dist/plugin-sdk/index.js +6 -6
  76. package/dist/{plugins-cli-B-zVOiQN.js → plugins-cli-CWj33_Hj.js} +27 -27
  77. package/dist/{program-BOh0XGsA.js → program-Z2OLlhsB.js} +33 -33
  78. package/dist/{prompt-select-styled-Bj-hrPCx.js → prompt-select-styled-BNwbBBcH.js} +16 -16
  79. package/dist/{provider-auth-helpers-BK6NFk2Q.js → provider-auth-helpers-5qb15Hje.js} +2 -2
  80. package/dist/{push-apns-CMxk3Hxf.js → push-apns-D6qV5P4h.js} +1 -1
  81. package/dist/{pw-ai-C5MJKzUM.js → pw-ai-BsEf8C15.js} +1 -1
  82. package/dist/{pw-ai-CE2TBGif.js → pw-ai-C7mvVllB.js} +2 -2
  83. package/dist/{qr-cli-BeHlC33g.js → qr-cli-oENcXq8d.js} +1 -1
  84. package/dist/{redact-identifier-B1VHIbnd.js → redact-identifier-ZE8OIUof.js} +1 -1
  85. package/dist/{register.agent-COv5OPLQ.js → register.agent-DpiHPMca.js} +36 -36
  86. package/dist/{register.configure-RhKVlfWH.js → register.configure-DRFaroYF.js} +40 -40
  87. package/dist/{register.maintenance-BAK-DiQY.js → register.maintenance-BGNNTt_1.js} +38 -38
  88. package/dist/{register.message-CCBmn5ja.js → register.message-R4AIHhlU.js} +28 -28
  89. package/dist/{register.onboard-B3jO_Goy.js → register.onboard-Cl-_Suyv.js} +13 -13
  90. package/dist/{register.setup-CYSISTDO.js → register.setup-Dt98v2V3.js} +15 -15
  91. package/dist/{register.status-health-sessions-CGbg9k6d.js → register.status-health-sessions-DiX5lVyr.js} +23 -23
  92. package/dist/{register.subclis-loifbhVx.js → register.subclis-DVObuR_P.js} +28 -28
  93. package/dist/{replies-Dj640LxQ.js → replies-VTJu1jUK.js} +1 -1
  94. package/dist/{routes-B5y-oAKp.js → routes-DOFg6X5M.js} +3 -3
  95. package/dist/{rpc-qWm1bRo5.js → rpc-BIHKXdlN.js} +1 -1
  96. package/dist/{run-main-B7Ria3zD.js → run-main-NAgYKzSu.js} +44 -44
  97. package/dist/{sandbox-BJgHc4Vu.js → sandbox-EB7cFSML.js} +6 -6
  98. package/dist/{sandbox-cli-CSudrHYL.js → sandbox-cli-D4dy6sUK.js} +8 -8
  99. package/dist/{security-cli-B2lCR2xX.js → security-cli-DwCKOrFm.js} +11 -11
  100. package/dist/{send-CY9na-Jw.js → send-CkRtuQbr.js} +1 -1
  101. package/dist/{server-context-DdWWaDf6.js → server-context-ww1jytet.js} +5 -5
  102. package/dist/{server-methods-Dk_EnINI.js → server-methods-BDiZyPvS.js} +23 -23
  103. package/dist/{server-node-events-BHegSgG9.js → server-node-events-9cY40B4m.js} +29 -29
  104. package/dist/{session-utils-CAVPV2Hh.js → session-utils-CStjqGE6.js} +3 -3
  105. package/dist/{sessions-BDM9kNY0.js → sessions-BSRHJH8T.js} +3 -3
  106. package/dist/{sessions-CqLQkkWg.js → sessions-CInbrqsF.js} +1 -1
  107. package/dist/{shared-CpwtQRJB.js → shared-DEAo94kI.js} +1 -1
  108. package/dist/{skill-commands-CRQniaHy.js → skill-commands-BkKfrbHt.js} +2 -2
  109. package/dist/{skills-CTcjFd_F.js → skills-CFLijAZ-.js} +1 -1
  110. package/dist/{skills-cli-9Mlb7VRg.js → skills-cli-BbagnO6p.js} +5 -5
  111. package/dist/{skills-install-D5ikt72d.js → skills-install-dLRO1KGp.js} +2 -2
  112. package/dist/{skills-remote-BASG1Doo.js → skills-remote-DQVB-3Pj.js} +1 -1
  113. package/dist/{skills-status-1iVfuVIG.js → skills-status-HWybfj0K.js} +2 -2
  114. package/dist/{status-Bci6O3aA.js → status-BSrVEUoJ.js} +2 -2
  115. package/dist/{status-D5RhVO0A.js → status-FIp7UKru.js} +12 -12
  116. package/dist/{status.update-D5CachQ_.js → status.update-Cm8Fm8QD.js} +1 -1
  117. package/dist/{subagent-registry-CsXaK_sy.js → subagent-registry-DqryocLT.js} +31 -31
  118. package/dist/{synthesis-dplLmFIP.js → synthesis-CS-lrEGy.js} +4 -4
  119. package/dist/{synthesis-BoZf8BeO.js → synthesis-DKhH0YwU.js} +26 -26
  120. package/dist/{system-cli-D5FffFX5.js → system-cli-DYSI6cIB.js} +3 -3
  121. package/dist/{systemd-hints-CufcSfMk.js → systemd-hints-EIfW5y8K.js} +1 -1
  122. package/dist/{tui-DZ8gksSZ.js → tui-C20rHOeB.js} +4 -4
  123. package/dist/{tui-cli-CyBJYh9u.js → tui-cli-JwlzHCza.js} +11 -11
  124. package/dist/{unified-runner-BFLLzEHE.js → unified-runner-AM9tFC5q.js} +10 -10
  125. package/dist/{update-cli-yW6Xl6fs.js → update-cli-BYfA3RPc.js} +41 -41
  126. package/dist/{update-runner-CIgLduhH.js → update-runner-CBG8p9iG.js} +1 -1
  127. package/dist/{webhooks-cli-4bmyKzwb.js → webhooks-cli-MuG_QGLp.js} +4 -4
  128. package/dist/{with-timeout-bWr6yBeX.js → with-timeout-BvxaeAo6.js} +1 -1
  129. package/dist/{workspace-DxscDsm6.js → workspace-D0d7Gi4-.js} +1 -1
  130. package/extensions/memory-core/node_modules/.bin/symi +0 -0
  131. package/extensions/msteams/node_modules/.bin/symi +0 -0
  132. package/extensions/outlook/node_modules/.bin/symi +0 -0
  133. package/extensions/slack/node_modules/.bin/symi +0 -0
  134. package/package.json +108 -79
  135. package/dist/control-ui/css/revert-red-theme.md +0 -141
  136. package/dist/control-ui/css/style.css +0 -5843
  137. package/dist/control-ui/css/style.css.backup-2026-03-03-162525 +0 -3546
  138. package/dist/control-ui/css/style.css.backup-before-red-2026-03-03-162525 +0 -3546
  139. package/dist/control-ui/css/style.css.backup-before-red-theme-2026-03-03-162530 +0 -3546
  140. package/dist/control-ui/css/style.css.pre-2row +0 -2165
  141. package/dist/control-ui/css/style.css.pre-brand +0 -1776
  142. package/dist/control-ui/css/style.css.pre-history +0 -1974
  143. package/dist/control-ui/css/style.css.pre-nav +0 -2264
  144. package/dist/control-ui/css/style.css.pre-newsession +0 -1898
  145. package/dist/control-ui/css/style.css.pre-queue +0 -2195
  146. package/dist/control-ui/css/style.css.pre-red-prompt +0 -2524
  147. package/dist/control-ui/css/style.css.pre-stop +0 -2239
  148. package/dist/control-ui/css/style.css.pre-textarea +0 -2184
  149. package/dist/control-ui/css/style.css.pre-watchdog +0 -1848
  150. package/dist/control-ui/css/style.css.red-theme +0 -2999
  151. package/dist/control-ui/index.html +0 -1042
  152. package/dist/control-ui/js/app.js +0 -1304
  153. package/dist/control-ui/js/app.js.pre-2row +0 -463
  154. package/dist/control-ui/js/app.js.pre-heartbeat-filter +0 -595
  155. package/dist/control-ui/js/app.js.pre-newsession +0 -408
  156. package/dist/control-ui/js/app.js.pre-queue +0 -476
  157. package/dist/control-ui/js/app.js.pre-stop +0 -564
  158. package/dist/control-ui/js/app.js.pre-textarea +0 -467
  159. package/dist/control-ui/js/app.js.pre-watchdog +0 -293
  160. package/dist/control-ui/js/connections.js +0 -438
  161. package/dist/control-ui/js/gateway.js +0 -233
  162. package/dist/control-ui/js/gateway.js.pre-stop +0 -110
  163. package/dist/control-ui/js/history.js +0 -732
  164. package/dist/control-ui/js/logs.js +0 -238
  165. package/dist/control-ui/js/menu.js +0 -230
  166. package/dist/control-ui/js/menu.js.pre-nav +0 -66
  167. package/dist/control-ui/js/metrics.js +0 -53
  168. package/dist/control-ui/js/models.js +0 -138
  169. package/dist/control-ui/js/render.js +0 -882
  170. package/dist/control-ui/js/render.test.js +0 -112
  171. package/dist/control-ui/js/scheduling.js +0 -461
  172. package/dist/control-ui/js/settings.js +0 -909
  173. package/dist/control-ui/js/slash-autocomplete.js +0 -168
  174. package/dist/control-ui/js/subagents.js +0 -560
  175. package/dist/control-ui/js/utils.js +0 -29
  176. package/dist/control-ui/vendor/highlight.min.js +0 -2518
  177. package/dist/control-ui/vendor/marked.min.js +0 -69
@@ -1,909 +0,0 @@
1
- // ── Native Settings Panels ────────────────────────────────────────────
2
- // Renders Config, Debug, and Logs natively inside the page overlay.
3
- // No iframe, no SPA proxy — uses gateway RPC over WebSocket.
4
-
5
- // ── Shared helpers ────────────────────────────────────────────────────
6
-
7
- function escapeSettingsHtml(s) {
8
- if (!s) {
9
- return "";
10
- }
11
- return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
12
- }
13
-
14
- /** Create or get the native content container inside the page overlay. */
15
- function getSettingsContainer() {
16
- let el = document.getElementById("native-settings-container");
17
- if (!el) {
18
- const overlay = document.querySelector("body > .page-overlay");
19
- if (!overlay) {
20
- return null;
21
- }
22
- el = document.createElement("div");
23
- el.id = "native-settings-container";
24
- overlay.appendChild(el);
25
- }
26
- return el;
27
- }
28
-
29
- /** Show native container, hide iframe. */
30
- function showSettingsContainer() {
31
- const container = getSettingsContainer();
32
- const frame = document.getElementById("page-overlay-frame");
33
- if (frame) {
34
- frame.style.display = "none";
35
- }
36
- if (container) {
37
- container.style.display = "flex";
38
- }
39
- return container;
40
- }
41
-
42
- /** Hide native container, restore iframe. */
43
- function hideSettingsContainer() {
44
- const container = document.getElementById("native-settings-container");
45
- if (container) {
46
- container.style.display = "none";
47
- container.innerHTML = "";
48
- }
49
- const frame = document.getElementById("page-overlay-frame");
50
- if (frame) {
51
- frame.style.display = "";
52
- }
53
- }
54
-
55
- // ══════════════════════════════════════════════════════════════════════
56
- // CONFIG PANEL
57
- // ══════════════════════════════════════════════════════════════════════
58
-
59
- let configData = null;
60
- let configBaseHash = null;
61
- let configEditMode = false;
62
-
63
- async function fetchConfig() {
64
- if (!window.gateway?.connected) {
65
- return null;
66
- }
67
- try {
68
- const result = await window.gateway.rpc("config.get", {});
69
- return result;
70
- } catch (err) {
71
- console.error("[settings:config] fetch error:", err);
72
- return null;
73
- }
74
- }
75
-
76
- function renderConfigValue(key, value, depth) {
77
- const indent = depth * 1;
78
- if (value === null || value === undefined) {
79
- return `<span class="cfg-null">null</span>`;
80
- }
81
- if (typeof value === "boolean") {
82
- return `<span class="cfg-bool">${value}</span>`;
83
- }
84
- if (typeof value === "number") {
85
- return `<span class="cfg-num">${value}</span>`;
86
- }
87
- if (typeof value === "string") {
88
- if (value.length > 120) {
89
- return `<span class="cfg-str">"${escapeSettingsHtml(value.slice(0, 120))}..."</span>`;
90
- }
91
- return `<span class="cfg-str">"${escapeSettingsHtml(value)}"</span>`;
92
- }
93
- if (Array.isArray(value)) {
94
- if (value.length === 0) {
95
- return `<span class="cfg-bracket">[]</span>`;
96
- }
97
- const items = value
98
- .map(
99
- (v, i) =>
100
- `<div class="cfg-row" style="padding-left:${indent + 1}em">${renderConfigValue(i, v, depth + 1)}</div>`,
101
- )
102
- .join("");
103
- return `<span class="cfg-bracket">[</span>${items}<div style="padding-left:${indent}em"><span class="cfg-bracket">]</span></div>`;
104
- }
105
- if (typeof value === "object") {
106
- const entries = Object.entries(value);
107
- if (entries.length === 0) {
108
- return `<span class="cfg-bracket">{}</span>`;
109
- }
110
- const rows = entries
111
- .map(
112
- ([k, v]) =>
113
- `<div class="cfg-row" style="padding-left:${indent + 1}em"><span class="cfg-key">${escapeSettingsHtml(k)}</span><span class="cfg-colon">: </span>${renderConfigValue(k, v, depth + 1)}</div>`,
114
- )
115
- .join("");
116
- return `<span class="cfg-bracket">{</span>${rows}<div style="padding-left:${indent}em"><span class="cfg-bracket">}</span></div>`;
117
- }
118
- return escapeSettingsHtml(String(value));
119
- }
120
-
121
- function renderConfigSection(key, value, isRedacted) {
122
- const isExpanded = typeof value === "object" && value !== null;
123
- const icon = isExpanded ? "&#9662;" : "&#9656;";
124
- const redactedBadge = isRedacted ? '<span class="cfg-redacted">REDACTED</span>' : "";
125
-
126
- return `
127
- <div class="settings-section" data-key="${escapeSettingsHtml(key)}">
128
- <div class="settings-section-header" onclick="this.parentElement.classList.toggle('collapsed')">
129
- <span class="settings-section-arrow">${icon}</span>
130
- <span class="settings-section-key">${escapeSettingsHtml(key)}</span>
131
- ${redactedBadge}
132
- </div>
133
- <div class="settings-section-body">
134
- ${renderConfigValue(key, value, 0)}
135
- </div>
136
- </div>
137
- `;
138
- }
139
-
140
- async function openConfigPanel() {
141
- const container = showSettingsContainer();
142
- if (!container) {
143
- return;
144
- }
145
-
146
- container.innerHTML = `
147
- <div class="settings-panel">
148
- <div class="settings-toolbar">
149
- <div class="settings-toolbar-left">
150
- <span class="settings-toolbar-label">SYSTEM CONFIGURATION</span>
151
- </div>
152
- <div class="settings-toolbar-right">
153
- <button class="settings-btn" id="config-refresh-btn" title="Refresh">Refresh</button>
154
- <button class="settings-btn settings-btn--primary" id="config-edit-btn" title="Edit raw JSON">Edit</button>
155
- </div>
156
- </div>
157
- <div class="settings-content" id="config-content">
158
- <div class="settings-loading">Loading configuration...</div>
159
- </div>
160
- </div>
161
- `;
162
-
163
- // Wire refresh
164
- document
165
- .getElementById("config-refresh-btn")
166
- .addEventListener("click", () => void loadConfigView());
167
- document.getElementById("config-edit-btn").addEventListener("click", () => toggleConfigEditor());
168
-
169
- await loadConfigView();
170
- }
171
-
172
- async function loadConfigView() {
173
- const content = document.getElementById("config-content");
174
- if (!content) {
175
- return;
176
- }
177
-
178
- content.innerHTML = '<div class="settings-loading">Loading...</div>';
179
- const result = await fetchConfig();
180
-
181
- if (!result || !result.config) {
182
- content.innerHTML =
183
- '<div class="settings-empty">Unable to load configuration. Gateway may not be connected.</div>';
184
- return;
185
- }
186
-
187
- configData = result.config;
188
- configBaseHash = result.hash;
189
- configEditMode = false;
190
-
191
- // Render tree view
192
- const redacted = new Set(result.redactedPaths || []);
193
- const sections = Object.entries(configData);
194
-
195
- if (sections.length === 0) {
196
- content.innerHTML = '<div class="settings-empty">Configuration is empty.</div>';
197
- return;
198
- }
199
-
200
- content.innerHTML = sections
201
- .map(([key, value]) => {
202
- const isRedacted = redacted.has(key);
203
- return renderConfigSection(key, value, isRedacted);
204
- })
205
- .join("");
206
- }
207
-
208
- function toggleConfigEditor() {
209
- const content = document.getElementById("config-content");
210
- const editBtn = document.getElementById("config-edit-btn");
211
- if (!content) {
212
- return;
213
- }
214
-
215
- if (configEditMode) {
216
- // Switch back to tree view
217
- configEditMode = false;
218
- editBtn.textContent = "Edit";
219
- editBtn.classList.remove("settings-btn--active");
220
- void loadConfigView();
221
- return;
222
- }
223
-
224
- // Switch to editor
225
- configEditMode = true;
226
- editBtn.textContent = "View";
227
- editBtn.classList.add("settings-btn--active");
228
-
229
- const json = JSON.stringify(configData, null, 2);
230
- content.innerHTML = `
231
- <div class="config-editor-wrap">
232
- <textarea class="config-editor" id="config-editor-textarea" spellcheck="false">${escapeSettingsHtml(json)}</textarea>
233
- <div class="config-editor-actions">
234
- <span class="config-editor-hint">Edit the JSON above, then save to apply changes.</span>
235
- <button class="settings-btn settings-btn--danger" id="config-save-btn">Save & Apply</button>
236
- </div>
237
- </div>
238
- `;
239
-
240
- document.getElementById("config-save-btn").addEventListener("click", saveConfig);
241
- }
242
-
243
- async function saveConfig() {
244
- const textarea = document.getElementById("config-editor-textarea");
245
- const saveBtn = document.getElementById("config-save-btn");
246
- if (!textarea || !saveBtn) {
247
- return;
248
- }
249
-
250
- let parsed;
251
- try {
252
- parsed = JSON.parse(textarea.value);
253
- } catch (err) {
254
- alert("Invalid JSON: " + err.message);
255
- return;
256
- }
257
-
258
- saveBtn.textContent = "Saving...";
259
- saveBtn.disabled = true;
260
-
261
- try {
262
- await window.gateway.rpc("config.apply", {
263
- raw: parsed,
264
- baseHash: configBaseHash,
265
- });
266
- saveBtn.textContent = "Saved!";
267
- setTimeout(() => {
268
- configEditMode = false;
269
- const editBtn = document.getElementById("config-edit-btn");
270
- if (editBtn) {
271
- editBtn.textContent = "Edit";
272
- editBtn.classList.remove("settings-btn--active");
273
- }
274
- void loadConfigView();
275
- }, 800);
276
- } catch (err) {
277
- saveBtn.textContent = "Save & Apply";
278
- saveBtn.disabled = false;
279
- alert("Save failed: " + err.message);
280
- }
281
- }
282
-
283
- // ══════════════════════════════════════════════════════════════════════
284
- // DEBUG PANEL
285
- // ══════════════════════════════════════════════════════════════════════
286
-
287
- let debugEntries = [];
288
- let debugPaused = false;
289
- const DEBUG_MAX = 500;
290
-
291
- function openDebugPanel() {
292
- const container = showSettingsContainer();
293
- if (!container) {
294
- return;
295
- }
296
-
297
- container.innerHTML = `
298
- <div class="settings-panel">
299
- <div class="settings-toolbar">
300
- <div class="settings-toolbar-left">
301
- <span class="settings-toolbar-label">DEBUG CONSOLE</span>
302
- <span class="settings-toolbar-count" id="debug-count">0 events</span>
303
- </div>
304
- <div class="settings-toolbar-right">
305
- <button class="settings-btn" id="debug-pause-btn">Pause</button>
306
- <button class="settings-btn" id="debug-clear-btn">Clear</button>
307
- <button class="settings-btn" id="debug-export-btn">Export</button>
308
- </div>
309
- </div>
310
- <div class="settings-content debug-scroll" id="debug-content">
311
- <div class="settings-empty">Listening for events...</div>
312
- </div>
313
- </div>
314
- `;
315
-
316
- debugEntries = [];
317
- debugPaused = false;
318
-
319
- document.getElementById("debug-pause-btn").addEventListener("click", () => {
320
- debugPaused = !debugPaused;
321
- document.getElementById("debug-pause-btn").textContent = debugPaused ? "Resume" : "Pause";
322
- });
323
-
324
- document.getElementById("debug-clear-btn").addEventListener("click", () => {
325
- debugEntries = [];
326
- renderDebugEntries();
327
- });
328
-
329
- document.getElementById("debug-export-btn").addEventListener("click", () => {
330
- const text = debugEntries.map((e) => `${e.time} [${e.type}] ${e.summary}`).join("\n");
331
- const blob = new Blob([text], { type: "text/plain" });
332
- const a = document.createElement("a");
333
- a.href = URL.createObjectURL(blob);
334
- a.download = `symi-debug-${new Date().toISOString().slice(0, 19)}.txt`;
335
- a.click();
336
- });
337
-
338
- // Start capturing events. If a previous capture closure is still attached
339
- // (rapid re-open race), mark it cancelled so its body no-ops before we
340
- // swap in the new one. Without this, the previous closure keeps reading
341
- // the old panel's DOM even after it's gone and throws silently.
342
- if (window.__debugPanelCapture && typeof window.__debugPanelCapture === "function") {
343
- try {
344
- window.__debugPanelCapture.__cancelled = true;
345
- } catch {
346
- /* non-extensible function — ignore */
347
- }
348
- }
349
- const capture = function (type, payload) {
350
- if (capture.__cancelled) {
351
- return;
352
- }
353
- if (debugPaused) {
354
- return;
355
- }
356
- const time = new Date().toLocaleTimeString("en-US", { hour12: false });
357
- let summary = "";
358
- try {
359
- summary =
360
- typeof payload === "object"
361
- ? JSON.stringify(payload).slice(0, 200)
362
- : String(payload).slice(0, 200);
363
- } catch {
364
- summary = "[unserializable]";
365
- }
366
- debugEntries.unshift({ time, type, summary, payload });
367
- if (debugEntries.length > DEBUG_MAX) {
368
- debugEntries.length = DEBUG_MAX;
369
- }
370
- renderDebugEntries();
371
- };
372
- capture.__cancelled = false;
373
- window.__debugPanelCapture = capture;
374
- }
375
-
376
- function renderDebugEntries() {
377
- const content = document.getElementById("debug-content");
378
- const count = document.getElementById("debug-count");
379
- if (!content) {
380
- return;
381
- }
382
- if (count) {
383
- count.textContent = `${debugEntries.length} events`;
384
- }
385
-
386
- if (debugEntries.length === 0) {
387
- content.innerHTML = '<div class="settings-empty">Listening for events...</div>';
388
- return;
389
- }
390
-
391
- const html = debugEntries
392
- .map((e) => {
393
- const typeClass =
394
- e.type === "error"
395
- ? "debug-type--error"
396
- : e.type === "chat"
397
- ? "debug-type--chat"
398
- : e.type === "agent"
399
- ? "debug-type--agent"
400
- : "debug-type--other";
401
- return `<div class="debug-entry-row">
402
- <span class="debug-time">${e.time}</span>
403
- <span class="debug-type ${typeClass}">${escapeSettingsHtml(e.type)}</span>
404
- <span class="debug-summary">${escapeSettingsHtml(e.summary)}</span>
405
- </div>`;
406
- })
407
- .join("");
408
-
409
- content.innerHTML = html;
410
- }
411
-
412
- function closeDebugPanel() {
413
- // Cancel any pending capture closure before dropping the reference so
414
- // in-flight capture() calls short-circuit if they were already scheduled.
415
- const existing = window.__debugPanelCapture;
416
- if (existing && typeof existing === "function") {
417
- try {
418
- existing.__cancelled = true;
419
- } catch {
420
- /* non-extensible — ignore */
421
- }
422
- }
423
- window.__debugPanelCapture = null;
424
- }
425
-
426
- // ══════════════════════════════════════════════════════════════════════
427
- // LOGS PANEL (replaces the dead-code logs.js)
428
- // ══════════════════════════════════════════════════════════════════════
429
-
430
- const LOGS_POLL_MS = 3000;
431
- const LOGS_LIMIT = 500;
432
- const LOGS_MAX_BYTES = 250000;
433
-
434
- const LEVEL_COLORS = {
435
- TRACE: "#6b7280",
436
- DEBUG: "#8b5cf6",
437
- INFO: "#60a5fa",
438
- WARN: "#f59e0b",
439
- ERROR: "#ef4444",
440
- FATAL: "#dc2626",
441
- };
442
-
443
- const LEVEL_ORDER = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"];
444
-
445
- let logsEntries = [];
446
- let logsCursor = null;
447
- let logsTimer = null;
448
- let logsAutoFollow = true;
449
- let logsLevelFilters = new Set(LEVEL_ORDER);
450
- let logsFilterText = "";
451
-
452
- function parseLogLine(line) {
453
- try {
454
- const obj = JSON.parse(line);
455
- const level = obj._meta?.logLevelName || "INFO";
456
- const time = obj.time || obj._meta?.date || "";
457
- const msg = obj["1"] || obj.msg || obj.message || "";
458
- const sub = obj["0"] || "";
459
- return { level, time, msg, sub, raw: line, parsed: obj };
460
- } catch {
461
- return { level: "INFO", time: "", msg: line, sub: "", raw: line, parsed: null };
462
- }
463
- }
464
-
465
- function getVisibleLogEntries() {
466
- return logsEntries.filter((e) => {
467
- if (!logsLevelFilters.has(e.level)) {
468
- return false;
469
- }
470
- if (logsFilterText && !e.raw.toLowerCase().includes(logsFilterText)) {
471
- return false;
472
- }
473
- return true;
474
- });
475
- }
476
-
477
- function renderLogEntries() {
478
- const el = document.getElementById("logs-entries");
479
- if (!el) {
480
- return;
481
- }
482
-
483
- const visible = getVisibleLogEntries();
484
- const html = visible
485
- .map((e) => {
486
- const color = LEVEL_COLORS[e.level] || "#999";
487
- const timeStr = e.time ? new Date(e.time).toLocaleTimeString("en-US", { hour12: false }) : "";
488
- const escapedMsg = escapeSettingsHtml(e.msg).substring(0, 500);
489
- const escapedSub = escapeSettingsHtml(e.sub);
490
- return `<div class="log-row">
491
- <span class="log-time">${timeStr}</span>
492
- <span class="log-level" style="color:${color}">${e.level.padEnd(5)}</span>
493
- ${escapedSub ? `<span class="log-sub">${escapedSub}</span>` : ""}
494
- <span class="log-msg">${escapedMsg}</span>
495
- </div>`;
496
- })
497
- .join("");
498
-
499
- el.innerHTML = html || '<div class="settings-empty">No log entries.</div>';
500
-
501
- if (logsAutoFollow) {
502
- const scroll = document.getElementById("logs-scroll-area");
503
- if (scroll) {
504
- scroll.scrollTop = scroll.scrollHeight;
505
- }
506
- }
507
- }
508
-
509
- async function fetchLogs() {
510
- if (!window.gateway?.connected) {
511
- return;
512
- }
513
- try {
514
- const params = { limit: LOGS_LIMIT, maxBytes: LOGS_MAX_BYTES };
515
- if (logsCursor != null) {
516
- params.cursor = logsCursor;
517
- }
518
- const result = await window.gateway.rpc("logs.tail", params);
519
- if (!result) {
520
- return;
521
- }
522
-
523
- if (result.reset) {
524
- logsEntries = [];
525
- logsCursor = null;
526
- }
527
-
528
- if (result.lines && result.lines.length > 0) {
529
- const newEntries = result.lines.map(parseLogLine);
530
- logsEntries.push(...newEntries);
531
- if (logsEntries.length > 2000) {
532
- logsEntries = logsEntries.slice(logsEntries.length - 2000);
533
- }
534
- renderLogEntries();
535
- }
536
-
537
- if (typeof result.cursor === "number") {
538
- logsCursor = result.cursor;
539
- }
540
- } catch (err) {
541
- console.error("[settings:logs] fetch error:", err);
542
- }
543
- }
544
-
545
- function startLogsPolling() {
546
- logsCursor = null;
547
- logsEntries = [];
548
-
549
- if (!window.gateway?.connected) {
550
- const waitIv = setInterval(() => {
551
- if (window.gateway?.connected) {
552
- clearInterval(waitIv);
553
- void fetchLogs();
554
- if (logsTimer) {
555
- clearInterval(logsTimer);
556
- }
557
- logsTimer = setInterval(fetchLogs, LOGS_POLL_MS);
558
- }
559
- }, 500);
560
- setTimeout(() => clearInterval(waitIv), 30000);
561
- return;
562
- }
563
-
564
- void fetchLogs();
565
- if (logsTimer) {
566
- clearInterval(logsTimer);
567
- }
568
- logsTimer = setInterval(fetchLogs, LOGS_POLL_MS);
569
- }
570
-
571
- function stopLogsPolling() {
572
- if (logsTimer) {
573
- clearInterval(logsTimer);
574
- logsTimer = null;
575
- }
576
- }
577
-
578
- function openLogsPanel() {
579
- const container = showSettingsContainer();
580
- if (!container) {
581
- return;
582
- }
583
-
584
- container.innerHTML = `
585
- <div class="settings-panel">
586
- <div class="settings-toolbar">
587
- <div class="settings-toolbar-left">
588
- <span class="settings-toolbar-label">SYSTEM LOGS</span>
589
- <span class="settings-toolbar-count" id="logs-count">0 entries</span>
590
- </div>
591
- <div class="settings-toolbar-right">
592
- <div class="logs-level-filters">
593
- ${LEVEL_ORDER.map(
594
- (l) => `
595
- <label class="logs-level-chip" data-level="${l}">
596
- <input type="checkbox" checked data-level="${l}">
597
- <span style="color:${LEVEL_COLORS[l]}">${l}</span>
598
- </label>
599
- `,
600
- ).join("")}
601
- </div>
602
- <input type="text" class="settings-search" id="logs-filter-input" placeholder="Filter..." />
603
- <label class="settings-checkbox-label">
604
- <input type="checkbox" id="logs-follow-cb" checked> Follow
605
- </label>
606
- <button class="settings-btn" id="logs-export-btn">Export</button>
607
- </div>
608
- </div>
609
- <div class="settings-content logs-scroll" id="logs-scroll-area">
610
- <div id="logs-entries">
611
- <div class="settings-loading">Connecting to log stream...</div>
612
- </div>
613
- </div>
614
- </div>
615
- `;
616
-
617
- // Wire level filters
618
- container.querySelectorAll(".logs-level-chip input").forEach((cb) => {
619
- cb.addEventListener("change", () => {
620
- const level = cb.dataset.level;
621
- if (cb.checked) {
622
- logsLevelFilters.add(level);
623
- } else {
624
- logsLevelFilters.delete(level);
625
- }
626
- renderLogEntries();
627
- });
628
- });
629
-
630
- // Wire filter text
631
- document.getElementById("logs-filter-input").addEventListener("input", (e) => {
632
- logsFilterText = e.target.value.toLowerCase();
633
- renderLogEntries();
634
- });
635
-
636
- // Wire auto-follow
637
- document.getElementById("logs-follow-cb").addEventListener("change", (e) => {
638
- logsAutoFollow = e.target.checked;
639
- if (logsAutoFollow) {
640
- const scroll = document.getElementById("logs-scroll-area");
641
- if (scroll) {
642
- scroll.scrollTop = scroll.scrollHeight;
643
- }
644
- }
645
- });
646
-
647
- // Wire export
648
- document.getElementById("logs-export-btn").addEventListener("click", () => {
649
- const visible = getVisibleLogEntries();
650
- const text = visible.map((e) => e.raw).join("\n");
651
- const blob = new Blob([text], { type: "text/plain" });
652
- const a = document.createElement("a");
653
- a.href = URL.createObjectURL(blob);
654
- a.download = `symi-logs-${new Date().toISOString().slice(0, 19)}.txt`;
655
- a.click();
656
- });
657
-
658
- startLogsPolling();
659
- }
660
-
661
- function closeLogsPanel() {
662
- stopLogsPolling();
663
- }
664
-
665
- // ══════════════════════════════════════════════════════════════════════
666
- // GENERIC RPC LIST PANEL — used by Overview, Channels, Sessions, etc.
667
- // ══════════════════════════════════════════════════════════════════════
668
-
669
- const PAGE_RPC = {
670
- overview: { method: "system.status", label: "SYSTEM OVERVIEW", type: "object" },
671
- channels: {
672
- method: "channels.status",
673
- label: "MESSAGING CHANNELS",
674
- type: "list",
675
- key: "channels",
676
- },
677
- instances: { method: "system.status", label: "AI INSTANCES", type: "object" },
678
- sessions: { method: "sessions.list", label: "ACTIVE SESSIONS", type: "list", key: "sessions" },
679
- usage: { method: "usage.status", label: "USAGE & TOKENS", type: "object" },
680
- cron: { method: "cron.list", label: "SCHEDULED TASKS", type: "list", key: "jobs" },
681
- tasks: { method: "tasks.list", label: "BACKGROUND TASKS", type: "list", key: "tasks" },
682
- agents: { method: "agents.list", label: "AGENT MANAGEMENT", type: "list", key: "agents" },
683
- skills: { method: "skills.status", label: "AGENT SKILLS", type: "object" },
684
- nodes: { method: "node.list", label: "CONNECTED DEVICES", type: "list", key: "nodes" },
685
- };
686
-
687
- function renderDataCard(key, value) {
688
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
689
- const rows = Object.entries(value)
690
- .map(([k, v]) => {
691
- const display = v == null ? "—" : JSON.stringify(v);
692
- return `<div class="data-card-row">
693
- <span class="data-card-label">${escapeSettingsHtml(k)}</span>
694
- <span class="data-card-value">${escapeSettingsHtml(display.slice(0, 200))}</span>
695
- </div>`;
696
- })
697
- .join("");
698
- return `<div class="data-card">
699
- <div class="data-card-header">${escapeSettingsHtml(String(key))}</div>
700
- ${rows}
701
- </div>`;
702
- }
703
- return `<div class="data-card">
704
- <div class="data-card-header">${escapeSettingsHtml(String(key))}</div>
705
- <div class="data-card-row"><span class="data-card-value">${escapeSettingsHtml(value != null ? JSON.stringify(value) : "—")}</span></div>
706
- </div>`;
707
- }
708
-
709
- // Pagination state per generic panel — keyed by the page identifier.
710
- // Resets when the panel is opened; survives across refresh within one session.
711
- const GENERIC_PAGE_SIZE = 50;
712
- const genericShownCount = new Map();
713
-
714
- function resetGenericPagination(page) {
715
- genericShownCount.set(page, GENERIC_PAGE_SIZE);
716
- }
717
-
718
- function bumpGenericPagination(page) {
719
- const cur = genericShownCount.get(page) ?? GENERIC_PAGE_SIZE;
720
- genericShownCount.set(page, cur + GENERIC_PAGE_SIZE);
721
- }
722
-
723
- function renderListData(items, page) {
724
- if (!items || items.length === 0) {
725
- return '<div class="settings-empty">No items found.</div>';
726
- }
727
- const shown = page ? (genericShownCount.get(page) ?? GENERIC_PAGE_SIZE) : items.length;
728
- const slice = items.slice(0, shown);
729
- const html = slice
730
- .map((item, i) => {
731
- const title = item.name || item.id || item.sessionKey || item.label || `Item ${i + 1}`;
732
- return renderDataCard(title, item);
733
- })
734
- .join("");
735
- if (!page || items.length <= shown) {
736
- return html;
737
- }
738
- const remaining = items.length - shown;
739
- const nextBatch = Math.min(GENERIC_PAGE_SIZE, remaining);
740
- return `${html}
741
- <div class="settings-pagination">
742
- <span class="settings-pagination-info">Showing ${shown} of ${items.length}</span>
743
- <button class="settings-btn" id="generic-show-more-btn" data-page="${escapeSettingsHtml(page)}">Show ${nextBatch} more</button>
744
- </div>`;
745
- }
746
-
747
- function renderObjectData(data) {
748
- if (!data || Object.keys(data).length === 0) {
749
- return '<div class="settings-empty">No data available.</div>';
750
- }
751
- return Object.entries(data)
752
- .map(([key, value]) => {
753
- if (Array.isArray(value)) {
754
- return `<div class="data-card">
755
- <div class="data-card-header">${escapeSettingsHtml(key)} <span class="data-card-badge">${value.length}</span></div>
756
- ${
757
- value.length === 0
758
- ? '<div class="data-card-row"><span class="data-card-value">Empty</span></div>'
759
- : value
760
- .slice(0, 20)
761
- .map((item, i) => {
762
- const label =
763
- typeof item === "object" ? item.name || item.id || `#${i + 1}` : String(item);
764
- const detail = typeof item === "object" ? JSON.stringify(item).slice(0, 150) : "";
765
- return `<div class="data-card-row">
766
- <span class="data-card-label">${escapeSettingsHtml(String(label))}</span>
767
- ${detail ? `<span class="data-card-value">${escapeSettingsHtml(detail)}</span>` : ""}
768
- </div>`;
769
- })
770
- .join("") +
771
- (value.length > 20
772
- ? `<div class="data-card-row"><span class="data-card-value">... and ${value.length - 20} more</span></div>`
773
- : "")
774
- }
775
- </div>`;
776
- }
777
- if (typeof value === "object" && value !== null) {
778
- return renderDataCard(key, value);
779
- }
780
- return `<div class="data-card-inline">
781
- <span class="data-card-label">${escapeSettingsHtml(key)}</span>
782
- <span class="data-card-value">${escapeSettingsHtml(value != null ? JSON.stringify(value) : "—")}</span>
783
- </div>`;
784
- })
785
- .join("");
786
- }
787
-
788
- async function openGenericPanel(page) {
789
- const spec = PAGE_RPC[page];
790
- if (!spec) {
791
- return;
792
- }
793
-
794
- const container = showSettingsContainer();
795
- if (!container) {
796
- return;
797
- }
798
-
799
- container.innerHTML = `
800
- <div class="settings-panel">
801
- <div class="settings-toolbar">
802
- <div class="settings-toolbar-left">
803
- <span class="settings-toolbar-label">${spec.label}</span>
804
- </div>
805
- <div class="settings-toolbar-right">
806
- <button class="settings-btn" id="generic-refresh-btn">Refresh</button>
807
- </div>
808
- </div>
809
- <div class="settings-content" id="generic-content">
810
- <div class="settings-loading">Loading...</div>
811
- </div>
812
- </div>
813
- `;
814
-
815
- document.getElementById("generic-refresh-btn").addEventListener("click", () => {
816
- resetGenericPagination(page);
817
- void loadGenericData(page);
818
- });
819
- // Delegated click handler for the "Show more" button rendered inside
820
- // generic-content. We attach on the container so it survives innerHTML
821
- // re-renders driven by loadGenericData.
822
- container.addEventListener("click", (e) => {
823
- const target = e.target;
824
- if (target && target.id === "generic-show-more-btn") {
825
- bumpGenericPagination(page);
826
- void loadGenericData(page);
827
- }
828
- });
829
- resetGenericPagination(page);
830
- await loadGenericData(page);
831
- }
832
-
833
- async function loadGenericData(page) {
834
- const spec = PAGE_RPC[page];
835
- const content = document.getElementById("generic-content");
836
- if (!spec || !content) {
837
- return;
838
- }
839
-
840
- content.innerHTML = '<div class="settings-loading">Loading...</div>';
841
-
842
- if (!window.gateway?.connected) {
843
- content.innerHTML = '<div class="settings-empty">Gateway not connected.</div>';
844
- return;
845
- }
846
-
847
- try {
848
- const result = await window.gateway.rpc(spec.method, {});
849
- if (!result) {
850
- content.innerHTML = '<div class="settings-empty">No data returned.</div>';
851
- return;
852
- }
853
- if (spec.type === "list") {
854
- const items = spec.key ? result[spec.key] : Array.isArray(result) ? result : [];
855
- content.innerHTML = renderListData(items, page);
856
- } else {
857
- content.innerHTML = renderObjectData(result);
858
- }
859
- } catch (err) {
860
- content.innerHTML = `<div class="settings-empty">Error: ${escapeSettingsHtml(err.message)}</div>`;
861
- }
862
- }
863
-
864
- // ══════════════════════════════════════════════════════════════════════
865
- // PUBLIC API — called by menu.js
866
- // ══════════════════════════════════════════════════════════════════════
867
-
868
- let activeSettingsPanel = null;
869
-
870
- window.openNativeSettings = function (page) {
871
- // Close previous panel cleanup
872
- closeActiveSettingsPanel();
873
-
874
- activeSettingsPanel = page;
875
-
876
- switch (page) {
877
- case "config":
878
- void openConfigPanel();
879
- break;
880
- case "debug":
881
- openDebugPanel();
882
- break;
883
- case "logs":
884
- openLogsPanel();
885
- break;
886
- default:
887
- if (PAGE_RPC[page]) {
888
- void openGenericPanel(page);
889
- } else {
890
- console.warn("[settings] unknown page:", page);
891
- }
892
- break;
893
- }
894
- };
895
-
896
- window.closeNativeSettings = function () {
897
- closeActiveSettingsPanel();
898
- hideSettingsContainer();
899
- activeSettingsPanel = null;
900
- };
901
-
902
- function closeActiveSettingsPanel() {
903
- if (activeSettingsPanel === "debug") {
904
- closeDebugPanel();
905
- }
906
- if (activeSettingsPanel === "logs") {
907
- closeLogsPanel();
908
- }
909
- }