aicodeman 1.1.2 → 1.1.3

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 (70) hide show
  1. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  2. package/dist/web/public/{app.eaa14cdd.js → app.6b133aaf.js} +5 -5
  3. package/dist/web/public/app.6b133aaf.js.br +0 -0
  4. package/dist/web/public/app.6b133aaf.js.gz +0 -0
  5. package/dist/web/public/constants.1c779517.js.gz +0 -0
  6. package/dist/web/public/image-input.0ea86695.js.gz +0 -0
  7. package/dist/web/public/index.html +13 -5
  8. package/dist/web/public/index.html.br +0 -0
  9. package/dist/web/public/index.html.gz +0 -0
  10. package/dist/web/public/input-cjk.b8686b5e.js.gz +0 -0
  11. package/dist/web/public/keyboard-accessory.bc753cc7.js.gz +0 -0
  12. package/dist/web/public/mobile-handlers.db3dc3c8.js.gz +0 -0
  13. package/dist/web/public/mobile.06b38d3a.css.gz +0 -0
  14. package/dist/web/public/notification-manager.9c984ac2.js.gz +0 -0
  15. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  16. package/dist/web/public/panels-ui.f3f08e26.js.gz +0 -0
  17. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  18. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  19. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  20. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  21. package/dist/web/public/session-ui.1463b824.js.gz +0 -0
  22. package/dist/web/public/settings-ui.08f7708b.js +55 -0
  23. package/dist/web/public/settings-ui.08f7708b.js.br +0 -0
  24. package/dist/web/public/settings-ui.08f7708b.js.gz +0 -0
  25. package/dist/web/public/{styles.7e612fc4.css → styles.379f31e0.css} +1 -1
  26. package/dist/web/public/styles.379f31e0.css.br +0 -0
  27. package/dist/web/public/styles.379f31e0.css.gz +0 -0
  28. package/dist/web/public/{subagent-windows.a366a4ad.js → subagent-windows.07e139f2.js} +9 -0
  29. package/dist/web/public/subagent-windows.07e139f2.js.br +0 -0
  30. package/dist/web/public/subagent-windows.07e139f2.js.gz +0 -0
  31. package/dist/web/public/sw.js.gz +0 -0
  32. package/dist/web/public/terminal-ui.a7e046da.js.gz +0 -0
  33. package/dist/web/public/ultracode-panel.js +10 -0
  34. package/dist/web/public/ultracode-panel.js.br +0 -0
  35. package/dist/web/public/ultracode-panel.js.gz +0 -0
  36. package/dist/web/public/ultracode-windows.js +382 -0
  37. package/dist/web/public/ultracode-windows.js.br +0 -0
  38. package/dist/web/public/ultracode-windows.js.gz +0 -0
  39. package/dist/web/public/upload.html.gz +0 -0
  40. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  41. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  42. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  43. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  44. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  45. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  46. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  47. package/dist/web/public/vendor/xterm.css.gz +0 -0
  48. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  49. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  50. package/dist/web/routes/system-routes.d.ts.map +1 -1
  51. package/dist/web/routes/system-routes.js +4 -2
  52. package/dist/web/routes/system-routes.js.map +1 -1
  53. package/dist/web/schemas.d.ts +1 -0
  54. package/dist/web/schemas.d.ts.map +1 -1
  55. package/dist/web/schemas.js +2 -0
  56. package/dist/web/schemas.js.map +1 -1
  57. package/dist/web/server.d.ts +2 -0
  58. package/dist/web/server.d.ts.map +1 -1
  59. package/dist/web/server.js +3 -1
  60. package/dist/web/server.js.map +1 -1
  61. package/package.json +1 -1
  62. package/dist/web/public/app.eaa14cdd.js.br +0 -0
  63. package/dist/web/public/app.eaa14cdd.js.gz +0 -0
  64. package/dist/web/public/settings-ui.601c30c1.js +0 -55
  65. package/dist/web/public/settings-ui.601c30c1.js.br +0 -0
  66. package/dist/web/public/settings-ui.601c30c1.js.gz +0 -0
  67. package/dist/web/public/styles.7e612fc4.css.br +0 -0
  68. package/dist/web/public/styles.7e612fc4.css.gz +0 -0
  69. package/dist/web/public/subagent-windows.a366a4ad.js.br +0 -0
  70. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
@@ -455,6 +455,12 @@ Object.assign(CodemanApp.prototype, {
455
455
  svg.appendChild(line);
456
456
  }
457
457
  }
458
+
459
+ // Ultracode floating run windows → parent tab (additional layer, ultracode-windows.js).
460
+ // Drawn into the same SVG and same batched read/write pass; the tab-rect cache is shared.
461
+ if (typeof this._appendUltracodeConnectionLines === 'function') {
462
+ this._appendUltracodeConnectionLines(svg, rects);
463
+ }
458
464
  },
459
465
 
460
466
  // ═══════════════════════════════════════════════════════════════
@@ -975,6 +981,9 @@ Object.assign(CodemanApp.prototype, {
975
981
  }
976
982
  this.imagePopups.clear();
977
983
 
984
+ // Clean up ultracode floating run windows (re-seeded from data.workflowRuns on reconnect)
985
+ if (typeof this.removeAllUltracodeWindows === 'function') this.removeAllUltracodeWindows();
986
+
978
987
  // Clear orphaned plan generation state
979
988
  this.activePlanOrchestratorId = null;
980
989
  this._planProgressHandler = null;
Binary file
@@ -31,6 +31,10 @@ Object.assign(CodemanApp.prototype, {
31
31
  this._ensureWorkflowState();
32
32
  this.workflowRuns.clear();
33
33
  (summaries || []).forEach((s) => this.workflowRuns.set(s.runId, s));
34
+ // Restore floating windows for runs that are still active & recent (additional layer).
35
+ if (typeof this._syncUltracodeFloatingWindow === 'function') {
36
+ (summaries || []).forEach((s) => this._syncUltracodeFloatingWindow(s, { fromSeed: true }));
37
+ }
34
38
  this.renderUltracodeAgentsPanel();
35
39
  },
36
40
 
@@ -47,6 +51,8 @@ Object.assign(CodemanApp.prototype, {
47
51
  this.workflowRuns.delete(data.runId);
48
52
  this.workflowRunDetails.delete(data.runId);
49
53
  if (this.activeWorkflowRunId === data.runId) this.activeWorkflowRunId = null;
54
+ // Retire the floating run window too (additional layer — ultracode-windows.js).
55
+ if (typeof this.closeUltracodeWindow === 'function') this.closeUltracodeWindow(data.runId, false);
50
56
  this.renderUltracodeAgentsPanel();
51
57
  },
52
58
 
@@ -58,6 +64,8 @@ Object.assign(CodemanApp.prototype, {
58
64
  if (this.activeWorkflowRunId === summary.runId) {
59
65
  this._fetchWorkflowRunDetail(summary.runId);
60
66
  }
67
+ // Auto-pop / refresh the floating run window for active runs (additional layer).
68
+ if (typeof this._syncUltracodeFloatingWindow === 'function') this._syncUltracodeFloatingWindow(summary);
61
69
  this.renderUltracodeAgentsPanel();
62
70
  },
63
71
 
@@ -97,6 +105,8 @@ Object.assign(CodemanApp.prototype, {
97
105
  if (run) {
98
106
  this.workflowRunDetails.set(runId, run);
99
107
  if (this.activeWorkflowRunId === runId) this._renderUltracodeDetail();
108
+ // Refresh the floating window (if one is open for this run) with the fetched agents[].
109
+ if (this.ultracodeWindows && this.ultracodeWindows.has(runId)) this.renderUltracodeWindowContent(runId);
100
110
  }
101
111
  } catch {
102
112
  /* transient — next update retries */
@@ -0,0 +1,382 @@
1
+ /**
2
+ * @fileoverview Ultracode floating run windows — auto-popping draggable windows
3
+ * with a connector line to the originating session tab.
4
+ *
5
+ * This is the "floating thing" companion to the docked master-detail panel in
6
+ * `ultracode-panel.js` (the dock panel stays — these windows are ADDITIONAL).
7
+ * When the `showUltracodeAgents` setting is on, a small floating window pops up
8
+ * for each ACTIVE ultracode/Workflow run (status not completed/killed/failed),
9
+ * mirroring the live agent grid, and is connected by a glowing line to the
10
+ * Codeman tab whose `claudeSessionId` matches the run's `sessionUuid` — the same
11
+ * line idiom subagent windows use. The window auto-closes a few seconds after
12
+ * its run finishes; an explicitly-closed run is remembered and never re-pops.
13
+ *
14
+ * Reuses, rather than duplicates:
15
+ * - `makeWindowDraggable` + the shared `#connectionLines` SVG (subagent-windows.js)
16
+ * - `_workflowAgentCardHtml`, `_fmtNum`, `_workflowStatusClass`, `_fetchWorkflowRunDetail`,
17
+ * and the `workflowRuns` / `workflowRunDetails` maps (ultracode-panel.js)
18
+ *
19
+ * The connector-line draw is appended to the shared SVG from inside
20
+ * `_updateConnectionLinesImmediate` (subagent-windows.js calls
21
+ * `_appendUltracodeConnectionLines` at the end of its render pass), so both the
22
+ * subagent and ultracode lines live in one batched read→write reflow pass.
23
+ *
24
+ * @mixin Extends CodemanApp.prototype via Object.assign
25
+ * @dependency subagent-windows.js (makeWindowDraggable, updateConnectionLines, #connectionLines)
26
+ * @dependency ultracode-panel.js (workflowRuns/workflowRunDetails, _workflowAgentCardHtml, _fmtNum)
27
+ * @loadorder 15.5 (after subagent-windows.js — needs makeWindowDraggable at runtime)
28
+ */
29
+ /* global CodemanApp, escapeHtml */
30
+
31
+ Object.assign(CodemanApp.prototype, {
32
+ /** Lazily seed the floating-window state maps (constructor also seeds them). */
33
+ _ensureUltracodeWindowState() {
34
+ if (!this.ultracodeWindows) this.ultracodeWindows = new Map(); // runId -> { element, parentSessionId, dragListeners, collapsed }
35
+ if (!this.ultracodeWindowsClosed) this.ultracodeWindowsClosed = new Set(); // runIds the user dismissed
36
+ if (!this.ultracodeWindowCloseTimers) this.ultracodeWindowCloseTimers = new Map(); // runId -> setTimeout id
37
+ if (this.ultracodeWindowZIndex === undefined) this.ultracodeWindowZIndex = 1000;
38
+ },
39
+
40
+ /** Floating windows have their own opt-in (default OFF), independent of the dock panel. */
41
+ _ultracodeFloatingEnabled() {
42
+ const settings = this.loadAppSettingsFromStorage ? this.loadAppSettingsFromStorage() : {};
43
+ return !!(settings && settings.ultracodeFloatingWindows);
44
+ },
45
+
46
+ /** A run is "working" until it reaches a terminal status. Mid-run status is absent. */
47
+ _isWorkflowRunActive(run) {
48
+ const s = String((run && run.status) || '');
49
+ return !(s === 'completed' || s === 'killed' || s === 'failed');
50
+ },
51
+
52
+ /**
53
+ * Resolve which Codeman tab a run belongs to: the session whose
54
+ * `claudeSessionId` equals the run's `sessionUuid` (the path segment the watcher
55
+ * captured). Falls back to the active session so the line still lands somewhere.
56
+ */
57
+ _resolveUltracodeParentSession(run) {
58
+ const uuid = run && run.sessionUuid;
59
+ if (uuid && this.sessions) {
60
+ for (const [sessionId, session] of this.sessions) {
61
+ if (session && session.claudeSessionId === uuid) return sessionId;
62
+ }
63
+ }
64
+ if (this.activeSessionId && this.sessions && this.sessions.has(this.activeSessionId)) {
65
+ return this.activeSessionId;
66
+ }
67
+ return null;
68
+ },
69
+
70
+ /**
71
+ * Auto-pop driver — called for every run discovered/updated and on reconnect seed.
72
+ * Creates a floating window for active runs, refreshes existing ones, and schedules
73
+ * an auto-close once a run finishes.
74
+ */
75
+ _syncUltracodeFloatingWindow(run, opts) {
76
+ this._ensureUltracodeWindowState();
77
+ if (!run || !run.runId) return;
78
+ if (!this._ultracodeFloatingEnabled()) return;
79
+ const runId = run.runId;
80
+ if (this.ultracodeWindowsClosed.has(runId)) return; // respect explicit dismissal
81
+
82
+ const active = this._isWorkflowRunActive(run);
83
+ const existing = this.ultracodeWindows.get(runId);
84
+
85
+ if (active) {
86
+ // Run is alive — cancel any pending auto-close.
87
+ const pending = this.ultracodeWindowCloseTimers.get(runId);
88
+ if (pending) {
89
+ clearTimeout(pending);
90
+ this.ultracodeWindowCloseTimers.delete(runId);
91
+ }
92
+ if (existing) {
93
+ this.renderUltracodeWindowContent(runId);
94
+ this._fetchWorkflowRunDetail(runId); // refresh agents[]; re-renders window on land
95
+ } else {
96
+ // On a reconnect snapshot, only restore windows for genuinely recent runs so
97
+ // a backlog of stale undefined-status runs doesn't carpet the screen.
98
+ if (opts && opts.fromSeed) {
99
+ const FLOAT_SEED_MAX_AGE_MS = 5 * 60 * 1000;
100
+ const age = Date.now() - (run.lastActivityAt || 0);
101
+ if (!(run.lastActivityAt && age < FLOAT_SEED_MAX_AGE_MS)) return;
102
+ }
103
+ this.createUltracodeWindow(run);
104
+ }
105
+ } else if (existing) {
106
+ // Finished — refresh to the final state (status + final agent states), show it
107
+ // briefly, then retire the floating window.
108
+ this._fetchWorkflowRunDetail(runId);
109
+ this.renderUltracodeWindowContent(runId);
110
+ if (!this.ultracodeWindowCloseTimers.has(runId)) {
111
+ const FLOAT_FINISH_GRACE_MS = 8000;
112
+ const timer = setTimeout(() => {
113
+ this.ultracodeWindowCloseTimers.delete(runId);
114
+ this.closeUltracodeWindow(runId, false);
115
+ }, FLOAT_FINISH_GRACE_MS);
116
+ this.ultracodeWindowCloseTimers.set(runId, timer);
117
+ }
118
+ }
119
+ },
120
+
121
+ /** Build and mount a floating window for a run, positioned near its parent tab. */
122
+ createUltracodeWindow(run) {
123
+ this._ensureUltracodeWindowState();
124
+ const runId = run.runId;
125
+ if (this.ultracodeWindows.has(runId)) return;
126
+ const parentSessionId = this._resolveUltracodeParentSession(run);
127
+ const titleText = run.workflowName || run.summary || runId;
128
+
129
+ const win = document.createElement('div');
130
+ win.className = 'ultracode-window spawning';
131
+ win.id = `ultracode-window-${runId}`;
132
+ win.style.zIndex = ++this.ultracodeWindowZIndex;
133
+ win.innerHTML = `
134
+ <div class="ultracode-window-header">
135
+ <div class="ultracode-window-title" title="${escapeHtml(titleText)}">
136
+ <span class="icon">🧬</span>
137
+ <span class="uw-name">${escapeHtml(titleText)}</span>
138
+ <span class="uw-status"></span>
139
+ </div>
140
+ <div class="ultracode-window-actions">
141
+ <button class="uw-min" type="button" title="Collapse">─</button>
142
+ <button class="uw-close" type="button" title="Close">&times;</button>
143
+ </div>
144
+ </div>
145
+ <div class="ultracode-window-body" id="ultracode-window-body-${runId}">
146
+ <div class="subagent-empty">Loading agents…</div>
147
+ </div>
148
+ `;
149
+
150
+ // Position: spawn from the parent tab if we can find it, else cascade.
151
+ const parentTab = parentSessionId ? document.querySelector(`.session-tab[data-id="${parentSessionId}"]`) : null;
152
+ if (parentTab) {
153
+ const r = parentTab.getBoundingClientRect();
154
+ const left = Math.max(8, Math.min(r.left, window.innerWidth - 392));
155
+ win.style.left = `${left}px`;
156
+ win.style.top = `${r.bottom + 14}px`;
157
+ } else {
158
+ const n = this.ultracodeWindows.size;
159
+ win.style.left = `${24 + n * 26}px`;
160
+ win.style.top = `${96 + n * 26}px`;
161
+ }
162
+
163
+ document.body.appendChild(win);
164
+ // Drop the spawn class on the next frame so the transition runs.
165
+ requestAnimationFrame(() => win.classList.remove('spawning'));
166
+
167
+ const header = win.querySelector('.ultracode-window-header');
168
+ const dragListeners = this.makeWindowDraggable(win, header);
169
+
170
+ win.querySelector('.uw-min').addEventListener('click', (e) => {
171
+ e.stopPropagation();
172
+ this.toggleUltracodeWindowCollapse(runId);
173
+ });
174
+ win.querySelector('.uw-close').addEventListener('click', (e) => {
175
+ e.stopPropagation();
176
+ this.closeUltracodeWindow(runId, true);
177
+ });
178
+ const nameEl = win.querySelector('.uw-name');
179
+ if (parentSessionId) {
180
+ nameEl.style.cursor = 'pointer';
181
+ nameEl.title = 'Go to session';
182
+ nameEl.addEventListener('click', () => this.selectSession(parentSessionId));
183
+ }
184
+
185
+ this.ultracodeWindows.set(runId, { element: win, parentSessionId, dragListeners, collapsed: false });
186
+
187
+ this.renderUltracodeWindowContent(runId);
188
+ this._fetchWorkflowRunDetail(runId); // pull agents[] for the body
189
+ this.updateConnectionLines();
190
+ },
191
+
192
+ /** Collapse/expand the window to header-only (line stays connected). */
193
+ toggleUltracodeWindowCollapse(runId) {
194
+ const data = this.ultracodeWindows.get(runId);
195
+ if (!data) return;
196
+ data.collapsed = !data.collapsed;
197
+ data.element.classList.toggle('collapsed', data.collapsed);
198
+ this.updateConnectionLines();
199
+ },
200
+
201
+ /** Remove a floating window. `userInitiated` records a dismissal so it won't re-pop. */
202
+ closeUltracodeWindow(runId, userInitiated) {
203
+ this._ensureUltracodeWindowState();
204
+ const pending = this.ultracodeWindowCloseTimers.get(runId);
205
+ if (pending) {
206
+ clearTimeout(pending);
207
+ this.ultracodeWindowCloseTimers.delete(runId);
208
+ }
209
+ const data = this.ultracodeWindows.get(runId);
210
+ if (userInitiated) this.ultracodeWindowsClosed.add(runId);
211
+ if (!data) return;
212
+ this._teardownUltracodeDrag(data.dragListeners);
213
+ data.element.remove();
214
+ this.ultracodeWindows.delete(runId);
215
+ this.updateConnectionLines();
216
+ },
217
+
218
+ /** Detach the document-level drag listeners returned by makeWindowDraggable. */
219
+ _teardownUltracodeDrag(dl) {
220
+ if (!dl) return;
221
+ document.removeEventListener('mousemove', dl.move);
222
+ document.removeEventListener('mouseup', dl.up);
223
+ if (dl.touchMove) {
224
+ document.removeEventListener('touchmove', dl.touchMove);
225
+ document.removeEventListener('touchend', dl.up);
226
+ document.removeEventListener('touchcancel', dl.up);
227
+ }
228
+ if (dl.handle) {
229
+ dl.handle.removeEventListener('mousedown', dl.handleMouseDown);
230
+ dl.handle.removeEventListener('touchstart', dl.handleTouchStart);
231
+ }
232
+ },
233
+
234
+ /** Tear down every floating window (called on SSE reconnect; keeps user dismissals). */
235
+ removeAllUltracodeWindows() {
236
+ this._ensureUltracodeWindowState();
237
+ const had = this.ultracodeWindows.size > 0;
238
+ for (const [, data] of this.ultracodeWindows) {
239
+ this._teardownUltracodeDrag(data.dragListeners);
240
+ if (data.element) data.element.remove();
241
+ }
242
+ this.ultracodeWindows.clear();
243
+ for (const t of this.ultracodeWindowCloseTimers.values()) clearTimeout(t);
244
+ this.ultracodeWindowCloseTimers.clear();
245
+ // Redraw so the now-orphaned connector lines are cleared from the shared SVG.
246
+ if (had) this.updateConnectionLines();
247
+ },
248
+
249
+ /** When the feature is toggled on, pop windows for any currently-active runs. */
250
+ syncAllUltracodeFloatingWindows() {
251
+ this._ensureUltracodeWindowState();
252
+ if (!this._ultracodeFloatingEnabled()) {
253
+ this.removeAllUltracodeWindows();
254
+ return;
255
+ }
256
+ if (!this.workflowRuns) return;
257
+ for (const run of this.workflowRuns.values()) {
258
+ this._syncUltracodeFloatingWindow(run, { fromSeed: true });
259
+ }
260
+ },
261
+
262
+ /** Refresh a floating window's header + body from the latest summary/detail. */
263
+ renderUltracodeWindowContent(runId) {
264
+ const data = this.ultracodeWindows.get(runId);
265
+ if (!data) return;
266
+ const summary = this.workflowRuns && this.workflowRuns.get(runId);
267
+ const detail = this.workflowRunDetails && this.workflowRunDetails.get(runId);
268
+ // Summary is the freshest run-level info (every SSE tick); detail supplies agents[]
269
+ // but is fetched less often. Merge so a completed summary isn't masked by stale detail.
270
+ const run = summary && detail ? { ...detail, ...summary, agents: detail.agents } : detail || summary;
271
+ if (!run) return;
272
+
273
+ const nameEl = data.element.querySelector('.uw-name');
274
+ if (nameEl) nameEl.textContent = run.workflowName || run.summary || runId;
275
+
276
+ const statusEl = data.element.querySelector('.uw-status');
277
+ if (statusEl) {
278
+ const finished = !this._isWorkflowRunActive(run);
279
+ const label = run.status ? String(run.status) : finished ? '—' : 'running';
280
+ const clsKey = run.status ? run.status : finished ? '' : 'running';
281
+ statusEl.textContent = label;
282
+ statusEl.className = 'uw-status ultracode-status ' + this._workflowStatusClass(clsKey);
283
+ }
284
+
285
+ const body = data.element.querySelector('.ultracode-window-body');
286
+ if (body) body.innerHTML = this._ultracodeWindowBodyHtml(run);
287
+ },
288
+
289
+ /** Compact body: a stats line + agent cards grouped by phase (reuses panel helpers). */
290
+ _ultracodeWindowBodyHtml(run) {
291
+ const phases = Array.isArray(run.phases) ? run.phases : [];
292
+ const agents = Array.isArray(run.agents) ? run.agents : null;
293
+ const agentCount = run.agentCount ?? (agents ? agents.length : 0);
294
+ const head = `<div class="uw-summary">${this._fmtNum(run.totalTokens)} tok · ${run.totalToolCalls ?? 0} tools · ${agentCount} agents</div>`;
295
+
296
+ if (!agents) {
297
+ // Summary-only (detail not fetched yet): show phase chips as a teaser.
298
+ if (phases.length) {
299
+ const chips = phases
300
+ .map(
301
+ (p) =>
302
+ `<span class="ultracode-phase-chip" title="${escapeHtml(p.detail || '')}">${escapeHtml(p.title || '')}</span>`
303
+ )
304
+ .join('');
305
+ return (
306
+ head + `<div class="ultracode-phase-list">${chips}</div><div class="subagent-empty">Loading agents…</div>`
307
+ );
308
+ }
309
+ return head + '<div class="subagent-empty">Loading agents…</div>';
310
+ }
311
+ if (!agents.length) return head + '<div class="subagent-empty">No agents yet</div>';
312
+
313
+ const groups = new Map();
314
+ agents.forEach((a) => {
315
+ const key = a.phaseIndex || 0;
316
+ if (!groups.has(key)) groups.set(key, []);
317
+ groups.get(key).push(a);
318
+ });
319
+ const orderedKeys = Array.from(groups.keys()).sort((a, b) => a - b);
320
+ const grid = orderedKeys
321
+ .map((key) => {
322
+ const group = groups.get(key);
323
+ const title = (phases[key - 1] && phases[key - 1].title) || `Phase ${key}`;
324
+ const tok = group.reduce((s, a) => s + (a.tokens || 0), 0);
325
+ const tools = group.reduce((s, a) => s + (a.toolCalls || 0), 0);
326
+ const header =
327
+ `<div class="ultracode-phase-header"><span>${escapeHtml(title)}</span>` +
328
+ `<span class="ultracode-phase-sub">${this._fmtNum(tok)} tok · ${tools} tools</span></div>`;
329
+ return header + group.map((a) => this._workflowAgentCardHtml(a)).join('');
330
+ })
331
+ .join('');
332
+ return head + grid;
333
+ },
334
+
335
+ /**
336
+ * Append ultracode-window → parent-tab connector lines into the shared SVG.
337
+ * Invoked at the tail of `_updateConnectionLinesImmediate` (subagent-windows.js),
338
+ * so it shares that pass's batched read/write discipline. `rects` is the tab-rect
339
+ * cache already populated for subagent lines — reuse it, fill any gaps.
340
+ */
341
+ _appendUltracodeConnectionLines(svg, rects) {
342
+ this._ensureUltracodeWindowState();
343
+ if (!svg || !this.ultracodeWindows.size) return;
344
+ if (!rects) rects = new Map();
345
+
346
+ // PHASE 1: layout reads (resolve parents, batch getBoundingClientRect).
347
+ const winList = [];
348
+ for (const [runId, data] of this.ultracodeWindows) {
349
+ if (!data.element) continue;
350
+ if (!data.parentSessionId) {
351
+ const summary = this.workflowRuns && this.workflowRuns.get(runId);
352
+ if (summary) data.parentSessionId = this._resolveUltracodeParentSession(summary);
353
+ }
354
+ const parentSessionId = data.parentSessionId;
355
+ if (!parentSessionId) continue;
356
+ const tabKey = 'tab:' + parentSessionId;
357
+ if (!rects.has(tabKey)) {
358
+ const tab = document.querySelector(`.session-tab[data-id="${parentSessionId}"]`);
359
+ if (tab) rects.set(tabKey, tab.getBoundingClientRect());
360
+ }
361
+ winList.push({ runId, parentSessionId, winRect: data.element.getBoundingClientRect() });
362
+ }
363
+
364
+ // PHASE 2: writes (curve from tab bottom-center to window top-center).
365
+ for (const { runId, parentSessionId, winRect } of winList) {
366
+ const tabRect = rects.get('tab:' + parentSessionId);
367
+ if (!tabRect) continue;
368
+ const x1 = tabRect.left + tabRect.width / 2;
369
+ const y1 = tabRect.bottom;
370
+ const x2 = winRect.left + winRect.width / 2;
371
+ const y2 = winRect.top;
372
+ const midY = (y1 + y2) / 2;
373
+ const path = `M ${x1} ${y1} C ${x1} ${midY}, ${x2} ${midY}, ${x2} ${y2}`;
374
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
375
+ line.setAttribute('d', path);
376
+ line.setAttribute('class', 'connection-line ultracode-connection');
377
+ line.setAttribute('data-run-id', runId);
378
+ line.setAttribute('data-parent-tab', parentSessionId);
379
+ svg.appendChild(line);
380
+ }
381
+ },
382
+ });
Binary file
Binary file
@@ -1 +1 @@
1
- {"version":3,"file":"system-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/system-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAmC1C,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AA2DjG;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,SAAS,GAAG,MAAM,CAI5F;AAED,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAC/D,IAAI,CA2xBN"}
1
+ {"version":3,"file":"system-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/system-routes.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAmC1C,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AA2DjG;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,YAAY,SAAS,GAAG,MAAM,CAI5F;AAED,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,GAC/D,IAAI,CAiyBN"}
@@ -446,8 +446,10 @@ export function registerSystemRoutes(app, ctx) {
446
446
  await fs.writeFile(SETTINGS_PATH, JSON.stringify(merged, null, 2));
447
447
  // Handle subagent tracking toggle dynamically
448
448
  toggleService(settings.subagentTrackingEnabled ?? true, subagentWatcher, 'Subagent watcher');
449
- // Handle ultracode/workflow run watcher toggle dynamically (default OFF)
450
- toggleService(settings.showUltracodeAgents ?? false, workflowRunWatcher, 'Workflow run watcher');
449
+ // Handle ultracode/workflow run watcher toggle dynamically (default OFF).
450
+ // Either the docked panel OR the floating windows keep the watcher running.
451
+ toggleService((settings.showUltracodeAgents ?? false) ||
452
+ (settings.ultracodeFloatingWindows ?? false), workflowRunWatcher, 'Workflow run watcher');
451
453
  // Handle image watcher toggle dynamically
452
454
  toggleService(settings.imageWatcherEnabled ?? false, imageWatcher, 'Image watcher', () => {
453
455
  // Re-watch all active sessions that have image watcher enabled