@symerian/symi 3.5.0 → 3.5.2

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 (62) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/bundled/boot-md/handler.js +4 -4
  3. package/dist/bundled/session-memory/handler.js +4 -4
  4. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  5. package/dist/{chrome-C_I81hbq.js → chrome-B7-rO4i9.js} +4 -4
  6. package/dist/{chrome-BKUACyeO.js → chrome-DPjznJQ-.js} +4 -4
  7. package/dist/control-ui/css/revert-red-theme.md +141 -0
  8. package/dist/control-ui/css/style.css +5843 -0
  9. package/dist/control-ui/css/style.css.backup-2026-03-03-162525 +3546 -0
  10. package/dist/control-ui/css/style.css.backup-before-red-2026-03-03-162525 +3546 -0
  11. package/dist/control-ui/css/style.css.backup-before-red-theme-2026-03-03-162530 +3546 -0
  12. package/dist/control-ui/css/style.css.pre-2row +2165 -0
  13. package/dist/control-ui/css/style.css.pre-brand +1776 -0
  14. package/dist/control-ui/css/style.css.pre-history +1974 -0
  15. package/dist/control-ui/css/style.css.pre-nav +2264 -0
  16. package/dist/control-ui/css/style.css.pre-newsession +1898 -0
  17. package/dist/control-ui/css/style.css.pre-queue +2195 -0
  18. package/dist/control-ui/css/style.css.pre-red-prompt +2524 -0
  19. package/dist/control-ui/css/style.css.pre-stop +2239 -0
  20. package/dist/control-ui/css/style.css.pre-textarea +2184 -0
  21. package/dist/control-ui/css/style.css.pre-watchdog +1848 -0
  22. package/dist/control-ui/css/style.css.red-theme +2999 -0
  23. package/dist/control-ui/index.html +1049 -0
  24. package/dist/control-ui/js/app.js +1304 -0
  25. package/dist/control-ui/js/app.js.pre-2row +463 -0
  26. package/dist/control-ui/js/app.js.pre-heartbeat-filter +595 -0
  27. package/dist/control-ui/js/app.js.pre-newsession +408 -0
  28. package/dist/control-ui/js/app.js.pre-queue +476 -0
  29. package/dist/control-ui/js/app.js.pre-stop +564 -0
  30. package/dist/control-ui/js/app.js.pre-textarea +467 -0
  31. package/dist/control-ui/js/app.js.pre-watchdog +293 -0
  32. package/dist/control-ui/js/connections.js +438 -0
  33. package/dist/control-ui/js/gateway.js +233 -0
  34. package/dist/control-ui/js/gateway.js.pre-stop +110 -0
  35. package/dist/control-ui/js/history.js +732 -0
  36. package/dist/control-ui/js/logs.js +238 -0
  37. package/dist/control-ui/js/menu.js +232 -0
  38. package/dist/control-ui/js/menu.js.pre-nav +66 -0
  39. package/dist/control-ui/js/metrics.js +53 -0
  40. package/dist/control-ui/js/models.js +138 -0
  41. package/dist/control-ui/js/render.js +882 -0
  42. package/dist/control-ui/js/render.test.js +112 -0
  43. package/dist/control-ui/js/scheduling.js +461 -0
  44. package/dist/control-ui/js/settings.js +910 -0
  45. package/dist/control-ui/js/slash-autocomplete.js +168 -0
  46. package/dist/control-ui/js/subagents.js +560 -0
  47. package/dist/control-ui/js/utils.js +29 -0
  48. package/dist/control-ui/vendor/highlight.min.js +2518 -0
  49. package/dist/control-ui/vendor/marked.min.js +69 -0
  50. package/dist/{deliver-DyO3QD8O.js → deliver-DTRkeYm3.js} +4 -4
  51. package/dist/{deliver-Cjyb6h4g.js → deliver-oWGJwzFf.js} +4 -4
  52. package/dist/extensionAPI.js +4 -4
  53. package/dist/llm-slug-generator.js +4 -4
  54. package/dist/{manager-rvtFoeFT.js → manager-CFenq_aO.js} +1 -1
  55. package/dist/{manager-PTSjHNVq.js → manager-CsxTf96V.js} +1 -1
  56. package/dist/{pi-embedded-BPuUM-gD.js → pi-embedded-Cdub5Vs9.js} +10 -10
  57. package/dist/{pw-ai-BFS9ezWe.js → pw-ai-BOOB8qoi.js} +1 -1
  58. package/dist/{pw-ai-Cx-Ko_FL.js → pw-ai-D2pEVS5n.js} +1 -1
  59. package/dist/{synthesis-7UL3pCpj.js → synthesis-Be9nYyDd.js} +4 -4
  60. package/dist/{synthesis-fD8J2vag.js → synthesis-CBIT6Vnk.js} +4 -4
  61. package/dist/{unified-runner-BIiKFnNF.js → unified-runner-BVvvnjXW.js} +10 -10
  62. package/package.json +3 -3
@@ -0,0 +1,560 @@
1
+ // ── Subagents Panel ─────────────────────────────────────────────────────────
2
+ // Displays active subagents and allows spawning new ones via quick-deploy or custom.
3
+ // ─────────────────────────────────────────────────────────────────────────────
4
+
5
+ "use strict";
6
+
7
+ (function () {
8
+ // ── Agent Presets ──────────────────────────────────────────────────────────
9
+ const AGENT_PRESETS = {
10
+ research: {
11
+ label: "Research Agent",
12
+ taskTemplate: "Research and summarize: ",
13
+ model: "sonnet",
14
+ thinking: "",
15
+ },
16
+ review: {
17
+ label: "Code Review Agent",
18
+ taskTemplate: "Review this code and provide detailed feedback:\n",
19
+ model: "sonnet",
20
+ thinking: "",
21
+ },
22
+ writer: {
23
+ label: "Writer Agent",
24
+ taskTemplate: "Draft content for: ",
25
+ model: "sonnet",
26
+ thinking: "",
27
+ },
28
+ analyst: {
29
+ label: "Analyst Agent",
30
+ taskTemplate: "Analyze and report on: ",
31
+ model: "sonnet",
32
+ thinking: "",
33
+ },
34
+ planner: {
35
+ label: "Planner Agent",
36
+ taskTemplate: "Create an action plan for: ",
37
+ model: "sonnet",
38
+ thinking: "",
39
+ },
40
+ debug: {
41
+ label: "Debug Agent",
42
+ taskTemplate: "Debug and fix this issue:\n",
43
+ model: "sonnet",
44
+ thinking: "",
45
+ },
46
+ docs: {
47
+ label: "Documentation Agent",
48
+ taskTemplate: "Write documentation for: ",
49
+ model: "sonnet",
50
+ thinking: "",
51
+ },
52
+ test: {
53
+ label: "Test Agent",
54
+ taskTemplate: "Write tests for: ",
55
+ model: "sonnet",
56
+ thinking: "",
57
+ },
58
+ };
59
+
60
+ // ── State ──────────────────────────────────────────────────────────────────
61
+ let subagents = [];
62
+ let stagedAgents = []; // Array of { id, type, label, task, model, thinking }
63
+ let _pollInterval = null;
64
+ let editingAgentId = null; // Track which staged agent is being edited
65
+
66
+ // ── DOM Elements ───────────────────────────────────────────────────────────
67
+ const _panel = document.getElementById("subagents-panel");
68
+ const list = document.getElementById("subagents-list");
69
+ const emptyState = document.getElementById("subagents-empty");
70
+ const countBadge = document.getElementById("subagents-count");
71
+ const spawnBtn = document.getElementById("subagent-spawn-btn");
72
+
73
+ // Quick deploy elements
74
+ const quickDeployBtns = document.querySelectorAll(".quick-deploy-btn");
75
+ const _stagingBox = document.getElementById("staging-box");
76
+ const stagingChips = document.getElementById("staging-chips");
77
+ const stagingDeployBtn = document.getElementById("staging-deploy-btn");
78
+
79
+ // Modal elements
80
+ const modalOverlay = document.getElementById("subagent-modal-overlay");
81
+ const modalClose = document.getElementById("subagent-modal-close");
82
+ const modalCancel = document.getElementById("subagent-modal-cancel");
83
+ const modalSubmit = document.getElementById("subagent-modal-submit");
84
+ const taskInput = document.getElementById("subagent-task");
85
+ const labelInput = document.getElementById("subagent-label");
86
+ const modelSelect = document.getElementById("subagent-model");
87
+ const thinkingSelect = document.getElementById("subagent-thinking");
88
+
89
+ // ── Icons ──────────────────────────────────────────────────────────────────
90
+ const ICONS = {
91
+ running: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
92
+ <circle cx="12" cy="12" r="3"/>
93
+ <path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>
94
+ </svg>`,
95
+ completed: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
96
+ <path d="M20 6L9 17l-5-5"/>
97
+ </svg>`,
98
+ error: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
99
+ <circle cx="12" cy="12" r="10"/>
100
+ <path d="M15 9l-6 6M9 9l6 6"/>
101
+ </svg>`,
102
+ };
103
+
104
+ // ── Staging Management ─────────────────────────────────────────────────────
105
+ function addToStaging(agentType) {
106
+ const preset = AGENT_PRESETS[agentType];
107
+ if (!preset) {
108
+ return;
109
+ }
110
+
111
+ const stagedAgent = {
112
+ id: crypto.randomUUID(),
113
+ type: agentType,
114
+ label: preset.label,
115
+ task: preset.taskTemplate,
116
+ model: preset.model,
117
+ thinking: preset.thinking,
118
+ };
119
+
120
+ stagedAgents.push(stagedAgent);
121
+ renderStagingChips();
122
+ }
123
+
124
+ function removeFromStaging(agentId) {
125
+ stagedAgents = stagedAgents.filter((a) => a.id !== agentId);
126
+ renderStagingChips();
127
+ }
128
+
129
+ function renderStagingChips() {
130
+ stagingChips.innerHTML = "";
131
+
132
+ if (stagedAgents.length === 0) {
133
+ stagingChips.innerHTML = '<span class="staging-empty">Select agents above</span>';
134
+ stagingDeployBtn.disabled = true;
135
+ return;
136
+ }
137
+
138
+ stagingDeployBtn.disabled = false;
139
+
140
+ for (const agent of stagedAgents) {
141
+ const chip = document.createElement("div");
142
+ chip.className = "staging-chip";
143
+ chip.setAttribute("data-agent-id", agent.id);
144
+
145
+ chip.innerHTML = `
146
+ <span class="staging-chip-name">${escHtml(agent.label.replace(" Agent", ""))}</span>
147
+ <span class="staging-chip-remove" title="Remove">×</span>
148
+ `;
149
+
150
+ // Click chip to edit
151
+ chip.addEventListener("click", (e) => {
152
+ if (e.target.classList.contains("staging-chip-remove")) {
153
+ e.stopPropagation();
154
+ removeFromStaging(agent.id);
155
+ } else {
156
+ openModalForEdit(agent);
157
+ }
158
+ });
159
+
160
+ stagingChips.appendChild(chip);
161
+ }
162
+ }
163
+
164
+ // ── Deploy Staged Agents ───────────────────────────────────────────────────
165
+ async function deployAllStaged() {
166
+ if (stagedAgents.length === 0) {
167
+ return;
168
+ }
169
+ if (!window.gateway?.connected) {
170
+ alert("Gateway not connected");
171
+ return;
172
+ }
173
+
174
+ stagingDeployBtn.disabled = true;
175
+ stagingDeployBtn.innerHTML = `
176
+ <svg class="spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
177
+ <circle cx="12" cy="12" r="10" stroke-dasharray="60" stroke-dashoffset="20"/>
178
+ </svg>
179
+ Deploying...
180
+ `;
181
+
182
+ const toSpawn = [...stagedAgents];
183
+ let successCount = 0;
184
+
185
+ for (const agent of toSpawn) {
186
+ try {
187
+ const uuid = crypto.randomUUID();
188
+ const sessionKey = `agent:main:subagent:${uuid}`;
189
+
190
+ await window.gateway.rpc("agent", {
191
+ message: agent.task,
192
+ sessionKey: sessionKey,
193
+ deliver: false,
194
+ lane: "subagent",
195
+ label: agent.label,
196
+ model: agent.model || undefined,
197
+ thinking: agent.thinking || undefined,
198
+ timeout: 300,
199
+ });
200
+
201
+ successCount++;
202
+ } catch (err) {
203
+ console.error("[subagents] Failed to spawn:", agent.label, err);
204
+ }
205
+ }
206
+
207
+ // Clear staging
208
+ stagedAgents = [];
209
+ renderStagingChips();
210
+
211
+ // Reset button
212
+ stagingDeployBtn.innerHTML = `
213
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
214
+ <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
215
+ </svg>
216
+ Deploy
217
+ `;
218
+
219
+ // Refresh subagents list
220
+ setTimeout(fetchSubagents, 500);
221
+
222
+ if (successCount < toSpawn.length) {
223
+ alert(`Deployed ${successCount} of ${toSpawn.length} agents. Check console for errors.`);
224
+ }
225
+ }
226
+
227
+ // ── Render Active Subagents ────────────────────────────────────────────────
228
+ function render() {
229
+ // Update count badge
230
+ const activeCount = subagents.filter((s) => s.status === "active" || !s.endedAt).length;
231
+ countBadge.textContent = activeCount > 0 ? activeCount : "";
232
+
233
+ // Clear existing items (except empty state)
234
+ const items = list.querySelectorAll(".subagent-item");
235
+ items.forEach((item) => item.remove());
236
+
237
+ if (subagents.length === 0) {
238
+ emptyState.style.display = "";
239
+ return;
240
+ }
241
+
242
+ emptyState.style.display = "none";
243
+
244
+ // Sort: running first, then by startedAt descending
245
+ const sorted = [...subagents].toSorted((a, b) => {
246
+ const aRunning = a.status === "active" || !a.endedAt;
247
+ const bRunning = b.status === "active" || !b.endedAt;
248
+ if (aRunning !== bRunning) {
249
+ return bRunning ? 1 : -1;
250
+ }
251
+ return (b.startedAt || 0) - (a.startedAt || 0);
252
+ });
253
+
254
+ // Only show top 5
255
+ const visible = sorted.slice(0, 5);
256
+
257
+ for (const sub of visible) {
258
+ const status = getStatus(sub);
259
+ const runtime = formatRuntime(sub);
260
+
261
+ const item = document.createElement("div");
262
+ item.className = "subagent-item";
263
+ item.setAttribute("data-status", status);
264
+ item.setAttribute("data-run-id", sub.runId || "");
265
+
266
+ item.innerHTML = `
267
+ <div class="subagent-icon">${ICONS[status] || ICONS.running}</div>
268
+ <div class="subagent-info">
269
+ <div class="subagent-label">${escHtml(sub.label || "Subagent")}</div>
270
+ <div class="subagent-task">${escHtml(truncate(sub.task || "", 40))}</div>
271
+ </div>
272
+ <div class="subagent-runtime">${runtime}</div>
273
+ `;
274
+
275
+ item.addEventListener("click", () => showSubagentDetails(sub));
276
+ list.appendChild(item);
277
+ }
278
+ }
279
+
280
+ function getStatus(sub) {
281
+ // Use status from backend if available
282
+ if (sub.status === "active") {
283
+ return "running";
284
+ }
285
+ if (sub.status === "completed") {
286
+ return "completed";
287
+ }
288
+ if (sub.status === "error" || sub.status === "timeout") {
289
+ return "error";
290
+ }
291
+ // Fallback for old format
292
+ if (!sub.endedAt) {
293
+ return "running";
294
+ }
295
+ if (sub.outcome?.status === "ok") {
296
+ return "completed";
297
+ }
298
+ return "error";
299
+ }
300
+
301
+ function formatRuntime(sub) {
302
+ const start = sub.startedAt;
303
+ const end = sub.endedAt || Date.now();
304
+ if (!start) {
305
+ return "--";
306
+ }
307
+
308
+ const seconds = Math.floor((end - start) / 1000);
309
+ if (seconds < 60) {
310
+ return `${seconds}s`;
311
+ }
312
+ const minutes = Math.floor(seconds / 60);
313
+ const secs = seconds % 60;
314
+ if (minutes < 60) {
315
+ return `${minutes}m${secs > 0 ? secs + "s" : ""}`;
316
+ }
317
+ const hours = Math.floor(minutes / 60);
318
+ const mins = minutes % 60;
319
+ return `${hours}h${mins}m`;
320
+ }
321
+
322
+ function truncate(str, len) {
323
+ if (str.length <= len) {
324
+ return str;
325
+ }
326
+ return str.slice(0, len - 1) + "…";
327
+ }
328
+
329
+ // ── Fetch Subagents ────────────────────────────────────────────────────────
330
+ async function fetchSubagents() {
331
+ if (!window.gateway?.connected) {
332
+ return;
333
+ }
334
+
335
+ try {
336
+ const result = await window.gateway.rpc("subagents.list", {
337
+ sessionKey: window.SESSION_KEY || "agent:main:main",
338
+ });
339
+
340
+ if (result?.subagents) {
341
+ subagents = result.subagents;
342
+ render();
343
+ }
344
+ } catch (err) {
345
+ console.debug("[subagents] Failed to fetch:", err.message);
346
+ }
347
+ }
348
+
349
+ // ── Show Subagent Details ──────────────────────────────────────────────────
350
+ function showSubagentDetails(sub) {
351
+ const status = getStatus(sub);
352
+ const statusText =
353
+ status === "running" ? "Running" : status === "completed" ? "Completed" : "Failed";
354
+
355
+ const details = [
356
+ `Status: ${statusText}`,
357
+ `Task: ${sub.task || "N/A"}`,
358
+ `Label: ${sub.label || "N/A"}`,
359
+ `Model: ${sub.model || "Default"}`,
360
+ `Runtime: ${formatRuntime(sub)}`,
361
+ `Session: ${sub.childSessionKey || "N/A"}`,
362
+ ].join("\n");
363
+
364
+ console.log("[subagent]", details);
365
+ }
366
+
367
+ // ── Modal Controls ─────────────────────────────────────────────────────────
368
+ function openModal() {
369
+ editingAgentId = null;
370
+ modalOverlay.classList.add("open");
371
+ modalOverlay.setAttribute("aria-hidden", "false");
372
+ taskInput.focus();
373
+ document.addEventListener("keydown", onModalKey);
374
+ }
375
+
376
+ function openModalForEdit(stagedAgent) {
377
+ editingAgentId = stagedAgent.id;
378
+
379
+ // Pre-fill form with staged agent config
380
+ taskInput.value = stagedAgent.task;
381
+ labelInput.value = stagedAgent.label;
382
+ modelSelect.value = stagedAgent.model || "";
383
+ thinkingSelect.value = stagedAgent.thinking || "";
384
+
385
+ modalOverlay.classList.add("open");
386
+ modalOverlay.setAttribute("aria-hidden", "false");
387
+ taskInput.focus();
388
+
389
+ // Position cursor at end of task template
390
+ taskInput.setSelectionRange(taskInput.value.length, taskInput.value.length);
391
+
392
+ document.addEventListener("keydown", onModalKey);
393
+ }
394
+
395
+ function closeModal() {
396
+ modalOverlay.classList.remove("open");
397
+ modalOverlay.setAttribute("aria-hidden", "true");
398
+ document.removeEventListener("keydown", onModalKey);
399
+ // Reset form
400
+ taskInput.value = "";
401
+ labelInput.value = "";
402
+ modelSelect.value = "";
403
+ thinkingSelect.value = "";
404
+ editingAgentId = null;
405
+ }
406
+
407
+ function onModalKey(e) {
408
+ if (e.key === "Escape") {
409
+ closeModal();
410
+ }
411
+ }
412
+
413
+ // ── Spawn/Save Subagent ────────────────────────────────────────────────────
414
+ async function spawnOrSaveSubagent() {
415
+ const task = taskInput.value.trim();
416
+ if (!task) {
417
+ taskInput.focus();
418
+ return;
419
+ }
420
+
421
+ const label = labelInput.value.trim() || "Subagent";
422
+ const model = modelSelect.value || undefined;
423
+ const thinking = thinkingSelect.value || undefined;
424
+
425
+ // If editing a staged agent, update it instead of spawning
426
+ if (editingAgentId) {
427
+ const agent = stagedAgents.find((a) => a.id === editingAgentId);
428
+ if (agent) {
429
+ agent.task = task;
430
+ agent.label = label;
431
+ agent.model = model;
432
+ agent.thinking = thinking;
433
+ renderStagingChips();
434
+ }
435
+ closeModal();
436
+ return;
437
+ }
438
+
439
+ // Otherwise, spawn immediately (custom spawn)
440
+ modalSubmit.disabled = true;
441
+ modalSubmit.innerHTML = `
442
+ <svg class="spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
443
+ <circle cx="12" cy="12" r="10" stroke-dasharray="60" stroke-dashoffset="20"/>
444
+ </svg>
445
+ Spawning...
446
+ `;
447
+
448
+ try {
449
+ if (!window.gateway?.connected) {
450
+ throw new Error("Gateway not connected");
451
+ }
452
+
453
+ const uuid = crypto.randomUUID();
454
+ const sessionKey = `agent:main:subagent:${uuid}`;
455
+
456
+ await window.gateway.rpc("agent", {
457
+ message: task,
458
+ sessionKey: sessionKey,
459
+ deliver: false,
460
+ lane: "subagent",
461
+ label: label,
462
+ model: model,
463
+ thinking: thinking,
464
+ timeout: 300,
465
+ });
466
+
467
+ closeModal();
468
+ setTimeout(fetchSubagents, 500);
469
+ } catch (err) {
470
+ console.error("[subagents] Spawn failed:", err);
471
+ alert("Failed to spawn subagent: " + (err.message || "Unknown error"));
472
+ } finally {
473
+ modalSubmit.disabled = false;
474
+ modalSubmit.innerHTML = `
475
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
476
+ <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
477
+ </svg>
478
+ Spawn
479
+ `;
480
+ }
481
+ }
482
+
483
+ // ── Initialize ─────────────────────────────────────────────────────────────
484
+ function init() {
485
+ // Quick deploy buttons
486
+ quickDeployBtns.forEach((btn) => {
487
+ btn.addEventListener("click", () => {
488
+ const agentType = btn.getAttribute("data-agent");
489
+ if (agentType) {
490
+ addToStaging(agentType);
491
+ }
492
+ });
493
+ });
494
+
495
+ // Staging deploy button
496
+ if (stagingDeployBtn) {
497
+ stagingDeployBtn.addEventListener("click", deployAllStaged);
498
+ }
499
+
500
+ // Custom spawn button
501
+ if (spawnBtn) {
502
+ spawnBtn.addEventListener("click", openModal);
503
+ }
504
+
505
+ // Modal controls
506
+ if (modalClose) {
507
+ modalClose.addEventListener("click", closeModal);
508
+ }
509
+ if (modalCancel) {
510
+ modalCancel.addEventListener("click", closeModal);
511
+ }
512
+ if (modalSubmit) {
513
+ modalSubmit.addEventListener("click", spawnOrSaveSubagent);
514
+ }
515
+ if (modalOverlay) {
516
+ modalOverlay.addEventListener("click", (e) => {
517
+ if (e.target === modalOverlay) {
518
+ closeModal();
519
+ }
520
+ });
521
+ }
522
+
523
+ // Submit on Enter in task field (with Ctrl/Cmd)
524
+ if (taskInput) {
525
+ taskInput.addEventListener("keydown", (e) => {
526
+ if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
527
+ void spawnOrSaveSubagent();
528
+ }
529
+ });
530
+ }
531
+
532
+ // Initial render of staging (empty)
533
+ renderStagingChips();
534
+
535
+ // Initial fetch
536
+ void fetchSubagents();
537
+
538
+ // Poll every 5 seconds
539
+ _pollInterval = setInterval(fetchSubagents, 5000);
540
+
541
+ // Also fetch on gateway connect
542
+ window.addEventListener("gateway:connected", fetchSubagents);
543
+ window.addEventListener("gateway:hello", fetchSubagents);
544
+ }
545
+
546
+ // Start when DOM is ready
547
+ if (document.readyState === "loading") {
548
+ document.addEventListener("DOMContentLoaded", init);
549
+ } else {
550
+ init();
551
+ }
552
+
553
+ // Expose for external use
554
+ window.subagentsPanel = {
555
+ refresh: fetchSubagents,
556
+ open: openModal,
557
+ addToStaging: addToStaging,
558
+ deployAll: deployAllStaged,
559
+ };
560
+ })();
@@ -0,0 +1,29 @@
1
+ // ── Glass UI Shared Utilities ──────────────────────────────────────────
2
+ // Single source of truth for common functions used across Glass UI modules.
3
+
4
+ /**
5
+ * Escape HTML special characters to prevent XSS attacks.
6
+ * Handles null/undefined safely via nullish coalescing.
7
+ * @param {*} t - Value to escape (converted to string)
8
+ * @returns {string} HTML-safe string
9
+ */
10
+ function escHtml(t) {
11
+ return String(t ?? "")
12
+ .replace(/&/g, "&amp;")
13
+ .replace(/</g, "&lt;")
14
+ .replace(/>/g, "&gt;")
15
+ .replace(/"/g, "&quot;")
16
+ .replace(/'/g, "&#39;");
17
+ }
18
+
19
+ /**
20
+ * Escape a value for use in HTML attributes.
21
+ * Alias for escHtml since both require the same escaping.
22
+ */
23
+ function escAttr(t) {
24
+ return escHtml(t);
25
+ }
26
+
27
+ // Expose globally for use by other Glass UI scripts
28
+ window.escHtml = escHtml;
29
+ window.escAttr = escAttr;