claudeck 1.0.0

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 (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -0
  3. package/cli.js +2 -0
  4. package/config/agent-chains.json +16 -0
  5. package/config/agent-dags.json +16 -0
  6. package/config/agents.json +46 -0
  7. package/config/bot-prompt.json +3 -0
  8. package/config/folders.json +66 -0
  9. package/config/prompts.json +92 -0
  10. package/config/repos.json +86 -0
  11. package/config/telegram-config.json +17 -0
  12. package/config/workflows.json +90 -0
  13. package/db.js +1198 -0
  14. package/package.json +55 -0
  15. package/plugins/claude-editor/client.css +171 -0
  16. package/plugins/claude-editor/client.js +183 -0
  17. package/plugins/event-stream/client.css +207 -0
  18. package/plugins/event-stream/client.js +271 -0
  19. package/plugins/linear/client.css +345 -0
  20. package/plugins/linear/client.js +380 -0
  21. package/plugins/linear/config.json +5 -0
  22. package/plugins/linear/server.js +312 -0
  23. package/plugins/repos/client.css +549 -0
  24. package/plugins/repos/client.js +663 -0
  25. package/plugins/repos/server.js +232 -0
  26. package/plugins/sudoku/client.css +196 -0
  27. package/plugins/sudoku/client.js +329 -0
  28. package/plugins/tasks/client.css +414 -0
  29. package/plugins/tasks/client.js +394 -0
  30. package/plugins/tasks/server.js +116 -0
  31. package/plugins/tic-tac-toe/client.css +167 -0
  32. package/plugins/tic-tac-toe/client.js +241 -0
  33. package/public/css/core/components.css +232 -0
  34. package/public/css/core/layout.css +330 -0
  35. package/public/css/core/print.css +18 -0
  36. package/public/css/core/reset.css +36 -0
  37. package/public/css/core/responsive.css +378 -0
  38. package/public/css/core/theme.css +116 -0
  39. package/public/css/core/variables.css +93 -0
  40. package/public/css/features/agent-monitor.css +297 -0
  41. package/public/css/features/agent-sidebar.css +525 -0
  42. package/public/css/features/agents.css +996 -0
  43. package/public/css/features/analytics.css +181 -0
  44. package/public/css/features/background-sessions.css +321 -0
  45. package/public/css/features/cost-dashboard.css +168 -0
  46. package/public/css/features/home.css +313 -0
  47. package/public/css/features/retro-terminal.css +88 -0
  48. package/public/css/features/telegram.css +127 -0
  49. package/public/css/features/tour.css +148 -0
  50. package/public/css/features/voice-input.css +60 -0
  51. package/public/css/features/welcome.css +241 -0
  52. package/public/css/panels/assistant-bot.css +442 -0
  53. package/public/css/panels/dev-docs.css +292 -0
  54. package/public/css/panels/file-explorer.css +322 -0
  55. package/public/css/panels/git-panel.css +221 -0
  56. package/public/css/panels/mcp-manager.css +199 -0
  57. package/public/css/panels/tips-feed.css +353 -0
  58. package/public/css/ui/commands.css +273 -0
  59. package/public/css/ui/context-gauge.css +76 -0
  60. package/public/css/ui/file-picker.css +69 -0
  61. package/public/css/ui/image-attachments.css +106 -0
  62. package/public/css/ui/messages.css +884 -0
  63. package/public/css/ui/modals.css +122 -0
  64. package/public/css/ui/parallel.css +217 -0
  65. package/public/css/ui/permissions.css +110 -0
  66. package/public/css/ui/right-panel.css +481 -0
  67. package/public/css/ui/sessions.css +689 -0
  68. package/public/css/ui/status-bar.css +425 -0
  69. package/public/css/ui/toolbox.css +206 -0
  70. package/public/data/tips.json +218 -0
  71. package/public/icons/favicon.png +0 -0
  72. package/public/icons/icon-192.png +0 -0
  73. package/public/icons/icon-512.png +0 -0
  74. package/public/icons/whaly.png +0 -0
  75. package/public/index.html +1140 -0
  76. package/public/js/core/api.js +591 -0
  77. package/public/js/core/constants.js +3 -0
  78. package/public/js/core/dom.js +270 -0
  79. package/public/js/core/events.js +10 -0
  80. package/public/js/core/plugin-loader.js +153 -0
  81. package/public/js/core/store.js +39 -0
  82. package/public/js/core/utils.js +25 -0
  83. package/public/js/core/ws.js +64 -0
  84. package/public/js/features/agent-monitor.js +222 -0
  85. package/public/js/features/agents.js +1209 -0
  86. package/public/js/features/analytics.js +397 -0
  87. package/public/js/features/attachments.js +251 -0
  88. package/public/js/features/background-sessions.js +475 -0
  89. package/public/js/features/chat.js +589 -0
  90. package/public/js/features/cost-dashboard.js +152 -0
  91. package/public/js/features/dag-editor.js +399 -0
  92. package/public/js/features/easter-egg.js +46 -0
  93. package/public/js/features/home.js +270 -0
  94. package/public/js/features/projects.js +372 -0
  95. package/public/js/features/prompts.js +228 -0
  96. package/public/js/features/sessions.js +332 -0
  97. package/public/js/features/telegram.js +131 -0
  98. package/public/js/features/tour.js +210 -0
  99. package/public/js/features/voice-input.js +185 -0
  100. package/public/js/features/welcome.js +43 -0
  101. package/public/js/features/workflows.js +277 -0
  102. package/public/js/main.js +51 -0
  103. package/public/js/panels/assistant-bot.js +445 -0
  104. package/public/js/panels/dev-docs.js +380 -0
  105. package/public/js/panels/file-explorer.js +486 -0
  106. package/public/js/panels/git-panel.js +285 -0
  107. package/public/js/panels/mcp-manager.js +311 -0
  108. package/public/js/panels/tips-feed.js +303 -0
  109. package/public/js/ui/commands.js +114 -0
  110. package/public/js/ui/context-gauge.js +100 -0
  111. package/public/js/ui/diff.js +124 -0
  112. package/public/js/ui/disabled-tools.js +36 -0
  113. package/public/js/ui/export.js +74 -0
  114. package/public/js/ui/formatting.js +206 -0
  115. package/public/js/ui/header-dropdowns.js +72 -0
  116. package/public/js/ui/input-meta.js +71 -0
  117. package/public/js/ui/max-turns.js +21 -0
  118. package/public/js/ui/messages.js +387 -0
  119. package/public/js/ui/model-selector.js +20 -0
  120. package/public/js/ui/notifications.js +232 -0
  121. package/public/js/ui/parallel.js +176 -0
  122. package/public/js/ui/permissions.js +168 -0
  123. package/public/js/ui/right-panel.js +173 -0
  124. package/public/js/ui/shortcuts.js +143 -0
  125. package/public/js/ui/sidebar-toggle.js +29 -0
  126. package/public/js/ui/status-bar.js +172 -0
  127. package/public/js/ui/tab-sdk.js +623 -0
  128. package/public/js/ui/theme.js +38 -0
  129. package/public/manifest.json +13 -0
  130. package/public/offline.html +190 -0
  131. package/public/style.css +42 -0
  132. package/public/sw.js +91 -0
  133. package/server/agent-loop.js +385 -0
  134. package/server/dag-executor.js +265 -0
  135. package/server/orchestrator.js +514 -0
  136. package/server/paths.js +61 -0
  137. package/server/plugin-mount.js +56 -0
  138. package/server/push-sender.js +31 -0
  139. package/server/routes/agents.js +294 -0
  140. package/server/routes/bot.js +45 -0
  141. package/server/routes/exec.js +35 -0
  142. package/server/routes/files.js +218 -0
  143. package/server/routes/mcp.js +82 -0
  144. package/server/routes/messages.js +36 -0
  145. package/server/routes/notifications.js +37 -0
  146. package/server/routes/projects.js +207 -0
  147. package/server/routes/prompts.js +53 -0
  148. package/server/routes/sessions.js +103 -0
  149. package/server/routes/stats.js +143 -0
  150. package/server/routes/telegram.js +71 -0
  151. package/server/routes/tips.js +135 -0
  152. package/server/routes/workflows.js +81 -0
  153. package/server/summarizer.js +55 -0
  154. package/server/telegram-poller.js +205 -0
  155. package/server/telegram-sender.js +304 -0
  156. package/server/ws-handler.js +926 -0
  157. package/server.js +179 -0
@@ -0,0 +1,1209 @@
1
+ // Agents — autonomous AI agents with CRUD + Agent Chains
2
+ import { $ } from '../core/dom.js';
3
+ import { getState, setState } from '../core/store.js';
4
+ import { escapeHtml, scrollToBottom } from '../core/utils.js';
5
+ import * as api from '../core/api.js';
6
+ import { commandRegistry, registerCommand } from '../ui/commands.js';
7
+ import { getPane } from '../ui/parallel.js';
8
+ import { showThinking, removeThinking, addStatus } from '../ui/messages.js';
9
+ import { getPermissionMode } from '../ui/permissions.js';
10
+ import { getSelectedModel } from '../ui/model-selector.js';
11
+ import { openDagModal, closeDagModal } from './dag-editor.js';
12
+ import { openAgentMonitor } from './agent-monitor.js';
13
+ import { renderWorkflowSidebar } from './workflows.js';
14
+
15
+ // ══════════════════════════════════════════════════════════
16
+ // Helpers
17
+ // ══════════════════════════════════════════════════════════
18
+
19
+ /** Reset streaming state — swap stop→send button, re-enable input */
20
+ function finishAgentStreaming(pane) {
21
+ if (!pane) return;
22
+ pane.isStreaming = false;
23
+ if ($.streamingTokens) $.streamingTokens.classList.add("hidden");
24
+ if ($.streamingTokensSep) $.streamingTokensSep.classList.add("hidden");
25
+ const parallelMode = getState("parallelMode");
26
+ if (parallelMode) {
27
+ if (pane.sendBtn) pane.sendBtn.classList.remove("hidden");
28
+ if (pane.stopBtn) pane.stopBtn.classList.add("hidden");
29
+ if (pane.messageInput) pane.messageInput.focus();
30
+ } else {
31
+ $.sendBtn.classList.remove("hidden");
32
+ $.stopBtn.classList.add("hidden");
33
+ $.sendBtn.disabled = false;
34
+ $.messageInput.focus();
35
+ }
36
+ }
37
+
38
+ // ══════════════════════════════════════════════════════════
39
+ // Agents
40
+ // ══════════════════════════════════════════════════════════
41
+
42
+ export async function loadAgents() {
43
+ try {
44
+ const [agents, chains, dags] = await Promise.all([
45
+ api.fetchAgents(),
46
+ api.fetchChains(),
47
+ api.fetchDags(),
48
+ ]);
49
+ setState("agents", agents);
50
+ setState("agentChains", chains);
51
+ setState("agentDags", dags);
52
+ renderAgentPanel();
53
+ registerAgentCommands();
54
+ } catch (err) {
55
+ console.error("Failed to load agents:", err);
56
+ }
57
+ }
58
+
59
+ function renderAgentPanel() {
60
+ const agents = getState("agents");
61
+ const chains = getState("agentChains") || [];
62
+ $.agentPanel.innerHTML = "";
63
+
64
+ // ── Orchestrate card ──
65
+ const orchCard = document.createElement("div");
66
+ orchCard.className = "toolbox-card agent-card orch-card";
67
+ orchCard.innerHTML = `
68
+ <div class="toolbox-card-title">
69
+ <span class="agent-icon">${getOrchIcon()}</span>
70
+ Orchestrate
71
+ </div>
72
+ <div class="toolbox-card-desc">Describe a task — the orchestrator decomposes it and delegates to the right agents automatically.</div>
73
+ `;
74
+ orchCard.addEventListener("click", () => {
75
+ $.agentSidebar?.classList.add("hidden");
76
+ $.agentBtn.classList.remove("active");
77
+ openOrchModal();
78
+ });
79
+ $.agentPanel.appendChild(orchCard);
80
+
81
+ // ── Monitor button ──
82
+ const monitorCard = document.createElement("div");
83
+ monitorCard.className = "toolbox-card agent-card monitor-card";
84
+ monitorCard.innerHTML = `
85
+ <div class="toolbox-card-title">
86
+ <span class="agent-icon">${getMonitorIcon()}</span>
87
+ Agent Monitor
88
+ </div>
89
+ <div class="toolbox-card-desc">Real-time metrics, cost aggregation, and comparative analysis across all agents.</div>
90
+ `;
91
+ monitorCard.addEventListener("click", () => {
92
+ $.agentSidebar?.classList.add("hidden");
93
+ $.agentBtn.classList.remove("active");
94
+ openAgentMonitor();
95
+ });
96
+ $.agentPanel.appendChild(monitorCard);
97
+
98
+ // ── Chains section ──
99
+ if (chains.length > 0 || true) {
100
+ const chainHeader = document.createElement("div");
101
+ chainHeader.className = "agent-section-header";
102
+ chainHeader.textContent = "Chains";
103
+ $.agentPanel.appendChild(chainHeader);
104
+
105
+ for (const chain of chains) {
106
+ const agentNames = chain.agents.map(id => {
107
+ const a = agents.find(ag => ag.id === id);
108
+ return a ? a.title : id;
109
+ });
110
+ const card = document.createElement("div");
111
+ card.className = "toolbox-card agent-card chain-card";
112
+ card.innerHTML = `
113
+ <div class="agent-card-actions">
114
+ <button class="agent-card-edit" data-chain-id="${escapeHtml(chain.id)}" title="Edit chain">
115
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
116
+ </button>
117
+ <button class="agent-card-delete" data-chain-id="${escapeHtml(chain.id)}" title="Delete chain">&times;</button>
118
+ </div>
119
+ <div class="toolbox-card-title">
120
+ <span class="agent-icon">${getChainIcon()}</span>
121
+ ${escapeHtml(chain.title)}
122
+ </div>
123
+ <div class="toolbox-card-desc">${escapeHtml(chain.description || agentNames.join(" → "))}</div>
124
+ <div class="chain-steps-preview">${agentNames.map(n => `<span class="chain-step-tag">${escapeHtml(n)}</span>`).join('<span class="chain-arrow">→</span>')}</div>
125
+ `;
126
+ card.addEventListener("click", (e) => {
127
+ if (e.target.closest(".agent-card-edit") || e.target.closest(".agent-card-delete")) return;
128
+ $.agentSidebar?.classList.add("hidden");
129
+ $.agentBtn.classList.remove("active");
130
+ startChain(chain, getPane(null));
131
+ });
132
+ card.querySelector(".agent-card-edit").addEventListener("click", (e) => {
133
+ e.stopPropagation();
134
+ openChainModal(chain);
135
+ });
136
+ card.querySelector(".agent-card-delete").addEventListener("click", (e) => {
137
+ e.stopPropagation();
138
+ deleteChain(chain.id, chain.title);
139
+ });
140
+ $.agentPanel.appendChild(card);
141
+ }
142
+
143
+ const addChainCard = document.createElement("div");
144
+ addChainCard.className = "toolbox-card-add";
145
+ addChainCard.innerHTML = `+ Add Chain`;
146
+ addChainCard.addEventListener("click", () => openChainModal());
147
+ $.agentPanel.appendChild(addChainCard);
148
+ }
149
+
150
+ // ── DAGs section ──
151
+ const dags = getState("agentDags") || [];
152
+ {
153
+ const dagHeader = document.createElement("div");
154
+ dagHeader.className = "agent-section-header";
155
+ dagHeader.textContent = "DAGs";
156
+ $.agentPanel.appendChild(dagHeader);
157
+
158
+ for (const dag of dags) {
159
+ const nodeNames = dag.nodes.map(n => {
160
+ const a = agents.find(ag => ag.id === n.agentId);
161
+ return a ? a.title : n.agentId;
162
+ });
163
+ const card = document.createElement("div");
164
+ card.className = "toolbox-card agent-card dag-card";
165
+ card.innerHTML = `
166
+ <div class="agent-card-actions">
167
+ <button class="agent-card-edit" data-dag-id="${escapeHtml(dag.id)}" title="Edit DAG">
168
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
169
+ </button>
170
+ <button class="agent-card-delete" data-dag-id="${escapeHtml(dag.id)}" title="Delete DAG">&times;</button>
171
+ </div>
172
+ <div class="toolbox-card-title">
173
+ <span class="agent-icon">${getDagIcon()}</span>
174
+ ${escapeHtml(dag.title)}
175
+ </div>
176
+ <div class="toolbox-card-desc">${escapeHtml(dag.description || `${dag.nodes.length} nodes, ${dag.edges.length} edges`)}</div>
177
+ <div class="dag-nodes-preview">${nodeNames.map(n => `<span class="chain-step-tag">${escapeHtml(n)}</span>`).join('')}</div>
178
+ `;
179
+ card.addEventListener("click", (e) => {
180
+ if (e.target.closest(".agent-card-edit") || e.target.closest(".agent-card-delete")) return;
181
+ $.agentSidebar?.classList.add("hidden");
182
+ $.agentBtn.classList.remove("active");
183
+ startDag(dag, getPane(null));
184
+ });
185
+ card.querySelector(".agent-card-edit").addEventListener("click", (e) => {
186
+ e.stopPropagation();
187
+ openDagModal(dag);
188
+ });
189
+ card.querySelector(".agent-card-delete").addEventListener("click", (e) => {
190
+ e.stopPropagation();
191
+ deleteDag(dag.id, dag.title);
192
+ });
193
+ $.agentPanel.appendChild(card);
194
+ }
195
+
196
+ const addDagCard = document.createElement("div");
197
+ addDagCard.className = "toolbox-card-add";
198
+ addDagCard.innerHTML = `+ Add DAG`;
199
+ addDagCard.addEventListener("click", () => openDagModal());
200
+ $.agentPanel.appendChild(addDagCard);
201
+ }
202
+
203
+ // ── Agents section ──
204
+ const agentHeader = document.createElement("div");
205
+ agentHeader.className = "agent-section-header";
206
+ agentHeader.textContent = "Agents";
207
+ $.agentPanel.appendChild(agentHeader);
208
+
209
+ for (const agent of agents) {
210
+ const card = document.createElement("div");
211
+ card.className = "toolbox-card agent-card";
212
+ card.innerHTML = `
213
+ <div class="agent-card-actions">
214
+ <button class="agent-card-edit" data-id="${escapeHtml(agent.id)}" title="Edit agent">
215
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
216
+ </button>
217
+ <button class="agent-card-delete" data-id="${escapeHtml(agent.id)}" title="Delete agent">&times;</button>
218
+ </div>
219
+ <div class="toolbox-card-title">
220
+ <span class="agent-icon">${getAgentIcon(agent.icon)}</span>
221
+ ${escapeHtml(agent.title)}
222
+ ${agent.custom ? '<span class="agent-custom-badge">custom</span>' : ''}
223
+ </div>
224
+ <div class="toolbox-card-desc">${escapeHtml(agent.description)}</div>
225
+ `;
226
+ card.addEventListener("click", (e) => {
227
+ if (e.target.closest(".agent-card-edit") || e.target.closest(".agent-card-delete")) return;
228
+ $.agentSidebar?.classList.add("hidden");
229
+ $.agentBtn.classList.remove("active");
230
+ startAgent(agent, getPane(null));
231
+ });
232
+ card.querySelector(".agent-card-edit").addEventListener("click", (e) => {
233
+ e.stopPropagation();
234
+ openAgentModal(agent);
235
+ });
236
+ card.querySelector(".agent-card-delete").addEventListener("click", (e) => {
237
+ e.stopPropagation();
238
+ deleteAgent(agent.id, agent.title);
239
+ });
240
+ $.agentPanel.appendChild(card);
241
+ }
242
+
243
+ const addCard = document.createElement("div");
244
+ addCard.className = "toolbox-card-add";
245
+ addCard.innerHTML = `+ Add Agent`;
246
+ addCard.addEventListener("click", () => openAgentModal());
247
+ $.agentPanel.appendChild(addCard);
248
+
249
+ // Render workflow cards in the sidebar
250
+ renderWorkflowSidebar();
251
+ }
252
+
253
+ // ══════════════════════════════════════════════════════════
254
+ // Agent CRUD Modal
255
+ // ══════════════════════════════════════════════════════════
256
+
257
+ function openAgentModal(agent) {
258
+ $.agentForm.reset();
259
+ if (agent) {
260
+ $.agentModalTitle.textContent = "Edit Agent";
261
+ $.agentFormTitle.value = agent.title;
262
+ $.agentFormDesc.value = agent.description;
263
+ $.agentFormIcon.value = agent.icon || "tool";
264
+ $.agentFormGoal.value = agent.goal;
265
+ $.agentFormMaxTurns.value = agent.constraints?.maxTurns || 50;
266
+ $.agentFormTimeout.value = Math.round((agent.constraints?.timeoutMs || 300000) / 1000);
267
+ $.agentFormEditId.value = agent.id;
268
+ } else {
269
+ $.agentModalTitle.textContent = "New Agent";
270
+ $.agentFormEditId.value = "";
271
+ $.agentFormMaxTurns.value = 50;
272
+ $.agentFormTimeout.value = 300;
273
+ }
274
+ $.agentModal.classList.remove("hidden");
275
+ $.agentFormTitle.focus();
276
+ }
277
+
278
+ function closeAgentModal() {
279
+ $.agentModal.classList.add("hidden");
280
+ }
281
+
282
+ $.agentForm.addEventListener("submit", async (e) => {
283
+ e.preventDefault();
284
+ const editId = $.agentFormEditId.value;
285
+ const data = {
286
+ title: $.agentFormTitle.value.trim(),
287
+ description: $.agentFormDesc.value.trim(),
288
+ icon: $.agentFormIcon.value,
289
+ goal: $.agentFormGoal.value.trim(),
290
+ constraints: {
291
+ maxTurns: parseInt($.agentFormMaxTurns.value, 10) || 50,
292
+ timeoutMs: (parseInt($.agentFormTimeout.value, 10) || 300) * 1000,
293
+ },
294
+ };
295
+ if (!data.title || !data.goal) return;
296
+ try {
297
+ if (editId) {
298
+ await api.updateAgent(editId, data);
299
+ } else {
300
+ await api.createAgent(data);
301
+ }
302
+ closeAgentModal();
303
+ await loadAgents();
304
+ } catch (err) {
305
+ console.error("Failed to save agent:", err);
306
+ alert(err.message);
307
+ }
308
+ });
309
+
310
+ $.agentModalClose.addEventListener("click", closeAgentModal);
311
+ $.agentModalCancel.addEventListener("click", closeAgentModal);
312
+ $.agentModal.addEventListener("click", (e) => {
313
+ if (e.target === $.agentModal) closeAgentModal();
314
+ });
315
+
316
+ async function deleteAgent(id, title) {
317
+ if (!confirm(`Delete agent "${title}"?`)) return;
318
+ try {
319
+ await api.deleteAgentApi(id);
320
+ await loadAgents();
321
+ } catch (err) {
322
+ console.error("Failed to delete agent:", err);
323
+ }
324
+ }
325
+
326
+ // ══════════════════════════════════════════════════════════
327
+ // Chain CRUD Modal
328
+ // ══════════════════════════════════════════════════════════
329
+
330
+ function openChainModal(chain) {
331
+ $.chainForm.reset();
332
+ $.chainAgentList.innerHTML = "";
333
+
334
+ if (chain) {
335
+ $.chainModalTitle.textContent = "Edit Chain";
336
+ $.chainFormTitle.value = chain.title;
337
+ $.chainFormDesc.value = chain.description || "";
338
+ $.chainFormContext.value = chain.contextPassing || "summary";
339
+ $.chainFormEditId.value = chain.id;
340
+ for (const agentId of chain.agents) {
341
+ addChainAgentRow(agentId);
342
+ }
343
+ } else {
344
+ $.chainModalTitle.textContent = "New Chain";
345
+ $.chainFormEditId.value = "";
346
+ $.chainFormContext.value = "summary";
347
+ // Start with two empty rows
348
+ addChainAgentRow();
349
+ addChainAgentRow();
350
+ }
351
+ $.chainModal.classList.remove("hidden");
352
+ $.chainFormTitle.focus();
353
+ }
354
+
355
+ function closeChainModal() {
356
+ $.chainModal.classList.add("hidden");
357
+ }
358
+
359
+ function addChainAgentRow(selectedId) {
360
+ const agents = getState("agents") || [];
361
+ const row = document.createElement("div");
362
+ row.className = "chain-agent-row";
363
+
364
+ const stepNum = $.chainAgentList.children.length + 1;
365
+ row.innerHTML = `
366
+ <span class="chain-agent-step">${stepNum}</span>
367
+ <select class="chain-agent-select">
368
+ <option value="">Select agent...</option>
369
+ ${agents.map(a => `<option value="${escapeHtml(a.id)}" ${a.id === selectedId ? 'selected' : ''}>${escapeHtml(a.title)}</option>`).join("")}
370
+ </select>
371
+ <button type="button" class="chain-agent-up" title="Move up">↑</button>
372
+ <button type="button" class="chain-agent-down" title="Move down">↓</button>
373
+ <button type="button" class="chain-agent-remove" title="Remove">&times;</button>
374
+ `;
375
+
376
+ row.querySelector(".chain-agent-up").addEventListener("click", () => {
377
+ const prev = row.previousElementSibling;
378
+ if (prev) {
379
+ $.chainAgentList.insertBefore(row, prev);
380
+ renumberChainSteps();
381
+ }
382
+ });
383
+
384
+ row.querySelector(".chain-agent-down").addEventListener("click", () => {
385
+ const next = row.nextElementSibling;
386
+ if (next) {
387
+ $.chainAgentList.insertBefore(next, row);
388
+ renumberChainSteps();
389
+ }
390
+ });
391
+
392
+ row.querySelector(".chain-agent-remove").addEventListener("click", () => {
393
+ row.remove();
394
+ renumberChainSteps();
395
+ });
396
+
397
+ $.chainAgentList.appendChild(row);
398
+ }
399
+
400
+ function renumberChainSteps() {
401
+ const rows = $.chainAgentList.querySelectorAll(".chain-agent-row");
402
+ rows.forEach((row, i) => {
403
+ row.querySelector(".chain-agent-step").textContent = i + 1;
404
+ });
405
+ }
406
+
407
+ $.chainAddAgentBtn.addEventListener("click", () => addChainAgentRow());
408
+
409
+ $.chainForm.addEventListener("submit", async (e) => {
410
+ e.preventDefault();
411
+ const editId = $.chainFormEditId.value;
412
+ const selects = $.chainAgentList.querySelectorAll(".chain-agent-select");
413
+ const agentIds = [...selects].map(s => s.value).filter(Boolean);
414
+
415
+ if (agentIds.length < 2) {
416
+ alert("A chain needs at least 2 agents.");
417
+ return;
418
+ }
419
+
420
+ const data = {
421
+ title: $.chainFormTitle.value.trim(),
422
+ description: $.chainFormDesc.value.trim(),
423
+ agents: agentIds,
424
+ contextPassing: $.chainFormContext.value,
425
+ };
426
+ if (!data.title) return;
427
+
428
+ try {
429
+ if (editId) {
430
+ await api.updateChain(editId, data);
431
+ } else {
432
+ await api.createChain(data);
433
+ }
434
+ closeChainModal();
435
+ await loadAgents();
436
+ } catch (err) {
437
+ console.error("Failed to save chain:", err);
438
+ alert(err.message);
439
+ }
440
+ });
441
+
442
+ $.chainModalClose.addEventListener("click", closeChainModal);
443
+ $.chainModalCancel.addEventListener("click", closeChainModal);
444
+ $.chainModal.addEventListener("click", (e) => {
445
+ if (e.target === $.chainModal) closeChainModal();
446
+ });
447
+
448
+ async function deleteChain(id, title) {
449
+ if (!confirm(`Delete chain "${title}"?`)) return;
450
+ try {
451
+ await api.deleteChainApi(id);
452
+ await loadAgents();
453
+ } catch (err) {
454
+ console.error("Failed to delete chain:", err);
455
+ }
456
+ }
457
+
458
+ // ══════════════════════════════════════════════════════════
459
+ // Commands
460
+ // ══════════════════════════════════════════════════════════
461
+
462
+ export function registerAgentCommands() {
463
+ for (const [name, cmd] of Object.entries(commandRegistry)) {
464
+ if (cmd.category === "agent") delete commandRegistry[name];
465
+ }
466
+ const agents = getState("agents");
467
+ for (const agent of agents) {
468
+ registerCommand(`agent-${agent.id}`, {
469
+ category: "agent",
470
+ description: agent.description,
471
+ execute(args, pane) {
472
+ startAgent(agent, pane, args.trim() || undefined);
473
+ },
474
+ });
475
+ }
476
+ // Orchestrate command
477
+ registerCommand("orchestrate", {
478
+ category: "agent",
479
+ description: "Orchestrate: decompose a task and delegate to specialist agents",
480
+ execute(args, pane) {
481
+ if (args.trim()) {
482
+ startOrchestration(args.trim(), pane);
483
+ }
484
+ },
485
+ });
486
+
487
+ const chains = getState("agentChains") || [];
488
+ for (const chain of chains) {
489
+ registerCommand(`chain-${chain.id}`, {
490
+ category: "agent",
491
+ description: `Chain: ${chain.description || chain.title}`,
492
+ execute(args, pane) {
493
+ startChain(chain, pane);
494
+ },
495
+ });
496
+ }
497
+
498
+ const dags = getState("agentDags") || [];
499
+ for (const dag of dags) {
500
+ registerCommand(`dag-${dag.id}`, {
501
+ category: "agent",
502
+ description: `DAG: ${dag.description || dag.title}`,
503
+ execute(args, pane) {
504
+ startDag(dag, pane);
505
+ },
506
+ });
507
+ }
508
+ }
509
+
510
+ // ══════════════════════════════════════════════════════════
511
+ // Start Agent
512
+ // ══════════════════════════════════════════════════════════
513
+
514
+ export function startAgent(agentDef, pane, userContext) {
515
+ pane = pane || getPane(null);
516
+ const cwd = $.projectSelect.value;
517
+ if (!cwd) {
518
+ addStatus("Select a project first", true, pane);
519
+ return;
520
+ }
521
+
522
+ // Render agent header card
523
+ const div = document.createElement("div");
524
+ div.className = "msg";
525
+ const header = document.createElement("div");
526
+ header.className = "agent-header";
527
+ header.id = `agent-header-${agentDef.id}`;
528
+ header.innerHTML = `
529
+ <div class="agent-header-top">
530
+ <span class="agent-header-icon">${getAgentIcon(agentDef.icon)}</span>
531
+ <span class="agent-header-title">${escapeHtml(agentDef.title)}</span>
532
+ <span class="agent-status-badge running" id="agent-badge-${agentDef.id}">Running</span>
533
+ </div>
534
+ <div class="agent-header-goal">${escapeHtml(agentDef.goal)}</div>
535
+ <div class="agent-header-stats" id="agent-stats-${agentDef.id}">
536
+ <span class="agent-stat" id="agent-elapsed-${agentDef.id}">0s</span>
537
+ <span class="agent-stat-sep"></span>
538
+ <span class="agent-stat" id="agent-turns-${agentDef.id}">0/${agentDef.constraints?.maxTurns || 50} turns</span>
539
+ </div>
540
+ <div class="agent-activity-log" id="agent-log-${agentDef.id}"></div>
541
+ `;
542
+ div.appendChild(header);
543
+ pane.messagesDiv.appendChild(div);
544
+ scrollToBottom(pane);
545
+
546
+ // Start elapsed timer
547
+ const startTime = Date.now();
548
+ const timerId = setInterval(() => {
549
+ const el = document.getElementById(`agent-elapsed-${agentDef.id}`);
550
+ if (!el) { clearInterval(timerId); return; }
551
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
552
+ const mins = Math.floor(elapsed / 60);
553
+ const secs = elapsed % 60;
554
+ el.textContent = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
555
+ }, 1000);
556
+
557
+ pane._agentTimerId = timerId;
558
+ pane._agentId = agentDef.id;
559
+
560
+ pane.isStreaming = true;
561
+ if (!getState("parallelMode")) {
562
+ $.sendBtn.classList.add("hidden");
563
+ $.stopBtn.classList.remove("hidden");
564
+ }
565
+
566
+ const selectedOption = $.projectSelect.options[$.projectSelect.selectedIndex];
567
+ const projectName = selectedOption?.textContent || "Session";
568
+ const ws = getState("ws");
569
+
570
+ const model = getSelectedModel();
571
+ const payload = {
572
+ type: "agent",
573
+ agentDef,
574
+ cwd,
575
+ sessionId: getState("sessionId"),
576
+ projectName,
577
+ permissionMode: getPermissionMode(),
578
+ };
579
+ if (userContext) payload.userContext = userContext;
580
+ if (model) payload.model = model;
581
+ ws.send(JSON.stringify(payload));
582
+
583
+ showThinking(`Agent: ${agentDef.title} starting...`, pane);
584
+ }
585
+
586
+ // ══════════════════════════════════════════════════════════
587
+ // Start Chain
588
+ // ══════════════════════════════════════════════════════════
589
+
590
+ export function startChain(chain, pane) {
591
+ pane = pane || getPane(null);
592
+ const cwd = $.projectSelect.value;
593
+ if (!cwd) {
594
+ addStatus("Select a project first", true, pane);
595
+ return;
596
+ }
597
+
598
+ const allAgents = getState("agents") || [];
599
+ const agentDefs = chain.agents.map(id => allAgents.find(a => a.id === id)).filter(Boolean);
600
+ if (agentDefs.length === 0) {
601
+ addStatus("No valid agents in this chain", true, pane);
602
+ return;
603
+ }
604
+
605
+ // Render chain header card
606
+ const div = document.createElement("div");
607
+ div.className = "msg";
608
+ const header = document.createElement("div");
609
+ header.className = "chain-header";
610
+ header.id = `chain-header-${chain.id}`;
611
+ header.innerHTML = `
612
+ <div class="chain-header-top">
613
+ <span class="chain-header-icon">${getChainIcon()}</span>
614
+ <span class="chain-header-title">${escapeHtml(chain.title)}</span>
615
+ <span class="agent-status-badge running" id="chain-badge-${chain.id}">Running</span>
616
+ </div>
617
+ <div class="chain-pipeline" id="chain-pipeline-${chain.id}">
618
+ ${agentDefs.map((a, i) => `
619
+ <div class="chain-pipeline-step" id="chain-step-${chain.id}-${i}">
620
+ <span class="chain-pipeline-num">${i + 1}</span>
621
+ <span class="chain-pipeline-name">${escapeHtml(a.title)}</span>
622
+ <span class="chain-pipeline-status" id="chain-step-status-${chain.id}-${i}">pending</span>
623
+ </div>
624
+ ${i < agentDefs.length - 1 ? '<div class="chain-pipeline-connector"></div>' : ''}
625
+ `).join("")}
626
+ </div>
627
+ `;
628
+ div.appendChild(header);
629
+ pane.messagesDiv.appendChild(div);
630
+ scrollToBottom(pane);
631
+
632
+ // Start elapsed timer
633
+ const startTime = Date.now();
634
+ const timerId = setInterval(() => {
635
+ const badge = document.getElementById(`chain-badge-${chain.id}`);
636
+ if (!badge || !badge.classList.contains("running")) { clearInterval(timerId); return; }
637
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
638
+ const mins = Math.floor(elapsed / 60);
639
+ const secs = elapsed % 60;
640
+ badge.textContent = mins > 0 ? `Running ${mins}m ${secs}s` : `Running ${secs}s`;
641
+ }, 1000);
642
+
643
+ pane._chainTimerId = timerId;
644
+
645
+ pane.isStreaming = true;
646
+ if (!getState("parallelMode")) {
647
+ $.sendBtn.classList.add("hidden");
648
+ $.stopBtn.classList.remove("hidden");
649
+ }
650
+
651
+ const selectedOption = $.projectSelect.options[$.projectSelect.selectedIndex];
652
+ const projectName = selectedOption?.textContent || "Session";
653
+ const ws = getState("ws");
654
+ const model = getSelectedModel();
655
+
656
+ ws.send(JSON.stringify({
657
+ type: "agent_chain",
658
+ chain,
659
+ agents: agentDefs,
660
+ cwd,
661
+ sessionId: getState("sessionId"),
662
+ projectName,
663
+ permissionMode: getPermissionMode(),
664
+ model,
665
+ }));
666
+
667
+ showThinking(`Chain: ${chain.title} starting...`, pane);
668
+ }
669
+
670
+ // ══════════════════════════════════════════════════════════
671
+ // Start Orchestration
672
+ // ══════════════════════════════════════════════════════════
673
+
674
+ export function startOrchestration(task, pane) {
675
+ pane = pane || getPane(null);
676
+ const cwd = $.projectSelect.value;
677
+ if (!cwd) {
678
+ addStatus("Select a project first", true, pane);
679
+ return;
680
+ }
681
+
682
+ // Render orchestrator header
683
+ const orchId = `orch-${Date.now()}`;
684
+ const div = document.createElement("div");
685
+ div.className = "msg";
686
+ const header = document.createElement("div");
687
+ header.className = "orchestrator-header";
688
+ header.id = orchId;
689
+ header.innerHTML = `
690
+ <div class="orch-header-top">
691
+ <span class="orch-header-icon">${getOrchIcon()}</span>
692
+ <span class="orch-header-title">Orchestrator</span>
693
+ <span class="agent-status-badge running" id="${orchId}-badge">Planning</span>
694
+ </div>
695
+ <div class="orch-task">${escapeHtml(task.length > 200 ? task.slice(0, 200) + '...' : task)}</div>
696
+ <div class="orch-dispatches" id="${orchId}-dispatches"></div>
697
+ `;
698
+ div.appendChild(header);
699
+ pane.messagesDiv.appendChild(div);
700
+ scrollToBottom(pane);
701
+
702
+ // Store orchId on pane for message routing
703
+ pane._orchId = orchId;
704
+
705
+ pane.isStreaming = true;
706
+ if (!getState("parallelMode")) {
707
+ $.sendBtn.classList.add("hidden");
708
+ $.stopBtn.classList.remove("hidden");
709
+ }
710
+
711
+ const selectedOption = $.projectSelect.options[$.projectSelect.selectedIndex];
712
+ const projectName = selectedOption?.textContent || "Session";
713
+ const ws = getState("ws");
714
+ const model = getSelectedModel();
715
+
716
+ ws.send(JSON.stringify({
717
+ type: "orchestrate",
718
+ task,
719
+ cwd,
720
+ sessionId: getState("sessionId"),
721
+ projectName,
722
+ permissionMode: getPermissionMode(),
723
+ model,
724
+ }));
725
+
726
+ showThinking("Orchestrator: analyzing task...", pane);
727
+ }
728
+
729
+ // ══════════════════════════════════════════════════════════
730
+ // DAG
731
+ // ══════════════════════════════════════════════════════════
732
+
733
+ async function deleteDag(id, title) {
734
+ if (!confirm(`Delete DAG "${title}"?`)) return;
735
+ try {
736
+ await api.deleteDagApi(id);
737
+ await loadAgents();
738
+ } catch (err) {
739
+ console.error("Failed to delete DAG:", err);
740
+ }
741
+ }
742
+
743
+ export function startDag(dag, pane) {
744
+ pane = pane || getPane(null);
745
+ const cwd = $.projectSelect.value;
746
+ if (!cwd) {
747
+ addStatus("Select a project first", true, pane);
748
+ return;
749
+ }
750
+
751
+ const allAgents = getState("agents") || [];
752
+ const agentDefs = dag.nodes.map(n => {
753
+ const a = allAgents.find(ag => ag.id === n.agentId);
754
+ return a ? { ...a, nodeId: n.id } : null;
755
+ }).filter(Boolean);
756
+
757
+ if (agentDefs.length < 2) {
758
+ addStatus("DAG needs at least 2 valid agent nodes", true, pane);
759
+ return;
760
+ }
761
+
762
+ const dagRunId = `dag-${Date.now()}`;
763
+
764
+ // Render DAG execution header
765
+ const div = document.createElement("div");
766
+ div.className = "msg";
767
+ const header = document.createElement("div");
768
+ header.className = "dag-header";
769
+ header.id = dagRunId;
770
+ header.innerHTML = `
771
+ <div class="dag-header-top">
772
+ <span class="dag-header-icon">${getDagIcon()}</span>
773
+ <span class="dag-header-title">${escapeHtml(dag.title)}</span>
774
+ <span class="agent-status-badge running" id="${dagRunId}-badge">Running</span>
775
+ </div>
776
+ <div class="dag-desc">${escapeHtml(dag.description || `${dag.nodes.length} nodes, ${dag.edges.length} edges`)}</div>
777
+ <div class="dag-graph" id="${dagRunId}-graph">
778
+ ${dag.nodes.map(n => {
779
+ const a = allAgents.find(ag => ag.id === n.agentId);
780
+ const name = a ? a.title : n.agentId;
781
+ return `<div class="dag-graph-node" id="${dagRunId}-node-${n.id}">
782
+ <span class="dag-graph-node-name">${escapeHtml(name)}</span>
783
+ <span class="dag-graph-node-status" id="${dagRunId}-node-status-${n.id}">pending</span>
784
+ </div>`;
785
+ }).join('')}
786
+ </div>
787
+ `;
788
+ div.appendChild(header);
789
+ pane.messagesDiv.appendChild(div);
790
+ scrollToBottom(pane);
791
+
792
+ // Store dagRunId on pane for message routing
793
+ pane._dagRunId = dagRunId;
794
+
795
+ pane.isStreaming = true;
796
+ if (!getState("parallelMode")) {
797
+ $.sendBtn.classList.add("hidden");
798
+ $.stopBtn.classList.remove("hidden");
799
+ }
800
+
801
+ const selectedOption = $.projectSelect.options[$.projectSelect.selectedIndex];
802
+ const projectName = selectedOption?.textContent || "Session";
803
+ const ws = getState("ws");
804
+ const model = getSelectedModel();
805
+
806
+ ws.send(JSON.stringify({
807
+ type: "agent_dag",
808
+ dag,
809
+ agents: allAgents,
810
+ cwd,
811
+ sessionId: getState("sessionId"),
812
+ projectName,
813
+ permissionMode: getPermissionMode(),
814
+ model,
815
+ }));
816
+
817
+ showThinking(`DAG: ${dag.title} starting...`, pane);
818
+ }
819
+
820
+ // ══════════════════════════════════════════════════════════
821
+ // WebSocket Message Handlers
822
+ // ══════════════════════════════════════════════════════════
823
+
824
+ export function handleAgentMessage(msg, pane) {
825
+ switch (msg.type) {
826
+ case "agent_started":
827
+ showThinking(`Agent: ${msg.title} working...`, pane);
828
+ break;
829
+
830
+ case "agent_progress": {
831
+ const turnsEl = document.getElementById(`agent-turns-${msg.agentId}`);
832
+ if (turnsEl) turnsEl.textContent = `${msg.turn}/${msg.maxTurns} turns`;
833
+
834
+ const log = document.getElementById(`agent-log-${msg.agentId}`);
835
+ if (log) {
836
+ const entry = document.createElement("div");
837
+ entry.className = "agent-log-entry";
838
+ const detail = msg.detail ? ` ${escapeHtml(msg.detail)}` : "";
839
+ entry.innerHTML = `<span class="agent-log-action">${escapeHtml(msg.action)}</span>${detail}`;
840
+ log.appendChild(entry);
841
+ log.scrollTop = log.scrollHeight;
842
+ }
843
+
844
+ showThinking(`Agent: ${msg.action}...`, pane);
845
+ break;
846
+ }
847
+
848
+ case "agent_completed": {
849
+ const badge = document.getElementById(`agent-badge-${msg.agentId}`);
850
+ if (badge) {
851
+ badge.textContent = "Completed";
852
+ badge.className = "agent-status-badge completed";
853
+ }
854
+
855
+ if (pane._agentTimerId) {
856
+ clearInterval(pane._agentTimerId);
857
+ pane._agentTimerId = null;
858
+ }
859
+
860
+ const turnsEl = document.getElementById(`agent-turns-${msg.agentId}`);
861
+ if (turnsEl) turnsEl.textContent = `${msg.totalTurns} turns`;
862
+
863
+ const elapsedEl = document.getElementById(`agent-elapsed-${msg.agentId}`);
864
+ if (elapsedEl) {
865
+ const secs = Math.round((msg.durationMs || 0) / 1000);
866
+ const mins = Math.floor(secs / 60);
867
+ const s = secs % 60;
868
+ elapsedEl.textContent = mins > 0 ? `${mins}m ${s}s` : `${secs}s`;
869
+ }
870
+
871
+ removeThinking(pane);
872
+ addStatus(`Agent completed (${msg.totalTurns} turns, $${(msg.costUsd || 0).toFixed(4)})`, false, pane);
873
+ finishAgentStreaming(pane);
874
+ break;
875
+ }
876
+
877
+ case "agent_error": {
878
+ const badge = document.getElementById(`agent-badge-${msg.agentId}`);
879
+ if (badge) {
880
+ badge.textContent = "Error";
881
+ badge.className = "agent-status-badge error";
882
+ }
883
+ if (pane._agentTimerId) {
884
+ clearInterval(pane._agentTimerId);
885
+ pane._agentTimerId = null;
886
+ }
887
+ removeThinking(pane);
888
+ finishAgentStreaming(pane);
889
+ break;
890
+ }
891
+
892
+ case "agent_aborted": {
893
+ const badge = document.getElementById(`agent-badge-${msg.agentId}`);
894
+ if (badge) {
895
+ badge.textContent = "Aborted";
896
+ badge.className = "agent-status-badge error";
897
+ }
898
+ if (pane._agentTimerId) {
899
+ clearInterval(pane._agentTimerId);
900
+ pane._agentTimerId = null;
901
+ }
902
+ removeThinking(pane);
903
+ finishAgentStreaming(pane);
904
+ break;
905
+ }
906
+
907
+ // ── Chain messages ──
908
+
909
+ case "agent_chain_started":
910
+ showThinking(`Chain: ${msg.title} — ${msg.totalSteps} agents...`, pane);
911
+ break;
912
+
913
+ case "agent_chain_step": {
914
+ const statusEl = document.getElementById(`chain-step-status-${msg.chainId}-${msg.stepIndex}`);
915
+ const stepEl = document.getElementById(`chain-step-${msg.chainId}-${msg.stepIndex}`);
916
+ if (statusEl) {
917
+ statusEl.textContent = msg.status;
918
+ statusEl.className = `chain-pipeline-status ${msg.status}`;
919
+ }
920
+ if (stepEl) {
921
+ stepEl.className = `chain-pipeline-step ${msg.status}`;
922
+ }
923
+ if (msg.status === "running") {
924
+ showThinking(`Chain step ${msg.stepIndex + 1}: ${msg.agentTitle} working...`, pane);
925
+ }
926
+ break;
927
+ }
928
+
929
+ case "agent_chain_completed": {
930
+ const chainBadge = document.getElementById(`chain-badge-${msg.chainId}`);
931
+ if (chainBadge) {
932
+ chainBadge.textContent = "Completed";
933
+ chainBadge.className = "agent-status-badge completed";
934
+ }
935
+ if (pane._chainTimerId) {
936
+ clearInterval(pane._chainTimerId);
937
+ pane._chainTimerId = null;
938
+ }
939
+
940
+ // Show shared context summary
941
+ if (msg.runId) {
942
+ api.fetchAgentContext(msg.runId).then(contexts => {
943
+ if (contexts.length > 0) {
944
+ const ctxDiv = document.createElement("div");
945
+ ctxDiv.className = "chain-context-summary";
946
+ ctxDiv.innerHTML = `
947
+ <div class="chain-context-header">Shared Context (${contexts.length} entries)</div>
948
+ ${contexts.map(c => `
949
+ <div class="chain-context-entry">
950
+ <span class="chain-context-agent">${escapeHtml(c.agent_id)}</span>
951
+ <span class="chain-context-preview">${escapeHtml((c.value || "").slice(0, 150))}${c.value?.length > 150 ? '...' : ''}</span>
952
+ </div>
953
+ `).join("")}
954
+ `;
955
+ const pipeline = document.getElementById(`chain-pipeline-${msg.chainId}`);
956
+ if (pipeline) pipeline.parentElement.appendChild(ctxDiv);
957
+ }
958
+ }).catch(() => {});
959
+ }
960
+
961
+ removeThinking(pane);
962
+ addStatus(`Chain completed`, false, pane);
963
+ finishAgentStreaming(pane);
964
+ break;
965
+ }
966
+
967
+ // ── Orchestrator messages ──
968
+
969
+ case "orchestrator_started":
970
+ showThinking("Orchestrator: planning...", pane);
971
+ break;
972
+
973
+ case "orchestrator_phase": {
974
+ const orchId = pane._orchId;
975
+ const badge = orchId ? document.getElementById(`${orchId}-badge`) : null;
976
+ if (badge) {
977
+ badge.textContent = msg.phase === "planning" ? "Planning" : "Synthesizing";
978
+ }
979
+ showThinking(`Orchestrator: ${msg.phase}...`, pane);
980
+ break;
981
+ }
982
+
983
+ case "orchestrator_dispatching": {
984
+ const orchId = pane._orchId;
985
+ const badge = orchId ? document.getElementById(`${orchId}-badge`) : null;
986
+ if (badge) badge.textContent = `Dispatching ${msg.totalAgents} agents`;
987
+
988
+ const container = orchId ? document.getElementById(`${orchId}-dispatches`) : null;
989
+ if (container && msg.dispatches) {
990
+ container.innerHTML = msg.dispatches.map((d, i) => `
991
+ <div class="orch-dispatch-row" id="orch-dispatch-${i}">
992
+ <span class="orch-dispatch-num">${i + 1}</span>
993
+ <span class="orch-dispatch-agent">${escapeHtml(d.agentId)}</span>
994
+ <span class="orch-dispatch-ctx">${escapeHtml(d.context)}</span>
995
+ <span class="orch-dispatch-status" id="orch-dispatch-status-${i}">queued</span>
996
+ </div>
997
+ `).join("");
998
+ }
999
+ showThinking(`Orchestrator: dispatching ${msg.totalAgents} agents...`, pane);
1000
+ break;
1001
+ }
1002
+
1003
+ case "orchestrator_dispatch": {
1004
+ const statusEl = document.getElementById(`orch-dispatch-status-${msg.stepIndex}`);
1005
+ const rowEl = document.getElementById(`orch-dispatch-${msg.stepIndex}`);
1006
+ if (statusEl) {
1007
+ statusEl.textContent = msg.status;
1008
+ statusEl.className = `orch-dispatch-status ${msg.status}`;
1009
+ }
1010
+ if (rowEl) {
1011
+ rowEl.className = `orch-dispatch-row ${msg.status}`;
1012
+ }
1013
+ if (msg.status === "running") {
1014
+ showThinking(`Orchestrator: ${msg.agentTitle} working...`, pane);
1015
+ }
1016
+ break;
1017
+ }
1018
+
1019
+ case "orchestrator_dispatch_skip":
1020
+ addStatus(`Skipped agent "${msg.agentId}": ${msg.reason}`, true, pane);
1021
+ break;
1022
+
1023
+ case "orchestrator_error": {
1024
+ const orchId = pane._orchId;
1025
+ const badge = orchId ? document.getElementById(`${orchId}-badge`) : null;
1026
+ if (badge) {
1027
+ badge.textContent = "Error";
1028
+ badge.className = "agent-status-badge error";
1029
+ }
1030
+ removeThinking(pane);
1031
+ finishAgentStreaming(pane);
1032
+ break;
1033
+ }
1034
+
1035
+ case "orchestrator_completed": {
1036
+ const orchId = pane._orchId;
1037
+ const badge = orchId ? document.getElementById(`${orchId}-badge`) : null;
1038
+ if (badge) {
1039
+ badge.textContent = "Completed";
1040
+ badge.className = "agent-status-badge completed";
1041
+ }
1042
+ removeThinking(pane);
1043
+ addStatus(`Orchestrator completed (${msg.dispatched} agents dispatched)`, false, pane);
1044
+ finishAgentStreaming(pane);
1045
+ break;
1046
+ }
1047
+
1048
+ // ── DAG messages ──
1049
+
1050
+ case "dag_started":
1051
+ showThinking(`DAG: ${msg.title} — ${msg.totalNodes} nodes...`, pane);
1052
+ break;
1053
+
1054
+ case "dag_level":
1055
+ showThinking(`DAG: running level ${msg.level + 1} (${msg.nodeIds.length} parallel nodes)...`, pane);
1056
+ break;
1057
+
1058
+ case "dag_node": {
1059
+ const dagRunId = pane._dagRunId;
1060
+ if (dagRunId) {
1061
+ const statusEl = document.getElementById(`${dagRunId}-node-status-${msg.nodeId}`);
1062
+ const nodeEl = document.getElementById(`${dagRunId}-node-${msg.nodeId}`);
1063
+ if (statusEl) {
1064
+ statusEl.textContent = msg.status;
1065
+ statusEl.className = `dag-graph-node-status ${msg.status}`;
1066
+ }
1067
+ if (nodeEl) {
1068
+ nodeEl.className = `dag-graph-node ${msg.status}`;
1069
+ }
1070
+ }
1071
+ if (msg.status === "running") {
1072
+ showThinking(`DAG: ${msg.agentTitle || msg.nodeId} working...`, pane);
1073
+ }
1074
+ break;
1075
+ }
1076
+
1077
+ case "dag_completed": {
1078
+ const dagRunId = pane._dagRunId;
1079
+ if (dagRunId) {
1080
+ const badge = document.getElementById(`${dagRunId}-badge`);
1081
+ if (badge) {
1082
+ badge.textContent = "Completed";
1083
+ badge.className = "agent-status-badge completed";
1084
+ }
1085
+ }
1086
+ removeThinking(pane);
1087
+ addStatus(`DAG completed (${msg.succeeded}/${msg.totalNodes} succeeded)`, false, pane);
1088
+ finishAgentStreaming(pane);
1089
+ break;
1090
+ }
1091
+
1092
+ case "dag_error": {
1093
+ const dagRunId = pane._dagRunId;
1094
+ if (dagRunId) {
1095
+ const badge = document.getElementById(`${dagRunId}-badge`);
1096
+ if (badge) {
1097
+ badge.textContent = "Error";
1098
+ badge.className = "agent-status-badge error";
1099
+ }
1100
+ }
1101
+ removeThinking(pane);
1102
+ addStatus(`DAG error: ${msg.error}`, true, pane);
1103
+ finishAgentStreaming(pane);
1104
+ break;
1105
+ }
1106
+ }
1107
+ }
1108
+
1109
+ // ══════════════════════════════════════════════════════════
1110
+ // Icons
1111
+ // ══════════════════════════════════════════════════════════
1112
+
1113
+ function getAgentIcon(icon) {
1114
+ const icons = {
1115
+ search: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>`,
1116
+ bug: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m8 2 1.88 1.88M14.12 3.88 16 2M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/><path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/><path d="M12 20v-9"/><path d="M6.53 9C4.6 8.8 3 7.1 3 5"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="M22 13h-4"/><path d="M17.2 17c2.1.1 3.8 1.9 3.8 4"/></svg>`,
1117
+ check: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>`,
1118
+ tool: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>`,
1119
+ };
1120
+ return icons[icon] || icons.tool;
1121
+ }
1122
+
1123
+ function getChainIcon() {
1124
+ return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>`;
1125
+ }
1126
+
1127
+ function getOrchIcon() {
1128
+ return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="5" r="3"/><circle cx="5" cy="19" r="3"/><circle cx="19" cy="19" r="3"/><path d="M12 8v4M8.5 16.5 10.5 13M15.5 16.5 13.5 13"/></svg>`;
1129
+ }
1130
+
1131
+ function getMonitorIcon() {
1132
+ return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/></svg>`;
1133
+ }
1134
+
1135
+ function getDagIcon() {
1136
+ return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="6" height="6" rx="1"/><rect x="15" y="3" width="6" height="6" rx="1"/><rect x="9" y="15" width="6" height="6" rx="1"/><path d="M9 6h6M6 9v3l3 3M18 9v3l-3 3"/></svg>`;
1137
+ }
1138
+
1139
+ // ══════════════════════════════════════════════════════════
1140
+ // Panel toggle
1141
+ // ══════════════════════════════════════════════════════════
1142
+
1143
+ function toggleAgentSidebar(forceOpen) {
1144
+ const sidebar = $.agentSidebar;
1145
+ if (!sidebar) return;
1146
+ const isOpen = !sidebar.classList.contains("hidden");
1147
+ if (forceOpen === true || !isOpen) {
1148
+ sidebar.classList.remove("hidden");
1149
+ $.agentBtn.classList.add("active");
1150
+ } else {
1151
+ sidebar.classList.add("hidden");
1152
+ $.agentBtn.classList.remove("active");
1153
+ }
1154
+ }
1155
+
1156
+ $.agentBtn.addEventListener("click", () => {
1157
+ $.toolboxPanel.classList.add("hidden");
1158
+ $.toolboxBtn.classList.remove("active");
1159
+ if ($.workflowPanel) $.workflowPanel.classList.add("hidden");
1160
+ if ($.workflowBtn) $.workflowBtn.classList.remove("active");
1161
+ toggleAgentSidebar();
1162
+ });
1163
+
1164
+ $.agentSidebarClose?.addEventListener("click", () => {
1165
+ toggleAgentSidebar(false);
1166
+ $.agentSidebar.classList.add("hidden");
1167
+ $.agentBtn.classList.remove("active");
1168
+ });
1169
+
1170
+ // ══════════════════════════════════════════════════════════
1171
+ // Orchestrate Modal
1172
+ // ══════════════════════════════════════════════════════════
1173
+
1174
+ function openOrchModal() {
1175
+ if (!$.orchModal) return;
1176
+ $.orchTaskInput.value = "";
1177
+ $.orchModal.classList.remove("hidden");
1178
+ setTimeout(() => $.orchTaskInput.focus(), 100);
1179
+ }
1180
+
1181
+ function closeOrchModal() {
1182
+ if (!$.orchModal) return;
1183
+ $.orchModal.classList.add("hidden");
1184
+ }
1185
+
1186
+ $.orchModalClose?.addEventListener("click", closeOrchModal);
1187
+ $.orchModalCancel?.addEventListener("click", closeOrchModal);
1188
+ $.orchModal?.addEventListener("click", (e) => {
1189
+ if (e.target === $.orchModal) closeOrchModal();
1190
+ });
1191
+
1192
+ $.orchModalRun?.addEventListener("click", () => {
1193
+ const task = $.orchTaskInput.value.trim();
1194
+ if (!task) {
1195
+ $.orchTaskInput.focus();
1196
+ return;
1197
+ }
1198
+ closeOrchModal();
1199
+ startOrchestration(task, getPane(null));
1200
+ });
1201
+
1202
+ // Ctrl/Cmd+Enter to submit
1203
+ $.orchTaskInput?.addEventListener("keydown", (e) => {
1204
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
1205
+ e.preventDefault();
1206
+ $.orchModalRun?.click();
1207
+ }
1208
+ if (e.key === "Escape") closeOrchModal();
1209
+ });