agim-cli 1.0.4 → 1.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.
@@ -89,6 +89,45 @@
89
89
  workspaceDeleted: 'Workspace deleted',
90
90
  workspaceIdHelp: 'Letters / digits / _ / - only',
91
91
  workspaceDefaultLocked: 'default (locked)',
92
+
93
+ // ── messengers (extras for v1.0.5) ───────────────────────
94
+ wechatScan: 'Scan to log in',
95
+ wechatScanTitle: 'WeChat — scan to log in',
96
+ wechatGenerating: 'Generating QR code…',
97
+ wechatWaiting: 'Waiting for scan…',
98
+ wechatScanned: 'QR scanned — confirm on your phone…',
99
+ wechatConfirmed: '✓ Logged in (account {account})',
100
+ wechatExpired: 'QR code expired',
101
+ wechatRegen: 'Regenerate',
102
+ wechatClose: 'Close',
103
+ wechatFailed: 'Failed: {error}',
104
+ dingtalk: 'DingTalk',
105
+ dingtalkHint: 'Stream-mode app: Client ID + Client Secret',
106
+ dingtalkClientId: 'Client ID',
107
+ dingtalkClientSecret: 'Client Secret',
108
+ discord: 'Discord',
109
+ discordHint: 'Bot token from Discord Developer Portal',
110
+ discordToken: 'Bot Token',
111
+ discordGuilds: 'Allowed guilds (comma-separated IDs, blank = any)',
112
+ discordChannels: 'Allowed channels (comma-separated IDs, blank = any)',
113
+ configure: 'Configure',
114
+ reconfigure: 'Edit credentials',
115
+
116
+ // ── service control ──────────────────────────────────────
117
+ svcTitle: 'Service',
118
+ svcStateLoading: 'Checking…',
119
+ svcStateRunning: 'Running ({mode}, pid {pid}, up {uptime})',
120
+ svcStateRunningNoUp: 'Running ({mode}, pid {pid})',
121
+ svcStateNone: 'Not running',
122
+ svcStart: 'Start',
123
+ svcStop: 'Stop',
124
+ svcRestart: 'Restart',
125
+ svcConfirmStop: 'Stop Agim? The web console will go offline until you start it again from a terminal.',
126
+ svcConfirmRestart: 'Restart Agim? The web console will reconnect automatically in a few seconds.',
127
+ svcRestarting: 'Restarting — waiting for the service to come back…',
128
+ svcRestarted: '✓ Service restarted',
129
+ svcStopped: '✓ Service stopped (this page is now disconnected)',
130
+ svcFgWarning: 'Foreground service is running in another terminal — restart it there.',
92
131
  },
93
132
  zh: {
94
133
  title: 'Agim — 设置',
@@ -164,6 +203,45 @@
164
203
  workspaceDeleted: '工作区已删除',
165
204
  workspaceIdHelp: '仅支持字母 / 数字 / _ / -',
166
205
  workspaceDefaultLocked: '默认(不可改)',
206
+
207
+ // ── messengers (extras for v1.0.5) ───────────────────────
208
+ wechatScan: '扫码登录',
209
+ wechatScanTitle: '微信 — 扫码登录',
210
+ wechatGenerating: '生成二维码中…',
211
+ wechatWaiting: '等待扫描…',
212
+ wechatScanned: '已扫描,请在手机端确认…',
213
+ wechatConfirmed: '✓ 登录成功(账号 {account})',
214
+ wechatExpired: '二维码已过期',
215
+ wechatRegen: '重新生成',
216
+ wechatClose: '关闭',
217
+ wechatFailed: '失败:{error}',
218
+ dingtalk: '钉钉',
219
+ dingtalkHint: 'Stream 模式应用:Client ID + Client Secret',
220
+ dingtalkClientId: 'Client ID',
221
+ dingtalkClientSecret: 'Client Secret',
222
+ discord: 'Discord',
223
+ discordHint: '在 Discord Developer Portal 获取 Bot Token',
224
+ discordToken: 'Bot Token',
225
+ discordGuilds: '允许的服务器(逗号分隔 ID,空 = 不限)',
226
+ discordChannels: '允许的频道(逗号分隔 ID,空 = 不限)',
227
+ configure: '配置',
228
+ reconfigure: '修改凭据',
229
+
230
+ // ── service control ──────────────────────────────────────
231
+ svcTitle: '服务',
232
+ svcStateLoading: '检查中…',
233
+ svcStateRunning: '运行中({mode},pid {pid},已运行 {uptime})',
234
+ svcStateRunningNoUp: '运行中({mode},pid {pid})',
235
+ svcStateNone: '未运行',
236
+ svcStart: '启动',
237
+ svcStop: '停止',
238
+ svcRestart: '重启',
239
+ svcConfirmStop: '停止 Agim?停止后 web 控制台会离线,需要回终端用 `agim start` 再启动。',
240
+ svcConfirmRestart: '重启 Agim?web 控制台会在几秒后自动重连。',
241
+ svcRestarting: '重启中——等待服务恢复…',
242
+ svcRestarted: '✓ 服务已重启',
243
+ svcStopped: '✓ 服务已停止(页面已断开连接)',
244
+ svcFgWarning: '前台服务在另一个终端运行——回那个终端重启。',
167
245
  },
168
246
  };
169
247
  function t(key) { return T[window.__lang][key] || T.en[key] || key; }
@@ -603,6 +681,7 @@
603
681
  function render() {
604
682
  app.innerHTML = `
605
683
  <h1>${t('h1')}</h1>
684
+ ${renderServiceCard()}
606
685
  ${renderAgentsCard()}
607
686
  ${renderMessengersCard()}
608
687
  ${renderAcpCard()}
@@ -610,6 +689,28 @@
610
689
  ${renderGeneralCard()}
611
690
  `;
612
691
  bindEvents();
692
+ // Service status loads asynchronously; the card renders with a
693
+ // placeholder, populated by the first /api/service/status response.
694
+ void loadServiceStatus();
695
+ }
696
+
697
+ // ==========================================
698
+ // Service control card
699
+ // ==========================================
700
+ function renderServiceCard() {
701
+ return `
702
+ <div class="card">
703
+ <h2>${t('svcTitle')}</h2>
704
+ <div class="status" id="svc-state" style="margin-bottom:14px">
705
+ <span class="dot dot-off"></span>${t('svcStateLoading')}
706
+ </div>
707
+ <div class="actions">
708
+ <button type="button" class="btn btn-primary" id="svc-restart">${t('svcRestart')}</button>
709
+ <button type="button" class="btn" id="svc-stop">${t('svcStop')}</button>
710
+ <button type="button" class="btn" id="svc-start" disabled>${t('svcStart')}</button>
711
+ </div>
712
+ </div>
713
+ `;
613
714
  }
614
715
 
615
716
  // ==========================================
@@ -665,10 +766,14 @@
665
766
  const messengers = config.messengers || [];
666
767
  const tg = config.telegram || {};
667
768
  const fs = config.feishu || {};
769
+ const dt = config.dingtalk || {};
770
+ const dc = config.discord || {};
668
771
 
669
772
  const wechatEnabled = messengers.includes('wechat-ilink');
670
773
  const telegramEnabled = messengers.includes('telegram');
671
774
  const feishuEnabled = messengers.includes('feishu');
775
+ const dingtalkEnabled = messengers.includes('dingtalk');
776
+ const discordEnabled = messengers.includes('discord');
672
777
 
673
778
  return `
674
779
  <div class="card">
@@ -685,7 +790,11 @@
685
790
  </div>
686
791
  <div class="toggle ${wechatEnabled ? 'active' : ''}" data-toggle-messenger="wechat-ilink"></div>
687
792
  </div>
688
- ${wechatEnabled ? `<div class="hint-box">${t('wechatBox')}</div>` : ''}
793
+ ${wechatEnabled ? `
794
+ <div class="actions">
795
+ <button type="button" class="btn" id="wechatScanBtn">${t('wechatScan')}</button>
796
+ </div>
797
+ ` : ''}
689
798
  </div>
690
799
 
691
800
  <!-- Telegram -->
@@ -714,7 +823,7 @@
714
823
  </div>
715
824
 
716
825
  <!-- Feishu -->
717
- <div>
826
+ <div style="margin-bottom:16px">
718
827
  <div class="agent-row">
719
828
  <div class="left">
720
829
  <div>
@@ -738,6 +847,66 @@
738
847
  ` : ''}
739
848
  </div>
740
849
 
850
+ <!-- DingTalk -->
851
+ <div style="margin-bottom:16px">
852
+ <div class="agent-row">
853
+ <div class="left">
854
+ <div>
855
+ <div class="name">${t('dingtalk')}</div>
856
+ <div class="hint">${t('dingtalkHint')}</div>
857
+ </div>
858
+ </div>
859
+ <div class="toggle ${dingtalkEnabled ? 'active' : ''}" data-toggle-messenger="dingtalk"></div>
860
+ </div>
861
+ ${dingtalkEnabled ? `
862
+ <div class="row">
863
+ <div>
864
+ <label>${t('dingtalkClientId')}</label>
865
+ <input type="text" id="dtClientId" value="${esc(dt.clientId || '')}" placeholder="dingxxxxxx">
866
+ </div>
867
+ <div>
868
+ <label>${t('dingtalkClientSecret')}</label>
869
+ <input type="password" id="dtClientSecret" value="${esc(dt.clientSecret || '')}" placeholder="••••••••">
870
+ </div>
871
+ </div>
872
+ ` : ''}
873
+ </div>
874
+
875
+ <!-- Discord -->
876
+ <div>
877
+ <div class="agent-row">
878
+ <div class="left">
879
+ <div>
880
+ <div class="name">${t('discord')}</div>
881
+ <div class="hint">${t('discordHint')}</div>
882
+ </div>
883
+ </div>
884
+ <div class="toggle ${discordEnabled ? 'active' : ''}" data-toggle-messenger="discord"></div>
885
+ </div>
886
+ ${discordEnabled ? `
887
+ <div class="row">
888
+ <div>
889
+ <label>${t('discordToken')}</label>
890
+ <input type="password" id="dcToken" value="${esc(dc.botToken || '')}" placeholder="••••••••">
891
+ </div>
892
+ <div>
893
+ <label>${t('channelId')}</label>
894
+ <input type="text" id="dcChannel" value="${esc(dc.channelId || '')}" placeholder="default">
895
+ </div>
896
+ </div>
897
+ <div class="row">
898
+ <div>
899
+ <label>${t('discordGuilds')}</label>
900
+ <input type="text" id="dcGuilds" value="${esc((dc.allowedGuilds || []).join(', '))}" placeholder="123, 456">
901
+ </div>
902
+ <div>
903
+ <label>${t('discordChannels')}</label>
904
+ <input type="text" id="dcChannels" value="${esc((dc.allowedChannels || []).join(', '))}" placeholder="789, 101112">
905
+ </div>
906
+ </div>
907
+ ` : ''}
908
+ </div>
909
+
741
910
  <div class="actions">
742
911
  <button type="button" class="btn btn-primary" id="saveMessengers">${t('saveMessengers')}</button>
743
912
  </div>
@@ -1037,19 +1206,78 @@
1037
1206
  // the Reveal buttons re-fetch with ?reveal=1 on demand.
1038
1207
  void loadEnvSection();
1039
1208
 
1209
+ // Service control card
1210
+ document.getElementById('svc-restart')?.addEventListener('click', () => svcAction('restart'));
1211
+ document.getElementById('svc-stop')?.addEventListener('click', () => svcAction('stop'));
1212
+ document.getElementById('svc-start')?.addEventListener('click', () => svcAction('start'));
1213
+
1040
1214
  // Agent toggles
1041
1215
  document.querySelectorAll('[data-toggle-agent]').forEach(el => {
1042
1216
  el.addEventListener('click', () => el.classList.toggle('active'));
1043
1217
  });
1044
1218
 
1045
- // Messenger toggles
1219
+ // Messenger toggles — flip in-memory config.messengers, then re-render.
1046
1220
  document.querySelectorAll('[data-toggle-messenger]').forEach(el => {
1047
1221
  el.addEventListener('click', () => {
1048
- el.classList.toggle('active');
1049
- syncMessengerToggles();
1222
+ const id = el.getAttribute('data-toggle-messenger');
1223
+ const set = new Set(config.messengers || []);
1224
+ if (set.has(id)) set.delete(id);
1225
+ else set.add(id);
1226
+ config.messengers = Array.from(set);
1227
+ render();
1050
1228
  });
1051
1229
  });
1052
1230
 
1231
+ // Save messengers — pull credentials from any visible fields, then PUT.
1232
+ document.getElementById('saveMessengers')?.addEventListener('click', async () => {
1233
+ const get = (id) => document.getElementById(id)?.value?.trim() ?? '';
1234
+ const messengers = (config.messengers || []);
1235
+
1236
+ if (messengers.includes('telegram')) {
1237
+ const botToken = get('tgToken');
1238
+ const channelId = get('tgChannel') || 'default';
1239
+ if (botToken) config.telegram = { ...(config.telegram || {}), botToken, channelId };
1240
+ else if (config.telegram?.channelId !== channelId) config.telegram = { ...(config.telegram || {}), channelId };
1241
+ }
1242
+ if (messengers.includes('feishu')) {
1243
+ const appId = get('fsAppId');
1244
+ const appSecret = get('fsAppSecret');
1245
+ config.feishu = {
1246
+ ...(config.feishu || {}),
1247
+ ...(appId ? { appId } : {}),
1248
+ ...(appSecret ? { appSecret } : {}),
1249
+ };
1250
+ }
1251
+ if (messengers.includes('dingtalk')) {
1252
+ const clientId = get('dtClientId');
1253
+ const clientSecret = get('dtClientSecret');
1254
+ config.dingtalk = {
1255
+ ...(config.dingtalk || {}),
1256
+ ...(clientId ? { clientId } : {}),
1257
+ ...(clientSecret ? { clientSecret } : {}),
1258
+ };
1259
+ }
1260
+ if (messengers.includes('discord')) {
1261
+ const botToken = get('dcToken');
1262
+ const channelId = get('dcChannel') || 'default';
1263
+ const guildsRaw = get('dcGuilds');
1264
+ const channelsRaw = get('dcChannels');
1265
+ config.discord = {
1266
+ ...(config.discord || {}),
1267
+ ...(botToken ? { botToken } : {}),
1268
+ channelId,
1269
+ allowedGuilds: guildsRaw ? guildsRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined,
1270
+ allowedChannels: channelsRaw ? channelsRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined,
1271
+ };
1272
+ }
1273
+ await saveConfig();
1274
+ });
1275
+
1276
+ // WeChat — open QR modal on click.
1277
+ document.getElementById('wechatScanBtn')?.addEventListener('click', () => {
1278
+ openWechatQrModal();
1279
+ });
1280
+
1053
1281
  // ACP toggles
1054
1282
  document.querySelectorAll('[data-toggle-acp]').forEach(el => {
1055
1283
  el.addEventListener('click', () => el.classList.toggle('active'));
@@ -1302,6 +1530,186 @@
1302
1530
  render();
1303
1531
  }
1304
1532
 
1533
+ // ==========================================
1534
+ // WeChat QR-login modal
1535
+ // ==========================================
1536
+ let wechatPollTimer = null;
1537
+ function closeWechatQrModal() {
1538
+ if (wechatPollTimer) { clearTimeout(wechatPollTimer); wechatPollTimer = null; }
1539
+ const m = document.getElementById('wechat-modal');
1540
+ if (m) m.remove();
1541
+ }
1542
+ async function openWechatQrModal() {
1543
+ // Tear down any prior modal first.
1544
+ closeWechatQrModal();
1545
+ const overlay = document.createElement('div');
1546
+ overlay.id = 'wechat-modal';
1547
+ overlay.style.cssText = 'position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.55)';
1548
+ overlay.innerHTML = `
1549
+ <div style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:24px 28px;max-width:380px;width:90%;text-align:center">
1550
+ <div style="font-weight:600;font-size:15px;margin-bottom:14px">${esc(t('wechatScanTitle'))}</div>
1551
+ <div id="wechat-qr-wrap" style="min-height:240px;display:flex;align-items:center;justify-content:center;background:var(--surface2);border-radius:8px;margin-bottom:12px">
1552
+ <div style="color:var(--text-dim);font-size:13px">${esc(t('wechatGenerating'))}</div>
1553
+ </div>
1554
+ <div id="wechat-qr-status" style="font-size:13px;color:var(--text-dim);margin-bottom:14px;min-height:18px"></div>
1555
+ <div style="display:flex;gap:8px;justify-content:center">
1556
+ <button type="button" class="btn" id="wechat-regen">${esc(t('wechatRegen'))}</button>
1557
+ <button type="button" class="btn" id="wechat-close">${esc(t('wechatClose'))}</button>
1558
+ </div>
1559
+ </div>
1560
+ `;
1561
+ document.body.appendChild(overlay);
1562
+ document.getElementById('wechat-close')?.addEventListener('click', closeWechatQrModal);
1563
+ document.getElementById('wechat-regen')?.addEventListener('click', () => { void startWechatQr(); });
1564
+ overlay.addEventListener('click', (e) => { if (e.target === overlay) closeWechatQrModal(); });
1565
+ await startWechatQr();
1566
+ }
1567
+
1568
+ async function startWechatQr() {
1569
+ if (wechatPollTimer) { clearTimeout(wechatPollTimer); wechatPollTimer = null; }
1570
+ const wrap = document.getElementById('wechat-qr-wrap');
1571
+ const statusEl = document.getElementById('wechat-qr-status');
1572
+ if (!wrap || !statusEl) return;
1573
+ wrap.innerHTML = `<div style="color:var(--text-dim);font-size:13px">${esc(t('wechatGenerating'))}</div>`;
1574
+ statusEl.textContent = '';
1575
+ let qrToken;
1576
+ try {
1577
+ const res = await authFetch('/api/messengers/wechat/qr-start', { method: 'POST' });
1578
+ if (!res.ok) {
1579
+ const j = await res.json().catch(() => ({}));
1580
+ throw new Error(j.error || res.statusText);
1581
+ }
1582
+ const data = await res.json();
1583
+ qrToken = data.qrToken;
1584
+ // qrUrl from iLink may be a data: URL (base64 png) or an https URL.
1585
+ // Either way, <img src=...> handles it directly.
1586
+ wrap.innerHTML = `<img src="${esc(data.qrUrl)}" alt="QR code" style="width:240px;height:240px;border-radius:6px;background:#fff;padding:8px">`;
1587
+ statusEl.textContent = t('wechatWaiting');
1588
+ } catch (err) {
1589
+ wrap.innerHTML = `<div style="color:var(--red);font-size:13px">${esc(t('wechatFailed').replace('{error}', err && err.message ? err.message : err))}</div>`;
1590
+ return;
1591
+ }
1592
+ // Begin polling.
1593
+ const poll = async () => {
1594
+ try {
1595
+ const res = await authFetch('/api/messengers/wechat/qr-status?token=' + encodeURIComponent(qrToken));
1596
+ if (!res.ok) {
1597
+ const j = await res.json().catch(() => ({}));
1598
+ throw new Error(j.error || res.statusText);
1599
+ }
1600
+ const data = await res.json();
1601
+ if (data.status === 'wait') {
1602
+ statusEl.textContent = t('wechatWaiting');
1603
+ wechatPollTimer = setTimeout(poll, 1500);
1604
+ return;
1605
+ }
1606
+ if (data.status === 'scaned') {
1607
+ statusEl.textContent = t('wechatScanned');
1608
+ wechatPollTimer = setTimeout(poll, 1000);
1609
+ return;
1610
+ }
1611
+ if (data.status === 'confirmed') {
1612
+ const account = (data.credentials && data.credentials.accountId) || '';
1613
+ statusEl.innerHTML = '<span style="color:var(--green)">' + esc(t('wechatConfirmed').replace('{account}', account)) + '</span>';
1614
+ // Refresh the underlying config (server added 'wechat-ilink' to messengers).
1615
+ toast(t('savedMsg'), 'success');
1616
+ setTimeout(() => { closeWechatQrModal(); void init(); }, 1500);
1617
+ return;
1618
+ }
1619
+ if (data.status === 'expired') {
1620
+ statusEl.innerHTML = '<span style="color:var(--red)">' + esc(t('wechatExpired')) + '</span>';
1621
+ return;
1622
+ }
1623
+ // Unknown status — keep polling once, then stop.
1624
+ wechatPollTimer = setTimeout(poll, 1500);
1625
+ } catch (err) {
1626
+ statusEl.innerHTML = '<span style="color:var(--red)">' + esc(t('wechatFailed').replace('{error}', err && err.message ? err.message : err)) + '</span>';
1627
+ }
1628
+ };
1629
+ wechatPollTimer = setTimeout(poll, 1000);
1630
+ }
1631
+
1632
+ // ==========================================
1633
+ // Service-control card (status + start/stop/restart)
1634
+ // ==========================================
1635
+ let svcPollTimer = null;
1636
+ async function loadServiceStatus() {
1637
+ const stateEl = document.getElementById('svc-state');
1638
+ const startBtn = document.getElementById('svc-start');
1639
+ const stopBtn = document.getElementById('svc-stop');
1640
+ const restartBtn = document.getElementById('svc-restart');
1641
+ if (!stateEl) return;
1642
+ try {
1643
+ const res = await authFetch('/api/service/status');
1644
+ if (!res.ok) throw new Error(res.statusText);
1645
+ const d = await res.json();
1646
+ if (d.mode === 'none') {
1647
+ stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('svcStateNone'));
1648
+ } else {
1649
+ const tpl = d.uptime ? t('svcStateRunning') : t('svcStateRunningNoUp');
1650
+ const label = tpl
1651
+ .replace('{mode}', d.mode || '?')
1652
+ .replace('{pid}', d.pid != null ? d.pid : '?')
1653
+ .replace('{uptime}', d.uptime || '');
1654
+ stateEl.innerHTML = '<span class="dot dot-on"></span>' + esc(label);
1655
+ }
1656
+ if (startBtn) startBtn.disabled = d.mode !== 'none';
1657
+ if (stopBtn) stopBtn.disabled = d.mode === 'none';
1658
+ if (restartBtn) restartBtn.disabled = d.mode === 'none' || d.mode === 'foreground';
1659
+ } catch (err) {
1660
+ stateEl.textContent = (t('error') + ': ' + (err && err.message ? err.message : err));
1661
+ }
1662
+ }
1663
+ async function svcAction(action) {
1664
+ const confirmKey = action === 'stop' ? 'svcConfirmStop' : action === 'restart' ? 'svcConfirmRestart' : null;
1665
+ if (confirmKey && !confirm(t(confirmKey))) return;
1666
+ const stateEl = document.getElementById('svc-state');
1667
+ try {
1668
+ const res = await authFetch('/api/service/' + action, { method: 'POST' });
1669
+ if (action === 'restart') {
1670
+ // The HTTP response may not arrive before the parent process
1671
+ // SIGTERMs itself. Either way, drop into a "wait for reconnect" loop.
1672
+ if (stateEl) stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('svcRestarting'));
1673
+ await waitForServiceBack(stateEl);
1674
+ if (stateEl) {
1675
+ // loadServiceStatus refreshes the badge to live state.
1676
+ await loadServiceStatus();
1677
+ toast(t('svcRestarted'), 'success');
1678
+ }
1679
+ return;
1680
+ }
1681
+ if (action === 'stop') {
1682
+ if (stateEl) stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('svcStopped'));
1683
+ // Don't poll — the server is gone.
1684
+ return;
1685
+ }
1686
+ if (!res.ok) {
1687
+ const j = await res.json().catch(() => ({}));
1688
+ throw new Error(j.error || res.statusText);
1689
+ }
1690
+ await loadServiceStatus();
1691
+ } catch (err) {
1692
+ toast(t('error') + ': ' + (err && err.message ? err.message : err), 'error');
1693
+ }
1694
+ }
1695
+ async function waitForServiceBack(stateEl) {
1696
+ // The new daemon takes ~2-4s to come back. Poll /api/service/status
1697
+ // every 700ms for up to 30s. Each successful response means the new
1698
+ // process has bound the port.
1699
+ const start = Date.now();
1700
+ for (;;) {
1701
+ if (Date.now() - start > 30000) {
1702
+ if (stateEl) stateEl.innerHTML = '<span class="dot dot-off"></span>' + esc(t('error'));
1703
+ return;
1704
+ }
1705
+ try {
1706
+ const r = await fetch('/api/service/status', { credentials: 'same-origin' });
1707
+ if (r.ok) return;
1708
+ } catch { /* not back yet */ }
1709
+ await new Promise(r => setTimeout(r, 700));
1710
+ }
1711
+ }
1712
+
1305
1713
  // ==========================================
1306
1714
  // API helpers
1307
1715
  // ==========================================
@@ -1313,6 +1721,8 @@
1313
1721
  defaultAgent: config.defaultAgent,
1314
1722
  telegram: config.telegram,
1315
1723
  feishu: config.feishu,
1724
+ dingtalk: config.dingtalk,
1725
+ discord: config.discord,
1316
1726
  acpAgents: config.acpAgents,
1317
1727
  webPort: config.webPort,
1318
1728
  };
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAqDA,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAmlB/C"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAqDA,wBAAgB,iBAAiB,IAAI,CAAC,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAknB/C"}