coderfleet 0.1.0__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 (45) hide show
  1. coderfleet/__init__.py +1 -0
  2. coderfleet/__main__.py +4 -0
  3. coderfleet/cli.py +212 -0
  4. coderfleet/compose.py +176 -0
  5. coderfleet/config.py +69 -0
  6. coderfleet/config_cmds.py +243 -0
  7. coderfleet/data/Dockerfile +92 -0
  8. coderfleet/data/__init__.py +0 -0
  9. coderfleet/data/accounts.conf.example +26 -0
  10. coderfleet/data/config.conf.example +31 -0
  11. coderfleet/data/entrypoint.sh +56 -0
  12. coderfleet/data/projects.conf.example +17 -0
  13. coderfleet/data/scripts/coderfleet_usage_status.py +138 -0
  14. coderfleet/docker_ops.py +385 -0
  15. coderfleet/init_wizard.py +227 -0
  16. coderfleet/login_cmd.py +168 -0
  17. coderfleet/server/__init__.py +0 -0
  18. coderfleet/server/docker_mgr.py +45 -0
  19. coderfleet/server/main.py +546 -0
  20. coderfleet/server/models.py +285 -0
  21. coderfleet/server/scheduler.py +1219 -0
  22. coderfleet/server/static/css/main.css +2906 -0
  23. coderfleet/server/static/index.html +378 -0
  24. coderfleet/server/static/js/accounts.js +85 -0
  25. coderfleet/server/static/js/app.js +28 -0
  26. coderfleet/server/static/js/chat.js +743 -0
  27. coderfleet/server/static/js/log.js +145 -0
  28. coderfleet/server/static/js/nav.js +46 -0
  29. coderfleet/server/static/js/projects.js +298 -0
  30. coderfleet/server/static/js/renderer.js +586 -0
  31. coderfleet/server/static/js/state.js +76 -0
  32. coderfleet/server/static/js/submit.js +200 -0
  33. coderfleet/server/static/js/tasks.js +92 -0
  34. coderfleet/server/static/js/terminal.js +347 -0
  35. coderfleet/server/static/js/utils.js +147 -0
  36. coderfleet/server/static/vendor/marked.min.js +6 -0
  37. coderfleet/server/static/vendor/xterm/addon-fit.js +2 -0
  38. coderfleet/server/static/vendor/xterm/xterm.css +218 -0
  39. coderfleet/server/static/vendor/xterm/xterm.js +2 -0
  40. coderfleet/server/terminal.py +129 -0
  41. coderfleet/task_cmds.py +311 -0
  42. coderfleet-0.1.0.dist-info/METADATA +492 -0
  43. coderfleet-0.1.0.dist-info/RECORD +45 -0
  44. coderfleet-0.1.0.dist-info/WHEEL +4 -0
  45. coderfleet-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,378 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>CoderFleet</title>
8
+ <link rel="stylesheet" href="/static/vendor/xterm/xterm.css">
9
+ <link rel="stylesheet" href="/static/css/main.css?v=20260521">
10
+ </head>
11
+
12
+ <body>
13
+
14
+ <div class="layout">
15
+
16
+ <!-- 侧边栏 -->
17
+ <aside class="sidebar">
18
+ <div class="logo">
19
+ <div class="brand-mark" aria-hidden="true">AI</div>
20
+ <div class="brand-text">
21
+ CoderFleet
22
+ <span>AI coding fleet manager</span>
23
+ </div>
24
+ <button class="btn sidebar-toggle" id="sidebar-toggle" type="button" aria-label="折叠侧边栏" aria-expanded="true"
25
+ onclick="toggleSidebar()" title="折叠侧边栏">
26
+ <svg id="sidebar-toggle-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"
27
+ aria-hidden="true">
28
+ <path d="M15 6l-6 6 6 6" />
29
+ </svg>
30
+ </button>
31
+ </div>
32
+ <nav>
33
+ <div class="nav-item active" data-page="chat" onclick="showPage('chat')">
34
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
35
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
36
+ </svg>
37
+ <span class="sidebar-label">AI 对话</span>
38
+ </div>
39
+ <div class="nav-item" data-page="tasks" onclick="showPage('tasks')">
40
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
41
+ <rect x="3" y="3" width="18" height="18" rx="3" />
42
+ <path d="M9 7h6M9 12h6M9 17h3" />
43
+ </svg>
44
+ <span class="sidebar-label">任务监控</span>
45
+ </div>
46
+ <div class="nav-item" data-page="projects" onclick="showPage('projects')">
47
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
48
+ <path
49
+ d="M3 6.5A2.5 2.5 0 015.5 4H10l2 2h6.5A2.5 2.5 0 0121 8.5v8A2.5 2.5 0 0118.5 19h-13A2.5 2.5 0 013 16.5z" />
50
+ </svg>
51
+ <span class="sidebar-label">项目</span>
52
+ </div>
53
+ <div class="nav-item" data-page="accounts" onclick="showPage('accounts')">
54
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
55
+ <circle cx="12" cy="7" r="4" />
56
+ <path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" />
57
+ </svg>
58
+ <span class="sidebar-label">账号</span>
59
+ </div>
60
+ </nav>
61
+ <div class="sidebar-footer">
62
+ <div class="flex gap-8 text-sm text-muted">
63
+ <span class="health-dot" id="health-dot"></span>
64
+ <span class="health-text" id="health-text">连接中...</span>
65
+ </div>
66
+ </div>
67
+ </aside>
68
+
69
+ <!-- 主区域 -->
70
+ <div class="main">
71
+ <div class="topbar">
72
+ <span class="page-title" id="page-title" style="cursor: pointer;" onclick="switchTab('chat')">任务开发</span>
73
+ <div class="topbar-tabs" id="topbar-tabs"></div>
74
+ <button class="add-tab-btn" id="add-tab-btn" onclick="showAddTabMenu(event)" title="连接项目终端">
75
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
76
+ <path d="M12 5v14M5 12h14" />
77
+ </svg>
78
+ </button>
79
+ <span class="spacer"></span>
80
+ <button class="btn" onclick="refreshCurrent()">
81
+ <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
82
+ <path d="M23 4v6h-6" />
83
+ <path d="M1 20v-6h6" />
84
+ <path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15" />
85
+ </svg>
86
+ <span>刷新</span>
87
+ </button>
88
+ </div>
89
+
90
+ <div class="content">
91
+
92
+ <!-- AI 对话页 -->
93
+ <div class="page active" id="page-chat">
94
+ <div class="chat-layout">
95
+ <div class="chat-sidebar">
96
+ <div class="chat-history-list" id="chat-history-list">
97
+ <!-- 会话历史由 JS 动态渲染 -->
98
+ </div>
99
+ </div>
100
+ <div class="chat-main" id="chat-workspace">
101
+ <!-- 右侧对话详情区由 JS 动态渲染 -->
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <!-- 多终端独立页面 -->
107
+ <div class="page" id="page-tab-terminal">
108
+ <!-- 动态创建的多个终端容器将被挂载到这里 -->
109
+ </div>
110
+
111
+ <!-- 任务列表页 -->
112
+ <div class="page" id="page-tasks">
113
+ <div id="task-submit-slot">
114
+ <div class="card submit-card" id="task-submit-panel" style="display:none;margin-bottom:14px">
115
+ <div class="section-head">
116
+ <div>
117
+ <div class="section-title">提交任务</div>
118
+ <div class="section-subtitle">在任务页直接创建一次性任务、开启任务链或续接任务链</div>
119
+ </div>
120
+ <button class="btn" id="submit-panel-close-btn" onclick="closeSubmitSurface()">收起</button>
121
+ </div>
122
+ <div class="form-group">
123
+ <label>提交模式 *</label>
124
+ <div class="mode-grid">
125
+ <label class="mode-option">
126
+ <input type="radio" name="submit-mode" value="one-off" checked
127
+ onchange="switchSubmitMode('one-off')"> 一次性任务
128
+ </label>
129
+ <label class="mode-option">
130
+ <input type="radio" name="submit-mode" value="resume" onchange="switchSubmitMode('resume')"> 续接任务链
131
+ </label>
132
+ <label class="mode-option">
133
+ <input type="radio" name="submit-mode" value="new-chain" onchange="switchSubmitMode('new-chain')">
134
+ 开启新任务链
135
+ </label>
136
+ </div>
137
+ </div>
138
+ <div class="form-group">
139
+ <label>任务描述 *</label>
140
+ <textarea id="f-prompt" placeholder="描述你希望 AI 完成的开发任务..."></textarea>
141
+ </div>
142
+ <div class="form-group" id="group-project">
143
+ <label>项目 *</label>
144
+ <select id="f-project">
145
+ <option value="">选择项目</option>
146
+ </select>
147
+ </div>
148
+ <div class="form-group" id="group-conversation" style="display:none">
149
+ <label>继续任务链 *</label>
150
+ <select id="f-conversation">
151
+ <option value="">选择任务链</option>
152
+ </select>
153
+ <div id="conversation-warning" class="inline-alert" style="display:none">
154
+ 当前任务链有任务运行中,请等待完成后再续接。
155
+ </div>
156
+ </div>
157
+ <div class="form-group" id="group-conversation-name" style="display:none">
158
+ <label>新任务链名称 *</label>
159
+ <input id="f-conversation-name" type="text" placeholder="例如:登录重构">
160
+ </div>
161
+ <div class="form-group">
162
+ <div class="toggle-row">
163
+ <input type="checkbox" id="f-auto" checked>
164
+ <label for="f-auto" style="margin:0;cursor:pointer">全自动模式(--dangerously-skip-permissions)</label>
165
+ </div>
166
+ </div>
167
+ <div class="flex gap-8">
168
+ <button class="btn primary" id="submit-btn" onclick="submitTask()">提交任务</button>
169
+ <button class="btn" onclick="resetForm()">重置</button>
170
+ </div>
171
+ <div id="submit-msg" class="mt-16" style="display:none"></div>
172
+ </div>
173
+ </div>
174
+
175
+ <div class="metric-grid" aria-label="任务概览">
176
+ <div class="metric-card">
177
+ <div class="metric-label">运行中</div>
178
+ <div class="metric-value" id="metric-running">-</div>
179
+ <div class="metric-note">正在占用账号</div>
180
+ </div>
181
+ <div class="metric-card">
182
+ <div class="metric-label">已完成</div>
183
+ <div class="metric-value" id="metric-done">-</div>
184
+ <div class="metric-note">最近 100 条任务</div>
185
+ </div>
186
+ <div class="metric-card">
187
+ <div class="metric-label">失败</div>
188
+ <div class="metric-value" id="metric-failed">-</div>
189
+ <div class="metric-note">需要回看日志</div>
190
+ </div>
191
+ <div class="metric-card">
192
+ <div class="metric-label">账号</div>
193
+ <div class="metric-value" id="metric-accounts">-</div>
194
+ <div class="metric-note" id="metric-account-note">可用状态同步中</div>
195
+ </div>
196
+ </div>
197
+ <div class="filter-bar">
198
+ <select id="filter-status" onchange="loadTasks({resetPage:true})">
199
+ <option value="">全部状态</option>
200
+ <option value="running">运行中</option>
201
+ <option value="done">已完成</option>
202
+ <option value="failed">失败</option>
203
+ <option value="killed">已终止</option>
204
+ </select>
205
+ <select id="filter-account" onchange="loadTasks({resetPage:true})">
206
+ <option value="">全部账号</option>
207
+ </select>
208
+ <span class="spacer"></span>
209
+ <button class="btn" onclick="cleanTasks()" style="font-size:12px">清理旧记录</button>
210
+ </div>
211
+ <div class="section-head">
212
+ <div>
213
+ <div class="section-title">任务队列</div>
214
+ <div class="section-subtitle">按创建时间倒序排列,点击行查看结构化日志和续接入口</div>
215
+ </div>
216
+ </div>
217
+ <div class="card">
218
+ <div class="table-wrap">
219
+ <table>
220
+ <thead>
221
+ <tr>
222
+ <th>状态</th>
223
+ <th>任务描述</th>
224
+ <th>账号</th>
225
+ <th>项目</th>
226
+ <th>创建时间</th>
227
+ <th>时长</th>
228
+ <th>操作</th>
229
+ </tr>
230
+ </thead>
231
+ <tbody id="task-tbody">
232
+ <tr>
233
+ <td colspan="7">
234
+ <div class="empty">加载中...</div>
235
+ </td>
236
+ </tr>
237
+ </tbody>
238
+ </table>
239
+ </div>
240
+ <div class="pagination-bar" id="task-pagination" style="display:none"></div>
241
+ </div>
242
+ </div>
243
+
244
+ <!-- 项目工作台页 -->
245
+ <div class="page" id="page-projects">
246
+ <div id="project-list-view">
247
+ <div class="section-head">
248
+ <div>
249
+ <div class="section-title">项目工作台</div>
250
+ <div class="section-subtitle">以项目为入口查看任务、任务链和绑定账号</div>
251
+ </div>
252
+ </div>
253
+ <div class="project-grid" id="project-grid">
254
+ <div class="empty">加载中...</div>
255
+ </div>
256
+ </div>
257
+
258
+ <div id="project-detail-view" style="display:none">
259
+ <div class="project-toolbar">
260
+ <button class="btn" onclick="backToProjects()">返回项目</button>
261
+ <button class="btn primary" onclick="submitForCurrentProject()">开启新对话</button>
262
+ </div>
263
+ <div class="project-detail">
264
+ <div class="card project-side" id="project-detail-summary">
265
+ <div class="empty">加载中...</div>
266
+ </div>
267
+ <div class="terminal-card">
268
+ <div class="terminal-toolbar">
269
+ <div class="terminal-status">
270
+ <span class="status-dot killed" id="project-terminal-dot">未连接</span>
271
+ <span class="terminal-status-text" id="project-terminal-status">点击「重新连接」进入容器终端</span>
272
+ </div>
273
+ <button class="btn" id="project-terminal-reconnect" type="button"
274
+ onclick="connectProjectTerminal()">重新连接</button>
275
+ </div>
276
+ <div class="terminal-warning" id="project-terminal-warning"></div>
277
+ <div class="terminal-mount" id="project-terminal"></div>
278
+ </div>
279
+ </div>
280
+ </div>
281
+ </div>
282
+
283
+ <!-- 账号页 -->
284
+ <div class="page" id="page-accounts">
285
+ <div class="section-head">
286
+ <div>
287
+ <div class="section-title">账号资源</div>
288
+ <div class="section-subtitle">容器状态、任务占用和项目绑定</div>
289
+ </div>
290
+ </div>
291
+ <div class="account-grid" id="account-grid">
292
+ <div class="empty">加载中...</div>
293
+ </div>
294
+ </div>
295
+
296
+ </div>
297
+ </div>
298
+ </div>
299
+
300
+ <!-- 提交任务弹窗 -->
301
+ <div class="modal-backdrop" id="submit-modal" style="display:none" onclick="closeSubmitModal(event)">
302
+ <div class="modal-sm" onclick="event.stopPropagation()">
303
+ <div class="section-head">
304
+ <div>
305
+ <div class="modal-title">在项目中提交任务</div>
306
+ <div class="section-subtitle" id="submit-modal-subtitle">当前项目上下文</div>
307
+ </div>
308
+ <button class="btn" onclick="closeSubmitModal()" aria-label="关闭提交任务">关闭</button>
309
+ </div>
310
+ <div id="submit-modal-slot"></div>
311
+ </div>
312
+ </div>
313
+
314
+ <!-- 日志模态框 -->
315
+ <div class="modal-backdrop" id="log-modal" style="display:none" onclick="closeLogModal(event)">
316
+ <div class="modal-lg" onclick="event.stopPropagation()">
317
+ <div class="modal-header">
318
+ <div style="flex:1;min-width:0">
319
+ <div class="modal-title" id="log-modal-title">任务日志</div>
320
+ <div id="log-meta" class="log-meta"></div>
321
+ </div>
322
+ <div style="display:flex;gap:6px;align-items:center;flex-shrink:0">
323
+ <button class="btn primary" id="resume-btn" onclick="resumeCurrentTask()" style="display:none">续接任务</button>
324
+ <button class="btn danger" id="kill-btn" onclick="killCurrentTask()" style="display:none">终止</button>
325
+ <button class="btn" id="follow-btn" onclick="toggleFollow()" aria-label="切换日志跟踪">
326
+ <span aria-hidden="true" id="follow-icon">▶</span><span>跟踪</span>
327
+ </button>
328
+ <button class="btn" onclick="closeLogModal()" aria-label="关闭日志">
329
+ <svg class="icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
330
+ <path d="M18 6 6 18M6 6l12 12" />
331
+ </svg>
332
+ </button>
333
+ </div>
334
+ </div>
335
+ <div class="log-shell">
336
+ <div class="log-summary" aria-label="日志摘要">
337
+ <div class="log-summary-item">
338
+ <div class="log-summary-label">状态</div>
339
+ <div class="log-summary-value" id="log-summary-status">-</div>
340
+ </div>
341
+ <div class="log-summary-item">
342
+ <div class="log-summary-label">账号</div>
343
+ <div class="log-summary-value" id="log-summary-account">-</div>
344
+ </div>
345
+ <div class="log-summary-item">
346
+ <div class="log-summary-label">项目</div>
347
+ <div class="log-summary-value" id="log-summary-project">-</div>
348
+ </div>
349
+ <div class="log-summary-item">
350
+ <div class="log-summary-label">创建时间</div>
351
+ <div class="log-summary-value" id="log-summary-created">-</div>
352
+ </div>
353
+ </div>
354
+ <div class="chat-viewport" id="log-panel">
355
+ <div id="log-content"></div>
356
+ </div>
357
+ </div>
358
+ </div>
359
+ </div>
360
+
361
+ <script src="/static/vendor/xterm/xterm.js"></script>
362
+ <script src="/static/vendor/xterm/addon-fit.js"></script>
363
+ <script src="/static/vendor/marked.min.js"></script>
364
+ <script src="/static/js/utils.js?v=20260521"></script>
365
+ <script src="/static/js/renderer.js?v=20260521"></script>
366
+ <script src="/static/js/state.js?v=20260521"></script>
367
+ <script src="/static/js/chat.js?v=20260521"></script>
368
+ <script src="/static/js/terminal.js?v=20260521"></script>
369
+ <script src="/static/js/nav.js?v=20260521"></script>
370
+ <script src="/static/js/tasks.js?v=20260521"></script>
371
+ <script src="/static/js/projects.js?v=20260522"></script>
372
+ <script src="/static/js/accounts.js?v=20260521"></script>
373
+ <script src="/static/js/submit.js?v=20260521"></script>
374
+ <script src="/static/js/log.js?v=20260521"></script>
375
+ <script src="/static/js/app.js?v=20260521"></script>
376
+ </body>
377
+
378
+ </html>
@@ -0,0 +1,85 @@
1
+ // ── 账号列表 ──────────────────────────────────────────────
2
+ async function loadAccounts() {
3
+ try {
4
+ const accounts = await fetch(`${API}/api/accounts`).then(r => r.json());
5
+ globalAccountsCache = accounts;
6
+ renderAccounts(accounts);
7
+ populateAccountFilters(accounts);
8
+ } catch (e) {
9
+ document.getElementById('account-grid').innerHTML = `<div class="empty">加载失败:${esc(e.message)}</div>`;
10
+ }
11
+ }
12
+
13
+ function renderAccounts(accounts) {
14
+ const grid = document.getElementById('account-grid');
15
+ if (!accounts.length) { grid.innerHTML = `<div class="empty">暂无账号配置</div>`; return; }
16
+ grid.innerHTML = accounts.map(a => {
17
+ const statusBadge = !a.running
18
+ ? `<span class="badge offline">离线</span>`
19
+ : a.busy ? `<span class="badge busy">工作中</span>` : `<span class="badge idle">空闲</span>`;
20
+ const proxy = a.proxy || 'relay';
21
+ const proxyBadge = `<span class="badge proxy-${proxy}">proxy: ${esc(proxy)}</span>`;
22
+ const projectNames = a.projects || [];
23
+ const containers = String(a.container || '').split(/\s+/).filter(Boolean);
24
+ const projectChips = projectNames.length
25
+ ? projectNames.map(p => `<span class="chip" title="${esc(p)}">${esc(p)}</span>`).join('')
26
+ : `<span class="chip">未关联项目</span>`;
27
+ const containerRows = containers.length
28
+ ? containers.map(c => `<div class="container-name" title="${esc(c)}">${esc(c)}</div>`).join('')
29
+ : `<div class="container-name">暂无容器</div>`;
30
+ const runningTaskBlock = (a.busy && a.running_task_id)
31
+ ? `<div class="account-running-task" onclick="openLogModal('${esc(a.running_task_id)}')">
32
+ <div class="account-running-label">▶ 正在执行</div>
33
+ <div class="account-running-prompt">${esc(a.running_task_prompt)}</div>
34
+ </div>`
35
+ : '';
36
+ return `<div class="account-card">
37
+ <div class="account-card-head">
38
+ <div class="account-identity">
39
+ <div class="account-badges">
40
+ <span class="badge ${a.type}">${a.type}</span>
41
+ <span class="badge idle">${esc(a.auth || 'login')}</span>
42
+ ${proxyBadge}
43
+ </div>
44
+ <div class="account-name">${esc(a.name)}</div>
45
+ </div>
46
+ <div class="account-status">${statusBadge}</div>
47
+ </div>
48
+ <div class="account-stats">
49
+ <div class="account-stat">
50
+ <div class="account-stat-label">项目</div>
51
+ <div class="account-stat-value">${projectNames.length}</div>
52
+ </div>
53
+ <div class="account-stat">
54
+ <div class="account-stat-label">已完成</div>
55
+ <div class="account-stat-value" style="color:var(--green)">${a.task_done_count ?? 0}</div>
56
+ </div>
57
+ <div class="account-stat">
58
+ <div class="account-stat-label">失败</div>
59
+ <div class="account-stat-value" style="${(a.task_failed_count ?? 0) > 0 ? 'color:var(--red)' : ''}">${a.task_failed_count ?? 0}</div>
60
+ </div>
61
+ </div>
62
+ <div class="chip-list">${projectChips}</div>
63
+ <div class="container-list">${containerRows}</div>
64
+ ${runningTaskBlock}
65
+ <div class="account-footer">
66
+ <button class="btn" style="font-size:12px" onclick="filterTasksByAccount('${esc(a.name)}')">查看任务</button>
67
+ </div>
68
+ </div>`;
69
+ }).join('');
70
+ }
71
+
72
+ function filterTasksByAccount(accountName) {
73
+ const sel = document.getElementById('filter-account');
74
+ if (sel) sel.value = accountName;
75
+ showPage('tasks');
76
+ }
77
+
78
+ function populateAccountFilters(accounts) {
79
+ const sel = document.getElementById('filter-account');
80
+ const prev = sel.value;
81
+ sel.innerHTML = '<option value="">全部账号</option>' +
82
+ accounts.map(a => `<option value="${esc(a.name)}">${esc(a.name)} (${a.type})</option>`).join('');
83
+ sel.value = prev;
84
+ }
85
+
@@ -0,0 +1,28 @@
1
+
2
+ // ── 初始化 ────────────────────────────────────────────────
3
+ initSidebarState();
4
+ initTopbarTabs();
5
+ checkHealth();
6
+ refreshGlobalCaches();
7
+ loadConversations();
8
+ loadAccountOptions();
9
+ window.addEventListener('resize', resizeProjectTerminal);
10
+ window.addEventListener('beforeunload', disconnectProjectTerminal);
11
+ window.addEventListener('beforeunload', () => {
12
+ if (typeof disconnectAllMultiTerminals === 'function') {
13
+ disconnectAllMultiTerminals();
14
+ } else {
15
+ Object.keys(multiTerminalContexts || {}).forEach(disconnectMultiTerminal);
16
+ }
17
+ });
18
+
19
+ // 高频刷新:任务状态(5s,仅当前页)
20
+ setInterval(() => {
21
+ checkHealth();
22
+ if (currentPage === 'chat' && !chatFollowMode) loadConversations(false);
23
+ if (currentPage === 'tasks') loadTasks();
24
+ if (currentPage === 'accounts') loadAccounts();
25
+ }, 5000);
26
+
27
+ // 低频刷新:全局账号 + 任务链缓存(30s,后台保持同步)
28
+ setInterval(refreshGlobalCaches, 30000);