aicodeman 1.1.13 → 1.1.14

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 (54) hide show
  1. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  2. package/dist/web/public/{app.a24e7b65.js → app.160e8c7d.js} +10 -9
  3. package/dist/web/public/app.160e8c7d.js.br +0 -0
  4. package/dist/web/public/app.160e8c7d.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 +4 -4
  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.4e950e0a.js.gz +0 -0
  11. package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
  12. package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
  13. package/dist/web/public/mobile.38346326.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.38feb956.js.gz +0 -0
  23. package/dist/web/public/{styles.e0388c26.css → styles.4d2984bd.css} +1 -1
  24. package/dist/web/public/styles.4d2984bd.css.br +0 -0
  25. package/dist/web/public/styles.4d2984bd.css.gz +0 -0
  26. package/dist/web/public/{subagent-windows.07e139f2.js → subagent-windows.e3207234.js} +4 -0
  27. package/dist/web/public/subagent-windows.e3207234.js.br +0 -0
  28. package/dist/web/public/subagent-windows.e3207234.js.gz +0 -0
  29. package/dist/web/public/sw.js.gz +0 -0
  30. package/dist/web/public/terminal-ui.70596bbd.js.gz +0 -0
  31. package/dist/web/public/ultracode-panel.js +12 -22
  32. package/dist/web/public/ultracode-panel.js.br +0 -0
  33. package/dist/web/public/ultracode-panel.js.gz +0 -0
  34. package/dist/web/public/ultracode-windows.js +449 -12
  35. package/dist/web/public/ultracode-windows.js.br +0 -0
  36. package/dist/web/public/ultracode-windows.js.gz +0 -0
  37. package/dist/web/public/upload.html.gz +0 -0
  38. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  39. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  40. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  41. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  42. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  43. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  44. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  45. package/dist/web/public/vendor/xterm.css.gz +0 -0
  46. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  47. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  48. package/package.json +1 -1
  49. package/dist/web/public/app.a24e7b65.js.br +0 -0
  50. package/dist/web/public/app.a24e7b65.js.gz +0 -0
  51. package/dist/web/public/styles.e0388c26.css.br +0 -0
  52. package/dist/web/public/styles.e0388c26.css.gz +0 -0
  53. package/dist/web/public/subagent-windows.07e139f2.js.br +0 -0
  54. package/dist/web/public/subagent-windows.07e139f2.js.gz +0 -0
@@ -461,6 +461,10 @@ Object.assign(CodemanApp.prototype, {
461
461
  if (typeof this._appendUltracodeConnectionLines === 'function') {
462
462
  this._appendUltracodeConnectionLines(svg, rects);
463
463
  }
464
+ // Agent-transcript windows → their run window / tab (ultracode-windows.js).
465
+ if (typeof this._appendUltracodeAgentConnectionLines === 'function') {
466
+ this._appendUltracodeAgentConnectionLines(svg, rects);
467
+ }
464
468
  },
465
469
 
466
470
  // ═══════════════════════════════════════════════════════════════
Binary file
@@ -131,12 +131,15 @@ Object.assign(CodemanApp.prototype, {
131
131
  }
132
132
  },
133
133
 
134
- // Phase 4: open an agent's live transcript by agentId. The workflow agent's
134
+ // Phase 4: fetch an agent's live transcript by agentId. The workflow agent's
135
135
  // agentId is byte-identical to the agent-<id>.jsonl stem already tracked by
136
136
  // subagent-watcher, so we reuse the existing transcript route — no watcher edits.
137
- // Graceful when the agent isn't tracked yet / aged out / tracking disabled.
138
- async openWorkflowAgentTranscript(agentId) {
139
- if (!agentId) return;
137
+ // Returns { formatted: string[], entryCount } or null when nothing is available
138
+ // (queued / aged out of tracking / tracking disabled). Rendering into a connected
139
+ // in-page floating window lives in ultracode-windows.js (openUltracodeAgentWindow)
140
+ // we no longer spawn a detached browser popup.
141
+ async _fetchWorkflowAgentTranscript(agentId) {
142
+ if (!agentId) return null;
140
143
  let data = null;
141
144
  try {
142
145
  const res = await fetch(`/api/subagents/${encodeURIComponent(agentId)}/transcript?format=formatted`);
@@ -147,21 +150,8 @@ Object.assign(CodemanApp.prototype, {
147
150
  const ok = data && data.success && data.data;
148
151
  const formatted = ok ? data.data.formatted : null;
149
152
  const entryCount = ok ? data.data.entryCount || 0 : 0;
150
- if (!formatted || !entryCount) {
151
- alert(
152
- 'No transcript available for this agent yet — it may be queued, aged out of tracking, or subagent tracking is disabled.'
153
- );
154
- return;
155
- }
156
- const win = window.open('', '_blank', 'width=860,height=640');
157
- if (!win) return; // popup blocked
158
- win.document.write(
159
- `<html><head><title>Workflow agent ${escapeHtml(agentId)} transcript</title>` +
160
- `<style>body{background:#1a1a2e;color:#eee;font-family:monospace;padding:20px}pre{white-space:pre-wrap;word-wrap:break-word}</style>` +
161
- `</head><body><h2>Workflow agent ${escapeHtml(agentId)} (${entryCount} entries)</h2>` +
162
- `<pre>${escapeHtml(formatted.join('\n'))}</pre></body></html>`
163
- );
164
- win.document.close();
153
+ if (!formatted || !entryCount) return null;
154
+ return { formatted, entryCount };
165
155
  },
166
156
 
167
157
  // ----- Render (debounced) -----
@@ -262,13 +252,13 @@ Object.assign(CodemanApp.prototype, {
262
252
  const header =
263
253
  `<div class="ultracode-phase-header"><span>${escapeHtml(title)}</span>` +
264
254
  `<span class="ultracode-phase-sub">${this._fmtNum(tok)} tok · ${tools} tools</span></div>`;
265
- return header + group.map((a) => this._workflowAgentCardHtml(a)).join('');
255
+ return header + group.map((a) => this._workflowAgentCardHtml(a, runId)).join('');
266
256
  })
267
257
  .join('');
268
258
  detail.innerHTML = html;
269
259
  },
270
260
 
271
- _workflowAgentCardHtml(a) {
261
+ _workflowAgentCardHtml(a, runId) {
272
262
  const state = String(a.state || 'start');
273
263
  const stateCls = this._workflowAgentStateClass(state);
274
264
  const stateLabel = state === 'start' ? 'queued' : state === 'progress' ? 'running' : state;
@@ -289,7 +279,7 @@ Object.assign(CodemanApp.prototype, {
289
279
  const cardStateCls = state === 'done' ? ' uw-state-done' : state === 'progress' ? ' uw-state-working' : '';
290
280
  const cardAttrs = clickable
291
281
  ? ` class="ultracode-agent-card ultracode-agent-card--clickable${cardStateCls}" role="button" tabindex="0"` +
292
- ` title="View transcript" onclick="app.openWorkflowAgentTranscript('${escapeHtml(a.agentId)}')"`
282
+ ` title="View transcript" onclick="app.openUltracodeAgentWindow('${escapeHtml(a.agentId)}','${escapeHtml(runId || '')}')"`
293
283
  : ` class="ultracode-agent-card${cardStateCls}"`;
294
284
  return (
295
285
  `<div${cardAttrs}>` +
@@ -33,9 +33,12 @@
33
33
  Object.assign(CodemanApp.prototype, {
34
34
  /** Lazily seed the floating-window state maps (constructor also seeds them). */
35
35
  _ensureUltracodeWindowState() {
36
- if (!this.ultracodeWindows) this.ultracodeWindows = new Map(); // runId -> { element, parentSessionId, dragListeners, collapsed }
36
+ if (!this.ultracodeWindows) this.ultracodeWindows = new Map(); // runId -> { element, parentSessionId, dragListeners }
37
37
  if (!this.ultracodeWindowsClosed) this.ultracodeWindowsClosed = new Set(); // runIds the user dismissed
38
38
  if (!this.ultracodeWindowCloseTimers) this.ultracodeWindowCloseTimers = new Map(); // runId -> setTimeout id
39
+ if (!this.ultracodeAgentWindows) this.ultracodeAgentWindows = new Map(); // agentId -> { element, runId, dragListeners }
40
+ if (!this.minimizedUltracodeRuns) this.minimizedUltracodeRuns = new Map(); // sessionId -> Set<runId> minimized to a tab
41
+ if (!this.minimizedUltracodeAgents) this.minimizedUltracodeAgents = new Map(); // sessionId -> Map<agentId,{runId,label}>
39
42
  if (this.ultracodeWindowZIndex === undefined) this.ultracodeWindowZIndex = 1000;
40
43
  },
41
44
 
@@ -86,6 +89,21 @@ Object.assign(CodemanApp.prototype, {
86
89
 
87
90
  const active = this._isWorkflowRunActive(run);
88
91
 
92
+ // Minimized to a tab — keep it there (don't re-pop a window). Clear the tab badge a
93
+ // short while after the run finishes, mirroring the floating window's finish grace.
94
+ if (this._isUltracodeRunMinimized(runId)) {
95
+ if (!active && !this.ultracodeWindowCloseTimers.has(runId)) {
96
+ const timer = setTimeout(() => {
97
+ this.ultracodeWindowCloseTimers.delete(runId);
98
+ this._removeMinimizedUltracodeRun(runId);
99
+ this.renderSessionTabs();
100
+ this.updateConnectionLines();
101
+ }, 8000);
102
+ this.ultracodeWindowCloseTimers.set(runId, timer);
103
+ }
104
+ return;
105
+ }
106
+
89
107
  if (active) {
90
108
  // Run is alive — cancel any pending auto-close.
91
109
  const pending = this.ultracodeWindowCloseTimers.get(runId);
@@ -134,11 +152,11 @@ Object.assign(CodemanApp.prototype, {
134
152
  const run = this.workflowRuns && this.workflowRuns.get(runId);
135
153
  if (!run) return;
136
154
  this.ultracodeWindowsClosed.delete(runId); // an explicit open overrides a past dismissal
155
+ this._removeMinimizedUltracodeRun(runId); // …and a past minimize-to-tab
137
156
  const existing = this.ultracodeWindows.get(runId);
138
157
  if (existing) {
139
- // Already open — bring to front, expand if collapsed, refresh.
158
+ // Already open — bring to front and refresh.
140
159
  existing.element.style.zIndex = ++this.ultracodeWindowZIndex;
141
- if (existing.collapsed) this.toggleUltracodeWindowCollapse(runId);
142
160
  this.renderUltracodeWindowContent(runId);
143
161
  this._fetchWorkflowRunDetail(runId);
144
162
  this.updateConnectionLines();
@@ -167,7 +185,7 @@ Object.assign(CodemanApp.prototype, {
167
185
  <span class="uw-status"></span>
168
186
  </div>
169
187
  <div class="ultracode-window-actions">
170
- <button class="uw-min" type="button" title="Collapse">─</button>
188
+ <button class="uw-min" type="button" title="Minimize to tab">─</button>
171
189
  <button class="uw-close" type="button" title="Close">&times;</button>
172
190
  </div>
173
191
  </div>
@@ -198,7 +216,7 @@ Object.assign(CodemanApp.prototype, {
198
216
 
199
217
  win.querySelector('.uw-min').addEventListener('click', (e) => {
200
218
  e.stopPropagation();
201
- this.toggleUltracodeWindowCollapse(runId);
219
+ this.minimizeUltracodeWindowToTab(runId);
202
220
  });
203
221
  win.querySelector('.uw-close').addEventListener('click', (e) => {
204
222
  e.stopPropagation();
@@ -211,19 +229,261 @@ Object.assign(CodemanApp.prototype, {
211
229
  nameEl.addEventListener('click', () => this.selectSession(parentSessionId));
212
230
  }
213
231
 
214
- this.ultracodeWindows.set(runId, { element: win, parentSessionId, dragListeners, collapsed: false });
232
+ this.ultracodeWindows.set(runId, { element: win, parentSessionId, dragListeners });
215
233
 
216
234
  this.renderUltracodeWindowContent(runId);
217
235
  this._fetchWorkflowRunDetail(runId); // pull agents[] for the body
218
236
  this.updateConnectionLines();
219
237
  },
220
238
 
221
- /** Collapse/expand the window to header-only (line stays connected). */
222
- toggleUltracodeWindowCollapse(runId) {
239
+ // ── Minimize a run window into its originating tab (same idiom as subagent windows) ──
240
+
241
+ /** Is this run currently minimized to a tab (so auto-pop should leave it alone)? */
242
+ _isUltracodeRunMinimized(runId) {
243
+ if (!this.minimizedUltracodeRuns) return false;
244
+ for (const set of this.minimizedUltracodeRuns.values()) {
245
+ if (set.has(runId)) return true;
246
+ }
247
+ return false;
248
+ },
249
+
250
+ /** Drop a run from the minimized-to-tab tracking (all sessions, or a specific one). */
251
+ _removeMinimizedUltracodeRun(runId, sessionId) {
252
+ if (!this.minimizedUltracodeRuns) return;
253
+ if (sessionId) {
254
+ const set = this.minimizedUltracodeRuns.get(sessionId);
255
+ if (set) {
256
+ set.delete(runId);
257
+ if (!set.size) this.minimizedUltracodeRuns.delete(sessionId);
258
+ }
259
+ return;
260
+ }
261
+ for (const [sid, set] of this.minimizedUltracodeRuns) {
262
+ if (set.delete(runId) && !set.size) this.minimizedUltracodeRuns.delete(sid);
263
+ }
264
+ },
265
+
266
+ /**
267
+ * Minimize the floating run window into its originating session tab: record it as
268
+ * minimized (so a badge renders on the tab), genie-animate the window toward that
269
+ * tab, then remove the floating element. Restorable from the tab badge dropdown.
270
+ */
271
+ minimizeUltracodeWindowToTab(runId) {
272
+ this._ensureUltracodeWindowState();
223
273
  const data = this.ultracodeWindows.get(runId);
224
274
  if (!data) return;
225
- data.collapsed = !data.collapsed;
226
- data.element.classList.toggle('collapsed', data.collapsed);
275
+
276
+ let parentSessionId = data.parentSessionId;
277
+ if (!parentSessionId) {
278
+ const summary = this.workflowRuns && this.workflowRuns.get(runId);
279
+ parentSessionId = summary ? this._resolveUltracodeParentSession(summary) : null;
280
+ }
281
+ // No tab to fly into → fall back to a plain close so the window isn't orphaned.
282
+ if (!parentSessionId) {
283
+ this.closeUltracodeWindow(runId, true);
284
+ return;
285
+ }
286
+
287
+ // Cancel any pending finish auto-close — the badge owns the run's lifecycle now.
288
+ const pending = this.ultracodeWindowCloseTimers.get(runId);
289
+ if (pending) {
290
+ clearTimeout(pending);
291
+ this.ultracodeWindowCloseTimers.delete(runId);
292
+ }
293
+
294
+ if (!this.minimizedUltracodeRuns.has(parentSessionId)) this.minimizedUltracodeRuns.set(parentSessionId, new Set());
295
+ this.minimizedUltracodeRuns.get(parentSessionId).add(runId);
296
+
297
+ const element = data.element;
298
+ const dragListeners = data.dragListeners;
299
+ this._animateUltracodeWindowToTab(element, parentSessionId, () => {
300
+ this._teardownUltracodeDrag(dragListeners);
301
+ if (element) element.remove();
302
+ this.ultracodeWindows.delete(runId);
303
+ // Full rebuild so the tab badge renders (the incremental path only knows subagent badges).
304
+ this._fullRenderSessionTabs();
305
+ this.updateConnectionLines();
306
+ });
307
+ },
308
+
309
+ /** Genie the window toward the center of its tab, then invoke `done` to tear it down. */
310
+ _animateUltracodeWindowToTab(element, sessionId, done) {
311
+ const tab = sessionId ? document.querySelector(`.session-tab[data-id="${sessionId}"]`) : null;
312
+ if (!tab || !element) {
313
+ done();
314
+ return;
315
+ }
316
+ const w = element.getBoundingClientRect();
317
+ const t = tab.getBoundingClientRect();
318
+ const dx = t.left + t.width / 2 - (w.left + w.width / 2);
319
+ const dy = t.top + t.height / 2 - (w.top + w.height / 2);
320
+ element.style.transformOrigin = 'center center';
321
+ element.style.transition = 'transform 0.26s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.26s ease';
322
+ element.style.pointerEvents = 'none';
323
+ requestAnimationFrame(() => {
324
+ element.style.transform = `translate(${dx}px, ${dy}px) scale(0.06)`;
325
+ element.style.opacity = '0';
326
+ });
327
+ let finished = false;
328
+ const finish = () => {
329
+ if (finished) return;
330
+ finished = true;
331
+ done();
332
+ };
333
+ element.addEventListener('transitionend', finish, { once: true });
334
+ setTimeout(finish, 320); // fallback in case transitionend doesn't fire
335
+ },
336
+
337
+ /** Tab badge (with restore/dismiss dropdown) for runs minimized to this session's tab. */
338
+ renderUltracodeTabBadge(sessionId) {
339
+ this._ensureUltracodeWindowState();
340
+ const runSet = this.minimizedUltracodeRuns.get(sessionId);
341
+ const agentMap = this.minimizedUltracodeAgents.get(sessionId);
342
+ const total = (runSet ? runSet.size : 0) + (agentMap ? agentMap.size : 0);
343
+ if (total === 0) return '';
344
+
345
+ const trunc = (s) => (s.length > 25 ? s.slice(0, 25) + '…' : s);
346
+ const items = [];
347
+ // Minimized run windows (🧬) first…
348
+ if (runSet) {
349
+ for (const runId of runSet) {
350
+ const run = this.workflowRuns && this.workflowRuns.get(runId);
351
+ const name = run ? run.workflowName || run.summary || runId : runId;
352
+ const statusCls = this._workflowStatusClass(run ? String(run.status || '') : '');
353
+ items.push(
354
+ `<div class="subagent-dropdown-item" onclick="event.stopPropagation(); app.restoreUltracodeRunFromTab('${escapeHtml(runId)}','${escapeHtml(sessionId)}')" title="Click to restore run">` +
355
+ `<span class="subagent-dropdown-status ${statusCls}"></span>` +
356
+ `<span class="ultracode-dd-icon">🧬</span>` +
357
+ `<span class="subagent-dropdown-name">${escapeHtml(trunc(name))}</span>` +
358
+ `<span class="subagent-dropdown-close" onclick="event.stopPropagation(); app.dismissMinimizedUltracodeRun('${escapeHtml(runId)}','${escapeHtml(sessionId)}')" title="Dismiss">&times;</span>` +
359
+ `</div>`
360
+ );
361
+ }
362
+ }
363
+ // …then minimized agent transcripts (📄).
364
+ if (agentMap) {
365
+ for (const [agentId, entry] of agentMap) {
366
+ const name = (entry && entry.label) || agentId;
367
+ items.push(
368
+ `<div class="subagent-dropdown-item" onclick="event.stopPropagation(); app.restoreUltracodeAgentFromTab('${escapeHtml(agentId)}','${escapeHtml(sessionId)}')" title="Click to restore transcript">` +
369
+ `<span class="subagent-dropdown-status"></span>` +
370
+ `<span class="ultracode-dd-icon">📄</span>` +
371
+ `<span class="subagent-dropdown-name">${escapeHtml(trunc(name))}</span>` +
372
+ `<span class="subagent-dropdown-close" onclick="event.stopPropagation(); app.dismissMinimizedUltracodeAgent('${escapeHtml(agentId)}','${escapeHtml(sessionId)}')" title="Dismiss">&times;</span>` +
373
+ `</div>`
374
+ );
375
+ }
376
+ }
377
+ const label = total === 1 ? 'ULTRA' : `ULTRA (${total})`;
378
+ return (
379
+ `<span class="tab-ultracode-badge" onmouseenter="app.showSubagentDropdown(this)" onmouseleave="app.scheduleHideSubagentDropdown(this)" onclick="event.stopPropagation(); app.pinSubagentDropdown(this);">` +
380
+ `<span class="subagent-label">${label}</span>` +
381
+ `<div class="subagent-dropdown" onmouseenter="app.cancelHideSubagentDropdown()" onmouseleave="app.scheduleHideSubagentDropdown(this.parentElement)">${items.join('')}</div>` +
382
+ `</span>`
383
+ );
384
+ },
385
+
386
+ /** Restore a minimized run from its tab badge: re-open the floating window. */
387
+ restoreUltracodeRunFromTab(runId, sessionId) {
388
+ this._ensureUltracodeWindowState();
389
+ this._removeMinimizedUltracodeRun(runId, sessionId);
390
+ this._fullRenderSessionTabs();
391
+ this.openUltracodeWindowForRun(runId);
392
+ },
393
+
394
+ /** Dismiss a minimized run from its tab badge (don't re-pop it). */
395
+ dismissMinimizedUltracodeRun(runId, sessionId) {
396
+ this._ensureUltracodeWindowState();
397
+ this._removeMinimizedUltracodeRun(runId, sessionId);
398
+ this.ultracodeWindowsClosed.add(runId);
399
+ this._fullRenderSessionTabs();
400
+ this.updateConnectionLines();
401
+ },
402
+
403
+ // ── Minimize an agent transcript window into its tab (same idiom as run windows) ──
404
+
405
+ /** Is this agent transcript currently minimized to a tab? */
406
+ _isUltracodeAgentMinimized(agentId) {
407
+ if (!this.minimizedUltracodeAgents) return false;
408
+ for (const map of this.minimizedUltracodeAgents.values()) {
409
+ if (map.has(agentId)) return true;
410
+ }
411
+ return false;
412
+ },
413
+
414
+ /** Look up a minimized agent's {runId,label} entry (across sessions). */
415
+ _getMinimizedUltracodeAgent(agentId) {
416
+ if (!this.minimizedUltracodeAgents) return null;
417
+ for (const map of this.minimizedUltracodeAgents.values()) {
418
+ if (map.has(agentId)) return map.get(agentId);
419
+ }
420
+ return null;
421
+ },
422
+
423
+ /** Drop an agent from minimized tracking (all sessions, or a specific one). */
424
+ _removeMinimizedUltracodeAgent(agentId, sessionId) {
425
+ if (!this.minimizedUltracodeAgents) return;
426
+ if (sessionId) {
427
+ const map = this.minimizedUltracodeAgents.get(sessionId);
428
+ if (map) {
429
+ map.delete(agentId);
430
+ if (!map.size) this.minimizedUltracodeAgents.delete(sessionId);
431
+ }
432
+ return;
433
+ }
434
+ for (const [sid, map] of this.minimizedUltracodeAgents) {
435
+ if (map.delete(agentId) && !map.size) this.minimizedUltracodeAgents.delete(sid);
436
+ }
437
+ },
438
+
439
+ /** Minimize an agent transcript window into the run's originating session tab. */
440
+ minimizeUltracodeAgentWindowToTab(agentId) {
441
+ this._ensureUltracodeWindowState();
442
+ const info = this.ultracodeAgentWindows.get(agentId);
443
+ if (!info) return;
444
+ const runId = info.runId;
445
+ const summary = runId && this.workflowRuns ? this.workflowRuns.get(runId) : null;
446
+ let parentSessionId = summary ? this._resolveUltracodeParentSession(summary) : null;
447
+ if (!parentSessionId && this.activeSessionId && this.sessions && this.sessions.has(this.activeSessionId)) {
448
+ parentSessionId = this.activeSessionId;
449
+ }
450
+ // No tab to fly into → plain close rather than orphan it.
451
+ if (!parentSessionId) {
452
+ this.closeUltracodeAgentWindow(agentId);
453
+ return;
454
+ }
455
+ const labelEl = info.element.querySelector('.uw-name');
456
+ const label = labelEl ? labelEl.textContent : agentId;
457
+ if (!this.minimizedUltracodeAgents.has(parentSessionId))
458
+ this.minimizedUltracodeAgents.set(parentSessionId, new Map());
459
+ this.minimizedUltracodeAgents.get(parentSessionId).set(agentId, { runId, label });
460
+
461
+ const element = info.element;
462
+ const dragListeners = info.dragListeners;
463
+ this._animateUltracodeWindowToTab(element, parentSessionId, () => {
464
+ this._teardownUltracodeDrag(dragListeners);
465
+ if (element) element.remove();
466
+ this.ultracodeAgentWindows.delete(agentId);
467
+ this._fullRenderSessionTabs();
468
+ this.updateConnectionLines();
469
+ });
470
+ },
471
+
472
+ /** Restore a minimized agent transcript from its tab badge: re-open its window. */
473
+ restoreUltracodeAgentFromTab(agentId, sessionId) {
474
+ this._ensureUltracodeWindowState();
475
+ const entry = this._getMinimizedUltracodeAgent(agentId);
476
+ const runId = entry ? entry.runId : null;
477
+ this._removeMinimizedUltracodeAgent(agentId, sessionId);
478
+ this._fullRenderSessionTabs();
479
+ this.openUltracodeAgentWindow(agentId, runId);
480
+ },
481
+
482
+ /** Dismiss a minimized agent transcript from its tab badge. */
483
+ dismissMinimizedUltracodeAgent(agentId, sessionId) {
484
+ this._ensureUltracodeWindowState();
485
+ this._removeMinimizedUltracodeAgent(agentId, sessionId);
486
+ this._fullRenderSessionTabs();
227
487
  this.updateConnectionLines();
228
488
  },
229
489
 
@@ -260,19 +520,149 @@ Object.assign(CodemanApp.prototype, {
260
520
  }
261
521
  },
262
522
 
523
+ // ── Agent-transcript windows ────────────────────────────────────────────────
524
+ // Clicking an agent card (in a run window OR the dock panel) opens the agent's
525
+ // live transcript as its OWN in-page floating window, line-tied to its parent run
526
+ // window (or the run's session tab when that window is closed). Replaces the old
527
+ // detached `window.open` browser popup so the transcript stays inside the same
528
+ // draggable, connector-line floating-window system as the run windows.
529
+
530
+ /** Open (or focus) the floating transcript window for a workflow agent. */
531
+ async openUltracodeAgentWindow(agentId, runId) {
532
+ this._ensureUltracodeWindowState();
533
+ if (!agentId) return;
534
+ this._removeMinimizedUltracodeAgent(agentId); // an explicit open overrides a past minimize
535
+ const existing = this.ultracodeAgentWindows.get(agentId);
536
+ if (existing && existing.element) {
537
+ // Already open — bring to front and refresh transcript.
538
+ existing.element.style.zIndex = ++this.ultracodeWindowZIndex;
539
+ this.updateConnectionLines();
540
+ } else if (!this.createUltracodeAgentWindow(agentId, runId)) {
541
+ return;
542
+ }
543
+ // Body shows a loading state until the fetch lands (re-fetch on focus too, so a
544
+ // still-running agent's transcript grows as you re-click).
545
+ const data = this._fetchWorkflowAgentTranscript ? await this._fetchWorkflowAgentTranscript(agentId) : null;
546
+ this.renderUltracodeAgentWindowContent(agentId, data);
547
+ },
548
+
549
+ /** Build and mount the floating agent-transcript window shell near its parent. */
550
+ createUltracodeAgentWindow(agentId, runId) {
551
+ this._ensureUltracodeWindowState();
552
+ if (this.ultracodeAgentWindows.has(agentId)) return this.ultracodeAgentWindows.get(agentId).element;
553
+
554
+ const label = this._ultracodeAgentLabel(agentId, runId) || agentId;
555
+ const win = document.createElement('div');
556
+ win.className = 'ultracode-window ultracode-agent-window spawning';
557
+ win.id = `ultracode-agent-window-${agentId}`;
558
+ win.style.zIndex = ++this.ultracodeWindowZIndex;
559
+ win.innerHTML = `
560
+ <div class="ultracode-window-header">
561
+ <div class="ultracode-window-title" title="${escapeHtml(label)} — transcript">
562
+ <span class="icon">📄</span>
563
+ <span class="uw-name">${escapeHtml(label)}</span>
564
+ </div>
565
+ <div class="ultracode-window-actions">
566
+ <button class="uw-min" type="button" title="Minimize to tab">─</button>
567
+ <button class="uw-close" type="button" title="Close">&times;</button>
568
+ </div>
569
+ </div>
570
+ <div class="ultracode-window-body">
571
+ <div class="subagent-empty">Loading transcript…</div>
572
+ </div>
573
+ `;
574
+
575
+ // Position: offset from the parent run window if it's open, else cascade.
576
+ const parentWin = runId ? this.ultracodeWindows.get(runId) : null;
577
+ if (parentWin && parentWin.element) {
578
+ const r = parentWin.element.getBoundingClientRect();
579
+ win.style.left = `${Math.max(8, Math.min(r.left + 40, window.innerWidth - 472))}px`;
580
+ win.style.top = `${Math.max(8, Math.min(r.top + 40, window.innerHeight - 160))}px`;
581
+ } else {
582
+ const n = this.ultracodeAgentWindows.size;
583
+ win.style.left = `${Math.min(140 + n * 28, Math.max(8, window.innerWidth - 472))}px`;
584
+ win.style.top = `${110 + n * 28}px`;
585
+ }
586
+
587
+ document.body.appendChild(win);
588
+ requestAnimationFrame(() => win.classList.remove('spawning'));
589
+
590
+ const header = win.querySelector('.ultracode-window-header');
591
+ const dragListeners = this.makeWindowDraggable(win, header);
592
+ win.querySelector('.uw-min').addEventListener('click', (e) => {
593
+ e.stopPropagation();
594
+ this.minimizeUltracodeAgentWindowToTab(agentId);
595
+ });
596
+ win.querySelector('.uw-close').addEventListener('click', (e) => {
597
+ e.stopPropagation();
598
+ this.closeUltracodeAgentWindow(agentId);
599
+ });
600
+
601
+ this.ultracodeAgentWindows.set(agentId, { element: win, runId, dragListeners });
602
+ this.updateConnectionLines();
603
+ return win;
604
+ },
605
+
606
+ /** Resolve a human label for an agent from the run's fetched detail.agents[]. */
607
+ _ultracodeAgentLabel(agentId, runId) {
608
+ const detail = this.workflowRunDetails && runId ? this.workflowRunDetails.get(runId) : null;
609
+ const agents = detail && Array.isArray(detail.agents) ? detail.agents : null;
610
+ if (agents) {
611
+ const found = agents.find((a) => a.agentId === agentId);
612
+ if (found && found.label) return found.label;
613
+ }
614
+ return null;
615
+ },
616
+
617
+ /** Fill an agent window's body with the fetched transcript (or a friendly empty state). */
618
+ renderUltracodeAgentWindowContent(agentId, data) {
619
+ const info = this.ultracodeAgentWindows.get(agentId);
620
+ if (!info || !info.element) return;
621
+ const body = info.element.querySelector('.ultracode-window-body');
622
+ if (!body) return;
623
+ if (!data || !data.formatted || !data.entryCount) {
624
+ body.innerHTML =
625
+ '<div class="subagent-empty">No transcript available yet — the agent may be queued, aged out of tracking, or subagent tracking is disabled.</div>';
626
+ return;
627
+ }
628
+ const text = escapeHtml(data.formatted.join('\n'));
629
+ body.innerHTML = `<div class="uw-summary">${data.entryCount} entries</div><pre class="uw-transcript">${text}</pre>`;
630
+ },
631
+
632
+ /** Close one floating agent-transcript window. */
633
+ closeUltracodeAgentWindow(agentId) {
634
+ this._ensureUltracodeWindowState();
635
+ const info = this.ultracodeAgentWindows.get(agentId);
636
+ if (!info) return;
637
+ this._teardownUltracodeDrag(info.dragListeners);
638
+ if (info.element) info.element.remove();
639
+ this.ultracodeAgentWindows.delete(agentId);
640
+ this.updateConnectionLines();
641
+ },
642
+
263
643
  /** Tear down every floating window (called on SSE reconnect; keeps user dismissals). */
264
644
  removeAllUltracodeWindows() {
265
645
  this._ensureUltracodeWindowState();
266
- const had = this.ultracodeWindows.size > 0;
646
+ const hadMinimized = this.minimizedUltracodeRuns.size > 0 || this.minimizedUltracodeAgents.size > 0;
647
+ const had = this.ultracodeWindows.size > 0 || this.ultracodeAgentWindows.size > 0;
267
648
  for (const [, data] of this.ultracodeWindows) {
268
649
  this._teardownUltracodeDrag(data.dragListeners);
269
650
  if (data.element) data.element.remove();
270
651
  }
271
652
  this.ultracodeWindows.clear();
653
+ for (const [, info] of this.ultracodeAgentWindows) {
654
+ this._teardownUltracodeDrag(info.dragListeners);
655
+ if (info.element) info.element.remove();
656
+ }
657
+ this.ultracodeAgentWindows.clear();
272
658
  for (const t of this.ultracodeWindowCloseTimers.values()) clearTimeout(t);
273
659
  this.ultracodeWindowCloseTimers.clear();
660
+ this.minimizedUltracodeRuns.clear();
661
+ this.minimizedUltracodeAgents.clear();
274
662
  // Redraw so the now-orphaned connector lines are cleared from the shared SVG.
275
663
  if (had) this.updateConnectionLines();
664
+ // Drop any now-stale tab badges.
665
+ if (hadMinimized) this.renderSessionTabs();
276
666
  },
277
667
 
278
668
  /** When the feature is toggled on, pop windows for any currently-active runs. */
@@ -355,7 +745,7 @@ Object.assign(CodemanApp.prototype, {
355
745
  const header =
356
746
  `<div class="ultracode-phase-header"><span>${escapeHtml(title)}</span>` +
357
747
  `<span class="ultracode-phase-sub">${this._fmtNum(tok)} tok · ${tools} tools</span></div>`;
358
- return header + group.map((a) => this._workflowAgentCardHtml(a)).join('');
748
+ return header + group.map((a) => this._workflowAgentCardHtml(a, run.runId)).join('');
359
749
  })
360
750
  .join('');
361
751
  return head + grid;
@@ -408,4 +798,51 @@ Object.assign(CodemanApp.prototype, {
408
798
  svg.appendChild(line);
409
799
  }
410
800
  },
801
+
802
+ /**
803
+ * Append agent-window → parent connector lines into the shared SVG. Parent is the
804
+ * agent's run floating window when open, else the run's session tab. Called right
805
+ * after `_appendUltracodeConnectionLines` so it shares the same batched pass + the
806
+ * tab-rect cache.
807
+ */
808
+ _appendUltracodeAgentConnectionLines(svg, rects) {
809
+ this._ensureUltracodeWindowState();
810
+ if (!svg || !this.ultracodeAgentWindows.size) return;
811
+ if (!rects) rects = new Map();
812
+
813
+ for (const [agentId, info] of this.ultracodeAgentWindows) {
814
+ if (!info.element) continue;
815
+ const winRect = info.element.getBoundingClientRect();
816
+ // Anchor: parent run window bottom-center if open, else the run's tab.
817
+ let px, py;
818
+ const runWin = info.runId ? this.ultracodeWindows.get(info.runId) : null;
819
+ if (runWin && runWin.element) {
820
+ const pr = runWin.element.getBoundingClientRect();
821
+ px = pr.left + pr.width / 2;
822
+ py = pr.bottom;
823
+ } else {
824
+ const summary = info.runId && this.workflowRuns ? this.workflowRuns.get(info.runId) : null;
825
+ const parentSessionId = summary ? this._resolveUltracodeParentSession(summary) : null;
826
+ if (!parentSessionId) continue;
827
+ const tabKey = 'tab:' + parentSessionId;
828
+ if (!rects.has(tabKey)) {
829
+ const tab = document.querySelector(`.session-tab[data-id="${parentSessionId}"]`);
830
+ if (tab) rects.set(tabKey, tab.getBoundingClientRect());
831
+ }
832
+ const tabRect = rects.get(tabKey);
833
+ if (!tabRect) continue;
834
+ px = tabRect.left + tabRect.width / 2;
835
+ py = tabRect.bottom;
836
+ }
837
+ const x2 = winRect.left + winRect.width / 2;
838
+ const y2 = winRect.top;
839
+ const midY = (py + y2) / 2;
840
+ const path = `M ${px} ${py} C ${px} ${midY}, ${x2} ${midY}, ${x2} ${y2}`;
841
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'path');
842
+ line.setAttribute('d', path);
843
+ line.setAttribute('class', 'connection-line ultracode-connection ultracode-agent-connection');
844
+ line.setAttribute('data-agent-id', agentId);
845
+ svg.appendChild(line);
846
+ }
847
+ },
411
848
  });
Binary file