tagword-codeagent 1.1.6__py3-none-any.whl

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 (173) hide show
  1. codeagent/__init__.py +10 -0
  2. codeagent/__main__.py +13 -0
  3. codeagent/cli/__init__.py +13 -0
  4. codeagent/cli/entrypoint.py +161 -0
  5. codeagent/cli/main.py +979 -0
  6. codeagent/core/__init__.py +2 -0
  7. codeagent/core/attachments.py +342 -0
  8. codeagent/core/audio_models.py +50 -0
  9. codeagent/core/bootstrap.py +178 -0
  10. codeagent/core/default_presets.py +159 -0
  11. codeagent/core/env.py +106 -0
  12. codeagent/core/folder_picker.py +151 -0
  13. codeagent/core/image_understanding.py +90 -0
  14. codeagent/core/models.py +6 -0
  15. codeagent/core/paths.py +238 -0
  16. codeagent/core/pricing.py +107 -0
  17. codeagent/core/process_ports.py +258 -0
  18. codeagent/core/seed_bridge.py +21 -0
  19. codeagent/core/settings.py +98 -0
  20. codeagent/core/speech_synth.py +330 -0
  21. codeagent/core/team_config.py +247 -0
  22. codeagent/core/team_manager.py +245 -0
  23. codeagent/core/token_counter.py +41 -0
  24. codeagent/core/tts_voices.py +103 -0
  25. codeagent/core/usage_billing.py +72 -0
  26. codeagent/core/vision_models.py +93 -0
  27. codeagent/memory/diary.py +144 -0
  28. codeagent/persona_defaults/README.md +12 -0
  29. codeagent/persona_defaults/agent.md +352 -0
  30. codeagent/persona_defaults/identity.md +29 -0
  31. codeagent/persona_defaults/memory.md +4 -0
  32. codeagent/persona_defaults/skills.md +325 -0
  33. codeagent/persona_defaults/soul.md +38 -0
  34. codeagent/persona_defaults/tools.md +41 -0
  35. codeagent/persona_defaults/user.md +18 -0
  36. codeagent/runtime/__init__.py +11 -0
  37. codeagent/runtime/agent_context.py +6 -0
  38. codeagent/runtime/agent_runtime.py +21 -0
  39. codeagent/runtime/compact_prompt.py +9 -0
  40. codeagent/runtime/compact_state.py +40 -0
  41. codeagent/runtime/llm_worker.py +132 -0
  42. codeagent/runtime/orchestrator.py +161 -0
  43. codeagent/runtime/prompt_enrichment.py +324 -0
  44. codeagent/runtime/session_manager.py +30 -0
  45. codeagent/runtime/task_split.py +38 -0
  46. codeagent/runtime/turn_loop.py +13 -0
  47. codeagent/runtime/worker.py +48 -0
  48. codeagent/server/__init__.py +505 -0
  49. codeagent/server/app_factory.py +1292 -0
  50. codeagent/server/attachment_api.py +261 -0
  51. codeagent/server/hub.py +175 -0
  52. codeagent/server/icon.png +0 -0
  53. codeagent/server/self_healing.py +143 -0
  54. codeagent/server/team_engine.py +243 -0
  55. codeagent/server/tts_api.py +51 -0
  56. codeagent/server/webui_api_app.py +3180 -0
  57. codeagent/skills/__init__.py +2 -0
  58. codeagent/skills/select.py +144 -0
  59. codeagent/tools/__init__.py +6 -0
  60. codeagent/tools/agent_tools.py +10 -0
  61. codeagent/web/__init__.py +1 -0
  62. codeagent/web/__pycache__/__init__.cpython-313.pyc +0 -0
  63. codeagent/web/__pycache__/app.cpython-313.pyc +0 -0
  64. codeagent/web/__pycache__/auth_impl.cpython-313.pyc +0 -0
  65. codeagent/web/auth_impl.py +326 -0
  66. codeagent/web/static/00-marked.min.js +6 -0
  67. codeagent/web/static/00-mermaid.min.js +2314 -0
  68. codeagent/web/static/00-purify.min.js +3 -0
  69. codeagent/web/static/00-storage.js +83 -0
  70. codeagent/web/static/00-utils.js +51 -0
  71. codeagent/web/static/01-theme.css +96 -0
  72. codeagent/web/static/01a-scroll.js +86 -0
  73. codeagent/web/static/01b-think.js +62 -0
  74. codeagent/web/static/01c-session-identity.js +81 -0
  75. codeagent/web/static/01c-session-thinking.js +61 -0
  76. codeagent/web/static/01c-session-tree-menus.js +349 -0
  77. codeagent/web/static/01c-session-tree.js +531 -0
  78. codeagent/web/static/01c-session-utilities.js +83 -0
  79. codeagent/web/static/01c-vision-model.js +132 -0
  80. codeagent/web/static/01d-image-gen-model.js +84 -0
  81. codeagent/web/static/01e-audio-model.js +97 -0
  82. codeagent/web/static/01f-compose-ui.js +88 -0
  83. codeagent/web/static/01g-tts-settings.js +128 -0
  84. codeagent/web/static/01h-music-model.js +85 -0
  85. codeagent/web/static/01i-model-stack.js +291 -0
  86. codeagent/web/static/01j-video-gen-model.js +85 -0
  87. codeagent/web/static/01k-model-stack-bootstrap.js +66 -0
  88. codeagent/web/static/01k-model-stack-state.js +209 -0
  89. codeagent/web/static/01q-inflight.js +195 -0
  90. codeagent/web/static/02-reset.css +22 -0
  91. codeagent/web/static/02-session-ui.js +201 -0
  92. codeagent/web/static/03-layout-p1.css +2 -0
  93. codeagent/web/static/03-layout-p2.css +2 -0
  94. codeagent/web/static/03a-links-stream.js +88 -0
  95. codeagent/web/static/03b-stream-text.js +119 -0
  96. codeagent/web/static/03c-render-bubble.js +520 -0
  97. codeagent/web/static/03d-agent-bubble.js +126 -0
  98. codeagent/web/static/03e-tool-trace.js +182 -0
  99. codeagent/web/static/03f-timeline.js +54 -0
  100. codeagent/web/static/03g-bubble-tts.js +292 -0
  101. codeagent/web/static/04-components-p1.css +1 -0
  102. codeagent/web/static/04-components-p2b.css +1 -0
  103. codeagent/web/static/04-components-p3.css +2 -0
  104. codeagent/web/static/04-components-p4.css +1 -0
  105. codeagent/web/static/04-components-p5.css +1 -0
  106. codeagent/web/static/04-components-p6.css +58 -0
  107. codeagent/web/static/04-components-p7.css +4 -0
  108. codeagent/web/static/04-components-p8.css +1 -0
  109. codeagent/web/static/04-components-p9.css +1 -0
  110. codeagent/web/static/04-ws-connect.js +351 -0
  111. codeagent/web/static/04-ws-helpers.js +116 -0
  112. codeagent/web/static/05-chat-p1.css +1058 -0
  113. codeagent/web/static/05-chat-p2.css +1 -0
  114. codeagent/web/static/05-chat-p3.css +295 -0
  115. codeagent/web/static/05a-session-history.js +176 -0
  116. codeagent/web/static/05b-session-list.js +114 -0
  117. codeagent/web/static/05c-dialog.js +123 -0
  118. codeagent/web/static/05d-project-create.js +172 -0
  119. codeagent/web/static/05e-project-remote.js +143 -0
  120. codeagent/web/static/06-chat.js +552 -0
  121. codeagent/web/static/06-utilities.css +181 -0
  122. codeagent/web/static/06b-attachments.js +304 -0
  123. codeagent/web/static/06c-camera.js +488 -0
  124. codeagent/web/static/07-activity-bar.css +127 -0
  125. codeagent/web/static/09-git-p1.css +1 -0
  126. codeagent/web/static/09-git-p2.css +1 -0
  127. codeagent/web/static/09a-init-bind.js +110 -0
  128. codeagent/web/static/09b-panel-toggle.js +80 -0
  129. codeagent/web/static/09c-api-refresh.js +100 -0
  130. codeagent/web/static/09d-status-log.js +80 -0
  131. codeagent/web/static/09e-commit.js +80 -0
  132. codeagent/web/static/09f-remote.js +93 -0
  133. codeagent/web/static/11a-cron-parse.js +117 -0
  134. codeagent/web/static/11b-cron-panel.js +100 -0
  135. codeagent/web/static/11c-cron-card.js +133 -0
  136. codeagent/web/static/11d-cron-form.js +105 -0
  137. codeagent/web/static/11e-env-config.js +168 -0
  138. codeagent/web/static/11f-env-llm-a-state.js +136 -0
  139. codeagent/web/static/11f-env-llm-b-form.js +409 -0
  140. codeagent/web/static/11f-env-llm-c-board.js +322 -0
  141. codeagent/web/static/11g-env-paths-git.js +973 -0
  142. codeagent/web/static/11h-env-mcp-a-state.js +161 -0
  143. codeagent/web/static/11h-env-mcp-b-form.js +229 -0
  144. codeagent/web/static/11h-env-mcp-c-board.js +565 -0
  145. codeagent/web/static/12-plugins.js +127 -0
  146. codeagent/web/static/13a-skills.js +156 -0
  147. codeagent/web/static/13a-todo-utils.js +140 -0
  148. codeagent/web/static/13b-todo-fetch.js +106 -0
  149. codeagent/web/static/13b-tools.js +127 -0
  150. codeagent/web/static/13c-todo-crud.js +92 -0
  151. codeagent/web/static/14-activity-bar.js +177 -0
  152. codeagent/web/static/15-plans.css +194 -0
  153. codeagent/web/static/15-plans.js +219 -0
  154. codeagent/web/static/16-mobile.css +612 -0
  155. codeagent/web/static/16-mobile.js +178 -0
  156. codeagent/web/static/17-preset-ux.css +287 -0
  157. codeagent/web/static/18-agent-mgr.css +616 -0
  158. codeagent/web/static/18-agent-mgr.js +204 -0
  159. codeagent/web/static/24-health-dashboard.js +68 -0
  160. codeagent/web/static/99-brand-polish.css +152 -0
  161. codeagent/web/static/body.html +824 -0
  162. codeagent/web/static/setup/setup_form.html +171 -0
  163. codeagent/web/static/setup/setup_head.html +100 -0
  164. codeagent/web/static/setup/setup_tail.html +264 -0
  165. codeagent/web/static/setup/setup_test.html +38 -0
  166. codeagent/web/web_login.html +265 -0
  167. codeagent/web/webui.html +35 -0
  168. tagword_codeagent-1.1.6.dist-info/METADATA +273 -0
  169. tagword_codeagent-1.1.6.dist-info/RECORD +173 -0
  170. tagword_codeagent-1.1.6.dist-info/WHEEL +5 -0
  171. tagword_codeagent-1.1.6.dist-info/entry_points.txt +3 -0
  172. tagword_codeagent-1.1.6.dist-info/licenses/LICENSE +21 -0
  173. tagword_codeagent-1.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,565 @@
1
+ /* ================================================================
2
+ * 11h-env-mcp-c-board.js
3
+ * MCP server 卡片构建、渲染、网络层、init IIFE。
4
+ * 依赖:a-state.js (isMinimaxMcpServer / mcpStatusForId / mcpStatusLine / formatArgsList)
5
+ * b-form.js (mcpGenericFormHtml / mcpMinimaxFormHtml / collectServerFromWrap
6
+ * / collectAllMcpServersFromBoard)
7
+ * 上游依赖:00-utils.js (escapeHtml)。
8
+ *
9
+ * DOM 缓存:mcpDom = { board, status, chkOn, chkReg, pathEl, uvxEl,
10
+ * btn, ref, addBtn, tplSel } 在 initMcpBoard IIFE
11
+ * 中一次性 getElementById 填充。
12
+ * ================================================================ */
13
+
14
+ const mcpDom = {};
15
+
16
+ /* ---- 卡片构建 ---- */
17
+
18
+ function buildMcpServerCard(serverId, cfg, status, meta) {
19
+ meta = meta || {};
20
+ const wrap = document.createElement('div');
21
+ wrap.className = 'mcp-card-wrap';
22
+ wrap.setAttribute('data-server-id', serverId);
23
+
24
+ const minimax = isMinimaxMcpServer(serverId, cfg);
25
+ const transport = cfg.transport || 'stdio';
26
+
27
+ // ---- Header(始终可见) ----
28
+ const header = document.createElement('div');
29
+ header.className = 'mcp-card';
30
+
31
+ const info = document.createElement('div');
32
+ info.className = 'mcp-card__info';
33
+
34
+ // 第一行:服务名 + 传输标签 + 状态
35
+ const titleRow = document.createElement('div');
36
+ titleRow.style.cssText = 'display:flex;align-items:center;gap:var(--sp-1);flex-wrap:wrap;';
37
+
38
+ const title = document.createElement('span');
39
+ title.className = 'mcp-card__name';
40
+ title.textContent = serverId + (minimax ? ' · MiniMax' : '');
41
+
42
+ const tTag = document.createElement('span');
43
+ tTag.className = 'mcp-card__transport';
44
+ if (transport === 'sse') {
45
+ tTag.textContent = 'SSE';
46
+ tTag.style.cssText = 'font-size:11px;padding:1px 6px;border-radius:var(--r-sm);background:#fff3e0;color:#e65100;font-weight:500;';
47
+ } else if (transport === 'streamable-http') {
48
+ tTag.textContent = 'Streamable HTTP';
49
+ tTag.style.cssText = 'font-size:11px;padding:1px 6px;border-radius:var(--r-sm);background:var(--accent-dim, #e8f0fe);color:var(--accent,#1a73e8);font-weight:500;';
50
+ } else {
51
+ tTag.textContent = 'stdio';
52
+ tTag.style.cssText = 'font-size:11px;padding:1px 6px;border-radius:var(--r-sm);background:var(--bg-2,#eee);color:var(--fg-2,#666);font-weight:500;';
53
+ }
54
+
55
+ const badge = document.createElement('span');
56
+ badge.className = 'mcp-card__badge' + (status && status.connected ? ' mcp-card__badge--ok' : '');
57
+ badge.textContent = mcpStatusLine(status);
58
+
59
+ titleRow.appendChild(title);
60
+ titleRow.appendChild(tTag);
61
+ titleRow.appendChild(badge);
62
+
63
+ // 第二行:详情(command / url)
64
+ const detail = document.createElement('div');
65
+ detail.className = 'mcp-card__detail';
66
+ if (transport === 'sse' || transport === 'streamable-http') {
67
+ detail.textContent = '→ ' + (cfg.url || '未配置 URL');
68
+ } else {
69
+ detail.textContent = '$ ' + (cfg.command || '') + (cfg.args && cfg.args.length ? ' ' + formatArgsList(cfg.args) : '');
70
+ }
71
+
72
+ // 错误信息(未连接时显示)
73
+ if (status && !status.connected && status.last_error) {
74
+ const errLine = document.createElement('div');
75
+ errLine.className = 'mcp-card__error';
76
+ errLine.textContent = '⚠ ' + status.last_error;
77
+ info.appendChild(titleRow);
78
+ info.appendChild(detail);
79
+ info.appendChild(errLine);
80
+ } else {
81
+ info.appendChild(titleRow);
82
+ info.appendChild(detail);
83
+ }
84
+
85
+ // ---- 操作按钮 ----
86
+ const actions = document.createElement('div');
87
+ actions.className = 'mcp-card__actions';
88
+
89
+ const btnTest = document.createElement('button');
90
+ btnTest.type = 'button';
91
+ btnTest.className = 'btn btn--ghost btn--xs';
92
+ btnTest.textContent = '测试';
93
+ btnTest.addEventListener('click', function(ev) {
94
+ ev.stopPropagation();
95
+ testMcpCard(wrap, meta);
96
+ });
97
+
98
+ const btnToggle = document.createElement('button');
99
+ btnToggle.type = 'button';
100
+ btnToggle.className = 'btn btn--ghost btn--xs';
101
+ btnToggle.textContent = cfg.enabled === false ? '启用' : '停用';
102
+
103
+ const btnDel = document.createElement('button');
104
+ btnDel.type = 'button';
105
+ btnDel.className = 'btn btn--ghost btn--xs mcp-del';
106
+ btnDel.textContent = '删除';
107
+ btnDel.addEventListener('click', function(ev) {
108
+ ev.stopPropagation();
109
+ if (!confirm('确定删除 MCP 服务「' + serverId + '」?')) return;
110
+ wrap.remove();
111
+ updateMcpBoardEmptyState();
112
+ var statusEl = mcpDom.status;
113
+ if (statusEl) statusEl.textContent = '已删除,正在保存…';
114
+ saveMcpEnvAndConfig().catch(function(e) {
115
+ if (statusEl) {
116
+ statusEl.classList.add('is-err');
117
+ statusEl.textContent = '删除成功但保存失败:' + String(e.message || e) + ',请刷新后重试。';
118
+ }
119
+ });
120
+ });
121
+
122
+ actions.appendChild(btnTest);
123
+ actions.appendChild(btnToggle);
124
+ actions.appendChild(btnDel);
125
+
126
+ header.appendChild(info);
127
+ header.appendChild(actions);
128
+
129
+ // ---- 编辑区(可展开) ----
130
+ const editWrap = document.createElement('div');
131
+ editWrap.className = 'mcp-edit-wrap';
132
+ editWrap.style.display = 'none';
133
+ const editStatus = document.createElement('div');
134
+ editStatus.className = 'status-line';
135
+
136
+ if (minimax) {
137
+ editWrap.innerHTML = mcpMinimaxFormHtml(cfg, meta);
138
+ } else {
139
+ editWrap.innerHTML = '<input type="hidden" class="mcp-fld-kind" value="generic"/>' +
140
+ mcpGenericFormHtml(cfg, { serverId: serverId, idReadonly: true });
141
+ }
142
+
143
+ btnToggle.addEventListener('click', function(ev) {
144
+ ev.stopPropagation();
145
+ toggleMcpCardEnabled(wrap, cfg.enabled === false, serverId);
146
+ });
147
+
148
+ // 内联操作栏:保存 + 测试 + 删除
149
+ const editActions = document.createElement('div');
150
+ editActions.className = 'row-actions mcp-edit-actions';
151
+ editActions.style.cssText = 'margin:var(--sp-2) 0 0;display:flex;gap:var(--sp-1);';
152
+ editActions.innerHTML =
153
+ '<button type="button" class="btn btn--primary btn--sm mcp-apply-btn">应用</button>' +
154
+ '<button type="button" class="btn btn--ghost btn--sm mcp-test-btn">测试</button>' +
155
+ '<button type="button" class="btn btn--subtle btn--sm mcp-edit-del-btn" style="margin-left:auto;color:var(--danger);">删除</button>';
156
+ editWrap.appendChild(editActions);
157
+ editWrap.appendChild(editStatus);
158
+
159
+ // 编辑窗「应用」按钮
160
+ editActions.querySelector('.mcp-apply-btn').addEventListener('click', function() {
161
+ applyMcpCard(wrap, meta);
162
+ });
163
+
164
+ // 编辑窗「测试」按钮
165
+ editActions.querySelector('.mcp-test-btn').addEventListener('click', function() {
166
+ testMcpCard(wrap, meta);
167
+ });
168
+
169
+ // 编辑窗「删除」按钮
170
+ editActions.querySelector('.mcp-edit-del-btn').addEventListener('click', function() {
171
+ if (!confirm('确定删除 MCP 服务「' + serverId + '」?')) return;
172
+ wrap.remove();
173
+ updateMcpBoardEmptyState();
174
+ var statusEl = mcpDom.status;
175
+ if (statusEl) statusEl.textContent = '已删除,正在保存…';
176
+ saveMcpEnvAndConfig().catch(function(e) {
177
+ if (statusEl) {
178
+ statusEl.classList.add('is-err');
179
+ statusEl.textContent = '删除失败:' + String(e.message || e);
180
+ }
181
+ });
182
+ });
183
+
184
+ // Header 点击展开/收起编辑区
185
+ header.style.cursor = 'pointer';
186
+ header.addEventListener('click', function(ev) {
187
+ if (ev.target.closest('.btn')) return;
188
+ var open = editWrap.style.display !== 'block';
189
+ editWrap.style.display = open ? 'block' : 'none';
190
+ editStatus.textContent = '';
191
+ editStatus.classList.remove('is-err');
192
+ });
193
+
194
+ wrap.appendChild(header);
195
+ wrap.appendChild(editWrap);
196
+ return wrap;
197
+ }
198
+
199
+ /** 应用单个卡片的修改到 board + 触发全局保存 */
200
+ function applyMcpCard(wrap, meta) {
201
+ var editStatus = wrap.querySelector('.mcp-edit-wrap .status-line');
202
+ if (editStatus) {
203
+ editStatus.textContent = '保存中…';
204
+ editStatus.classList.remove('is-err');
205
+ }
206
+ try {
207
+ var row = collectServerFromWrap(wrap, meta || {});
208
+ if (!row) throw new Error('请填写完整配置');
209
+
210
+ // 更新卡片 DOM 中的 data-server-id(如果 ID 变了)
211
+ wrap.setAttribute('data-server-id', row.id);
212
+
213
+ // 刷新卡片 header 显示
214
+ var board = mcpDom.board;
215
+ if (board) {
216
+ // 重建卡片(保留原有的 expand/close 状态比较麻烦,走全量 reload)
217
+ saveMcpEnvAndConfig();
218
+ }
219
+ } catch (e) {
220
+ if (editStatus) {
221
+ editStatus.classList.add('is-err');
222
+ editStatus.textContent = String(e.message || e);
223
+ }
224
+ }
225
+ }
226
+
227
+ function toggleMcpCardEnabled(wrap, nextEnabled, serverId) {
228
+ const chk = wrap.querySelector('.mcp-fld-enabled');
229
+ const statusEl = mcpDom.status || wrap.querySelector('.mcp-edit-wrap .status-line');
230
+ const prevEnabled = chk ? !!chk.checked : !nextEnabled;
231
+ if (chk) chk.checked = !!nextEnabled;
232
+ if (statusEl) {
233
+ statusEl.classList.remove('is-err');
234
+ statusEl.textContent = (nextEnabled ? '正在启用' : '正在停用') + '「' + serverId + '」…';
235
+ }
236
+ saveMcpEnvAndConfig({ throwOnError: true }).then(function() {
237
+ if (mcpDom.status) {
238
+ mcpDom.status.textContent = '✅ 已' + (nextEnabled ? '启用' : '停用') + '「' + serverId + '」。';
239
+ }
240
+ }).catch(function(e) {
241
+ if (chk) chk.checked = prevEnabled;
242
+ if (statusEl) {
243
+ statusEl.classList.add('is-err');
244
+ statusEl.textContent = '❌ ' + String(e.message || e);
245
+ }
246
+ });
247
+ }
248
+
249
+ /* ---- 渲染 ---- */
250
+
251
+ function renderMcpServerBoard(servers, statusRows, meta) {
252
+ const board = mcpDom.board;
253
+ if (!board) return;
254
+ board.innerHTML = '';
255
+ const ids = Object.keys(servers || {}).sort();
256
+ if (!ids.length) {
257
+ updateMcpBoardEmptyState();
258
+ return;
259
+ }
260
+ ids.forEach(function(sid) {
261
+ const st = mcpStatusForId(sid);
262
+ board.appendChild(buildMcpServerCard(sid, servers[sid], st, meta));
263
+ });
264
+ }
265
+
266
+ function updateMcpBoardEmptyState() {
267
+ const board = mcpDom.board;
268
+ if (!board) return;
269
+ if (board.querySelector('.mcp-card-wrap')) {
270
+ const empty = board.querySelector('.mcp-board-empty');
271
+ if (empty) empty.remove();
272
+ return;
273
+ }
274
+ if (!board.querySelector('.mcp-board-empty')) {
275
+ const p = document.createElement('p');
276
+ p.className = 'mcp-board-empty';
277
+ p.textContent = '尚未添加 MCP 服务。点击「新增」后选择 stdio、HTTP 或 SSE。stdio 可填写 uvx / npx / 本地二进制。';
278
+ board.appendChild(p);
279
+ }
280
+ }
281
+
282
+ /* ---- 网络层 ---- */
283
+
284
+ async function testMcpServerPayload(payload) {
285
+ const r = await fetch('/api/ui/mcp/test', {
286
+ method: 'POST',
287
+ headers: { 'Content-Type': 'application/json' },
288
+ credentials: 'same-origin',
289
+ body: JSON.stringify(payload),
290
+ });
291
+ const j = await r.json().catch(function() { return {}; });
292
+ if (!r.ok) throw new Error(j.detail || r.statusText || '测试失败');
293
+ return j;
294
+ }
295
+
296
+ async function testMcpCard(wrap, meta) {
297
+ const statusEl = wrap.querySelector('.mcp-edit-wrap .status-line') || mcpDom.status;
298
+ if (statusEl) {
299
+ statusEl.textContent = '测试中…';
300
+ statusEl.classList.remove('is-err');
301
+ }
302
+ try {
303
+ const row = collectServerFromWrap(wrap, meta);
304
+ if (!row) throw new Error('请填写完整配置');
305
+ const payload = Object.assign({ id: row.id }, row.config);
306
+ const j = await testMcpServerPayload(payload);
307
+ const tools = (j.tools || []).join('、');
308
+ const skills = (j.skills || []).join('、');
309
+ const details = [];
310
+ if (tools) details.push('工具:' + tools);
311
+ if (skills) details.push('skills:' + skills);
312
+ if (statusEl) statusEl.textContent = details.length ? ('✅ 测试通过:' + details.join(';')) : '✅ 测试通过';
313
+ if (!wrap.classList.contains('is-new')) await loadMcpEnvConfig();
314
+ } catch (e) {
315
+ if (statusEl) {
316
+ statusEl.classList.add('is-err');
317
+ statusEl.textContent = '❌ ' + String(e.message || e);
318
+ }
319
+ }
320
+ }
321
+
322
+ /* ---- 添加新服务 ---- */
323
+
324
+ function showNewMcpServerForm(templateId) {
325
+ const board = mcpDom.board;
326
+ if (!board) return;
327
+ const existing = board.querySelector('.mcp-card-wrap.is-new');
328
+ if (existing) existing.remove();
329
+
330
+ const tpl = MCP_TEMPLATES.find(function(t) { return t.id === templateId; })
331
+ || MCP_TEMPLATES.find(function(t) { return t.id === 'custom'; })
332
+ || MCP_TEMPLATES[MCP_TEMPLATES.length - 1];
333
+ const wrap = document.createElement('div');
334
+ wrap.className = 'mcp-card-wrap is-new';
335
+
336
+ const editWrap = document.createElement('div');
337
+ editWrap.className = 'mcp-edit-wrap';
338
+ editWrap.style.display = 'block';
339
+
340
+ // 新表单自带一个较大的 header 说明
341
+ const hintBar = document.createElement('div');
342
+ hintBar.style.cssText = 'padding:var(--sp-1) var(--sp-2);background:var(--accent-dim,#e8f0fe);border-radius:var(--r-md) var(--r-md) 0 0;font-size:var(--fs-sm);color:var(--accent,#1a73e8);';
343
+ hintBar.textContent = '添加新 MCP 服务';
344
+
345
+ const editStatus = document.createElement('div');
346
+ editStatus.className = 'status-line';
347
+
348
+ if (tpl.kind === 'minimax') {
349
+ editWrap.innerHTML = mcpMinimaxFormHtml({ enabled: true, command: 'uvx', env: {} }, window._mcpMeta || {});
350
+ } else {
351
+ editWrap.innerHTML = '<input type="hidden" class="mcp-fld-kind" value="generic"/>' +
352
+ mcpGenericFormHtml(tpl.config || {}, { serverId: tpl.serverId || '' });
353
+ }
354
+
355
+ const actions = document.createElement('div');
356
+ actions.className = 'row-actions';
357
+ actions.style.marginTop = 'var(--sp-2)';
358
+ actions.innerHTML =
359
+ '<button type="button" class="btn btn--primary btn--sm mcp-new-save">添加并保存</button>' +
360
+ '<button type="button" class="btn btn--ghost btn--sm mcp-new-test">测试</button>' +
361
+ '<button type="button" class="btn btn--subtle btn--sm mcp-new-cancel">取消</button>';
362
+ editWrap.appendChild(actions);
363
+ editWrap.appendChild(editStatus);
364
+
365
+ if (tpl.hint) {
366
+ const hint = document.createElement('p');
367
+ hint.className = 'form-hint';
368
+ hint.textContent = tpl.hint;
369
+ editWrap.insertBefore(hint, actions);
370
+ }
371
+
372
+ actions.querySelector('.mcp-new-cancel').addEventListener('click', function() { wrap.remove(); updateMcpBoardEmptyState(); });
373
+ actions.querySelector('.mcp-new-test').addEventListener('click', function() { testMcpCard(wrap, window._mcpMeta || {}); });
374
+ actions.querySelector('.mcp-new-save').addEventListener('click', function() {
375
+ var saveBtn = actions.querySelector('.mcp-new-save');
376
+ try {
377
+ const row = collectServerFromWrap(wrap, window._mcpMeta || {});
378
+ if (!row) throw new Error('请填写完整配置');
379
+ if (board.querySelector('.mcp-card-wrap[data-server-id="' + row.id + '"]')) {
380
+ throw new Error('服务 ID「' + row.id + '」已存在');
381
+ }
382
+ if (saveBtn) saveBtn.disabled = true;
383
+ const empty = board.querySelector('.mcp-board-empty');
384
+ if (empty) empty.remove();
385
+ const card = buildMcpServerCard(row.id, row.config, null, window._mcpMeta || {});
386
+ board.appendChild(card);
387
+ wrap.remove();
388
+ editStatus.textContent = '已添加,正在保存…';
389
+ saveMcpEnvAndConfig({ throwOnError: true }).then(function() {
390
+ if (mcpDom.status) mcpDom.status.textContent = '✅ 已添加并保存。';
391
+ }).catch(function(e) {
392
+ if (saveBtn) saveBtn.disabled = false;
393
+ editStatus.classList.add('is-err');
394
+ editStatus.textContent = '❌ 添加成功但保存失败:' + String(e.message || e) + ',请刷新后重试。';
395
+ });
396
+ } catch (e) {
397
+ editStatus.classList.add('is-err');
398
+ editStatus.textContent = '❌ ' + String(e.message || e);
399
+ }
400
+ });
401
+
402
+ wrap.appendChild(hintBar);
403
+ wrap.appendChild(editWrap);
404
+ board.insertBefore(wrap, board.firstChild);
405
+ const empty = board.querySelector('.mcp-board-empty');
406
+ if (empty) empty.remove();
407
+ wrap.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
408
+ }
409
+
410
+ /* ---- 数据加载与保存 ---- */
411
+
412
+ async function loadMcpEnvConfig() {
413
+ const status = mcpDom.status;
414
+ if (!status) return;
415
+ status.textContent = '加载中…';
416
+ status.classList.remove('is-err');
417
+ try {
418
+ const [envR, mcpR] = await Promise.all([
419
+ fetch('/api/ui/env/mcp'),
420
+ fetch('/api/ui/mcp'),
421
+ ]);
422
+ if (!envR.ok) throw new Error(envR.statusText);
423
+ if (!mcpR.ok) throw new Error(mcpR.statusText);
424
+ const envJ = await envR.json();
425
+ const mcpJ = await mcpR.json();
426
+
427
+ if (mcpDom.pathEl) mcpDom.pathEl.textContent = mcpJ.path || '';
428
+
429
+ const uvxEl = mcpDom.uvxEl;
430
+ if (uvxEl) {
431
+ uvxEl.textContent = mcpJ.uvx_path
432
+ ? ('已检测到 uvx:' + mcpJ.uvx_path)
433
+ : '未检测到 uvx;MiniMax / uvx 类 MCP 请将 command 填为 uvx 的绝对路径';
434
+ }
435
+
436
+ const servers = (mcpJ.config && mcpJ.config.servers) ? mcpJ.config.servers : {};
437
+ window._mcpConfigCache = { servers: servers };
438
+
439
+ // 初始状态:仅显示已缓存的状态(不 probe 不阻塞)
440
+ window._mcpStatusCache = mcpJ.servers_status || [];
441
+ window._mcpMeta = {
442
+ uvx_path: mcpJ.uvx_path || '',
443
+ minimax_output_dir: mcpJ.minimax_output_dir || '',
444
+ };
445
+
446
+ renderMcpServerBoard(servers, window._mcpStatusCache, window._mcpMeta);
447
+
448
+ if (typeof refreshVisionModelSelect === 'function') refreshVisionModelSelect();
449
+ status.textContent = '共 ' + Object.keys(servers).length + ' 个 MCP 服务';
450
+
451
+ // ⚡ 异步探测:页面渲染完成后,后台检测 MCP 连接状态
452
+ // 不阻塞 UI,探测完成后自动刷新卡片状态
453
+ loadMcpStatusProbe().catch(function(e) {
454
+ // probe 失败不阻塞 UI,静默处理
455
+ if (status) {
456
+ status.textContent = status.textContent + '(探测中)';
457
+ }
458
+ });
459
+ } catch (e) {
460
+ status.classList.add('is-err');
461
+ status.textContent = '加载失败:' + String(e);
462
+ }
463
+ }
464
+
465
+ async function loadMcpStatusProbe() {
466
+ try {
467
+ const r = await fetch('/api/ui/mcp/probe', { credentials: 'same-origin' });
468
+ if (!r.ok) return;
469
+ const j = await r.json();
470
+ window._mcpStatusCache = j.servers_status || [];
471
+ window._mcpMeta = {
472
+ uvx_path: j.uvx_path || '',
473
+ minimax_output_dir: j.minimax_output_dir || '',
474
+ };
475
+ const servers = (window._mcpConfigCache && window._mcpConfigCache.servers) || {};
476
+ renderMcpServerBoard(servers, window._mcpStatusCache, window._mcpMeta);
477
+
478
+ // 刷新识图选项(探测结果可能影响 MCP 识图可用性)
479
+ if (typeof refreshVisionModelSelect === 'function') refreshVisionModelSelect();
480
+
481
+ const status = mcpDom.status;
482
+ if (status) {
483
+ const count = Object.keys(servers).length;
484
+ const connected = window._mcpStatusCache.filter(function(s) { return s.connected; }).length;
485
+ status.textContent = '共 ' + count + ' 个 MCP 服务 · ' + connected + ' 已连接';
486
+ }
487
+ } catch (_) {
488
+ // probe 失败——不抛异常,静默保留初始状态
489
+ }
490
+ }
491
+
492
+ async function saveMcpEnvAndConfig(opts) {
493
+ opts = opts || {};
494
+ const status = mcpDom.status;
495
+ if (!status) return;
496
+ status.textContent = '保存中…';
497
+ status.classList.remove('is-err');
498
+ try {
499
+ const envBody = {
500
+ SEED_MCP_ENABLED: '1',
501
+ SEED_MCP_REGISTER_TOOLS: '1',
502
+ };
503
+ const envR = await fetch('/api/ui/env/mcp', {
504
+ method: 'POST',
505
+ headers: { 'Content-Type': 'application/json' },
506
+ credentials: 'same-origin',
507
+ body: JSON.stringify(envBody),
508
+ });
509
+ const envJ = await envR.json().catch(function() { return {}; });
510
+ if (!envR.ok) throw new Error(envJ.detail || envR.statusText);
511
+
512
+ const servers = collectAllMcpServersFromBoard(window._mcpMeta || {});
513
+ const mcpR = await fetch('/api/ui/mcp', {
514
+ method: 'POST',
515
+ headers: { 'Content-Type': 'application/json' },
516
+ credentials: 'same-origin',
517
+ body: JSON.stringify({ servers: servers }),
518
+ });
519
+ const mcpSave = await mcpR.json().catch(function() { return {}; });
520
+ if (!mcpR.ok) throw new Error(mcpSave.detail || mcpR.statusText);
521
+
522
+ status.textContent = '✅ 已保存 ' + Object.keys(servers).length + ' 个 MCP 服务。';
523
+ await loadMcpEnvConfig();
524
+ if (typeof refreshMultimodalModelSelects === 'function') {
525
+ refreshMultimodalModelSelects().catch(function() {});
526
+ } else if (typeof refreshTtsOptions === 'function') {
527
+ refreshTtsOptions().catch(function() {});
528
+ }
529
+ } catch (e) {
530
+ status.classList.add('is-err');
531
+ status.textContent = '❌ ' + String(e.message || e);
532
+ if (opts.throwOnError) throw e;
533
+ }
534
+ }
535
+
536
+ /* ---- Init ---- */
537
+
538
+ (function initMcpBoard() {
539
+ [
540
+ ['board', 'mcpServerBoard'],
541
+ ['status', 'mcpEnvStatus'],
542
+ ['pathEl', 'mcpConfigPath'],
543
+ ['uvxEl', 'mcpUvxHint'],
544
+ ['btn', 'btnMcpEnvSave'],
545
+ ['ref', 'btnMcpEnvRefresh'],
546
+ ['addBtn', 'btnMcpAdd'],
547
+ ].forEach(function(pair) {
548
+ mcpDom[pair[0]] = document.getElementById(pair[1]);
549
+ });
550
+
551
+ if (mcpDom.btn) {
552
+ mcpDom.btn.addEventListener('click', function() { saveMcpEnvAndConfig(); });
553
+ }
554
+ if (mcpDom.ref) {
555
+ mcpDom.ref.addEventListener('click', function() { loadMcpEnvConfig(); });
556
+ }
557
+
558
+ if (mcpDom.addBtn) {
559
+ mcpDom.addBtn.addEventListener('click', function() {
560
+ showNewMcpServerForm('custom');
561
+ });
562
+ }
563
+
564
+ loadMcpEnvConfig();
565
+ })();
@@ -0,0 +1,127 @@
1
+ /* ================================================================
2
+ * 12-plugins.js
3
+ * - System-prompt MD file list: click to edit inline (no toggles).
4
+ *
5
+ * Depends on (from earlier files): agentId
6
+ * ================================================================ */
7
+
8
+ const mdFileList = document.getElementById('mdFileList');
9
+ const mdFileStatus = document.getElementById('mdFileStatus');
10
+
11
+ var _mdAgentId = null;
12
+
13
+ function _currentMdAgentId() {
14
+ return _mdAgentId || agentId;
15
+ }
16
+
17
+ async function loadMdFiles(overrideAgentId) {
18
+ _mdAgentId = overrideAgentId || agentId;
19
+ if (!mdFileList || !mdFileStatus) return;
20
+ mdFileStatus.textContent = '加载中…';
21
+ mdFileStatus.classList.remove('is-err');
22
+ try {
23
+ // Get the list of config filenames from the plugins endpoint
24
+ const r = await fetch('/api/ui/plugins');
25
+ const j = await r.json();
26
+ if (!r.ok) throw new Error(j.detail || r.statusText);
27
+ const names = j.config_filenames || [];
28
+ mdFileList.innerHTML = '';
29
+ if (names.length === 0) {
30
+ mdFileList.innerHTML = '<div class="cron-empty">暂无配置文件。</div>';
31
+ mdFileStatus.textContent = '';
32
+ return;
33
+ }
34
+ names.forEach(function(name) {
35
+ mdFileList.appendChild(buildMdFileCard(name));
36
+ });
37
+ mdFileStatus.textContent = '';
38
+ } catch (e) {
39
+ mdFileStatus.classList.add('is-err');
40
+ mdFileStatus.textContent = String(e);
41
+ }
42
+ }
43
+
44
+ function buildMdFileCard(name) {
45
+ var wrap = document.createElement('div'); wrap.className = 'cron-card-wrap';
46
+
47
+ var header = document.createElement('div'); header.className = 'cron-card';
48
+ var info = document.createElement('div'); info.className = 'cron-card__info';
49
+ var nameRow = document.createElement('div'); nameRow.className = 'cron-card__name';
50
+ nameRow.textContent = name;
51
+ info.appendChild(nameRow);
52
+ header.appendChild(info);
53
+ header.style.cursor = 'pointer';
54
+
55
+ var editWrap = document.createElement('div'); editWrap.className = 'cron-edit-wrap'; editWrap.style.display = 'none';
56
+ var editStatus = document.createElement('div'); editStatus.className = 'status-line';
57
+ var textarea = document.createElement('textarea');
58
+ textarea.className = 'skill-fld-body';
59
+ textarea.spellcheck = false;
60
+ textarea.style.cssText = 'height:300px;font-family:var(--font-mono);font-size:13px;width:100%;padding:var(--sp-2);border:1px solid var(--border);border-radius:var(--r-sm);background:var(--bg);color:var(--text);resize:vertical;';
61
+ textarea.placeholder = '加载中…';
62
+ editWrap.appendChild(textarea);
63
+
64
+ var actionsRow = document.createElement('div');
65
+ actionsRow.className = 'row-actions';
66
+ actionsRow.style.marginTop = 'var(--sp-2)';
67
+
68
+ var btnSave = document.createElement('button');
69
+ btnSave.type = 'button'; btnSave.className = 'btn btn--primary btn--sm';
70
+ btnSave.textContent = '保存';
71
+ actionsRow.appendChild(btnSave);
72
+
73
+ var btnCancel = document.createElement('button');
74
+ btnCancel.type = 'button'; btnCancel.className = 'btn btn--subtle btn--sm';
75
+ btnCancel.textContent = '取消';
76
+ actionsRow.appendChild(btnCancel);
77
+
78
+ editWrap.appendChild(actionsRow);
79
+ editWrap.appendChild(editStatus);
80
+
81
+ var toggleEdit = function(show) {
82
+ editWrap.style.display = show ? 'block' : 'none';
83
+ editStatus.textContent = '';
84
+ editStatus.classList.remove('is-err');
85
+ };
86
+
87
+ // Click header to toggle edit
88
+ header.addEventListener('click', function(e) {
89
+ if (e.target.closest('.btn')) return;
90
+ var opening = editWrap.style.display !== 'block';
91
+ toggleEdit(opening);
92
+ if (opening) {
93
+ loadMdFileContent(name, textarea, editStatus);
94
+ }
95
+ });
96
+
97
+ btnCancel.addEventListener('click', function() { toggleEdit(false); });
98
+ btnSave.addEventListener('click', async function() {
99
+ var content = textarea.value;
100
+ editStatus.textContent = '保存中…';
101
+ editStatus.classList.remove('is-err');
102
+ try {
103
+ var sr = await fetch('/api/ui/md/' + encodeURIComponent(name) + '?agent_id=' + encodeURIComponent(agentId), {
104
+ method: 'POST', headers: { 'Content-Type': 'text/plain; charset=utf-8' }, body: content
105
+ });
106
+ var sj = await sr.json().catch(function() { return {}; });
107
+ if (!sr.ok) throw new Error(sj.detail || sr.statusText);
108
+ editStatus.textContent = sj.hint || '已保存。';
109
+ } catch (e) { editStatus.classList.add('is-err'); editStatus.textContent = String(e); }
110
+ });
111
+
112
+ wrap.appendChild(header); wrap.appendChild(editWrap);
113
+ return wrap;
114
+ }
115
+
116
+ async function loadMdFileContent(name, textarea, statusEl) {
117
+ try {
118
+ var r = await fetch('/api/ui/md/' + encodeURIComponent(name) + '?agent_id=' + encodeURIComponent(_currentMdAgentId()));
119
+ var j = await r.json();
120
+ if (!r.ok) throw new Error(j.detail || r.statusText);
121
+ textarea.value = j.content || '';
122
+ if (statusEl) statusEl.textContent = j.exists ? '' : '(文件尚不存在,保存后将自动创建)';
123
+ } catch (e) {
124
+ textarea.value = '';
125
+ if (statusEl) { statusEl.classList.add('is-err'); statusEl.textContent = String(e); }
126
+ }
127
+ }